feat: Add Static WADO display (#2499)

* Rebased to have just the static view changes

* Changes based on the PR

* Couple more changes for the PR

* Removed an extraneous log

* Fix the study worklist display when returning to the page

* Added some documentation on the static wado data source setup, and a
bounds check change on the Queue test which was failing

* PR updates - change the undefined study description to '' and remove the aws deploy

* Fixed the e2e tests to actually pass/fail correctly on actual results
This commit is contained in:
Bill Wallace authored and Alireza committed 2021-08-24 17:53:03 -04:00
1 parent ca900f80e1
commit 2327b4ae12
21 files changed
+483 -41

No files matched your search

@@ -1,12 +1,12 @@
//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.
describe('OHIF Study List', function() {
context('Desktop resolution', function() {
before(function() {
describe('OHIF Study List', function () {
context('Desktop resolution', function () {
before(function () {
cy.openStudyList();
});
beforeEach(function() {
beforeEach(function () {
cy.viewport(1750, 720);
cy.initStudyListAliasesOnDesktop();
//Clear all text fields
@@ -21,31 +21,42 @@ describe('OHIF Study List', function() {
//cy.get('@modalities')
});
it('searches Patient Name with exact string', function() {
it('Displays several studies initially', function () {
cy.waitStudyList();
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.greaterThan(1);
expect($list).to.contain('Juno');
expect($list).to.contain('832040');
});
});
it('searches Patient Name with exact string', function () {
cy.get('@PatientName').type('Juno');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('Juno');
});
});
it('searches MRN with exact string', function() {
/* TODO - re-enable this once the test server is fixed to allow searching by mrn
it('searches MRN with exact string', function () {
cy.get('@MRN').type('0000003');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('0000003');
});
});
*/
it('searches Accession with exact string', function() {
it('searches Accession with exact string', function () {
cy.get('@AccessionNumber').type('0000155811');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('0000155811');
});
@@ -56,7 +67,7 @@ describe('OHIF Study List', function() {
cy.get('@modalities').type('Ct');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.greaterThan(1);
expect($list).to.contain('CT');
});
@@ -68,7 +79,7 @@ describe('OHIF Study List', function() {
cy.get('@StudyDescription').type('PETCT');
//Wait result list to be displayed
cy.waitStudyList();
cy.get('@searchResult').should($list => {
cy.get('@searchResult2').should($list => {
expect($list.length).to.be.eq(1);
expect($list).to.contain('PETCT');
});
@@ -92,7 +103,7 @@ describe('OHIF Study List', function() {
})
.then(numStudies => {
//Compare to the number of Rows in the search result
cy.get('@searchResult').then($searchResult => {
cy.get('@searchResult2').then($searchResult => {
let countResults = $searchResult.length;
expect(numStudies.text()).to.be.eq(countResults.toString());
});
+4
View File
@@ -18,12 +18,16 @@
"proxy": "http://localhost:8042",
"scripts": {
"build:viewer": "cross-env NODE_ENV=production yarn run build",
"build:dev": "cross-env NODE_ENV=development yarn run build",
"build:aws": "cross-env NODE_ENV=development APP_CONFIG=config/aws_static.js yarn run build && gzip -9 -r dist",
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/ APP_CONFIG=config/netlify.js QUICK_BUILD=true yarn run build",
"build:viewer:qa": "cross-env NODE_ENV=production APP_CONFIG=config/google.js yarn run build",
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=true yarn run build",
"build": "node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
"dev": "cross-env NODE_ENV=development webpack serve --config .webpack/webpack.pwa.js",
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack serve --config .webpack/webpack.pwa.js",
"dev:dcm4chee": "cross-env NODE_ENV=development APP_CONFIG=config/local_dcm4chee.js webpack serve --config .webpack/webpack.pwa.js",
"dev:static": "cross-env NODE_ENV=development APP_CONFIG=config/local_static.js webpack serve --config .webpack/webpack.pwa.js",
"dev:viewer": "yarn run dev",
"start": "yarn run dev",
"test:e2e": "cypress open",
+159
View File
@@ -0,0 +1,159 @@
window.config = {
routerBasename: '/',
// whiteLabelling: {},
extensions: [],
modes: [],
showStudyList: true,
// filterQueryParam: false,
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
namespace: 'org.ohif.default.dataSourcesModule.dicomweb',
sourceName: 'dicomweb',
configuration: {
name: 'DCM4CHEE',
// Something here to check build
wadoUriRoot: 'https://myserver.com/dicomweb',
qidoRoot: 'https://myserver.com/dicomweb',
wadoRoot: 'https://myserver.com/dicomweb',
qidoSupportsIncludeField: false,
supportsReject: false,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
supportsFuzzyMatching: false,
supportsWildcard: false,
staticWado: true,
},
},
{
friendlyName: 'dicom json',
namespace: 'org.ohif.default.dataSourcesModule.dicomjson',
sourceName: 'dicomjson',
configuration: {
name: 'json',
},
},
{
friendlyName: 'dicom local',
namespace: 'org.ohif.default.dataSourcesModule.dicomlocal',
sourceName: 'dicomlocal',
configuration: {},
},
],
httpErrorHandler: error => {
// This is 429 when rejected from the public idc sandbox too often.
console.warn(error.status);
// Could use services manager here to bring up a dialog/modal if needed.
console.warn('test, navigate to https://ohif.org/');
},
// whiteLabeling: {
// /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */
// createLogoComponentFn: function (React) {
// return React.createElement(
// 'a',
// {
// target: '_self',
// rel: 'noopener noreferrer',
// className: 'text-purple-600 line-through',
// href: '/',
// },
// React.createElement('img',
// {
// src: './customLogo.svg',
// className: 'w-8 h-8',
// }
// ))
// },
// },
defaultDataSourceName: 'dicomweb',
hotkeys: [
{
commandName: 'incrementActiveViewport',
label: 'Next Viewport',
keys: ['right'],
},
{
commandName: 'decrementActiveViewport',
label: 'Previous Viewport',
keys: ['left'],
},
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
{
commandName: 'flipViewportVertical',
label: 'Flip Horizontally',
keys: ['h'],
},
{
commandName: 'flipViewportHorizontal',
label: 'Flip Vertically',
keys: ['v'],
},
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
{
commandName: 'previousViewportDisplaySet',
label: 'Previous Series',
keys: ['pagedown'],
},
{
commandName: 'nextViewportDisplaySet',
label: 'Next Series',
keys: ['pageup'],
},
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
// ~ Window level presets
{
commandName: 'windowLevelPreset1',
label: 'W/L Preset 1',
keys: ['1'],
},
{
commandName: 'windowLevelPreset2',
label: 'W/L Preset 2',
keys: ['2'],
},
{
commandName: 'windowLevelPreset3',
label: 'W/L Preset 3',
keys: ['3'],
},
{
commandName: 'windowLevelPreset4',
label: 'W/L Preset 4',
keys: ['4'],
},
{
commandName: 'windowLevelPreset5',
label: 'W/L Preset 5',
keys: ['5'],
},
{
commandName: 'windowLevelPreset6',
label: 'W/L Preset 6',
keys: ['6'],
},
{
commandName: 'windowLevelPreset7',
label: 'W/L Preset 7',
keys: ['7'],
},
{
commandName: 'windowLevelPreset8',
label: 'W/L Preset 8',
keys: ['8'],
},
{
commandName: 'windowLevelPreset9',
label: 'W/L Preset 9',
keys: ['9'],
},
],
};
@@ -20,8 +20,8 @@ window.config = {
imageRendering: 'wadouri',
thumbnailRendering: 'wadouri',
enableStudyLazyLoad: true,
supportsFuzzyMatching: true,
supportsWildcard: true,
supportsFuzzyMatching: false,
supportsWildcard: false,
},
},
],
@@ -0,0 +1,158 @@
window.config = {
routerBasename: '/',
// whiteLabelling: {},
extensions: [],
modes: [],
showStudyList: true,
// filterQueryParam: false,
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
namespace: 'org.ohif.default.dataSourcesModule.dicomweb',
sourceName: 'dicomweb',
configuration: {
name: 'DCM4CHEE',
wadoUriRoot: 'http://localhost:5000',
qidoRoot: 'http://localhost:5000',
wadoRoot: 'http://localhost:5000',
qidoSupportsIncludeField: false,
supportsReject: false,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
supportsFuzzyMatching: false,
supportsWildcard: false,
staticWado: true,
},
},
{
friendlyName: 'dicom json',
namespace: 'org.ohif.default.dataSourcesModule.dicomjson',
sourceName: 'dicomjson',
configuration: {
name: 'json',
},
},
{
friendlyName: 'dicom local',
namespace: 'org.ohif.default.dataSourcesModule.dicomlocal',
sourceName: 'dicomlocal',
configuration: {},
},
],
httpErrorHandler: error => {
// This is 429 when rejected from the public idc sandbox too often.
console.warn(error.status);
// Could use services manager here to bring up a dialog/modal if needed.
console.warn('test, navigate to https://ohif.org/');
},
// whiteLabeling: {
// /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */
// createLogoComponentFn: function (React) {
// return React.createElement(
// 'a',
// {
// target: '_self',
// rel: 'noopener noreferrer',
// className: 'text-purple-600 line-through',
// href: '/',
// },
// React.createElement('img',
// {
// src: './customLogo.svg',
// className: 'w-8 h-8',
// }
// ))
// },
// },
defaultDataSourceName: 'dicomweb',
hotkeys: [
{
commandName: 'incrementActiveViewport',
label: 'Next Viewport',
keys: ['right'],
},
{
commandName: 'decrementActiveViewport',
label: 'Previous Viewport',
keys: ['left'],
},
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
{
commandName: 'flipViewportVertical',
label: 'Flip Horizontally',
keys: ['h'],
},
{
commandName: 'flipViewportHorizontal',
label: 'Flip Vertically',
keys: ['v'],
},
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
{
commandName: 'previousViewportDisplaySet',
label: 'Previous Series',
keys: ['pagedown'],
},
{
commandName: 'nextViewportDisplaySet',
label: 'Next Series',
keys: ['pageup'],
},
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
// ~ Window level presets
{
commandName: 'windowLevelPreset1',
label: 'W/L Preset 1',
keys: ['1'],
},
{
commandName: 'windowLevelPreset2',
label: 'W/L Preset 2',
keys: ['2'],
},
{
commandName: 'windowLevelPreset3',
label: 'W/L Preset 3',
keys: ['3'],
},
{
commandName: 'windowLevelPreset4',
label: 'W/L Preset 4',
keys: ['4'],
},
{
commandName: 'windowLevelPreset5',
label: 'W/L Preset 5',
keys: ['5'],
},
{
commandName: 'windowLevelPreset6',
label: 'W/L Preset 6',
keys: ['6'],
},
{
commandName: 'windowLevelPreset7',
label: 'W/L Preset 7',
keys: ['7'],
},
{
commandName: 'windowLevelPreset8',
label: 'W/L Preset 8',
keys: ['8'],
},
{
commandName: 'windowLevelPreset9',
label: 'W/L Preset 9',
keys: ['9'],
},
],
};
@@ -49,6 +49,7 @@ function DataSourceWrapper(props) {
total: 0,
resultsPerPage: 25,
pageNumber: 1,
location: 'Not a valid location, causes first load to occur',
});
const [isLoading, setIsLoading] = useState(false);
@@ -68,6 +69,7 @@ function DataSourceWrapper(props) {
total: studies.length,
resultsPerPage: queryFilterValues.resultsPerPage,
pageNumber: queryFilterValues.pageNumber,
location,
});
setIsLoading(false);
@@ -77,7 +79,6 @@ function DataSourceWrapper(props) {
// Cache invalidation :thinking:
// - Anytime change is not just next/previous page
// - And we didn't cross a result offset range
const isFirstLoad = data.studies.length === 0 && !isLoading;
const isSamePage = data.pageNumber === queryFilterValues.pageNumber;
const previousOffset =
Math.floor((data.pageNumber * data.resultsPerPage) / STUDIES_LIMIT) *
@@ -85,11 +86,12 @@ function DataSourceWrapper(props) {
const newOffset =
Math.floor(
(queryFilterValues.pageNumber * queryFilterValues.resultsPerPage) /
STUDIES_LIMIT
STUDIES_LIMIT
) *
(STUDIES_LIMIT - 1);
const isDataInvalid =
isFirstLoad || !isSamePage || newOffset !== previousOffset;
const isLocationUpdated = data.location !== location;
const isDataInvalid = !isSamePage || !isLoading &&
(newOffset !== previousOffset || isLocationUpdated);
if (isDataInvalid) {
getData();
+2 -2
View File
@@ -55,8 +55,8 @@ function Local() {
return acc.concat(mods)
}, [])
const fistLocalDataSource = localDataSources[0]
const dataSource = fistLocalDataSource.createDataSource({})
const firstLocalDataSource = localDataSources[0]
const dataSource = firstLocalDataSource.createDataSource({})
const onDrop = async (acceptedFiles) => {
const studies = await filesToStudies(acceptedFiles, dataSource)