Compare commits
93
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f7eb8a5d3 | ||
|
|
7b2d44f2c1 | ||
|
|
225da81135 | ||
|
|
686d12da5c | ||
|
|
c22756051b | ||
|
|
8fdfcc0c7d | ||
|
|
44ab65c981 | ||
|
|
b9574b6efc | ||
|
|
96a8e26786 | ||
|
|
80f5b23622 | ||
|
|
2b4950e954 | ||
|
|
e16f14089d | ||
|
|
c3807a89ed | ||
|
|
049ef4a666 | ||
|
|
d5fa880a29 | ||
|
|
1b9a6db73f | ||
|
|
ed2692908e | ||
|
|
0c6c5a5d51 | ||
|
|
b449f8eb0e | ||
|
|
cf5160fb13 | ||
|
|
4c1c91c006 | ||
|
|
9ed39c149b | ||
|
|
b3f0fe029d | ||
|
|
a1b78afdb9 | ||
|
|
3d3cfb1b41 | ||
|
|
b812fe03d2 | ||
|
|
84f087bca4 | ||
|
|
a7e612eb88 | ||
|
|
9e3afda2f1 | ||
|
|
4d7a91a584 | ||
|
|
1dc320edcd | ||
|
|
1d02f5485f | ||
|
|
4c9a977920 | ||
|
|
b5db09c25e | ||
|
|
8dc4a5b82a | ||
|
|
44add9ce2f | ||
|
|
c19eceeede | ||
|
|
72bc5a5ae5 | ||
|
|
dfdf46dbdd | ||
|
|
571dff2c09 | ||
|
|
b9a66d4424 | ||
|
|
7ab2e3e60d | ||
|
|
a5d6bc6a51 | ||
|
|
df612ca4a0 | ||
|
|
a91ce57c43 | ||
|
|
342884f83e | ||
|
|
0fb39bad84 | ||
|
|
c74c80bb6f | ||
|
|
910d5c65d5 | ||
|
|
d0fb4843ab | ||
|
|
f488b857fe | ||
|
|
256e8b1116 | ||
|
|
168dd87747 | ||
|
|
13c41e6d86 | ||
|
|
7ab0bbb325 | ||
|
|
2213187b14 | ||
|
|
4c4d8a61b6 | ||
|
|
67f58f5f09 | ||
|
|
dfc35885bb | ||
|
|
e9c449d1db | ||
|
|
6dac50c7c6 | ||
|
|
9be54f6164 | ||
|
|
341cc5a4eb | ||
|
|
7b80171203 | ||
|
|
2e5a385e53 | ||
|
|
a3caeb381e | ||
|
|
ab0af86379 | ||
|
|
86426cb024 | ||
|
|
348840ef26 | ||
|
|
52ef934961 | ||
|
|
be995f93b6 | ||
|
|
5036615e00 | ||
|
|
4ee2b135a7 | ||
|
|
388fe04e09 | ||
|
|
2e215aed4c | ||
|
|
ffd27d6db4 | ||
|
|
6540e36818 | ||
|
|
a436436def | ||
|
|
d6d92afbda | ||
|
|
e1142910e4 | ||
|
|
067e6c769b | ||
|
|
d375d02a4b | ||
|
|
63594d36b0 | ||
|
|
6c160f517f | ||
|
|
23305cec9c | ||
|
|
36a741f0bc | ||
|
|
695ad732e1 | ||
|
|
73a3f07e2b | ||
|
|
4ea239a953 | ||
|
|
625d5d70a7 | ||
|
|
5c3d8b37b6 | ||
|
|
8bbbf3a889 | ||
|
|
d5bf72851a |
No files matched your search
@@ -328,6 +328,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: yarn run test:e2e:serve
|
start: yarn run test:e2e:serve
|
||||||
|
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
|
||||||
wait-on: 'http://localhost:3000'
|
wait-on: 'http://localhost:3000'
|
||||||
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
|
||||||
@@ -352,7 +353,7 @@ workflows:
|
|||||||
working_directory: platform/viewer
|
working_directory: platform/viewer
|
||||||
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
||||||
start: yarn run test:e2e:serve
|
start: yarn run test:e2e:serve
|
||||||
spec: 'cypress/integration/common/**/*'
|
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
|
||||||
wait-on: 'http://localhost:3000'
|
wait-on: 'http://localhost:3000'
|
||||||
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
|
||||||
|
|||||||
@@ -1,17 +1,31 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
|
if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
||||||
if [ ! -z "$CLIENT_ID" ]
|
|
||||||
then
|
then
|
||||||
echo "Google Cloud Healthcare $CLIENT_ID has been provided: "
|
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
|
||||||
echo $CLIENT_ID
|
if [ -n "$CLIENT_ID" ]
|
||||||
echo "Updating config..."
|
then
|
||||||
|
echo "Google Cloud Healthcare \$CLIENT_ID has been provided: "
|
||||||
|
echo "$CLIENT_ID"
|
||||||
|
echo "Updating config..."
|
||||||
|
|
||||||
# - Use SED to replace the CLIENT_ID that is currently in google.js
|
# - Use SED to replace the CLIENT_ID that is currently in google.js
|
||||||
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/google.js
|
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/google.js
|
||||||
|
fi
|
||||||
|
|
||||||
# - Copy google.js to overwrite app-config.js
|
# If HEALTHCARE_API_ENDPOINT is specified, use the google.js configuration with the modified endpoint
|
||||||
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
|
if [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
||||||
|
then
|
||||||
|
echo "Google Cloud Healthcare \$HEALTHCARE_API_ENDPOINT has been provided: "
|
||||||
|
echo "$HEALTHCARE_API_ENDPOINT"
|
||||||
|
echo "Updating config..."
|
||||||
|
|
||||||
|
# - Use SED to replace the HEALTHCARE_API_ENDPOINT that is currently in google.js
|
||||||
|
sed -i -e "s+https://healthcare.googleapis.com/v1beta1+$HEALTHCARE_API_ENDPOINT+g" /usr/share/nginx/html/google.js
|
||||||
|
fi
|
||||||
|
|
||||||
|
# - Copy google.js to overwrite app-config.js
|
||||||
|
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting Nginx to serve the OHIF Viewer..."
|
echo "Starting Nginx to serve the OHIF Viewer..."
|
||||||
|
|||||||
+2
-1
@@ -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:'
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ docker/dcm4che/dcm4che-arc
|
|||||||
|
|
||||||
# Cypress test results
|
# Cypress test results
|
||||||
videos/
|
videos/
|
||||||
|
screenshots/
|
||||||
|
|
||||||
# Locize settings
|
# Locize settings
|
||||||
.locize
|
.locize
|
||||||
@@ -193,7 +193,8 @@ today, create a GitHub issue!
|
|||||||
|
|
||||||
The `ExtensionManager` is a class made available to us via the `@ohif/core`
|
The `ExtensionManager` is a class made available to us via the `@ohif/core`
|
||||||
project (platform/core). Our application instantiates a single instance of it,
|
project (platform/core). Our application instantiates a single instance of it,
|
||||||
and provides a `ServicesManager` and `CommandsManager`.
|
and provides a `ServicesManager` and `CommandsManager` along with the application's
|
||||||
|
configuration through the appConfig key (optional).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const commandsManager = new CommandsManager();
|
const commandsManager = new CommandsManager();
|
||||||
@@ -201,6 +202,7 @@ const servicesManager = new ServicesManager();
|
|||||||
const extensionManager = new ExtensionManager({
|
const extensionManager = new ExtensionManager({
|
||||||
commandsManager,
|
commandsManager,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
|
appConfig
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -210,8 +212,8 @@ The `ExtensionManager` only has a few public members:
|
|||||||
- `registerExtensions` - Registers an array of extensions
|
- `registerExtensions` - Registers an array of extensions
|
||||||
- `modules` - An object containing registered extensions by `MODULE_TYPE`
|
- `modules` - An object containing registered extensions by `MODULE_TYPE`
|
||||||
|
|
||||||
During registration, lifecycle hooks and modules have access to
|
During registration, lifecycle hooks and modules have access to the extension's config,
|
||||||
`ExtensionManager`'s `ServicesManager` and `CommandsManager`.
|
the application's config and `ExtensionManager`'s `ServicesManager` and `CommandsManager` instances.
|
||||||
|
|
||||||
Our `@ohif/viewer` uses the `modules` member to access registered extensions at
|
Our `@ohif/viewer` uses the `modules` member to access registered extensions at
|
||||||
appropriate places in our application.
|
appropriate places in our application.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# Viewer: Configuration
|
# Viewer: Configuration
|
||||||
|
|
||||||
We maintain a number of common viewer application configurations at [`<root>/platform/viewer/public/configs`][config-dir]. How these values are passed to the viewer depend on how it's deployed, but the two most common paths are:
|
We maintain a number of common viewer application configurations at
|
||||||
|
[`<root>/platform/viewer/public/configs`][config-dir]. How these values are
|
||||||
|
passed to the viewer depend on how it's deployed, but the two most common paths
|
||||||
|
are:
|
||||||
|
|
||||||
- `index.html` looks for `https://your-website.com/app-config.js` OR
|
- `index.html` looks for `https://your-website.com/app-config.js` OR
|
||||||
- `index.html` passes the values to `OHIF.installViewer()`
|
- `index.html` passes the values to `OHIF.installViewer()`
|
||||||
@@ -42,8 +45,16 @@ window.config = {
|
|||||||
keys: ['v'],
|
keys: ['v'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// Config to pass to the bundled cornerstone extension
|
/* Configuration passed to the bundled cornerstone extension
|
||||||
cornerstoneExtensionConfig: {},
|
*
|
||||||
|
* The cornerstone extension is currently tightly coupled to the platform.
|
||||||
|
* Until we're able to decouple it, this key will serve as a workaround to
|
||||||
|
* pass it configuration.
|
||||||
|
*/
|
||||||
|
cornerstoneExtensionConfig: {
|
||||||
|
/* Whether to show/hide annotation "handles" */
|
||||||
|
hideHandles: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default {
|
|||||||
preRegistration({
|
preRegistration({
|
||||||
servicesManager = {},
|
servicesManager = {},
|
||||||
commandsManager = {},
|
commandsManager = {},
|
||||||
|
appConfig = {},
|
||||||
configuration = {},
|
configuration = {},
|
||||||
}) {},
|
}) {},
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,47 @@
|
|||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.1.1...@ohif/extension-cornerstone@2.2.0) (2019-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Configuration so viewer tools can nix handles ([#1304](https://github.com/OHIF/Viewers/issues/1304)) ([63594d3](https://github.com/OHIF/Viewers/commit/63594d36b0bdba59f0901095aed70b75fb05172d)), closes [#1223](https://github.com/OHIF/Viewers/issues/1223)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.1.0...@ohif/extension-cornerstone@2.1.1) (2019-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 add WwwcRegionTool to cornerstone tools initialization ([#1302](https://github.com/OHIF/Viewers/issues/1302)) ([d5bf728](https://github.com/OHIF/Viewers/commit/d5bf72851a32dff9fd3fc09332ea5250bc7e6114))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.2...@ohif/extension-cornerstone@2.1.0) (2019-12-11)
|
# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.2...@ohif/extension-cornerstone@2.1.0) (2019-12-11)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,20 @@ Tools can be configured through extension configuration using the tools key:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Annotate Tools Configuration
|
||||||
|
|
||||||
|
*We currently support one property for annotation tools.*
|
||||||
|
|
||||||
|
### Hide handles
|
||||||
|
This extension configuration allows you to toggle on/off handle rendering for all annotate tools:
|
||||||
|
|
||||||
|
```js
|
||||||
|
...
|
||||||
|
cornerstoneExtensionConfig: {
|
||||||
|
hideHandles: true,
|
||||||
|
},
|
||||||
|
...
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
### Repositories
|
### Repositories
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-cornerstone",
|
"name": "@ohif/extension-cornerstone",
|
||||||
"version": "2.1.0",
|
"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.8.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,6 +2,7 @@ import cornerstone from 'cornerstone-core';
|
|||||||
import cornerstoneTools from 'cornerstone-tools';
|
import cornerstoneTools from 'cornerstone-tools';
|
||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
|
|
||||||
|
import setCornerstoneLayout from './utils/setCornerstoneLayout.js';
|
||||||
import { getEnabledElement } from './state';
|
import { getEnabledElement } from './state';
|
||||||
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
||||||
const scroll = cornerstoneTools.import('util/scroll');
|
const scroll = cornerstoneTools.import('util/scroll');
|
||||||
@@ -242,6 +243,9 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
cornerstoneTools.removeToolState(element, toolType, tool);
|
cornerstoneTools.removeToolState(element, toolType, tool);
|
||||||
cornerstone.updateImage(element);
|
cornerstone.updateImage(element);
|
||||||
},
|
},
|
||||||
|
setCornerstoneLayout: () => {
|
||||||
|
setCornerstoneLayout();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const definitions = {
|
const definitions = {
|
||||||
@@ -348,6 +352,12 @@ const commandsModule = ({ servicesManager }) => {
|
|||||||
storeContexts: [],
|
storeContexts: [],
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
|
setCornerstoneLayout: {
|
||||||
|
commandFn: actions.setCornerstoneLayout,
|
||||||
|
storeContexts: [],
|
||||||
|
options: {},
|
||||||
|
context: 'VIEWER',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import OHIF from '@ohif/core';
|
import OHIF from '@ohif/core';
|
||||||
|
import { SimpleDialog } from '@ohif/ui';
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
import csTools from 'cornerstone-tools';
|
import csTools from 'cornerstone-tools';
|
||||||
import initCornerstoneTools from './initCornerstoneTools.js';
|
|
||||||
import queryString from 'query-string';
|
|
||||||
import { SimpleDialog } from '@ohif/ui';
|
|
||||||
import merge from 'lodash.merge';
|
import merge from 'lodash.merge';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
import initCornerstoneTools from './initCornerstoneTools.js';
|
||||||
|
|
||||||
function fallbackMetaDataProvider(type, imageId) {
|
function fallbackMetaDataProvider(type, imageId) {
|
||||||
if (!imageId.includes('wado?requestType=WADO')) {
|
if (!imageId.includes('wado?requestType=WADO')) {
|
||||||
@@ -76,30 +76,36 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
|
|
||||||
initCornerstoneTools(defaultCsToolsConfig);
|
initCornerstoneTools(defaultCsToolsConfig);
|
||||||
|
|
||||||
// ~~ Toooools 🙌
|
const toolsGroupedByType = {
|
||||||
const tools = [
|
touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool],
|
||||||
csTools.PanTool,
|
annotations: [
|
||||||
csTools.ZoomTool,
|
csTools.ArrowAnnotateTool,
|
||||||
csTools.WwwcTool,
|
csTools.EraserTool,
|
||||||
csTools.MagnifyTool,
|
csTools.BidirectionalTool,
|
||||||
csTools.StackScrollTool,
|
csTools.LengthTool,
|
||||||
csTools.StackScrollMouseWheelTool,
|
csTools.AngleTool,
|
||||||
// Touch
|
csTools.FreehandRoiTool,
|
||||||
csTools.PanMultiTouchTool,
|
csTools.EllipticalRoiTool,
|
||||||
csTools.ZoomTouchPinchTool,
|
csTools.DragProbeTool,
|
||||||
// Annotations
|
csTools.RectangleRoiTool,
|
||||||
csTools.ArrowAnnotateTool,
|
],
|
||||||
csTools.EraserTool,
|
segmentation: [csTools.BrushTool],
|
||||||
csTools.BidirectionalTool,
|
other: [
|
||||||
csTools.LengthTool,
|
csTools.PanTool,
|
||||||
csTools.AngleTool,
|
csTools.ZoomTool,
|
||||||
csTools.FreehandRoiTool,
|
csTools.WwwcTool,
|
||||||
csTools.EllipticalRoiTool,
|
csTools.WwwcRegionTool,
|
||||||
csTools.DragProbeTool,
|
csTools.MagnifyTool,
|
||||||
csTools.RectangleRoiTool,
|
csTools.StackScrollTool,
|
||||||
// Segmentation
|
csTools.StackScrollMouseWheelTool,
|
||||||
csTools.BrushTool,
|
csTools.OverlayTool,
|
||||||
];
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let tools = [];
|
||||||
|
Object.keys(toolsGroupedByType).forEach(toolsGroup =>
|
||||||
|
tools.push(...toolsGroupedByType[toolsGroup])
|
||||||
|
);
|
||||||
|
|
||||||
/* Add extension tools configuration here. */
|
/* Add extension tools configuration here. */
|
||||||
const internalToolsConfig = {
|
const internalToolsConfig = {
|
||||||
@@ -113,13 +119,48 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Abstract tools configuration using extension configuration. */
|
||||||
|
const parseToolProps = (props, tool) => {
|
||||||
|
const { annotations } = toolsGroupedByType;
|
||||||
|
// An alternative approach would be to remove the `drawHandlesOnHover` config
|
||||||
|
// from the supported configuration properties in `cornerstone-tools`
|
||||||
|
const toolsWithHideableHandles = annotations.filter(
|
||||||
|
tool => !['RectangleRoiTool', 'EllipticalRoiTool'].includes(tool.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
let parsedProps = { ...props };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* drawHandles - Never/Always show handles
|
||||||
|
* drawHandlesOnHover - Only show handles on handle hover (pointNearHandle)
|
||||||
|
*
|
||||||
|
* Does not apply to tools where handles aren't placed in predictable
|
||||||
|
* locations.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
configuration.hideHandles !== false &&
|
||||||
|
toolsWithHideableHandles.includes(tool)
|
||||||
|
) {
|
||||||
|
if (props.configuration) {
|
||||||
|
parsedProps.configuration.drawHandlesOnHover = true;
|
||||||
|
} else {
|
||||||
|
parsedProps.configuration = { drawHandlesOnHover: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedProps;
|
||||||
|
};
|
||||||
|
|
||||||
/* Add tools with its custom props through extension configuration. */
|
/* Add tools with its custom props through extension configuration. */
|
||||||
tools.forEach(tool => {
|
tools.forEach(tool => {
|
||||||
const toolName = tool.name.replace('Tool', '');
|
const toolName = tool.name.replace('Tool', '');
|
||||||
const externalToolsConfig = configuration.tools || {};
|
const externalToolsConfig = configuration.tools || {};
|
||||||
const externalToolProps = externalToolsConfig[toolName] || {};
|
const externalToolProps = externalToolsConfig[toolName] || {};
|
||||||
const internalToolProps = internalToolsConfig[toolName] || {};
|
const internalToolProps = internalToolsConfig[toolName] || {};
|
||||||
const props = merge(internalToolProps, externalToolProps);
|
const props = merge(
|
||||||
|
internalToolProps,
|
||||||
|
parseToolProps(externalToolProps, tool)
|
||||||
|
);
|
||||||
csTools.addTool(tool, props);
|
csTools.addTool(tool, props);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,11 +173,12 @@ export default function init({ servicesManager, configuration }) {
|
|||||||
csTools.setToolPassive(toolName); // there may be a better place to determine name; may not be on uninstantiated class
|
csTools.setToolPassive(toolName); // there may be a better place to determine name; may not be on uninstantiated class
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
csTools.setToolActive('Pan', { mouseButtonMask: 4 });
|
||||||
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
|
||||||
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
|
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
|
||||||
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', {});
|
||||||
}
|
}
|
||||||
@@ -237,6 +237,15 @@ const definitions = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'Exit2DMPR',
|
||||||
|
label: 'Exit 2D MPR',
|
||||||
|
icon: 'times',
|
||||||
|
//
|
||||||
|
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||||
|
commandName: 'setCornerstoneLayout',
|
||||||
|
context: 'ACTIVE_VIEWPORT::VTK',
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { redux } from '@ohif/core';
|
||||||
|
|
||||||
|
const { setLayout } = redux.actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the current layout with a simple Cornerstone one
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
const setCornerstoneLayout = () => {
|
||||||
|
const layout = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 1,
|
||||||
|
viewports: [{ plugin: 'cornerstone' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = setLayout(layout);
|
||||||
|
|
||||||
|
window.store.dispatch(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default setCornerstoneLayout;
|
||||||
@@ -3,6 +3,159 @@
|
|||||||
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.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.13...@ohif/extension-vtk@1.2.14) (2020-02-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.5...@ohif/extension-vtk@1.2.6) (2020-01-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.4...@ohif/extension-vtk@1.2.5) (2020-01-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.3...@ohif/extension-vtk@1.2.4) (2019-12-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.2...@ohif/extension-vtk@1.2.3) (2019-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.1...@ohif/extension-vtk@1.2.2) (2019-12-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.0...@ohif/extension-vtk@1.2.1) (2019-12-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.7...@ohif/extension-vtk@1.2.0) (2019-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Configuration so viewer tools can nix handles ([#1304](https://github.com/OHIF/Viewers/issues/1304)) ([63594d3](https://github.com/OHIF/Viewers/commit/63594d36b0bdba59f0901095aed70b75fb05172d)), closes [#1223](https://github.com/OHIF/Viewers/issues/1223)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.1.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.6...@ohif/extension-vtk@1.1.7) (2019-12-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.5...@ohif/extension-vtk@1.1.6) (2019-12-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.1.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.4...@ohif/extension-vtk@1.1.5) (2019-12-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.3...@ohif/extension-vtk@1.1.4) (2019-12-16)
|
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.1.3...@ohif/extension-vtk@1.1.4) (2019-12-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/extension-vtk
|
**Note:** Version bump only for package @ohif/extension-vtk
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/extension-vtk",
|
"name": "@ohif/extension-vtk",
|
||||||
"version": "1.1.4",
|
"version": "1.2.14",
|
||||||
"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.1.1",
|
"@ohif/core": "^2.3.8",
|
||||||
"@ohif/ui": "^1.1.4",
|
"@ohif/ui": "^1.1.9",
|
||||||
"cornerstone-tools": "^4.8.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",
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.vtk-viewport-handler svg {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -234,7 +237,7 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Sagital
|
// Sagittal
|
||||||
orientation: {
|
orientation: {
|
||||||
sliceNormal: [1, 0, 0],
|
sliceNormal: [1, 0, 0],
|
||||||
viewUp: [0, 0, 1],
|
viewUp: [0, 0, 1],
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { ToolbarButton } from '@ohif/ui';
|
||||||
|
import { utils } from '@ohif/core';
|
||||||
|
|
||||||
|
const { studyMetadataManager } = utils;
|
||||||
|
|
||||||
|
let isVisible = true;
|
||||||
|
|
||||||
|
const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportIndex) => {
|
||||||
|
if (!viewportSpecificData[activeViewportIndex]) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { displaySetInstanceUid, studyInstanceUid } = viewportSpecificData[
|
||||||
|
activeViewportIndex
|
||||||
|
];
|
||||||
|
|
||||||
|
const studies = studyMetadataManager.all();
|
||||||
|
|
||||||
|
const study = studies.find(
|
||||||
|
study => study.studyInstanceUID === studyInstanceUid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!study) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displaySet = study._displaySets.find(set => set.displaySetInstanceUid === displaySetInstanceUid);
|
||||||
|
|
||||||
|
if (!displaySet) {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return displaySet.isReconstructable;
|
||||||
|
};
|
||||||
|
|
||||||
|
function VTKMPRToolbarButton({
|
||||||
|
parentContext,
|
||||||
|
toolbarClickCallback,
|
||||||
|
button,
|
||||||
|
activeButtons,
|
||||||
|
isActive,
|
||||||
|
className,
|
||||||
|
}) {
|
||||||
|
const { id, label, icon } = button;
|
||||||
|
const { viewportSpecificData, activeViewportIndex } = useSelector(state => {
|
||||||
|
const { viewports = {} } = state;
|
||||||
|
const { viewportSpecificData, activeViewportIndex } = viewports;
|
||||||
|
|
||||||
|
return {
|
||||||
|
viewportSpecificData,
|
||||||
|
activeViewportIndex,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
isVisible = _isDisplaySetReconstructable(
|
||||||
|
viewportSpecificData,
|
||||||
|
activeViewportIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{isVisible && (
|
||||||
|
<ToolbarButton
|
||||||
|
key={id}
|
||||||
|
label={label}
|
||||||
|
icon={icon}
|
||||||
|
onClick={evt => toolbarClickCallback(button, evt)}
|
||||||
|
isActive={isActive}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VTKMPRToolbarButton.propTypes = {
|
||||||
|
parentContext: PropTypes.object.isRequired,
|
||||||
|
toolbarClickCallback: PropTypes.func.isRequired,
|
||||||
|
button: PropTypes.object.isRequired,
|
||||||
|
activeButtons: PropTypes.array.isRequired,
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VTKMPRToolbarButton;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import SlabThicknessToolbarComponent from './toolbarComponents/SlabThicknessToolbarComponent';
|
import SlabThicknessToolbarComponent from './toolbarComponents/SlabThicknessToolbarComponent';
|
||||||
|
import VTKMPRToolbarButton from './toolbarComponents/VTKMPRToolbarButton';
|
||||||
|
|
||||||
const TOOLBAR_BUTTON_TYPES = {
|
const TOOLBAR_BUTTON_TYPES = {
|
||||||
COMMAND: 'command',
|
COMMAND: 'command',
|
||||||
@@ -115,9 +116,19 @@ const definitions = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: '2DMPR',
|
||||||
|
label: '2D MPR',
|
||||||
|
icon: 'cube',
|
||||||
|
//
|
||||||
|
CustomComponent: VTKMPRToolbarButton,
|
||||||
|
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||||
|
commandName: 'mpr2d',
|
||||||
|
context: 'ACTIVE_VIEWPORT::CORNERSTONE',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
definitions,
|
definitions,
|
||||||
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
||||||
};
|
}
|
||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -3,6 +3,109 @@
|
|||||||
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.8](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.7...@ohif/core@2.3.8) (2020-02-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Remove trash data from redux storage after updates ([#1358](https://github.com/OHIF/Viewers/issues/1358)) ([7b2d44f](https://github.com/OHIF/Viewers/commit/7b2d44f2c18241ea521b8d3652aee32e36eaddb8))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.3.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.0...@ohif/core@2.3.1) (2019-12-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.3.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.2.1...@ohif/core@2.3.0) (2019-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Configuration so viewer tools can nix handles ([#1304](https://github.com/OHIF/Viewers/issues/1304)) ([63594d3](https://github.com/OHIF/Viewers/commit/63594d36b0bdba59f0901095aed70b75fb05172d)), closes [#1223](https://github.com/OHIF/Viewers/issues/1223)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.2.0...@ohif/core@2.2.1) (2019-12-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.2.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.1.1...@ohif/core@2.2.0) (2019-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Expose extension config to modules ([#1279](https://github.com/OHIF/Viewers/issues/1279)) ([4ea239a](https://github.com/OHIF/Viewers/commit/4ea239a9535ef297e23387c186e537ab273744ea)), closes [#1268](https://github.com/OHIF/Viewers/issues/1268)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.1.0...@ohif/core@2.1.1) (2019-12-16)
|
## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.1.0...@ohif/core@2.1.1) (2019-12-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/core
|
**Note:** Version bump only for package @ohif/core
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/core",
|
"name": "@ohif/core",
|
||||||
"version": "2.1.1",
|
"version": "2.3.8",
|
||||||
"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,13 +32,14 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-tools": "^4.8.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"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.5.5",
|
"@babel/runtime": "^7.5.5",
|
||||||
"ajv": "^6.10.0",
|
"ajv": "^6.10.0",
|
||||||
|
"dcmjs": "^0.8.3",
|
||||||
"dicomweb-client": "^0.5.2",
|
"dicomweb-client": "^0.5.2",
|
||||||
"isomorphic-base64": "^1.0.2",
|
"isomorphic-base64": "^1.0.2",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
|||||||
+10
-3
@@ -1,5 +1,10 @@
|
|||||||
import { log, studies, utils } from '@ohif/core';
|
import log from '../log';
|
||||||
import { retrieveMeasurementFromSR, stowSRFromMeasurements } from './handleStructuredReport';
|
import studies from '../studies';
|
||||||
|
import utils from '../utils';
|
||||||
|
import {
|
||||||
|
retrieveMeasurementFromSR,
|
||||||
|
stowSRFromMeasurements,
|
||||||
|
} from './handleStructuredReport';
|
||||||
import findMostRecentStructuredReport from './utils/findMostRecentStructuredReport';
|
import findMostRecentStructuredReport from './utils/findMostRecentStructuredReport';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +70,9 @@ const storeMeasurements = async (measurementData, filter, server) => {
|
|||||||
message: 'Measurements were saved with success',
|
message: 'Measurements were saved with success',
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`);
|
log.error(
|
||||||
|
`[DICOMSR] Error while saving the measurements: ${error.message}`
|
||||||
|
);
|
||||||
throw new Error('Error while saving the measurements.');
|
throw new Error('Error while saving the measurements.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
+9
-9
@@ -1,11 +1,11 @@
|
|||||||
import * as dcmjs from 'dcmjs';
|
import * as dcmjs from 'dcmjs';
|
||||||
import { DICOMWeb } from '@ohif/core';
|
import { api } from 'dicomweb-client';
|
||||||
|
|
||||||
|
import DICOMWeb from '../DICOMWeb';
|
||||||
import parseDicomStructuredReport from './parseDicomStructuredReport';
|
import parseDicomStructuredReport from './parseDicomStructuredReport';
|
||||||
import parseMeasurementsData from './parseMeasurementsData';
|
import parseMeasurementsData from './parseMeasurementsData';
|
||||||
import getAllDisplaySets from './utils/getAllDisplaySets';
|
import getAllDisplaySets from './utils/getAllDisplaySets';
|
||||||
|
|
||||||
import { api } from 'dicomweb-client';
|
|
||||||
|
|
||||||
const VERSION_NAME = 'dcmjs-0.0';
|
const VERSION_NAME = 'dcmjs-0.0';
|
||||||
const TRANSFER_SYNTAX_UID = '1.2.840.10008.1.2.1';
|
const TRANSFER_SYNTAX_UID = '1.2.840.10008.1.2.1';
|
||||||
|
|
||||||
@@ -34,7 +34,10 @@ const retrieveMeasurementFromSR = async (series, studies, serverUrl) => {
|
|||||||
|
|
||||||
const part10SRArrayBuffer = await dicomWeb.retrieveInstance(options);
|
const part10SRArrayBuffer = await dicomWeb.retrieveInstance(options);
|
||||||
const displaySets = getAllDisplaySets(studies);
|
const displaySets = getAllDisplaySets(studies);
|
||||||
const measurementsData = parseDicomStructuredReport(part10SRArrayBuffer, displaySets);
|
const measurementsData = parseDicomStructuredReport(
|
||||||
|
part10SRArrayBuffer,
|
||||||
|
displaySets
|
||||||
|
);
|
||||||
|
|
||||||
return measurementsData;
|
return measurementsData;
|
||||||
};
|
};
|
||||||
@@ -47,13 +50,10 @@ const retrieveMeasurementFromSR = async (series, studies, serverUrl) => {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
const stowSRFromMeasurements = async (measurements, serverUrl) => {
|
const stowSRFromMeasurements = async (measurements, serverUrl) => {
|
||||||
const { dataset } = parseMeasurementsData(
|
const { dataset } = parseMeasurementsData(measurements);
|
||||||
measurements
|
|
||||||
);
|
|
||||||
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
|
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
|
||||||
const meta = {
|
const meta = {
|
||||||
FileMetaInformationVersion:
|
FileMetaInformationVersion: dataset._meta.FileMetaInformationVersion.Value,
|
||||||
dataset._meta.FileMetaInformationVersion.Value,
|
|
||||||
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
||||||
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
||||||
TransferSyntaxUID: TRANSFER_SYNTAX_UID,
|
TransferSyntaxUID: TRANSFER_SYNTAX_UID,
|
||||||
File renamed without changes.
+17
-4
@@ -1,4 +1,5 @@
|
|||||||
import * as dcmjs from 'dcmjs';
|
import * as dcmjs from 'dcmjs';
|
||||||
|
|
||||||
import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBySopInstanceUid';
|
import findInstanceMetadataBySopInstanceUid from './utils/findInstanceMetadataBySopInstanceUid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,7 +38,12 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
|||||||
const { studyInstanceUid, patientId } = study;
|
const { studyInstanceUid, patientId } = study;
|
||||||
const { seriesInstanceUid } = series;
|
const { seriesInstanceUid } = series;
|
||||||
const { sopInstanceUid, frameIndex } = measurement;
|
const { sopInstanceUid, frameIndex } = measurement;
|
||||||
const imagePath = getImagePath(studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex);
|
const imagePath = getImagePath(
|
||||||
|
studyInstanceUid,
|
||||||
|
seriesInstanceUid,
|
||||||
|
sopInstanceUid,
|
||||||
|
frameIndex
|
||||||
|
);
|
||||||
|
|
||||||
const imageId = instanceMetadata.getImageId();
|
const imageId = instanceMetadata.getImageId();
|
||||||
if (!imageId) {
|
if (!imageId) {
|
||||||
@@ -75,8 +81,15 @@ const parseDicomStructuredReport = (part10SRArrayBuffer, displaySets) => {
|
|||||||
* @param {string} frameIndex
|
* @param {string} frameIndex
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const getImagePath = (studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex) => {
|
const getImagePath = (
|
||||||
return [studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex].join('_');
|
studyInstanceUid,
|
||||||
}
|
seriesInstanceUid,
|
||||||
|
sopInstanceUid,
|
||||||
|
frameIndex
|
||||||
|
) => {
|
||||||
|
return [studyInstanceUid, seriesInstanceUid, sopInstanceUid, frameIndex].join(
|
||||||
|
'_'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default parseDicomStructuredReport;
|
export default parseDicomStructuredReport;
|
||||||
+3
-1
@@ -1,6 +1,8 @@
|
|||||||
import * as dcmjs from 'dcmjs';
|
import * as dcmjs from 'dcmjs';
|
||||||
import { log, measurements } from '@ohif/core';
|
|
||||||
import cornerstone from 'cornerstone-core';
|
import cornerstone from 'cornerstone-core';
|
||||||
|
|
||||||
|
import log from '../log';
|
||||||
|
import measurements from '../measurements';
|
||||||
import isToolSupported from './utils/isToolSupported';
|
import isToolSupported from './utils/isToolSupported';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
+3
-1
@@ -11,7 +11,9 @@ const findInstanceMetadataBySopInstanceUid = (displaySets, sopInstanceUid) => {
|
|||||||
displaySets.find(displaySet => {
|
displaySets.find(displaySet => {
|
||||||
if (!displaySet.images) return false;
|
if (!displaySet.images) return false;
|
||||||
|
|
||||||
instanceFound = displaySet.images.find(instanceMetadata => instanceMetadata._sopInstanceUID === sopInstanceUid);
|
instanceFound = displaySet.images.find(
|
||||||
|
instanceMetadata => instanceMetadata._sopInstanceUID === sopInstanceUid
|
||||||
|
);
|
||||||
|
|
||||||
return !!instanceFound;
|
return !!instanceFound;
|
||||||
});
|
});
|
||||||
+12
-9
@@ -4,15 +4,17 @@
|
|||||||
* @param {Array} studies
|
* @param {Array} studies
|
||||||
* @returns {Object} Series
|
* @returns {Object} Series
|
||||||
*/
|
*/
|
||||||
const findMostRecentStructuredReport = (studies) => {
|
const findMostRecentStructuredReport = studies => {
|
||||||
let mostRecentStructuredReport;
|
let mostRecentStructuredReport;
|
||||||
|
|
||||||
studies.forEach(study => {
|
studies.forEach(study => {
|
||||||
const allSeries = study.getSeries ? study.getSeries() : [];
|
const allSeries = study.getSeries ? study.getSeries() : [];
|
||||||
allSeries.forEach(series => {
|
allSeries.forEach(series => {
|
||||||
|
|
||||||
if (isStructuredReportSeries(series)) {
|
if (isStructuredReportSeries(series)) {
|
||||||
if (!mostRecentStructuredReport || compareSeriesDate(series, mostRecentStructuredReport)) {
|
if (
|
||||||
|
!mostRecentStructuredReport ||
|
||||||
|
compareSeriesDate(series, mostRecentStructuredReport)
|
||||||
|
) {
|
||||||
mostRecentStructuredReport = series;
|
mostRecentStructuredReport = series;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +30,7 @@ const findMostRecentStructuredReport = (studies) => {
|
|||||||
* @param {Object} series - Series metadata
|
* @param {Object} series - Series metadata
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
const isStructuredReportSeries = (series) => {
|
const isStructuredReportSeries = series => {
|
||||||
const supportedSopClassUIDs = [
|
const supportedSopClassUIDs = [
|
||||||
'1.2.840.10008.5.1.4.1.1.88.22',
|
'1.2.840.10008.5.1.4.1.1.88.22',
|
||||||
'1.2.840.10008.5.1.4.1.1.11.1',
|
'1.2.840.10008.5.1.4.1.1.11.1',
|
||||||
@@ -38,7 +40,7 @@ const isStructuredReportSeries = (series) => {
|
|||||||
const sopClassUid = firstInstance._instance.sopClassUid;
|
const sopClassUid = firstInstance._instance.sopClassUid;
|
||||||
|
|
||||||
return supportedSopClassUIDs.includes(sopClassUid);
|
return supportedSopClassUIDs.includes(sopClassUid);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checkes if series1 is newer than series2
|
* Checkes if series1 is newer than series2
|
||||||
@@ -48,10 +50,11 @@ const isStructuredReportSeries = (series) => {
|
|||||||
* @returns {boolean} true/false if series1 is newer than series2
|
* @returns {boolean} true/false if series1 is newer than series2
|
||||||
*/
|
*/
|
||||||
const compareSeriesDate = (series1, series2) => {
|
const compareSeriesDate = (series1, series2) => {
|
||||||
return series1._data.seriesDate > series2._data.seriesDate ||
|
return (
|
||||||
|
series1._data.seriesDate > series2._data.seriesDate ||
|
||||||
(series1._data.seriesDate === series2._data.seriesDate &&
|
(series1._data.seriesDate === series2._data.seriesDate &&
|
||||||
series1._data.seriesTime > series2._data.seriesTime);
|
series1._data.seriesTime > series2._data.seriesTime)
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default findMostRecentStructuredReport;
|
export default findMostRecentStructuredReport;
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
* @param {Object} studies - List of studies loaded into the viewer
|
* @param {Object} studies - List of studies loaded into the viewer
|
||||||
* @returns {Object} List of DisplaySets
|
* @returns {Object} List of DisplaySets
|
||||||
*/
|
*/
|
||||||
const getAllDisplaySets = (studies) => {
|
const getAllDisplaySets = studies => {
|
||||||
let allDisplaySets = [];
|
let allDisplaySets = [];
|
||||||
|
|
||||||
studies.forEach(study => {
|
studies.forEach(study => {
|
||||||
File renamed without changes.
@@ -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 = {};
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import MODULE_TYPES from './MODULE_TYPES.js';
|
|||||||
import log from './../log.js';
|
import log from './../log.js';
|
||||||
|
|
||||||
export default class ExtensionManager {
|
export default class ExtensionManager {
|
||||||
constructor({ commandsManager, servicesManager }) {
|
constructor({ commandsManager, servicesManager, appConfig = {} }) {
|
||||||
this.modules = {};
|
this.modules = {};
|
||||||
this.registeredExtensionIds = [];
|
this.registeredExtensionIds = [];
|
||||||
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
||||||
//
|
//
|
||||||
this._commandsManager = commandsManager;
|
this._commandsManager = commandsManager;
|
||||||
this._servicesManager = servicesManager;
|
this._servicesManager = servicesManager;
|
||||||
|
this._appConfig = appConfig;
|
||||||
|
|
||||||
this.moduleTypeNames.forEach(moduleType => {
|
this.moduleTypeNames.forEach(moduleType => {
|
||||||
this.modules[moduleType] = [];
|
this.modules[moduleType] = [];
|
||||||
@@ -70,6 +71,7 @@ export default class ExtensionManager {
|
|||||||
extension.preRegistration({
|
extension.preRegistration({
|
||||||
servicesManager: this._servicesManager,
|
servicesManager: this._servicesManager,
|
||||||
commandsManager: this._commandsManager,
|
commandsManager: this._commandsManager,
|
||||||
|
appConfig: this._appConfig,
|
||||||
configuration,
|
configuration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -79,7 +81,8 @@ export default class ExtensionManager {
|
|||||||
const extensionModule = this._getExtensionModule(
|
const extensionModule = this._getExtensionModule(
|
||||||
moduleType,
|
moduleType,
|
||||||
extension,
|
extension,
|
||||||
extensionId
|
extensionId,
|
||||||
|
configuration
|
||||||
);
|
);
|
||||||
|
|
||||||
if (extensionModule) {
|
if (extensionModule) {
|
||||||
@@ -102,7 +105,7 @@ export default class ExtensionManager {
|
|||||||
* @param {Object} extension
|
* @param {Object} extension
|
||||||
* @param {string} extensionId - Used for logging warnings
|
* @param {string} extensionId - Used for logging warnings
|
||||||
*/
|
*/
|
||||||
_getExtensionModule(moduleType, extension, extensionId) {
|
_getExtensionModule(moduleType, extension, extensionId, configuration) {
|
||||||
const getModuleFnName = 'get' + _capitalizeFirstCharacter(moduleType);
|
const getModuleFnName = 'get' + _capitalizeFirstCharacter(moduleType);
|
||||||
const getModuleFn = extension[getModuleFnName];
|
const getModuleFn = extension[getModuleFnName];
|
||||||
|
|
||||||
@@ -114,6 +117,8 @@ export default class ExtensionManager {
|
|||||||
const extensionModule = getModuleFn({
|
const extensionModule = getModuleFn({
|
||||||
servicesManager: this._servicesManager,
|
servicesManager: this._servicesManager,
|
||||||
commandsManager: this._commandsManager,
|
commandsManager: this._commandsManager,
|
||||||
|
appConfig: this._appConfig,
|
||||||
|
configuration,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!extensionModule) {
|
if (!extensionModule) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import log from './../log.js';
|
|||||||
jest.mock('./../log.js');
|
jest.mock('./../log.js');
|
||||||
|
|
||||||
describe('ExtensionManager.js', () => {
|
describe('ExtensionManager.js', () => {
|
||||||
let extensionManager, commandsManager;
|
let extensionManager, commandsManager, servicesManager, appConfig;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
commandsManager = {
|
commandsManager = {
|
||||||
@@ -14,7 +14,17 @@ describe('ExtensionManager.js', () => {
|
|||||||
getContext: jest.fn(),
|
getContext: jest.fn(),
|
||||||
registerCommand: jest.fn(),
|
registerCommand: jest.fn(),
|
||||||
};
|
};
|
||||||
extensionManager = new ExtensionManager({ commandsManager });
|
servicesManager = {
|
||||||
|
registerService: jest.fn(),
|
||||||
|
};
|
||||||
|
appConfig = {
|
||||||
|
testing: true,
|
||||||
|
};
|
||||||
|
extensionManager = new ExtensionManager({
|
||||||
|
servicesManager,
|
||||||
|
commandsManager,
|
||||||
|
appConfig,
|
||||||
|
});
|
||||||
log.warn.mockClear();
|
log.warn.mockClear();
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
@@ -51,7 +61,7 @@ describe('ExtensionManager.js', () => {
|
|||||||
extensionManager.registerExtensions(fakeExtensions);
|
extensionManager.registerExtensions(fakeExtensions);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(extensionManager.registerExtension.mock.calls[1]).toContain(
|
expect(extensionManager.registerExtension.mock.calls[1][1]).toEqual(
|
||||||
fakeConfiguration
|
fakeConfiguration
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -67,19 +77,19 @@ describe('ExtensionManager.js', () => {
|
|||||||
expect(fakeExtension.preRegistration.mock.calls.length).toBe(1);
|
expect(fakeExtension.preRegistration.mock.calls.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls preRegistration() passing configuration along with servicesManager and commandsManager instances for extension', () => {
|
it('calls preRegistration() passing dependencies and extension configuration to extension', () => {
|
||||||
const configuration = { config: 'Some configuration' };
|
const extensionConfiguration = { config: 'Some configuration' };
|
||||||
extensionManager._servicesManager = { services: { TestService: {} } };
|
|
||||||
|
|
||||||
// SUT
|
// SUT
|
||||||
const fakeExtension = { one: '1', preRegistration: jest.fn() };
|
const extension = { one: '1', preRegistration: jest.fn() };
|
||||||
extensionManager.registerExtension(fakeExtension, configuration);
|
extensionManager.registerExtension(extension, extensionConfiguration);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(fakeExtension.preRegistration.mock.calls[0][0]).toEqual({
|
expect(extension.preRegistration.mock.calls[0][0]).toEqual({
|
||||||
servicesManager: extensionManager._servicesManager,
|
servicesManager,
|
||||||
commandsManager: extensionManager._commandsManager,
|
commandsManager,
|
||||||
configuration,
|
appConfig,
|
||||||
|
configuration: extensionConfiguration,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,8 +163,8 @@ describe('ExtensionManager.js', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('successfully passes a servicesManager and commandsManager instances to each module', () => {
|
it('successfully passes dependencies to each module along with extension configuration', () => {
|
||||||
extensionManager._servicesManager = { services: { TestService: {} } };
|
const extensionConfiguration = { testing: true };
|
||||||
|
|
||||||
const extension = {
|
const extension = {
|
||||||
id: 'hello-world',
|
id: 'hello-world',
|
||||||
@@ -165,11 +175,17 @@ describe('ExtensionManager.js', () => {
|
|||||||
getCommandsModule: jest.fn(),
|
getCommandsModule: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
extensionManager.registerExtension(extension);
|
extensionManager.registerExtension(extension, extensionConfiguration);
|
||||||
|
|
||||||
expect(extension.getViewportModule.mock.calls[0][0]).toEqual({
|
Object.keys(extension).forEach(module => {
|
||||||
servicesManager: extensionManager._servicesManager,
|
if (typeof extension[module] === 'function') {
|
||||||
commandsManager: extensionManager._commandsManager,
|
expect(extension[module].mock.calls[0][0]).toEqual({
|
||||||
|
servicesManager,
|
||||||
|
commandsManager,
|
||||||
|
appConfig,
|
||||||
|
configuration: extensionConfiguration,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -210,7 +226,7 @@ describe('ExtensionManager.js', () => {
|
|||||||
return {
|
return {
|
||||||
definitions: {
|
definitions: {
|
||||||
exampleDefinition: {
|
exampleDefinition: {
|
||||||
commandFn: () => {},
|
commandFn: () => { },
|
||||||
storeContexts: [],
|
storeContexts: [],
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ServicesManager } from './services';
|
|||||||
import classes, { CommandsManager, HotkeysManager } from './classes/';
|
import classes, { CommandsManager, HotkeysManager } from './classes/';
|
||||||
|
|
||||||
import DICOMWeb from './DICOMWeb';
|
import DICOMWeb from './DICOMWeb';
|
||||||
|
import DICOMSR from './DICOMSR';
|
||||||
import cornerstone from './cornerstone.js';
|
import cornerstone from './cornerstone.js';
|
||||||
import hangingProtocols from './hanging-protocols';
|
import hangingProtocols from './hanging-protocols';
|
||||||
import header from './header.js';
|
import header from './header.js';
|
||||||
@@ -46,6 +47,7 @@ const OHIF = {
|
|||||||
object,
|
object,
|
||||||
log,
|
log,
|
||||||
DICOMWeb,
|
DICOMWeb,
|
||||||
|
DICOMSR,
|
||||||
viewer: {},
|
viewer: {},
|
||||||
measurements,
|
measurements,
|
||||||
hangingProtocols,
|
hangingProtocols,
|
||||||
@@ -76,6 +78,7 @@ export {
|
|||||||
object,
|
object,
|
||||||
log,
|
log,
|
||||||
DICOMWeb,
|
DICOMWeb,
|
||||||
|
DICOMSR,
|
||||||
measurements,
|
measurements,
|
||||||
hangingProtocols,
|
hangingProtocols,
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ describe('Top level exports', () => {
|
|||||||
'object',
|
'object',
|
||||||
'log',
|
'log',
|
||||||
'DICOMWeb',
|
'DICOMWeb',
|
||||||
|
'DICOMSR',
|
||||||
'OHIF', //
|
'OHIF', //
|
||||||
'measurements',
|
'measurements',
|
||||||
'hangingProtocols',
|
'hangingProtocols',
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|
||||||
|
|||||||
@@ -11,20 +11,63 @@ import {
|
|||||||
SET_VIEWPORT_LAYOUT_AND_DATA,
|
SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||||
} from './../constants/ActionTypes.js';
|
} from './../constants/ActionTypes.js';
|
||||||
|
|
||||||
const DEFAULT_STATE = {
|
export const DEFAULT_STATE = {
|
||||||
numRows: 1,
|
numRows: 1,
|
||||||
numColumns: 1,
|
numColumns: 1,
|
||||||
activeViewportIndex: 0,
|
activeViewportIndex: 0,
|
||||||
layout: {
|
layout: {
|
||||||
viewports: [
|
viewports: [{}],
|
||||||
{
|
|
||||||
// plugin: 'cornerstone',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
viewportSpecificData: {},
|
viewportSpecificData: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the new number of rows and columns, delete all not used viewport data and also set
|
||||||
|
* active viewport as default in case current one is not available anymore.
|
||||||
|
*
|
||||||
|
* @param {Number} numRows
|
||||||
|
* @param {Number} numColumns
|
||||||
|
* @param {Object} currentViewportSpecificData
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const findActiveViewportSpecificData = (
|
||||||
|
numRows,
|
||||||
|
numColumns,
|
||||||
|
currentViewportSpecificData = {}
|
||||||
|
) => {
|
||||||
|
const numberOfViewports = numRows * numColumns;
|
||||||
|
const viewportSpecificData = cloneDeep(currentViewportSpecificData);
|
||||||
|
|
||||||
|
if (numberOfViewports < Object.keys(viewportSpecificData).length) {
|
||||||
|
Object.keys(viewportSpecificData).forEach(key => {
|
||||||
|
if (key > numberOfViewports - 1) {
|
||||||
|
delete viewportSpecificData[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewportSpecificData;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Take new number of rows and columns and make sure the current active viewport index is still available, if not, return the default
|
||||||
|
*
|
||||||
|
* @param {Number} numRows
|
||||||
|
* @param {Number} numColumns
|
||||||
|
* @param {Number} currentActiveViewportIndex
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getActiveViewportIndex = (
|
||||||
|
numRows,
|
||||||
|
numColumns,
|
||||||
|
currentActiveViewportIndex
|
||||||
|
) => {
|
||||||
|
const numberOfViewports = numRows * numColumns;
|
||||||
|
|
||||||
|
return currentActiveViewportIndex > numberOfViewports - 1
|
||||||
|
? DEFAULT_STATE.activeViewportIndex
|
||||||
|
: currentActiveViewportIndex;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The definition of a viewport action.
|
* The definition of a viewport action.
|
||||||
*
|
*
|
||||||
@@ -50,7 +93,12 @@ const viewports = (state = DEFAULT_STATE, action) => {
|
|||||||
* @return {Object} New state.
|
* @return {Object} New state.
|
||||||
*/
|
*/
|
||||||
case SET_VIEWPORT_ACTIVE: {
|
case SET_VIEWPORT_ACTIVE: {
|
||||||
return { ...state, activeViewportIndex: action.viewportIndex };
|
const activeViewportIndex = getActiveViewportIndex(
|
||||||
|
state.numRows,
|
||||||
|
state.numColumns,
|
||||||
|
action.viewportIndex
|
||||||
|
);
|
||||||
|
return { ...state, activeViewportIndex };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,11 +107,25 @@ const viewports = (state = DEFAULT_STATE, action) => {
|
|||||||
* @return {Object} New state.
|
* @return {Object} New state.
|
||||||
*/
|
*/
|
||||||
case SET_VIEWPORT_LAYOUT: {
|
case SET_VIEWPORT_LAYOUT: {
|
||||||
|
const { numRows, numColumns } = action;
|
||||||
|
const viewportSpecificData = findActiveViewportSpecificData(
|
||||||
|
numRows,
|
||||||
|
numColumns,
|
||||||
|
state.viewportSpecificData
|
||||||
|
);
|
||||||
|
const activeViewportIndex = getActiveViewportIndex(
|
||||||
|
numRows,
|
||||||
|
numColumns,
|
||||||
|
state.activeViewportIndex
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
numRows: action.numRows,
|
numRows: action.numRows,
|
||||||
numColumns: action.numColumns,
|
numColumns: action.numColumns,
|
||||||
layout: { viewports: [...action.viewports] },
|
layout: { viewports: [...action.viewports] },
|
||||||
|
viewportSpecificData,
|
||||||
|
activeViewportIndex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,12 +135,25 @@ const viewports = (state = DEFAULT_STATE, action) => {
|
|||||||
* @return {Object} New state.
|
* @return {Object} New state.
|
||||||
*/
|
*/
|
||||||
case SET_VIEWPORT_LAYOUT_AND_DATA: {
|
case SET_VIEWPORT_LAYOUT_AND_DATA: {
|
||||||
|
const { numRows, numColumns } = action;
|
||||||
|
const viewportSpecificData = findActiveViewportSpecificData(
|
||||||
|
numRows,
|
||||||
|
numColumns,
|
||||||
|
action.viewportSpecificData
|
||||||
|
);
|
||||||
|
const activeViewportIndex = getActiveViewportIndex(
|
||||||
|
numRows,
|
||||||
|
numColumns,
|
||||||
|
state.activeViewportIndex
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
numRows: action.numRows,
|
numRows: action.numRows,
|
||||||
numColumns: action.numColumns,
|
numColumns: action.numColumns,
|
||||||
layout: { viewports: [...action.viewports] },
|
layout: { viewports: [...action.viewports] },
|
||||||
viewportSpecificData: cloneDeep(action.viewportSpecificData),
|
viewportSpecificData,
|
||||||
|
activeViewportIndex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,61 @@
|
|||||||
// https://medium.com/@netxm/testing-redux-reducers-with-jest-6653abbfe3e1
|
import { Reducer } from 'redux-testkit';
|
||||||
import reducer from './viewports.js';
|
|
||||||
|
import reducer, { DEFAULT_STATE } from './viewports.js';
|
||||||
import * as types from './../constants/ActionTypes.js';
|
import * as types from './../constants/ActionTypes.js';
|
||||||
|
|
||||||
describe('viewports reducer', () => {
|
describe('viewports reducer', () => {
|
||||||
it('should return the initial state', () => {
|
it('should return the initial state', () => {
|
||||||
expect(reducer(undefined, {})).toEqual({
|
expect(reducer(undefined, {})).toEqual(DEFAULT_STATE);
|
||||||
activeViewportIndex: 0,
|
|
||||||
numRows: 1,
|
|
||||||
numColumns: 1,
|
|
||||||
layout: {
|
|
||||||
viewports: [{}],
|
|
||||||
},
|
|
||||||
viewportSpecificData: {},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle SET_VIEWPORT_ACTIVE', () => {
|
it('should handle SET_VIEWPORT_ACTIVE with inexistent viewport index', () => {
|
||||||
const setViewportActiveAction = {
|
const initialState = {
|
||||||
|
numRows: 4,
|
||||||
|
numColumns: 4,
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
type: types.SET_VIEWPORT_ACTIVE,
|
type: types.SET_VIEWPORT_ACTIVE,
|
||||||
viewportIndex: 100,
|
viewportIndex: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedState = reducer({}, setViewportActiveAction);
|
const expectedToChangeInState = {
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
expect(updatedState.activeViewportIndex).toEqual(
|
Reducer(reducer)
|
||||||
setViewportActiveAction.viewportIndex
|
.withState(initialState)
|
||||||
);
|
.expect(action)
|
||||||
|
.toChangeInState(expectedToChangeInState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle SET_VIEWPORT_ACTIVE with existent viewport index', () => {
|
||||||
|
const initialState = {
|
||||||
|
numRows: 4,
|
||||||
|
numColumns: 4,
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: types.SET_VIEWPORT_ACTIVE,
|
||||||
|
viewportIndex: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedToChangeInState = {
|
||||||
|
activeViewportIndex: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
Reducer(reducer)
|
||||||
|
.withState(initialState)
|
||||||
|
.expect(action)
|
||||||
|
.toChangeInState(expectedToChangeInState);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle SET_VIEWPORT_LAYOUT', () => {
|
it('should handle SET_VIEWPORT_LAYOUT', () => {
|
||||||
const setViewportLayoutAction = {
|
const initialState = DEFAULT_STATE;
|
||||||
|
|
||||||
|
const action = {
|
||||||
type: types.SET_VIEWPORT_LAYOUT,
|
type: types.SET_VIEWPORT_LAYOUT,
|
||||||
numRows: 1,
|
numRows: 1,
|
||||||
numColumns: 2,
|
numColumns: 2,
|
||||||
@@ -43,18 +69,193 @@ describe('viewports reducer', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedState = reducer({}, setViewportLayoutAction);
|
const expectedToChangeInState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 2,
|
||||||
|
layout: {
|
||||||
|
viewports: [
|
||||||
|
{
|
||||||
|
plugin: 'cornerstone',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugin: 'vtk',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
expect(updatedState.numRows).toEqual(setViewportLayoutAction.numRows);
|
Reducer(reducer)
|
||||||
expect(updatedState.numColumns).toEqual(setViewportLayoutAction.numColumns);
|
.withState(initialState)
|
||||||
expect(updatedState.layout.viewports).toEqual(
|
.expect(action)
|
||||||
setViewportLayoutAction.viewports
|
.toChangeInState(expectedToChangeInState);
|
||||||
);
|
});
|
||||||
|
|
||||||
|
it('should handle SET_VIEWPORT_LAYOUT when we reduce the number of viewports', () => {
|
||||||
|
const initialState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 2,
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'viewportData0' },
|
||||||
|
1: { viewportData1: 'viewportData1' },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
viewports: [],
|
||||||
|
},
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: types.SET_VIEWPORT_LAYOUT,
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 1,
|
||||||
|
viewports: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 1,
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'viewportData0' },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
viewports: [],
|
||||||
|
},
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Reducer(reducer)
|
||||||
|
.withState(initialState)
|
||||||
|
.expect(action)
|
||||||
|
.toReturnState(expectedState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle SET_VIEWPORT_LAYOUT_AND_DATA', () => {
|
||||||
|
const initialState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 1,
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'data0' },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
viewports: [{ plugin: 'cornerstone' }],
|
||||||
|
},
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: types.SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 2,
|
||||||
|
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'NEWdata0' },
|
||||||
|
1: { viewportData1: 'NEWdata1' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 2,
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'NEWdata0' },
|
||||||
|
1: { viewportData1: 'NEWdata1' },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
|
||||||
|
},
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Reducer(reducer)
|
||||||
|
.withState(initialState)
|
||||||
|
.expect(action)
|
||||||
|
.toReturnState(expectedState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle SET_VIEWPORT_LAYOUT_AND_DATA when we reduce the number of viewports', () => {
|
||||||
|
const initialState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 3,
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'vtkData0' },
|
||||||
|
1: { viewportData1: 'vtkData1' },
|
||||||
|
2: { viewportData2: 'vtkData2' },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
viewports: [{ plugin: 'vtk' }, { plugin: 'vtk' }, { plugin: 'vtk' }],
|
||||||
|
},
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: types.SET_VIEWPORT_LAYOUT_AND_DATA,
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 1,
|
||||||
|
viewports: [{ plugin: 'cornerstone' }],
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'cornerstoneData0' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 1,
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'cornerstoneData0' },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
viewports: [{ plugin: 'cornerstone' }],
|
||||||
|
},
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Reducer(reducer)
|
||||||
|
.withState(initialState)
|
||||||
|
.expect(action)
|
||||||
|
.toReturnState(expectedState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle SET_VIEWPORT when we only set one viewport specific data', () => {
|
||||||
|
const initialState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 2,
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'data0' },
|
||||||
|
1: { viewportData1: 'data1' },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
|
||||||
|
},
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: types.SET_VIEWPORT,
|
||||||
|
viewportIndex: 1,
|
||||||
|
viewportSpecificData: {
|
||||||
|
viewportData1: 'NEWdata1',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedState = {
|
||||||
|
numRows: 1,
|
||||||
|
numColumns: 2,
|
||||||
|
viewportSpecificData: {
|
||||||
|
0: { viewportData0: 'data0' },
|
||||||
|
1: { viewportData1: 'NEWdata1' },
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
viewports: [{ plugin: 'cornerstone' }, { plugin: 'cornerstone' }],
|
||||||
|
},
|
||||||
|
activeViewportIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Reducer(reducer)
|
||||||
|
.withState(initialState)
|
||||||
|
.expect(action)
|
||||||
|
.toReturnState(expectedState);
|
||||||
});
|
});
|
||||||
|
|
||||||
// If there were previous keys, this would have
|
|
||||||
// "merge" behavior, not a clear & set
|
|
||||||
// May be worth another test?
|
|
||||||
it('should handle SET_VIEWPORT', () => {
|
it('should handle SET_VIEWPORT', () => {
|
||||||
const viewportToSet = 0;
|
const viewportToSet = 0;
|
||||||
const setViewportAction = {
|
const setViewportAction = {
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ export default class ServicesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (service.create) {
|
if (service.create) {
|
||||||
this.services[service.name] = service.create({ configuration });
|
this.services[service.name] = service.create({
|
||||||
|
configuration,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
log.warn(`Service create factory function not defined. Exiting early.`);
|
log.warn(`Service create factory function not defined. Exiting early.`);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ describe('ServicesManager.js', () => {
|
|||||||
[{ name: 'UIModalTestService', create: jest.fn() }, fakeConfiguration],
|
[{ name: 'UIModalTestService', create: jest.fn() }, fakeConfiguration],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(servicesManager.registerService.mock.calls[1]).toContain(
|
expect(servicesManager.registerService.mock.calls[1][1]).toEqual(
|
||||||
fakeConfiguration
|
fakeConfiguration
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -85,7 +85,7 @@ describe('ServicesManager.js', () => {
|
|||||||
expect(log.warn.mock.calls.length).toBe(1);
|
expect(log.warn.mock.calls.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pass configuration to service create factory function', () => {
|
it('pass dependencies and configuration to service create factory function', () => {
|
||||||
const configuration = { config: 'Some configuration' };
|
const configuration = { config: 'Some configuration' };
|
||||||
|
|
||||||
servicesManager.registerService(fakeService, configuration);
|
servicesManager.registerService(fakeService, configuration);
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ export default class RetrieveMetadataLoader {
|
|||||||
if (result && result.length) {
|
if (result && result.length) {
|
||||||
break; // closes iterator in case data is retrieved successfully
|
break; // closes iterator in case data is retrieved successfully
|
||||||
}
|
}
|
||||||
} catch (e) { }
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loaders.next().done && !result) {
|
if (loaders.next().done && !result) {
|
||||||
@@ -51,8 +53,8 @@ export default class RetrieveMetadataLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Methods to be overwrite
|
// Methods to be overwrite
|
||||||
async configLoad() { }
|
async configLoad() {}
|
||||||
async preLoad() { }
|
async preLoad() {}
|
||||||
async load(preLoadData) { }
|
async load(preLoadData) {}
|
||||||
async posLoad(loadData) { }
|
async posLoad(loadData) {}
|
||||||
}
|
}
|
||||||
@@ -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'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,413 +0,0 @@
|
|||||||
import DICOMWeb from '../DICOMWeb/';
|
|
||||||
|
|
||||||
const WADOProxy = {
|
|
||||||
convertURL: (url, server) => {
|
|
||||||
// TODO: Remove all WADOProxy stuff from this file
|
|
||||||
return url;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
function parseFloatArray(obj) {
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
if (!obj) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const objs = obj.split('\\');
|
|
||||||
for (let i = 0; i < objs.length; i++) {
|
|
||||||
result.push(parseFloat(objs[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a plain JS object that describes a study (a study descriptor object)
|
|
||||||
* @param {Object} server Object with server configuration parameters
|
|
||||||
* @param {Object} aSopInstance a SOP Instance from which study information will be added
|
|
||||||
*/
|
|
||||||
function createStudy(server, aSopInstance) {
|
|
||||||
// TODO: Pass a reference ID to the server instead of including the URLs here
|
|
||||||
return {
|
|
||||||
seriesList: [],
|
|
||||||
seriesMap: Object.create(null),
|
|
||||||
seriesLoader: null,
|
|
||||||
wadoUriRoot: server.wadoUriRoot,
|
|
||||||
wadoRoot: server.wadoRoot,
|
|
||||||
qidoRoot: server.qidoRoot,
|
|
||||||
patientName: DICOMWeb.getName(aSopInstance['00100010']),
|
|
||||||
patientId: DICOMWeb.getString(aSopInstance['00100020']),
|
|
||||||
patientAge: DICOMWeb.getNumber(aSopInstance['00101010']),
|
|
||||||
patientSize: DICOMWeb.getNumber(aSopInstance['00101020']),
|
|
||||||
patientWeight: DICOMWeb.getNumber(aSopInstance['00101030']),
|
|
||||||
accessionNumber: DICOMWeb.getString(aSopInstance['00080050']),
|
|
||||||
studyDate: DICOMWeb.getString(aSopInstance['00080020']),
|
|
||||||
modalities: DICOMWeb.getString(aSopInstance['00080061']),
|
|
||||||
studyDescription: DICOMWeb.getString(aSopInstance['00081030']),
|
|
||||||
imageCount: DICOMWeb.getString(aSopInstance['00201208']),
|
|
||||||
studyInstanceUid: DICOMWeb.getString(aSopInstance['0020000D']),
|
|
||||||
institutionName: DICOMWeb.getString(aSopInstance['00080080']),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a WADO url for an instance
|
|
||||||
*
|
|
||||||
* @param studyInstanceUid
|
|
||||||
* @param seriesInstanceUid
|
|
||||||
* @param sopInstanceUid
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function buildInstanceWadoUrl(
|
|
||||||
server,
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid
|
|
||||||
) {
|
|
||||||
// TODO: This can be removed, since DICOMWebClient has the same function. Not urgent, though
|
|
||||||
const params = [];
|
|
||||||
|
|
||||||
params.push('requestType=WADO');
|
|
||||||
params.push(`studyUID=${studyInstanceUid}`);
|
|
||||||
params.push(`seriesUID=${seriesInstanceUid}`);
|
|
||||||
params.push(`objectUID=${sopInstanceUid}`);
|
|
||||||
params.push('contentType=application/dicom');
|
|
||||||
params.push('transferSyntax=*');
|
|
||||||
|
|
||||||
const paramString = params.join('&');
|
|
||||||
|
|
||||||
return `${server.wadoUriRoot}?${paramString}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildInstanceWadoRsUri(
|
|
||||||
server,
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid
|
|
||||||
) {
|
|
||||||
return `${server.wadoRoot}/studies/${studyInstanceUid}/series/${seriesInstanceUid}/instances/${sopInstanceUid}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildInstanceFrameWadoRsUri(
|
|
||||||
server,
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid,
|
|
||||||
frame
|
|
||||||
) {
|
|
||||||
const baseWadoRsUri = buildInstanceWadoRsUri(
|
|
||||||
server,
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid
|
|
||||||
);
|
|
||||||
frame = frame != null || 1;
|
|
||||||
|
|
||||||
return `${baseWadoRsUri}/frames/${frame}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFrameIncrementPointer(element) {
|
|
||||||
const frameIncrementPointerNames = {
|
|
||||||
'00181065': 'frameTimeVector',
|
|
||||||
'00181063': 'frameTime',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!element || !element.Value || !element.Value.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = element.Value[0];
|
|
||||||
return frameIncrementPointerNames[value];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRadiopharmaceuticalInfo(instance) {
|
|
||||||
const modality = DICOMWeb.getString(instance['00080060']);
|
|
||||||
|
|
||||||
if (modality !== 'PT') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const radiopharmaceuticalInfo = instance['00540016'];
|
|
||||||
if (
|
|
||||||
radiopharmaceuticalInfo === undefined ||
|
|
||||||
!radiopharmaceuticalInfo.Value ||
|
|
||||||
!radiopharmaceuticalInfo.Value.length
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstPetRadiopharmaceuticalInfo = radiopharmaceuticalInfo.Value[0];
|
|
||||||
return {
|
|
||||||
radiopharmaceuticalStartTime: DICOMWeb.getString(
|
|
||||||
firstPetRadiopharmaceuticalInfo['00181072']
|
|
||||||
),
|
|
||||||
radionuclideTotalDose: DICOMWeb.getNumber(
|
|
||||||
firstPetRadiopharmaceuticalInfo['00181074']
|
|
||||||
),
|
|
||||||
radionuclideHalfLife: DICOMWeb.getNumber(
|
|
||||||
firstPetRadiopharmaceuticalInfo['00181075']
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the SourceImageSequence, if it exists, in order
|
|
||||||
* to return a ReferenceSOPInstanceUID. The ReferenceSOPInstanceUID
|
|
||||||
* is used to refer to this image in any accompanying DICOM-SR documents.
|
|
||||||
*
|
|
||||||
* @param instance
|
|
||||||
* @returns {String} The ReferenceSOPInstanceUID
|
|
||||||
*/
|
|
||||||
function getSourceImageInstanceUid(instance) {
|
|
||||||
// TODO= Parse the whole Source Image Sequence
|
|
||||||
// This is a really poor workaround for now.
|
|
||||||
// Later we should probably parse the whole sequence.
|
|
||||||
var SourceImageSequence = instance['00082112'];
|
|
||||||
if (
|
|
||||||
SourceImageSequence &&
|
|
||||||
SourceImageSequence.Value &&
|
|
||||||
SourceImageSequence.Value.length &&
|
|
||||||
SourceImageSequence.Value[0]['00081155'].Value
|
|
||||||
) {
|
|
||||||
return SourceImageSequence.Value[0]['00081155'].Value[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeSOPInstance(server, study, instance) {
|
|
||||||
const { studyInstanceUid } = study;
|
|
||||||
const seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
|
|
||||||
let series = study.seriesMap[seriesInstanceUid];
|
|
||||||
|
|
||||||
if (!series) {
|
|
||||||
series = {
|
|
||||||
seriesInstanceUid,
|
|
||||||
seriesDescription: DICOMWeb.getString(instance['0008103E']),
|
|
||||||
modality: DICOMWeb.getString(instance['00080060']),
|
|
||||||
seriesNumber: DICOMWeb.getNumber(instance['00200011']),
|
|
||||||
seriesDate: DICOMWeb.getString(instance['00080021']),
|
|
||||||
seriesTime: DICOMWeb.getString(instance['00080031']),
|
|
||||||
instances: [],
|
|
||||||
};
|
|
||||||
study.seriesMap[seriesInstanceUid] = series;
|
|
||||||
study.seriesList.push(series);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sopInstanceUid = DICOMWeb.getString(instance['00080018']);
|
|
||||||
const wadouri = buildInstanceWadoUrl(
|
|
||||||
server,
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid
|
|
||||||
);
|
|
||||||
const baseWadoRsUri = buildInstanceWadoRsUri(
|
|
||||||
server,
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid
|
|
||||||
);
|
|
||||||
const wadorsuri = buildInstanceFrameWadoRsUri(
|
|
||||||
server,
|
|
||||||
studyInstanceUid,
|
|
||||||
seriesInstanceUid,
|
|
||||||
sopInstanceUid
|
|
||||||
);
|
|
||||||
|
|
||||||
const sopInstance = {
|
|
||||||
imageType: DICOMWeb.getString(instance['00080008']),
|
|
||||||
sopClassUid: DICOMWeb.getString(instance['00080016']),
|
|
||||||
modality: DICOMWeb.getString(instance['00080060']),
|
|
||||||
sopInstanceUid,
|
|
||||||
instanceNumber: DICOMWeb.getNumber(instance['00200013']),
|
|
||||||
imagePositionPatient: DICOMWeb.getString(instance['00200032']),
|
|
||||||
imageOrientationPatient: DICOMWeb.getString(instance['00200037']),
|
|
||||||
frameOfReferenceUID: DICOMWeb.getString(instance['00200052']),
|
|
||||||
sliceLocation: DICOMWeb.getNumber(instance['00201041']),
|
|
||||||
samplesPerPixel: DICOMWeb.getNumber(instance['00280002']),
|
|
||||||
photometricInterpretation: DICOMWeb.getString(instance['00280004']),
|
|
||||||
planarConfiguration: DICOMWeb.getNumber(instance['00280006']),
|
|
||||||
rows: DICOMWeb.getNumber(instance['00280010']),
|
|
||||||
columns: DICOMWeb.getNumber(instance['00280011']),
|
|
||||||
pixelSpacing: DICOMWeb.getString(instance['00280030']),
|
|
||||||
pixelAspectRatio: DICOMWeb.getString(instance['00280034']),
|
|
||||||
bitsAllocated: DICOMWeb.getNumber(instance['00280100']),
|
|
||||||
bitsStored: DICOMWeb.getNumber(instance['00280101']),
|
|
||||||
highBit: DICOMWeb.getNumber(instance['00280102']),
|
|
||||||
pixelRepresentation: DICOMWeb.getNumber(instance['00280103']),
|
|
||||||
smallestPixelValue: DICOMWeb.getNumber(instance['00280106']),
|
|
||||||
largestPixelValue: DICOMWeb.getNumber(instance['00280107']),
|
|
||||||
windowCenter: DICOMWeb.getString(instance['00281050']),
|
|
||||||
windowWidth: DICOMWeb.getString(instance['00281051']),
|
|
||||||
rescaleIntercept: DICOMWeb.getNumber(instance['00281052']),
|
|
||||||
rescaleSlope: DICOMWeb.getNumber(instance['00281053']),
|
|
||||||
rescaleType: DICOMWeb.getNumber(instance['00281054']),
|
|
||||||
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
|
|
||||||
laterality: DICOMWeb.getString(instance['00200062']),
|
|
||||||
viewPosition: DICOMWeb.getString(instance['00185101']),
|
|
||||||
acquisitionDateTime: DICOMWeb.getString(instance['0008002A']),
|
|
||||||
numberOfFrames: DICOMWeb.getNumber(instance['00280008']),
|
|
||||||
frameIncrementPointer: getFrameIncrementPointer(instance['00280009']),
|
|
||||||
frameTime: DICOMWeb.getNumber(instance['00181063']),
|
|
||||||
frameTimeVector: parseFloatArray(DICOMWeb.getString(instance['00181065'])),
|
|
||||||
sliceThickness: DICOMWeb.getNumber(instance['00180050']),
|
|
||||||
spacingBetweenSlices: DICOMWeb.getString(instance['00180088']),
|
|
||||||
lossyImageCompression: DICOMWeb.getString(instance['00282110']),
|
|
||||||
derivationDescription: DICOMWeb.getString(instance['00282111']),
|
|
||||||
lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']),
|
|
||||||
lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']),
|
|
||||||
echoNumber: DICOMWeb.getString(instance['00180086']),
|
|
||||||
contrastBolusAgent: DICOMWeb.getString(instance['00180010']),
|
|
||||||
radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance),
|
|
||||||
baseWadoRsUri: baseWadoRsUri,
|
|
||||||
wadouri: WADOProxy.convertURL(wadouri, server),
|
|
||||||
wadorsuri: WADOProxy.convertURL(wadorsuri, server),
|
|
||||||
wadoRoot: server.wadoRoot,
|
|
||||||
imageRendering: server.imageRendering,
|
|
||||||
thumbnailRendering: server.thumbnailRendering,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get additional information if the instance uses "PALETTE COLOR" photometric interpretation
|
|
||||||
if (sopInstance.photometricInterpretation === 'PALETTE COLOR') {
|
|
||||||
const redPaletteColorLookupTableDescriptor = parseFloatArray(
|
|
||||||
DICOMWeb.getString(instance['00281101'])
|
|
||||||
);
|
|
||||||
const greenPaletteColorLookupTableDescriptor = parseFloatArray(
|
|
||||||
DICOMWeb.getString(instance['00281102'])
|
|
||||||
);
|
|
||||||
const bluePaletteColorLookupTableDescriptor = parseFloatArray(
|
|
||||||
DICOMWeb.getString(instance['00281103'])
|
|
||||||
);
|
|
||||||
const palettes = await getPaletteColors(
|
|
||||||
server,
|
|
||||||
instance,
|
|
||||||
redPaletteColorLookupTableDescriptor
|
|
||||||
);
|
|
||||||
|
|
||||||
if (palettes) {
|
|
||||||
if (palettes.uid) {
|
|
||||||
sopInstance.paletteColorLookupTableUID = palettes.uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
sopInstance.redPaletteColorLookupTableData = palettes.red;
|
|
||||||
sopInstance.greenPaletteColorLookupTableData = palettes.green;
|
|
||||||
sopInstance.bluePaletteColorLookupTableData = palettes.blue;
|
|
||||||
sopInstance.redPaletteColorLookupTableDescriptor = redPaletteColorLookupTableDescriptor;
|
|
||||||
sopInstance.greenPaletteColorLookupTableDescriptor = greenPaletteColorLookupTableDescriptor;
|
|
||||||
sopInstance.bluePaletteColorLookupTableDescriptor = bluePaletteColorLookupTableDescriptor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
series.instances.push(sopInstance);
|
|
||||||
return sopInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPaletteColor(server, instance, tag, lutDescriptor) {
|
|
||||||
const numLutEntries = lutDescriptor[0];
|
|
||||||
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) => {
|
|
||||||
return byteArray[position] + byteArray[position + 1] * 256;
|
|
||||||
};
|
|
||||||
|
|
||||||
const arrayBufferToPaletteColorLUT = result => {
|
|
||||||
const arraybuffer = result[0];
|
|
||||||
const byteArray = new Uint8Array(arraybuffer);
|
|
||||||
const lut = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < numLutEntries; i++) {
|
|
||||||
if (bits === 16) {
|
|
||||||
lut[i] = readUInt16(byteArray, i * 2);
|
|
||||||
} else {
|
|
||||||
lut[i] = byteArray[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lut;
|
|
||||||
};
|
|
||||||
|
|
||||||
return dicomWeb.retrieveBulkData(options).then(arrayBufferToPaletteColorLUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch palette colors for instances with "PALETTE COLOR" photometricInterpretation.
|
|
||||||
*
|
|
||||||
* @param server {Object} Current server;
|
|
||||||
* @param instance {Object} The retrieved instance metadata;
|
|
||||||
* @returns {String} The ReferenceSOPInstanceUID
|
|
||||||
*/
|
|
||||||
async function getPaletteColors(server, instance, lutDescriptor) {
|
|
||||||
let paletteUID = DICOMWeb.getString(instance['00281199']);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let entry;
|
|
||||||
if (paletteColorCache.isValidUID(paletteUID)) {
|
|
||||||
entry = paletteColorCache.get(paletteUID);
|
|
||||||
|
|
||||||
if (entry) {
|
|
||||||
return resolve(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no entry in cache... Fetch remote data.
|
|
||||||
const r = getPaletteColor(server, instance, '00281201', lutDescriptor);
|
|
||||||
const g = getPaletteColor(server, instance, '00281202', lutDescriptor);
|
|
||||||
const b = getPaletteColor(server, instance, '00281203', lutDescriptor);
|
|
||||||
|
|
||||||
const promises = [r, g, b];
|
|
||||||
|
|
||||||
Promise.all(promises).then(args => {
|
|
||||||
entry = {
|
|
||||||
red: args[0],
|
|
||||||
green: args[1],
|
|
||||||
blue: args[2],
|
|
||||||
};
|
|
||||||
|
|
||||||
// when paletteUID is present, the entry can be cached...
|
|
||||||
entry.uid = paletteUID;
|
|
||||||
paletteColorCache.add(entry);
|
|
||||||
|
|
||||||
resolve(entry);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a list of SOP Instances to a given study object descriptor
|
|
||||||
* @param {Object} server Object with server configuration parameters
|
|
||||||
* @param {Object} study The study descriptor to which the given SOP instances will be added
|
|
||||||
* @param {Array} sopInstanceList A list of SOP instance objects
|
|
||||||
*/
|
|
||||||
async function addInstancesToStudy(server, study, sopInstanceList) {
|
|
||||||
return Promise.all(
|
|
||||||
sopInstanceList.map(function(sopInstance) {
|
|
||||||
return makeSOPInstance(server, study, sopInstance);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const createStudyFromSOPInstanceList = async (server, sopInstanceList) => {
|
|
||||||
if (Array.isArray(sopInstanceList) && sopInstanceList.length > 0) {
|
|
||||||
const firstSopInstance = sopInstanceList[0];
|
|
||||||
const study = createStudy(server, firstSopInstance);
|
|
||||||
await addInstancesToStudy(server, study, sopInstanceList);
|
|
||||||
return study;
|
|
||||||
}
|
|
||||||
throw new Error('Failed to create study out of provided SOP instance list');
|
|
||||||
};
|
|
||||||
|
|
||||||
export { createStudyFromSOPInstanceList, addInstancesToStudy };
|
|
||||||
@@ -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,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"
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,55 @@
|
|||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* measurements panel css and delete button visibility ([#1352](https://github.com/OHIF/Viewers/issues/1352)) ([7ab0bbb](https://github.com/OHIF/Viewers/commit/7ab0bbb32581dcba16ee16b49b92406e2856ac76))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.1.7](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.6...@ohif/ui@1.1.7) (2019-12-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/ui
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.1.6](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.5...@ohif/ui@1.1.6) (2019-12-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/ui
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.1.5](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.4...@ohif/ui@1.1.5) (2019-12-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix drag-n-drop of local files into OHIF ([#1319](https://github.com/OHIF/Viewers/issues/1319)) ([23305ce](https://github.com/OHIF/Viewers/commit/23305cec9c0f514e73a8dd17f984ffc87ad8d131)), closes [#1307](https://github.com/OHIF/Viewers/issues/1307)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.3...@ohif/ui@1.1.4) (2019-12-16)
|
## [1.1.4](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.3...@ohif/ui@1.1.4) (2019-12-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/ui
|
**Note:** Version bump only for package @ohif/ui
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ohif/ui",
|
"name": "@ohif/ui",
|
||||||
"version": "1.1.4",
|
"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%;
|
||||||
|
|||||||
@@ -79,7 +79,11 @@ class MeasurementTable extends Component {
|
|||||||
</ScrollableArea>
|
</ScrollableArea>
|
||||||
<div className="measurementTableFooter">
|
<div className="measurementTableFooter">
|
||||||
{saveFunction && (
|
{saveFunction && (
|
||||||
<button onClick={this.saveFunction} className="saveBtn">
|
<button
|
||||||
|
onClick={this.saveFunction}
|
||||||
|
className="saveBtn"
|
||||||
|
data-cy="save-measurements-btn"
|
||||||
|
>
|
||||||
<Icon name="save" width="14px" height="14px" />
|
<Icon name="save" width="14px" height="14px" />
|
||||||
Save measurements
|
Save measurements
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.measurementItem
|
.measurementItem
|
||||||
.rowActions
|
.rowActions
|
||||||
|
margin-left: -1px;
|
||||||
background-color: var(--ui-gray-darker)
|
background-color: var(--ui-gray-darker)
|
||||||
height: 0
|
height: 0
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
@@ -28,7 +29,6 @@
|
|||||||
visibility: visible
|
visibility: visible
|
||||||
|
|
||||||
.measurementLocation
|
.measurementLocation
|
||||||
margin-left: 9px;
|
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: calc(100% - 50px);
|
max-width: calc(100% - 50px);
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
.measurementDisplayText
|
.measurementDisplayText
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
margin-left: 9px;
|
|
||||||
padding-left: 9px;
|
padding-left: 9px;
|
||||||
width: 90px;
|
width: 90px;
|
||||||
border-left: 1px solid var(--text-secondary-color);
|
border-left: 1px solid var(--text-secondary-color);
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
transition(all 0.3s ease)
|
transition(all 0.3s ease)
|
||||||
font-size: 14px
|
font-size: 14px
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
margin-right: 5px
|
margin-right: 6px
|
||||||
padding: 10px
|
padding: 10px
|
||||||
text-align: center
|
text-align: center
|
||||||
-webkit-box-sizing: initial;
|
-webkit-box-sizing: initial;
|
||||||
@@ -38,9 +38,8 @@
|
|||||||
.itemContent
|
.itemContent
|
||||||
flex: 1
|
flex: 1
|
||||||
color: var(--text-secondary-color)
|
color: var(--text-secondary-color)
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 51px);
|
||||||
font-weight: 400
|
font-weight: 400
|
||||||
margin-left: 9px;
|
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
|
|
||||||
.warning-icon
|
.warning-icon
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const renderTabsHeader = (tabs, activeTabIndex, onHeaderChanged) => {
|
|||||||
? tabs.map((tab, index) => {
|
? tabs.map((tab, index) => {
|
||||||
const { name, hidden = false } = tab;
|
const { name, hidden = false } = tab;
|
||||||
|
|
||||||
|
const cypressSelectorId = name.toLowerCase();
|
||||||
const tabClass =
|
const tabClass =
|
||||||
index === activeTabIndex ? 'nav-link active' : 'nav-link';
|
index === activeTabIndex ? 'nav-link active' : 'nav-link';
|
||||||
return !hidden ? (
|
return !hidden ? (
|
||||||
@@ -52,6 +53,7 @@ const renderTabsHeader = (tabs, activeTabIndex, onHeaderChanged) => {
|
|||||||
onHeaderChanged(index);
|
onHeaderChanged(index);
|
||||||
}}
|
}}
|
||||||
className={tabClass}
|
className={tabClass}
|
||||||
|
data-cy={cypressSelectorId}
|
||||||
>
|
>
|
||||||
<button>{name}</button>
|
<button>{name}</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ const DialogProvider = ({ children, service }) => {
|
|||||||
onStart,
|
onStart,
|
||||||
onStop,
|
onStop,
|
||||||
onDrag,
|
onDrag,
|
||||||
|
showOverlay,
|
||||||
} = dialog;
|
} = dialog;
|
||||||
|
|
||||||
let position =
|
let position =
|
||||||
@@ -160,7 +161,7 @@ const DialogProvider = ({ children, service }) => {
|
|||||||
position = centerPositions.find(position => position.id === id);
|
position = centerPositions.find(position => position.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const dragableItem = () => (
|
||||||
<Draggable
|
<Draggable
|
||||||
key={id}
|
key={id}
|
||||||
disabled={!isDraggable}
|
disabled={!isDraggable}
|
||||||
@@ -217,6 +218,16 @@ const DialogProvider = ({ children, service }) => {
|
|||||||
</div>
|
</div>
|
||||||
</Draggable>
|
</Draggable>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
showOverlay ? (
|
||||||
|
<div className="Overlay" key={id}>
|
||||||
|
{dragableItem()}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
dragableItem()
|
||||||
|
)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,13 +249,11 @@ const DialogProvider = ({ children, service }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContext.Provider value={{ create, dismiss, dismissAll, isEmpty }}>
|
<DialogContext.Provider value={{ create, dismiss, dismissAll, isEmpty }}>
|
||||||
<div className="DraggableArea">
|
{!isEmpty() &&
|
||||||
{dialogs.some(dialog => dialog.showOverlay) ? (
|
<div className="DraggableArea">
|
||||||
<div className="Overlay active">{renderDialogs()}</div>
|
{renderDialogs()}
|
||||||
) : (
|
</div>
|
||||||
renderDialogs()
|
}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{children}
|
{children}
|
||||||
</DialogContext.Provider>
|
</DialogContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,17 +6,16 @@
|
|||||||
div
|
div
|
||||||
cursor: grabbing !important
|
cursor: grabbing !important
|
||||||
|
|
||||||
.DraggableArea, .Overlay
|
.DraggableArea
|
||||||
width: 100%
|
width: 100%
|
||||||
height: 100%
|
height: 100%
|
||||||
position: absolute
|
position: absolute
|
||||||
|
.Overlay
|
||||||
.Overlay.active
|
position: fixed
|
||||||
position: fixed
|
z-index: 999
|
||||||
z-index: 999
|
left: 0
|
||||||
left: 0
|
top: 0
|
||||||
top: 0
|
width: 100%
|
||||||
width: 100%
|
height: 100%
|
||||||
height: 100%
|
overflow: auto
|
||||||
overflow: auto
|
background: rgba(0,0,0,.1)
|
||||||
background: rgba(0,0,0,.1)
|
|
||||||
@@ -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) }
|
||||||
|
|||||||
@@ -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 |
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,14 @@ export function ToolbarButton(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cypressSelectorId = props.label.toLowerCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} onClick={handleClick}>
|
<div
|
||||||
|
className={className}
|
||||||
|
onClick={handleClick}
|
||||||
|
data-cy={cypressSelectorId}
|
||||||
|
>
|
||||||
{iconProps && <Icon {...iconProps} />}
|
{iconProps && <Icon {...iconProps} />}
|
||||||
<div className="toolbar-button-label">
|
<div className="toolbar-button-label">
|
||||||
{t(label)}
|
{t(label)}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -3,6 +3,262 @@
|
|||||||
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.14](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.13...@ohif/viewer@3.4.14) (2020-02-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.4.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.0...@ohif/viewer@3.4.1) (2020-01-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Metadata is being mistakenly purged ([#1360](https://github.com/OHIF/Viewers/issues/1360)) ([b9a66d4](https://github.com/OHIF/Viewers/commit/b9a66d44241f2896ef184511287fb4984671e16d)), closes [#1326](https://github.com/OHIF/Viewers/issues/1326)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.4.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.8...@ohif/viewer@3.4.0) (2020-01-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Custom Healthcare API endpoint ([#1367](https://github.com/OHIF/Viewers/issues/1367)) ([a5d6bc6](https://github.com/OHIF/Viewers/commit/a5d6bc6a51784ed3a8a40d4ae773de9099f116b9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.3.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.7...@ohif/viewer@3.3.8) (2020-01-10)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.3.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.6...@ohif/viewer@3.3.7) (2020-01-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.3.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.5...@ohif/viewer@3.3.6) (2020-01-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.3.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.4...@ohif/viewer@3.3.5) (2020-01-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.3.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.3...@ohif/viewer@3.3.4) (2019-12-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.3.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.2...@ohif/viewer@3.3.3) (2019-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.3.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.1...@ohif/viewer@3.3.2) (2019-12-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.3.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.3.0...@ohif/viewer@3.3.1) (2019-12-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.3.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.2.2...@ohif/viewer@3.3.0) (2019-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Configuration so viewer tools can nix handles ([#1304](https://github.com/OHIF/Viewers/issues/1304)) ([63594d3](https://github.com/OHIF/Viewers/commit/63594d36b0bdba59f0901095aed70b75fb05172d)), closes [#1223](https://github.com/OHIF/Viewers/issues/1223)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.2.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.2.1...@ohif/viewer@3.2.2) (2019-12-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix drag-n-drop of local files into OHIF ([#1319](https://github.com/OHIF/Viewers/issues/1319)) ([23305ce](https://github.com/OHIF/Viewers/commit/23305cec9c0f514e73a8dd17f984ffc87ad8d131)), closes [#1307](https://github.com/OHIF/Viewers/issues/1307)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.2.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.2.0...@ohif/viewer@3.2.1) (2019-12-18)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [3.2.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.12...@ohif/viewer@3.2.0) (2019-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 Expose extension config to modules ([#1279](https://github.com/OHIF/Viewers/issues/1279)) ([4ea239a](https://github.com/OHIF/Viewers/commit/4ea239a9535ef297e23387c186e537ab273744ea)), closes [#1268](https://github.com/OHIF/Viewers/issues/1268)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.1.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.11...@ohif/viewer@3.1.12) (2019-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 Dismiss all dialogs if leaving viewer route [#1242](https://github.com/OHIF/Viewers/issues/1242) ([#1301](https://github.com/OHIF/Viewers/issues/1301)) ([5c3d8b3](https://github.com/OHIF/Viewers/commit/5c3d8b37b6f723fbd8edcc447c37984e7eee8d40))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.1.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.10...@ohif/viewer@3.1.11) (2019-12-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.1.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.9...@ohif/viewer@3.1.10) (2019-12-16)
|
## [3.1.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.1.9...@ohif/viewer@3.1.10) (2019-12-16)
|
||||||
|
|
||||||
**Note:** Version bump only for package @ohif/viewer
|
**Note:** Version bump only for package @ohif/viewer
|
||||||
|
|||||||
@@ -5,5 +5,6 @@
|
|||||||
"defaultCommandTimeout": 10000,
|
"defaultCommandTimeout": 10000,
|
||||||
"requestTimeout": 10000,
|
"requestTimeout": 10000,
|
||||||
"responseTimeout": 10000,
|
"responseTimeout": 10000,
|
||||||
"projectId": "4oe38f"
|
"projectId": "4oe38f",
|
||||||
|
"video": false
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -47,7 +47,6 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
|||||||
cy.get('body').type('I');
|
cy.get('body').type('I');
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Hotkey I - Should Invert Image');
|
cy.screenshot('Hotkey I - Should Invert Image');
|
||||||
cy.percyCanvasSnapshot('Hotkey I - Should Invert Image');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => {
|
it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => {
|
||||||
@@ -77,7 +76,6 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
|||||||
|
|
||||||
// Visual comparison to make sure the 'inverted' image was reset
|
// Visual comparison to make sure the 'inverted' image was reset
|
||||||
cy.screenshot('Hotkey SPACEBAR - Should Reset Image');
|
cy.screenshot('Hotkey SPACEBAR - Should Reset Image');
|
||||||
cy.percyCanvasSnapshot('Hotkey SPACEBAR - Should Reset Image');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
|
it('uses hotkeys "RightArrow" and "LeftArrow" to navigate between multiple viewports', () => {
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Pan tool moved the image inside the viewport');
|
cy.screenshot('Pan tool moved the image inside the viewport');
|
||||||
cy.percyCanvasSnapshot('Pan tool moved the image inside the viewport');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
|
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
|
||||||
@@ -266,7 +265,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
|
|
||||||
let iconName;
|
let iconName;
|
||||||
//Click on one of the secondary tools from the overlay
|
//Click on one of the secondary tools from the overlay
|
||||||
cy.get('.tooltip-inner > :nth-child(1)')
|
cy.get('[data-cy="magnify"]')
|
||||||
.click()
|
.click()
|
||||||
.then($magnifyBtn => {
|
.then($magnifyBtn => {
|
||||||
cy.wrap($magnifyBtn)
|
cy.wrap($magnifyBtn)
|
||||||
@@ -407,9 +406,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
.as('toolbarOverlay')
|
.as('toolbarOverlay')
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
//Click on Clear button
|
//Click on Clear button
|
||||||
cy.get('.tooltip-inner')
|
cy.get('[data-cy="clear"]').click();
|
||||||
.contains('Clear')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
//Verify if measurements were removed from the measurements panel
|
//Verify if measurements were removed from the measurements panel
|
||||||
cy.get('.measurementItem').should('not.exist');
|
cy.get('.measurementItem').should('not.exist');
|
||||||
@@ -446,9 +443,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
//Click Eraser button
|
//Click Eraser button
|
||||||
cy.get('.tooltip-inner')
|
cy.get('[data-cy="eraser"]').click({ force: true });
|
||||||
.contains('Eraser')
|
|
||||||
.click({ force: true });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//Erase measurement #1 and Verify if it was removed from the measurements panel
|
//Erase measurement #1 and Verify if it was removed from the measurements panel
|
||||||
@@ -471,13 +466,10 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
// Click on Invert button
|
// Click on Invert button
|
||||||
cy.get('.tooltip-inner')
|
cy.get('[data-cy="invert"]').click();
|
||||||
.contains('Invert')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Invert tool - Should Invert Canvas');
|
cy.screenshot('Invert tool - Should Invert Canvas');
|
||||||
cy.percyCanvasSnapshot('Invert tool - Should Invert Canvas');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('check if Rotate tool will change the image orientation in the viewport', () => {
|
it('check if Rotate tool will change the image orientation in the viewport', () => {
|
||||||
@@ -488,14 +480,11 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
.should('be.visible')
|
.should('be.visible')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
//Click on Rotate button
|
//Click on Rotate button
|
||||||
cy.get('.tooltip-inner')
|
cy.get('[data-cy="rotate right"]').click({ force: true });
|
||||||
.contains('Rotate')
|
|
||||||
.click({ force: true });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Rotate tool - Should Rotate Image to Right');
|
cy.screenshot('Rotate tool - Should Rotate Image to Right');
|
||||||
cy.percyCanvasSnapshot('Rotate tool - Should Rotate Image to Right');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('check if Flip H tool will flip the image horizontally in the viewport', () => {
|
it('check if Flip H tool will flip the image horizontally in the viewport', () => {
|
||||||
@@ -505,13 +494,10 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
//Click on Flip H button
|
//Click on Flip H button
|
||||||
cy.get('.tooltip-inner')
|
cy.get('[data-cy="flip h"]').click();
|
||||||
.contains('Flip H')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Flip H tool - Should Flip Image on Y axis');
|
cy.screenshot('Flip H tool - Should Flip Image on Y axis');
|
||||||
cy.percyCanvasSnapshot('Flip H tool - Should Flip Image on Y axis');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('check if Flip V tool will flip the image vertically in the viewport', () => {
|
it('check if Flip V tool will flip the image vertically in the viewport', () => {
|
||||||
@@ -521,12 +507,9 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
//Click on Flip V button
|
//Click on Flip V button
|
||||||
cy.get('.tooltip-inner')
|
cy.get('[data-cy="flip v"]').click();
|
||||||
.contains('Flip V')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Flip V tool - Should Flip Image on X axis');
|
cy.screenshot('Flip V tool - Should Flip Image on X axis');
|
||||||
cy.percyCanvasSnapshot('Flip V tool - Should Flip Image on X axis');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -16,20 +16,18 @@ 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');
|
||||||
cy.percyCanvasSnapshot('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
|
||||||
cy.screenshot('Download Image Modal - Desktop experience');
|
cy.screenshot('Download Image Modal - Desktop experience');
|
||||||
cy.percyCanvasSnapshot('Download Image Modal - Desktop experience');
|
|
||||||
//Check if all elements are displayed
|
//Check if all elements are displayed
|
||||||
cy.get('[data-cy=modal-header]')
|
cy.get('[data-cy=modal-header]')
|
||||||
.as('downloadImageModal')
|
.as('downloadImageModal')
|
||||||
@@ -54,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()
|
||||||
@@ -93,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();
|
||||||
|
|
||||||
@@ -109,7 +107,6 @@ describe('OHIF Download Snapshot File', () => {
|
|||||||
cy.get('[data-cy="image-preview"]').scrollIntoView();
|
cy.get('[data-cy="image-preview"]').scrollIntoView();
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Download Image Modal - Show Annotations checked');
|
cy.screenshot('Download Image Modal - Show Annotations checked');
|
||||||
cy.percyCanvasSnapshot('Download Image Modal - Show Annotations checked');
|
|
||||||
//Compare classes that exists on Image Preview with Annotations and Without Annotation
|
//Compare classes that exists on Image Preview with Annotations and Without Annotation
|
||||||
cy.get('[data-cy="modal-content"]')
|
cy.get('[data-cy="modal-content"]')
|
||||||
.find('canvas')
|
.find('canvas')
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,7 +6,9 @@ describe('OHIF Routes', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('checks PT/CT json url study route', function() {
|
it('checks PT/CT json url study route', function() {
|
||||||
cy.visit('/viewer?url=https://s3.eu-central-1.amazonaws.com/ohif-viewer/JSON/PTCTStudy.json');
|
cy.visit(
|
||||||
|
'/viewer?url=https://s3.eu-central-1.amazonaws.com/ohif-viewer/JSON/PTCTStudy.json'
|
||||||
|
);
|
||||||
|
|
||||||
cy.server();
|
cy.server();
|
||||||
cy.route('GET', '**/PTCTStudy/**').as('getPTCTStudy');
|
cy.route('GET', '**/PTCTStudy/**').as('getPTCTStudy');
|
||||||
@@ -15,8 +17,5 @@ describe('OHIF Routes', function() {
|
|||||||
cy.get('@getPTCTStudy').should($route => {
|
cy.get('@getPTCTStudy').should($route => {
|
||||||
expect($route.status).to.be.eq(200);
|
expect($route.status).to.be.eq(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.screenshot();
|
|
||||||
cy.percyCanvasSnapshot('PT/CT json study route');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -4,9 +4,6 @@ describe('OHIFStandaloneViewer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('loads route with at least 2 rows', () => {
|
it('loads route with at least 2 rows', () => {
|
||||||
cy.screenshot('Study List');
|
|
||||||
cy.percyCanvasSnapshot('Study List');
|
|
||||||
|
|
||||||
cy.get('[data-cy="study-list-results"] tr')
|
cy.get('[data-cy="study-list-results"] tr')
|
||||||
.its('length')
|
.its('length')
|
||||||
.should('be.gt', 2);
|
.should('be.gt', 2);
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
//We are keeping the hardcoded results values for the study list tests
|
//We are keeping the hardcoded results values for the study list tests
|
||||||
//this is intended to be running in a controled docker environment with test data.
|
//this is intended to be running in a controled docker environment with test data.
|
||||||
describe('OHIF Study List', function () {
|
describe('OHIF Study List', function() {
|
||||||
context('Desktop resolution', function () {
|
context('Desktop resolution', function() {
|
||||||
beforeEach(function () {
|
beforeEach(function() {
|
||||||
cy.viewport(1750, 720);
|
cy.viewport(1750, 720);
|
||||||
cy.openStudyList();
|
cy.openStudyList();
|
||||||
cy.initStudyListAliasesOnDesktop();
|
cy.initStudyListAliasesOnDesktop();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches Patient Name with exact string', function () {
|
it('searches Patient Name with exact string', function() {
|
||||||
cy.get('@patientName').type('Juno');
|
cy.get('@patientName').type('Juno');
|
||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
@@ -18,7 +18,7 @@ describe('OHIF Study List', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches MRN with exact string', function () {
|
it('searches MRN with exact string', function() {
|
||||||
cy.get('@MRN').type('ProstateX-0000');
|
cy.get('@MRN').type('ProstateX-0000');
|
||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
@@ -28,7 +28,7 @@ describe('OHIF Study List', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches Accession with exact string', function () {
|
it('searches Accession with exact string', function() {
|
||||||
cy.get('@accessionNumber').type('fpcben98890');
|
cy.get('@accessionNumber').type('fpcben98890');
|
||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
@@ -38,17 +38,17 @@ describe('OHIF Study List', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches Modality with camel case', function () {
|
it('searches Modality with camel case', function() {
|
||||||
cy.get('@modalities').type('Mr');
|
cy.get('@modalities').type('Mr');
|
||||||
//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(18);
|
expect($list.length).to.be.eq(16);
|
||||||
expect($list).to.contain('MR');
|
expect($list).to.contain('MR');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches Description with exact string', function () {
|
it('searches Description with exact string', function() {
|
||||||
cy.get('@studyDescription').type('CHEST');
|
cy.get('@studyDescription').type('CHEST');
|
||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
@@ -58,7 +58,7 @@ describe('OHIF Study List', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('changes rows per page and checks the study count', function () {
|
it('changes rows per page and checks the study count', function() {
|
||||||
//Show rows per page options
|
//Show rows per page options
|
||||||
const pageRows = [25, 50, 100];
|
const pageRows = [25, 50, 100];
|
||||||
|
|
||||||
@@ -100,14 +100,14 @@ describe('OHIF Study List', function () {
|
|||||||
// });
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
context('Tablet resolution', function () {
|
context('Tablet resolution', function() {
|
||||||
beforeEach(function () {
|
beforeEach(function() {
|
||||||
cy.viewport(1000, 660);
|
cy.viewport(1000, 660);
|
||||||
cy.openStudyList();
|
cy.openStudyList();
|
||||||
cy.initStudyListAliasesOnTablet();
|
cy.initStudyListAliasesOnTablet();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches Patient Name with exact string', function () {
|
it('searches Patient Name with exact string', function() {
|
||||||
cy.get('@patientNameOrMRN').type('Juno');
|
cy.get('@patientNameOrMRN').type('Juno');
|
||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
@@ -117,7 +117,7 @@ describe('OHIF Study List', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches MRN with with exact string', function () {
|
it('searches MRN with with exact string', function() {
|
||||||
cy.get('@patientNameOrMRN').type('ProstateX-0000');
|
cy.get('@patientNameOrMRN').type('ProstateX-0000');
|
||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
@@ -127,17 +127,17 @@ describe('OHIF Study List', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches Modality with exact string', function () {
|
it('searches Modality with exact string', function() {
|
||||||
cy.get('@accessionModalityDescription').type('MR');
|
cy.get('@accessionModalityDescription').type('MR');
|
||||||
//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(18);
|
expect($list.length).to.be.eq(16);
|
||||||
expect($list).to.contain('MR');
|
expect($list).to.contain('MR');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches Accession with exact string', function () {
|
it('searches Accession with exact string', function() {
|
||||||
cy.get('@accessionModalityDescription').type('fpcben98890');
|
cy.get('@accessionModalityDescription').type('fpcben98890');
|
||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
@@ -147,7 +147,7 @@ describe('OHIF Study List', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('searches Description with exact string', function () {
|
it('searches Description with exact string', function() {
|
||||||
cy.get('@accessionModalityDescription').type('CHEST');
|
cy.get('@accessionModalityDescription').type('CHEST');
|
||||||
//Wait result list to be displayed
|
//Wait result list to be displayed
|
||||||
cy.waitStudyList();
|
cy.waitStudyList();
|
||||||
@@ -157,7 +157,7 @@ describe('OHIF Study List', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('changes rows per page and checks the study count', function () {
|
it('changes rows per page and checks the study count', function() {
|
||||||
//Show rows per page options
|
//Show rows per page options
|
||||||
const pageRows = [25, 50, 100];
|
const pageRows = [25, 50, 100];
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
|
|
||||||
it('checks if series thumbnails are being displayed', function() {
|
it('checks if series thumbnails are being displayed', function() {
|
||||||
cy.screenshot('Series Thumbnails - Should Display Thumbnails');
|
cy.screenshot('Series Thumbnails - Should Display Thumbnails');
|
||||||
cy.percyCanvasSnapshot('Series Thumbnails - Should Display Thumbnails');
|
|
||||||
|
|
||||||
cy.get('[data-cy="thumbnail-list"]')
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
.its('length')
|
.its('length')
|
||||||
@@ -293,7 +292,6 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('About modal - Should display modal');
|
cy.screenshot('About modal - Should display modal');
|
||||||
cy.percyCanvasSnapshot('About modal - Should display modal');
|
|
||||||
|
|
||||||
//close modal
|
//close modal
|
||||||
cy.get('[data-cy="close-button"]').click();
|
cy.get('[data-cy="close-button"]').click();
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ describe('OHIF User Preferences', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot(
|
cy.screenshot(
|
||||||
'User Preferences Modal - Hotkeys tab initial state in Study Viewer page'
|
'User Preferences Modal - Hotkeys tab initial state in Study List page'
|
||||||
);
|
|
||||||
cy.percyCanvasSnapshot(
|
|
||||||
'User Preferences Modal - Hotkeys tab initial state in Study Viewer page'
|
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="close-button"]').click();
|
cy.get('[data-cy="close-button"]').click();
|
||||||
});
|
});
|
||||||
@@ -42,16 +39,10 @@ describe('OHIF User Preferences', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot(
|
cy.screenshot(
|
||||||
'User Preferences Modal - General tab initial state in Study Viewer page'
|
'User Preferences Modal - General tab initial state in Study List page'
|
||||||
);
|
);
|
||||||
cy.percyCanvasSnapshot(
|
// Set language to Spanish and save
|
||||||
'User Preferences Modal - General tab initial state in Study Viewer page'
|
cy.setLanguage('Spanish');
|
||||||
);
|
|
||||||
// Select Spanish and Save
|
|
||||||
cy.get('#language-select').select('Spanish');
|
|
||||||
cy.get('@saveBtn')
|
|
||||||
.scrollIntoView()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Header should be translated to Spanish
|
// Header should be translated to Spanish
|
||||||
cy.get('.research-use')
|
cy.get('.research-use')
|
||||||
@@ -161,11 +152,8 @@ describe('OHIF User Preferences', () => {
|
|||||||
// Go to General tab
|
// Go to General tab
|
||||||
cy.get('@userPreferencesGeneralTab').click();
|
cy.get('@userPreferencesGeneralTab').click();
|
||||||
|
|
||||||
// Select Spanish and Save
|
// Set language to Spanish
|
||||||
cy.get('#language-select').select('Spanish');
|
cy.setLanguage('Spanish');
|
||||||
cy.get('@saveBtn')
|
|
||||||
.scrollIntoView()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Go to Study Viewer page
|
// Go to Study Viewer page
|
||||||
cy.openStudy('MISTER^MR');
|
cy.openStudy('MISTER^MR');
|
||||||
@@ -191,7 +179,9 @@ describe('OHIF User Preferences', () => {
|
|||||||
.should('contain.text', 'Preferencias');
|
.should('contain.text', 'Preferencias');
|
||||||
|
|
||||||
// Check if new hotkey is working on viewport
|
// Check if new hotkey is working on viewport
|
||||||
cy.get('body').type('{shift}Q', { release: false });
|
cy.get('body').type('{shift}Q', {
|
||||||
|
release: false,
|
||||||
|
});
|
||||||
cy.get('@viewportInfoMidTop').should('contains.text', 'R');
|
cy.get('@viewportInfoMidTop').should('contains.text', 'R');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -225,10 +215,7 @@ describe('OHIF User Preferences', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot(
|
cy.screenshot(
|
||||||
'User Preferences Modal - Hotkeys tab initial state in Study List page'
|
'User Preferences Modal - Hotkeys tab initial state in Study Viewer page'
|
||||||
);
|
|
||||||
cy.percyCanvasSnapshot(
|
|
||||||
'User Preferences Modal - Hotkeys tab initial state in Study List page'
|
|
||||||
);
|
);
|
||||||
cy.get('[data-cy="close-button"]').click(); //close User Preferences modal
|
cy.get('[data-cy="close-button"]').click(); //close User Preferences modal
|
||||||
});
|
});
|
||||||
@@ -243,16 +230,10 @@ describe('OHIF User Preferences', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot(
|
cy.screenshot(
|
||||||
'User Preferences Modal - General tab initial state in Study List page'
|
'User Preferences Modal - General tab initial state in Study Viewer page'
|
||||||
);
|
);
|
||||||
cy.percyCanvasSnapshot(
|
// Set language to Spanish
|
||||||
'User Preferences Modal - General tab initial state in Study List page'
|
cy.setLanguage('Spanish');
|
||||||
);
|
|
||||||
// Select Spanish and Save
|
|
||||||
cy.get('#language-select').select('Spanish');
|
|
||||||
cy.get('@saveBtn')
|
|
||||||
.scrollIntoView()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Header should be translated to Spanish
|
// Header should be translated to Spanish
|
||||||
cy.get('.research-use')
|
cy.get('.research-use')
|
||||||
@@ -310,19 +291,11 @@ describe('OHIF User Preferences', () => {
|
|||||||
// Language dropdown should be displayed
|
// Language dropdown should be displayed
|
||||||
cy.get('#language-select').should('be.visible');
|
cy.get('#language-select').should('be.visible');
|
||||||
|
|
||||||
// Select Spanish and Save
|
// Set language to Spanish
|
||||||
cy.get('#language-select').select('Spanish');
|
cy.setLanguage('Spanish');
|
||||||
cy.get('@saveBtn')
|
|
||||||
.scrollIntoView()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
//Open Preferences again
|
// Open User Preferences modal
|
||||||
cy.get('[data-cy="options-menu"]')
|
cy.openPreferences();
|
||||||
.scrollIntoView()
|
|
||||||
.click();
|
|
||||||
cy.get('[data-cy="dd-item-menu"]')
|
|
||||||
.last()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Go to general tab
|
// Go to general tab
|
||||||
cy.get('@userPreferencesGeneralTab').click();
|
cy.get('@userPreferencesGeneralTab').click();
|
||||||
@@ -437,9 +410,7 @@ describe('OHIF User Preferences', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Viewport Navigation - 2nd viewport inverted and rotated');
|
cy.screenshot('Viewport Navigation - 2nd viewport inverted and rotated');
|
||||||
cy.percyCanvasSnapshot(
|
|
||||||
'Viewport Navigation - 2nd viewport inverted and rotated'
|
|
||||||
);
|
|
||||||
// Set 1 viewport layout
|
// Set 1 viewport layout
|
||||||
cy.setLayout(1, 1);
|
cy.setLayout(1, 1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,8 +23,5 @@ describe('OHIF Microscopy Extension', () => {
|
|||||||
cy.wait(3000); //Waiting for image to render before taking the snapshot
|
cy.wait(3000); //Waiting for image to render before taking the snapshot
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('Microscopy Extension - Should display loaded canvas');
|
cy.screenshot('Microscopy Extension - Should display loaded canvas');
|
||||||
cy.percyCanvasSnapshot(
|
|
||||||
'Microscopy Extension - Should display loaded canvas'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -23,6 +23,5 @@ describe('OHIF PDF Extension', () => {
|
|||||||
// This won't work unless we switch to an extension that renders using `canvas`
|
// This won't work unless we switch to an extension that renders using `canvas`
|
||||||
// Currently, we rely on the browser's built-in implementation
|
// Currently, we rely on the browser's built-in implementation
|
||||||
cy.screenshot('PDF Extension - Should load PDF file');
|
cy.screenshot('PDF Extension - Should load PDF file');
|
||||||
cy.percyCanvasSnapshot('PDF Extension - Should load PDF file');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -24,10 +24,9 @@ describe('OHIF VTK Extension', () => {
|
|||||||
cy.get('[data-cy="thumbnail-list"]')
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
.contains('CT WB 5.0 B35f')
|
.contains('CT WB 5.0 B35f')
|
||||||
.drag('.viewport-drop-target')
|
.drag('.viewport-drop-target')
|
||||||
.wait(1000); // TODO: Once issue #1167 is fixed, the wait command should be removed. (https://github.com/OHIF/Viewers/issues/1167)
|
|
||||||
|
|
||||||
//Select 2D MPR button
|
//Select 2D MPR button
|
||||||
cy.get('.PluginSwitch > .toolbar-button').click();
|
cy.get('[data-cy="2d mpr"]').click();
|
||||||
|
|
||||||
//Wait Reformatting Images
|
//Wait Reformatting Images
|
||||||
cy.waitVTKReformatting();
|
cy.waitVTKReformatting();
|
||||||
@@ -58,9 +57,6 @@ describe('OHIF VTK Extension', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('VTK initial state - Should display toolbar and 3 viewports');
|
cy.screenshot('VTK initial state - Should display toolbar and 3 viewports');
|
||||||
cy.percyCanvasSnapshot(
|
|
||||||
'VTK initial state - Should display toolbar and 3 viewports'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks Crosshairs tool', () => {
|
it('checks Crosshairs tool', () => {
|
||||||
@@ -76,9 +72,6 @@ describe('OHIF VTK Extension', () => {
|
|||||||
cy.screenshot(
|
cy.screenshot(
|
||||||
"VTK Crosshairs tool - Should display crosshairs' green lines"
|
"VTK Crosshairs tool - Should display crosshairs' green lines"
|
||||||
);
|
);
|
||||||
cy.percyCanvasSnapshot(
|
|
||||||
"VTK Crosshairs tool - Should display crosshairs' green lines"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/* TODO: Non-deterministic behavior (const expectedText = 'W: 350 L: -1044';)
|
/* TODO: Non-deterministic behavior (const expectedText = 'W: 350 L: -1044';)
|
||||||
@@ -102,7 +95,6 @@ describe('OHIF VTK Extension', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('VTK WWWC tool - Canvas should be bright');
|
cy.screenshot('VTK WWWC tool - Canvas should be bright');
|
||||||
cy.percyCanvasSnapshot('VTK WWWC tool - Canvas should be bright');
|
|
||||||
}); */
|
}); */
|
||||||
|
|
||||||
it('checks Rotate tool', () => {
|
it('checks Rotate tool', () => {
|
||||||
@@ -118,6 +110,5 @@ describe('OHIF VTK Extension', () => {
|
|||||||
|
|
||||||
// Visual comparison
|
// Visual comparison
|
||||||
cy.screenshot('VTK Rotate tool - Should rotate image');
|
cy.screenshot('VTK Rotate tool - Should rotate image');
|
||||||
cy.percyCanvasSnapshot('VTK Rotate tool - Should rotate image');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
describe('OHIF Save Measurements', function() {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('MISTER^MR');
|
||||||
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Wait image to load on viewport
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
cy.resetViewport();
|
||||||
|
cy.initCommonElementsAliases();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('saves new measurement annotation', function() {
|
||||||
|
// Add measurement in the viewport
|
||||||
|
cy.addLengthMeasurement();
|
||||||
|
|
||||||
|
// Verify if measurement annotation was added into the measurements panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('.measurementItem')
|
||||||
|
.its('length')
|
||||||
|
.should('be.at.least', 1);
|
||||||
|
|
||||||
|
// Save new measurement
|
||||||
|
cy.get('[data-cy="save-measurements-btn"]').click();
|
||||||
|
|
||||||
|
// Verify that success message overlay is displayed
|
||||||
|
cy.get('.sb-success')
|
||||||
|
.should('be.visible')
|
||||||
|
.and('contains.text', 'Measurements were saved with success');
|
||||||
|
|
||||||
|
// Visual test comparison
|
||||||
|
cy.screenshot('Save Measurements - new measurement added');
|
||||||
|
cy.percyCanvasSnapshot('Save Measurements - new measurement added');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retrieves saved measurements', function() {
|
||||||
|
// Add measurement in the viewport
|
||||||
|
cy.addLengthMeasurement();
|
||||||
|
|
||||||
|
// Verify if measurement annotation was added into the measurements panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('.measurementDisplayText') // Get label size of the recently added measurement
|
||||||
|
.last()
|
||||||
|
.then($measurementSizeLabel => {
|
||||||
|
// Save new measurement
|
||||||
|
cy.get('[data-cy="save-measurements-btn"]').click();
|
||||||
|
|
||||||
|
// Verify that success message overlay is displayed
|
||||||
|
cy.get('.sb-success').should('be.visible');
|
||||||
|
|
||||||
|
// Reload the page
|
||||||
|
cy.reload()
|
||||||
|
.wait(1000) //Wait page to load
|
||||||
|
.expectMinimumThumbnails(2); //wait all thumbnails to load
|
||||||
|
|
||||||
|
//Verify that recently added measurement was retrieved
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('.measurementDisplayText') // Get label size of the recently added measurement
|
||||||
|
.last()
|
||||||
|
.then($retrivedMeasurementSizeLabel => {
|
||||||
|
expect($retrivedMeasurementSizeLabel.textContent).to.eq(
|
||||||
|
$measurementSizeLabel.textContent
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks error message when saving without any measurement', function() {
|
||||||
|
// Checks that measurement list is empty
|
||||||
|
cy.get('.numberOfItems').should('have.text', '0');
|
||||||
|
|
||||||
|
// Click on Save Measurement button
|
||||||
|
cy.get('[data-cy="save-measurements-btn"]').click();
|
||||||
|
|
||||||
|
// Verify that error message overlay is displayed
|
||||||
|
cy.get('.sb-error')
|
||||||
|
.should('be.visible')
|
||||||
|
.and('contains.text', 'Error while saving the measurements');
|
||||||
|
// Close message overlay
|
||||||
|
cy.get('.sb-closeIcon').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if warning message is displayed on measurements of unsupported tools', function() {
|
||||||
|
// Add measurement for unsupported tool in the viewport
|
||||||
|
cy.addAngleMeasurement();
|
||||||
|
|
||||||
|
// Verify if measurement annotation was added into the measurements panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('.measurementItem')
|
||||||
|
.its('length')
|
||||||
|
.should('be.at.least', 1);
|
||||||
|
|
||||||
|
// Check that warning is displayed for unsupported tool
|
||||||
|
cy.get('.hasWarnings').should('be.visible');
|
||||||
|
|
||||||
|
// Save new measurement
|
||||||
|
cy.get('[data-cy="save-measurements-btn"]').click();
|
||||||
|
|
||||||
|
// Verify that error message overlay is displayed
|
||||||
|
cy.get('.sb-error')
|
||||||
|
.should('be.visible')
|
||||||
|
.and('contains.text', 'Error while saving the measurements');
|
||||||
|
|
||||||
|
// Close Measurements panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if measurements of unsupported tools were not saved', function() {
|
||||||
|
// Add measurement for supported tool in the viewport
|
||||||
|
cy.addLengthMeasurement();
|
||||||
|
// Add measurement for unsupported tool in the viewport
|
||||||
|
cy.addAngleMeasurement();
|
||||||
|
|
||||||
|
// Verify if measurement annotation was added into the measurements panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('.measurementItem')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 2);
|
||||||
|
|
||||||
|
// Check that warning is displayed for unsupported tool
|
||||||
|
cy.get('.hasWarnings').should('be.visible');
|
||||||
|
|
||||||
|
// Save new measurement
|
||||||
|
cy.get('[data-cy="save-measurements-btn"]').click();
|
||||||
|
|
||||||
|
// Verify that success message overlay is displayed
|
||||||
|
cy.get('.sb-success')
|
||||||
|
.should('be.visible')
|
||||||
|
.and('contains.text', 'Measurements were saved with success');
|
||||||
|
|
||||||
|
// Reload the page
|
||||||
|
cy.reload()
|
||||||
|
.wait(1000) //Wait page to load
|
||||||
|
.expectMinimumThumbnails(2); //wait all thumbnails to load
|
||||||
|
|
||||||
|
//Verify that measurement for unsupported tool was not saved
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
cy.get('.measurementItem')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 1);
|
||||||
|
|
||||||
|
// Close Measurements panel
|
||||||
|
cy.get('@measurementsBtn').click();
|
||||||
|
});
|
||||||
|
});
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
describe('Visual Regression - OHIF Cornerstone Hotkeys', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('MISTER^MR');
|
||||||
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.initCornerstoneToolsAliases();
|
||||||
|
cy.initCommonElementsAliases();
|
||||||
|
cy.resetViewport();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if hotkey "I" can invert the image', () => {
|
||||||
|
// Hotkey I
|
||||||
|
cy.get('body').type('I');
|
||||||
|
// Visual comparison
|
||||||
|
cy.screenshot('Hotkey I - Should Invert Image');
|
||||||
|
cy.percyCanvasSnapshot('Hotkey I - Should Invert Image');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if hotkey "SPACEBAR" can reset the image', () => {
|
||||||
|
// Press multiples hotkeys
|
||||||
|
cy.get('body').type('V+++I');
|
||||||
|
|
||||||
|
// Hotkey SPACEBAR
|
||||||
|
cy.get('body').type(' ');
|
||||||
|
|
||||||
|
// Visual comparison to make sure the 'inverted' image was reset
|
||||||
|
cy.screenshot('Hotkey SPACEBAR - Should Reset Image');
|
||||||
|
cy.percyCanvasSnapshot('Hotkey SPACEBAR - Should Reset Image');
|
||||||
|
});
|
||||||
|
});
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
describe('Visual Regression - OHIF Cornerstone Toolbar', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('MISTER^MR');
|
||||||
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.initCornerstoneToolsAliases();
|
||||||
|
cy.initCommonElementsAliases();
|
||||||
|
cy.resetViewport();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if Pan tool will move the image inside the viewport', () => {
|
||||||
|
//Click on button and vefiry if icon is active on toolbar
|
||||||
|
cy.get('@panBtn')
|
||||||
|
.click()
|
||||||
|
.then($panBtn => {
|
||||||
|
cy.wrap($panBtn).should('have.class', 'active');
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('@viewport')
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'bottom', { which: 1 })
|
||||||
|
.trigger('mouseup', 'bottom');
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('Pan tool moved the image inside the viewport');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check if Invert tool will change the colors of the image in the viewport', () => {
|
||||||
|
// Click on More button
|
||||||
|
cy.get('@moreBtn').click();
|
||||||
|
// Verify if overlay is displayed
|
||||||
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
|
// Click on Invert button
|
||||||
|
cy.get('[data-cy="invert"]').click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('Invert tool - Should Invert Canvas');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check if Rotate tool will change the image orientation in the viewport', () => {
|
||||||
|
//Click on More button
|
||||||
|
cy.get('@moreBtn').click();
|
||||||
|
//Verify if overlay is displayed
|
||||||
|
cy.get('.tooltip-toolbar-overlay')
|
||||||
|
.should('be.visible')
|
||||||
|
.then(() => {
|
||||||
|
//Click on Rotate button
|
||||||
|
cy.get('[data-cy="rotate right"]').click({ force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('Rotate tool - Should Rotate Image to Right');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check if Flip H tool will flip the image horizontally in the viewport', () => {
|
||||||
|
//Click on More button
|
||||||
|
cy.get('@moreBtn').click();
|
||||||
|
//Verify if overlay is displayed
|
||||||
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
|
//Click on Flip H button
|
||||||
|
cy.get('[data-cy="flip h"]').click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('Flip H tool - Should Flip Image on Y axis');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('check if Flip V tool will flip the image vertically in the viewport', () => {
|
||||||
|
//Click on More button
|
||||||
|
cy.get('@moreBtn').click();
|
||||||
|
//Verify if overlay is displayed
|
||||||
|
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
||||||
|
|
||||||
|
//Click on Flip V button
|
||||||
|
cy.get('[data-cy="flip v"]').click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('Flip V tool - Should Flip Image on X axis');
|
||||||
|
});
|
||||||
|
});
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
describe('Visual Regression - OHIF Download Snapshot File', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('MISTER^MR');
|
||||||
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.openDownloadImageModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Close modal
|
||||||
|
cy.get('[data-cy="close-button"]')
|
||||||
|
.scrollIntoView()
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks displayed information for Tablet experience', function() {
|
||||||
|
// Set Tablet resolution
|
||||||
|
cy.viewport(1000, 660);
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('Download Image Modal - Tablet experience');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks displayed information for Desktop experience', function() {
|
||||||
|
// Set Desktop resolution
|
||||||
|
cy.viewport(1750, 720);
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('Download Image Modal - Desktop experience');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if "Show Annotations" checkbox will display annotations', function() {
|
||||||
|
// Close modal that is initially opened
|
||||||
|
cy.get('[data-cy="close-button"]').click();
|
||||||
|
|
||||||
|
// Add measurements in the viewport
|
||||||
|
cy.addLengthMeasurement();
|
||||||
|
cy.addAngleMeasurement();
|
||||||
|
|
||||||
|
// Open Modal
|
||||||
|
cy.openDownloadImageModal();
|
||||||
|
// Select "Show Annotations" option
|
||||||
|
cy.get('[data-cy="show-annotations"]').check();
|
||||||
|
// Check image preview
|
||||||
|
cy.get('[data-cy="image-preview"]').scrollIntoView();
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('Download Image Modal - Show Annotations checked');
|
||||||
|
});
|
||||||
|
});
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
describe('Visual Regression - OHIF Microscopy Extension', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudyModality('SM');
|
||||||
|
cy.expectMinimumThumbnails(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drags and drop a SM thumbnail into viewport', () => {
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('SM')
|
||||||
|
.drag('.viewport-drop-target');
|
||||||
|
|
||||||
|
cy.get('.DicomMicroscopyViewer')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 1);
|
||||||
|
|
||||||
|
cy.wait(3000); //Waiting for image to render before taking the snapshot
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'Microscopy Extension - Should display loaded canvas'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
describe('Visual Regression - OHIF PDF Extension', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('Dummy');
|
||||||
|
cy.expectMinimumThumbnails(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drags and drop a PDF thumbnail into viewport', () => {
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('DOC')
|
||||||
|
.drag('.viewport-drop-target');
|
||||||
|
|
||||||
|
cy.get('.DicomPDFViewport')
|
||||||
|
.its('length')
|
||||||
|
.should('be.eq', 1);
|
||||||
|
|
||||||
|
// This won't work unless we switch to an extension that renders using `canvas`
|
||||||
|
// Currently, we rely on the browser's built-in implementation
|
||||||
|
cy.percyCanvasSnapshot('PDF Extension - Should load PDF file');
|
||||||
|
});
|
||||||
|
});
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
describe('Visual Regression - OHIF VTK Extension', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('Bellona');
|
||||||
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(5);
|
||||||
|
|
||||||
|
//Waiting for the desired thumbnail content to be displayed
|
||||||
|
cy.get('[data-cy="thumbnail-list"]').should($list => {
|
||||||
|
expect($list).to.contain('Chest 1x10 Soft');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag and drop thumbnail into viewport
|
||||||
|
cy.get('[data-cy="thumbnail-list"]')
|
||||||
|
.contains('Chest 1x10 Soft')
|
||||||
|
.drag('.viewport-drop-target');
|
||||||
|
|
||||||
|
//Select 2D MPR button
|
||||||
|
cy.get('[data-cy="2d mpr"]').click();
|
||||||
|
|
||||||
|
//Wait Reformatting Images
|
||||||
|
cy.waitVTKReformatting();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.initVTKToolsAliases();
|
||||||
|
cy.wait(1000); //Wait toolbar to finish loading
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.wait(5000); //wait screen loads back after screenshot
|
||||||
|
|
||||||
|
//Select Exit 2D MPR button
|
||||||
|
cy.get('[data-cy="exit 2d mpr"]').should($btn => {
|
||||||
|
expect($btn).to.be.visible;
|
||||||
|
$btn.click();
|
||||||
|
});
|
||||||
|
//Select 2D MPR button
|
||||||
|
cy.get('[data-cy="2d mpr"]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if VTK buttons are displayed on the toolbar', () => {
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'VTK initial state - Should display toolbar and 3 viewports'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks Crosshairs tool', () => {
|
||||||
|
cy.get('@crosshairsBtn').click();
|
||||||
|
|
||||||
|
// Click and Move the mouse inside the viewport
|
||||||
|
cy.get('[data-cy="viewport-container-0"]')
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mouseup');
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
"VTK Crosshairs tool - Should display crosshairs' green lines"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks WWWC tool', () => {
|
||||||
|
cy.get('@wwwcBtn').click();
|
||||||
|
|
||||||
|
// Click and Move the mouse inside the viewport
|
||||||
|
cy.get('[data-cy="viewport-container-0"]')
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mouseup', { which: 1 });
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('VTK WWWC tool - Canvas should be bright');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks Rotate tool', () => {
|
||||||
|
cy.get('@rotateBtn').click();
|
||||||
|
|
||||||
|
// Click and Move the mouse inside the viewport
|
||||||
|
cy.get('[data-cy="viewport-container-0"]')
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mousedown', 'center', { which: 1 })
|
||||||
|
.trigger('mousemove', 'top', { which: 1 })
|
||||||
|
.trigger('mouseup', { which: 1 });
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('VTK Rotate tool - Should rotate image');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//We excluded the tests for '**/studies/**' because the bulk/all of our other study/viewer tests use this route
|
||||||
|
|
||||||
|
describe('Visual Regression - OHIF Routes', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
cy.openStudyList();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks PT/CT json url study route', function() {
|
||||||
|
cy.visit(
|
||||||
|
'/viewer?url=https://s3.eu-central-1.amazonaws.com/ohif-viewer/JSON/PTCTStudy.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.server();
|
||||||
|
cy.route('GET', '**/PTCTStudy/**').as('getPTCTStudy');
|
||||||
|
|
||||||
|
cy.wait('@getPTCTStudy.all');
|
||||||
|
cy.get('@getPTCTStudy').should($route => {
|
||||||
|
expect($route.status).to.be.eq(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.percyCanvasSnapshot('PT/CT json study route');
|
||||||
|
});
|
||||||
|
});
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
describe('Visual Regression - OHIF Study Viewer Page', function() {
|
||||||
|
before(function() {
|
||||||
|
cy.openStudy('MISTER^MR');
|
||||||
|
cy.waitDicomImage();
|
||||||
|
cy.expectMinimumThumbnails(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
cy.initCommonElementsAliases();
|
||||||
|
cy.resetViewport();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if series thumbnails are being displayed', function() {
|
||||||
|
cy.percyCanvasSnapshot('Series Thumbnails - Should Display Thumbnails');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens About modal and verify the displayed information', function() {
|
||||||
|
cy.get('[data-cy="options-menu"]')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
cy.get('[data-cy="dd-item-menu"]')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
cy.get('[data-cy="about-modal"]')
|
||||||
|
.as('aboutOverlay')
|
||||||
|
.should('be.visible');
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot('About modal - Should display modal');
|
||||||
|
|
||||||
|
//close modal
|
||||||
|
cy.get('[data-cy="close-button"]').click();
|
||||||
|
cy.get('@aboutOverlay').should('not.be.enabled');
|
||||||
|
});
|
||||||
|
});
|
||||||
+188
@@ -0,0 +1,188 @@
|
|||||||
|
describe('Visual Regression - OHIF User Preferences', () => {
|
||||||
|
context('Study List Page', function() {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Open User Preferences modal
|
||||||
|
cy.openPreferences();
|
||||||
|
cy.initPreferencesModalAliases();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks displayed information on User Preferences modal', function() {
|
||||||
|
cy.get('@restoreBtn').scrollIntoView();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'User Preferences Modal - Hotkeys tab initial state in StudyList page'
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="close-button"]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks translation by selecting Spanish language', function() {
|
||||||
|
cy.get('@userPreferencesGeneralTab')
|
||||||
|
.click()
|
||||||
|
.should('have.class', 'active');
|
||||||
|
|
||||||
|
// Language dropdown should be displayed
|
||||||
|
cy.get('#language-select').should('be.visible');
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'User Preferences Modal - General tab initial state in StudyList page'
|
||||||
|
);
|
||||||
|
// Set language to Spanish and save
|
||||||
|
cy.setLanguage('Spanish');
|
||||||
|
|
||||||
|
// Options menu should be translated
|
||||||
|
cy.get('[data-cy="options-menu"]')
|
||||||
|
.should('have.text', 'Opciones')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'User Preferences Modal - Spanish selected in StudyList page'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Close Options menu
|
||||||
|
cy.get('[data-cy="options-menu"]').click();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('Study Viewer Page', function() {
|
||||||
|
before(() => {
|
||||||
|
cy.openStudy('MISTER^MR');
|
||||||
|
cy.expectMinimumThumbnails(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.initCommonElementsAliases();
|
||||||
|
cy.resetViewport();
|
||||||
|
|
||||||
|
cy.resetUserHoktkeyPreferences();
|
||||||
|
// Open User Preferences modal
|
||||||
|
cy.openPreferences();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks displayed information on User Preferences modal', function() {
|
||||||
|
cy.get('@restoreBtn').scrollIntoView();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'User Preferences Modal - Hotkeys tab initial state in StudyViewer page'
|
||||||
|
);
|
||||||
|
cy.get('[data-cy="close-button"]').click(); //close User Preferences modal
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks translation by selecting Spanish language', function() {
|
||||||
|
cy.get('@userPreferencesGeneralTab')
|
||||||
|
.click()
|
||||||
|
.should('have.class', 'active');
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'User Preferences Modal - General tab initial state in StudyViewer page'
|
||||||
|
);
|
||||||
|
// Set language to Spanish
|
||||||
|
cy.setLanguage('Spanish');
|
||||||
|
|
||||||
|
// Options menu should be translated
|
||||||
|
cy.get('[data-cy="options-menu"]')
|
||||||
|
.should('have.text', 'Opciones')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'User Preferences Modal - Spanish selected in StudyViewer page'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Close Options menu
|
||||||
|
cy.get('[data-cy="options-menu"]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if user can restore to default the language selection and application will be in English', function() {
|
||||||
|
cy.get('@userPreferencesGeneralTab')
|
||||||
|
.click()
|
||||||
|
.should('have.class', 'active');
|
||||||
|
|
||||||
|
// Set language to Spanish
|
||||||
|
cy.setLanguage('Spanish');
|
||||||
|
|
||||||
|
// Open User Preferences modal
|
||||||
|
cy.openPreferences();
|
||||||
|
|
||||||
|
// Go to general tab
|
||||||
|
cy.get('@userPreferencesGeneralTab').click();
|
||||||
|
|
||||||
|
cy.get('@restoreBtn')
|
||||||
|
.scrollIntoView()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('@saveBtn')
|
||||||
|
.scrollIntoView()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Options menu should be in English
|
||||||
|
cy.get('[data-cy="options-menu"]')
|
||||||
|
.should('have.text', 'Options')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'User Preferences Modal - English selected in StudyViewer page'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Close Options menu
|
||||||
|
cy.get('[data-cy="options-menu"]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks new hotkeys for "Next" and "Previous" Image on Viewport', function() {
|
||||||
|
// Go go hotkeys tab
|
||||||
|
cy.get('@userPreferencesHotkeysTab')
|
||||||
|
.click()
|
||||||
|
.should('have.class', 'active');
|
||||||
|
|
||||||
|
// Set new hotkey for 'Next Image Viewport' function
|
||||||
|
cy.setNewHotkeyShortcutOnUserPreferencesModal(
|
||||||
|
'Next Image Viewport',
|
||||||
|
'{shift}{rightarrow}'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set new hotkey for 'Previous Image Viewport' function
|
||||||
|
cy.setNewHotkeyShortcutOnUserPreferencesModal(
|
||||||
|
'Previous Image Viewport',
|
||||||
|
'{shift}{leftarrow}'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save new hotkeys
|
||||||
|
cy.get('@saveBtn')
|
||||||
|
.scrollIntoView()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Set 3 viewports layout
|
||||||
|
cy.setLayout(3, 1);
|
||||||
|
cy.waitViewportImageLoading();
|
||||||
|
|
||||||
|
// Rotate Right and Invert colors on Viewport #1
|
||||||
|
cy.get('body').type('RI');
|
||||||
|
|
||||||
|
//Move to Next Viewport
|
||||||
|
cy.get('body').type('{shift}{rightarrow}');
|
||||||
|
// Rotate Left and Invert colors on Viewport #2
|
||||||
|
cy.get('body').type('LI');
|
||||||
|
|
||||||
|
//Move to Previous Viewport
|
||||||
|
cy.get('body').type('{shift}{leftarrow}');
|
||||||
|
// Reset viewport #1 with spacebar hotkey
|
||||||
|
cy.get('body').type(' ');
|
||||||
|
|
||||||
|
// Visual comparison
|
||||||
|
cy.percyCanvasSnapshot(
|
||||||
|
'Viewport Navigation - 2nd viewport inverted and rotated'
|
||||||
|
);
|
||||||
|
// Set 1 viewport layout
|
||||||
|
cy.setLayout(1, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
//Creating aliases for Cornerstone tools buttons
|
//Creating aliases for Cornerstone tools buttons
|
||||||
export function initCornerstoneToolsAliases() {
|
export function initCornerstoneToolsAliases() {
|
||||||
cy.get('.ToolbarRow > :nth-child(2)').as('stackScrollBtn');
|
cy.get('[data-cy="stack scroll"]').as('stackScrollBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(3)').as('zoomBtn');
|
cy.get('[data-cy="zoom"]').as('zoomBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(4)').as('levelsBtn');
|
cy.get('[data-cy="levels"]').as('levelsBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(5)').as('panBtn');
|
cy.get('[data-cy="pan"]').as('panBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(6)').as('lengthBtn');
|
cy.get('[data-cy="length"]').as('lengthBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(7)').as('annotateBtn');
|
cy.get('[data-cy="annotate"]').as('annotateBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(8)').as('angleBtn');
|
cy.get('[data-cy="angle"]').as('angleBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(9)').as('resetBtn');
|
cy.get('[data-cy="reset"]').as('resetBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(10)').as('cineBtn');
|
cy.get('[data-cy="cine"]').as('cineBtn');
|
||||||
cy.get('.expandableToolMenu').as('moreBtn');
|
cy.get('[data-cy="more"]').as('moreBtn');
|
||||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
cy.get('[data-cy="layout"]').as('layoutBtn');
|
||||||
cy.get('.viewport-element').as('viewport');
|
cy.get('.viewport-element').as('viewport');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,14 +44,14 @@ export function initRouteAliases() {
|
|||||||
|
|
||||||
//Creating aliases for VTK tools buttons
|
//Creating aliases for VTK tools buttons
|
||||||
export function initVTKToolsAliases() {
|
export function initVTKToolsAliases() {
|
||||||
cy.get('.PluginSwitch > .toolbar-button').as('twodmprBtn');
|
cy.get('[data-cy="exit 2d mpr"]').as('exit2dmprBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(2)').as('crosshairsBtn');
|
cy.get('[data-cy="crosshairs"]').as('crosshairsBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(3)').as('wwwcBtn');
|
cy.get('[data-cy="wwwc"]').as('wwwcBtn');
|
||||||
cy.get('.ToolbarRow > :nth-child(4)').as('rotateBtn');
|
cy.get('[data-cy="rotate"]').as('rotateBtn');
|
||||||
cy.get('.slab-thickness').as('slabSlider');
|
cy.get('.slab-thickness').as('slabSlider');
|
||||||
cy.get('.select-ohif').as('modeDropdown');
|
cy.get('.select-ohif').as('modeDropdown');
|
||||||
cy.get('.ohif-check-label').as('modeCheckbox');
|
cy.get('.ohif-check-label').as('modeCheckbox');
|
||||||
cy.get('.btn-group > .toolbar-button').as('layoutBtn');
|
cy.get('[data-cy="layout"]').as('layoutBtn');
|
||||||
}
|
}
|
||||||
|
|
||||||
//Creating aliases for Study List page elements on Desktop experience
|
//Creating aliases for Study List page elements on Desktop experience
|
||||||
@@ -82,12 +82,8 @@ export function initStudyListAliasesOnTablet() {
|
|||||||
//Creating aliases for User Preferences modal
|
//Creating aliases for User Preferences modal
|
||||||
export function initPreferencesModalAliases() {
|
export function initPreferencesModalAliases() {
|
||||||
cy.get('.OHIFModal').as('preferencesModal');
|
cy.get('.OHIFModal').as('preferencesModal');
|
||||||
cy.get('.nav-link')
|
cy.get('[data-cy="hotkeys"]').as('userPreferencesHotkeysTab');
|
||||||
.first()
|
cy.get('[data-cy="general"]').as('userPreferencesGeneralTab');
|
||||||
.as('userPreferencesHotkeysTab');
|
|
||||||
cy.get('.nav-link')
|
|
||||||
.last()
|
|
||||||
.as('userPreferencesGeneralTab');
|
|
||||||
cy.get('[data-cy="reset-default-btn"]').as('restoreBtn');
|
cy.get('[data-cy="reset-default-btn"]').as('restoreBtn');
|
||||||
cy.get('[data-cy="cancel-btn"]').as('cancelBtn');
|
cy.get('[data-cy="cancel-btn"]').as('cancelBtn');
|
||||||
cy.get('[data-cy="save-btn"]').as('saveBtn');
|
cy.get('[data-cy="save-btn"]').as('saveBtn');
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -168,7 +169,7 @@ Cypress.Commands.add(
|
|||||||
);
|
);
|
||||||
|
|
||||||
Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => {
|
Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => {
|
||||||
cy.get('[data-cy=thumbnail-list]', { timeout: 10000 }).should($itemList => {
|
cy.get('[data-cy=thumbnail-list]', { timeout: 20000 }).should($itemList => {
|
||||||
expect($itemList.length >= seriesToWait).to.be.true;
|
expect($itemList.length >= seriesToWait).to.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -209,7 +210,7 @@ Cypress.Commands.add('waitDicomImage', (timeout = 20000) => {
|
|||||||
//Command to reset and clear all the changes made to the viewport
|
//Command to reset and clear all the changes made to the viewport
|
||||||
Cypress.Commands.add('resetViewport', () => {
|
Cypress.Commands.add('resetViewport', () => {
|
||||||
//Click on More button
|
//Click on More button
|
||||||
cy.get('.expandableToolMenu')
|
cy.get('[data-cy="more"]')
|
||||||
.as('moreBtn')
|
.as('moreBtn')
|
||||||
.click();
|
.click();
|
||||||
//Verify if overlay is displayed
|
//Verify if overlay is displayed
|
||||||
@@ -219,11 +220,11 @@ Cypress.Commands.add('resetViewport', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
//Click on Clear button
|
//Click on Clear button
|
||||||
cy.get('.tooltip-inner > :nth-child(10)')
|
cy.get('[data-cy="clear"]')
|
||||||
.as('clearBtn')
|
.as('clearBtn')
|
||||||
.click();
|
.click();
|
||||||
//Click on Reset button
|
//Click on Reset button
|
||||||
cy.get('.ToolbarRow > :nth-child(9)')
|
cy.get('[data-cy="reset"]')
|
||||||
.as('resetBtn')
|
.as('resetBtn')
|
||||||
.click();
|
.click();
|
||||||
});
|
});
|
||||||
@@ -284,9 +285,8 @@ Cypress.Commands.add('initStudyListAliasesOnTablet', () => {
|
|||||||
Cypress.Commands.add(
|
Cypress.Commands.add(
|
||||||
'addLengthMeasurement',
|
'addLengthMeasurement',
|
||||||
(firstClick = [150, 100], secondClick = [130, 170]) => {
|
(firstClick = [150, 100], secondClick = [130, 170]) => {
|
||||||
cy.initCornerstoneToolsAliases();
|
cy.get('[data-cy="length"]').click();
|
||||||
cy.get('@lengthBtn').click();
|
cy.addLine('.viewport-element', firstClick, secondClick);
|
||||||
cy.addLine('@viewport', firstClick, secondClick);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -294,9 +294,8 @@ Cypress.Commands.add(
|
|||||||
Cypress.Commands.add(
|
Cypress.Commands.add(
|
||||||
'addAngleMeasurement',
|
'addAngleMeasurement',
|
||||||
(initPos = [180, 390], midPos = [300, 410], finalPos = [180, 450]) => {
|
(initPos = [180, 390], midPos = [300, 410], finalPos = [180, 450]) => {
|
||||||
cy.initCornerstoneToolsAliases();
|
cy.get('[data-cy="angle"]').click();
|
||||||
cy.get('@angleBtn').click();
|
cy.addAngle('.viewport-element', initPos, midPos, finalPos);
|
||||||
cy.addAngle('@viewport', initPos, midPos, finalPos);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -369,7 +368,7 @@ Cypress.Commands.add('percyCanvasSnapshot', (name, options = {}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('setLayout', (columns = 1, rows = 1) => {
|
Cypress.Commands.add('setLayout', (columns = 1, rows = 1) => {
|
||||||
cy.get('.btn-group > .toolbar-button').click();
|
cy.get('[data-cy="layout"]').click();
|
||||||
|
|
||||||
cy.get('.layoutChooser')
|
cy.get('.layoutChooser')
|
||||||
.find('tr')
|
.find('tr')
|
||||||
@@ -471,12 +470,12 @@ Cypress.Commands.add(
|
|||||||
|
|
||||||
Cypress.Commands.add('openDownloadImageModal', () => {
|
Cypress.Commands.add('openDownloadImageModal', () => {
|
||||||
// Click on More button
|
// Click on More button
|
||||||
cy.get('.expandableToolMenu')
|
cy.get('[data-cy="more"]')
|
||||||
.as('moreBtn')
|
.as('moreBtn')
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
// Click on Download button
|
// Click on Download button
|
||||||
cy.get('.tooltip-inner > :nth-child(13)')
|
cy.get('[data-cy="download"]')
|
||||||
.as('downloadBtn')
|
.as('downloadBtn')
|
||||||
.click();
|
.click();
|
||||||
});
|
});
|
||||||
|
|||||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user