From 0e6cdde6cc9bfe9bfac29b1ed7bfa0545ba641d5 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Tue, 25 May 2021 07:27:34 -0300 Subject: [PATCH 01/11] Bump dcmjs and fix metadata provider after dcmjs server update --- extensions/cornerstone/package.json | 4 ++-- extensions/default/package.json | 2 +- extensions/dicom-sr/package.json | 2 +- extensions/measurement-tracking/package.json | 2 +- platform/core/package.json | 6 +++--- platform/core/src/classes/MetadataProvider.js | 13 ++++++++----- platform/core/src/utils/index.js | 7 +++++-- platform/core/src/utils/validNumber.js | 15 +++++++++++++++ platform/viewer/package.json | 4 ++-- yarn.lock | 15 +++++++++++---- 10 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 platform/core/src/utils/validNumber.js diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index fb63d6d2e4a..a3c0f2d879a 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -35,7 +35,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "5.1.2", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.16.1", + "dcmjs": "0.18.8", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", @@ -45,8 +45,8 @@ }, "dependencies": { "@babel/runtime": "7.7.6", - "lodash.merge": "^4.6.2", "lodash.debounce": "4.0.8", + "lodash.merge": "^4.6.2", "react-cornerstone-viewport": "4.0.2" } } diff --git a/extensions/default/package.json b/extensions/default/package.json index 2ae7ebc391f..c03a3e4ee22 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -29,7 +29,7 @@ "peerDependencies": { "@ohif/core": "^0.50.0", "@ohif/i18n": "^0.52.8", - "dcmjs": "0.16.1", + "dcmjs": "0.18.8", "dicomweb-client": "^0.6.0", "prop-types": "^15.6.2", "react": "^16.13.1", diff --git a/extensions/dicom-sr/package.json b/extensions/dicom-sr/package.json index ba35bbcb770..66de9d40c44 100644 --- a/extensions/dicom-sr/package.json +++ b/extensions/dicom-sr/package.json @@ -35,7 +35,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "5.1.2", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.16.1", + "dcmjs": "0.18.8", "dicom-parser": "^1.8.3", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index 45f62bd2b22..ac3a487d192 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -31,7 +31,7 @@ "classnames": "^2.2.6", "cornerstone-core": "^2.3.0", "cornerstone-tools": "5.1.2", - "dcmjs": "0.16.1", + "dcmjs": "0.18.8", "prop-types": "^15.6.2", "react": "^16.13.1", "react-cornerstone-viewport": "^4.0.4", diff --git a/platform/core/package.json b/platform/core/package.json index a5a2cc6ca75..5e7499f2aaf 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -37,15 +37,15 @@ }, "dependencies": { "@babel/runtime": "7.7.6", - "dcmjs": "0.16.1", + "cornerstone-math": "0.1.9", + "dcmjs": "0.18.8", "dicomweb-client": "^0.6.0", "isomorphic-base64": "^1.0.2", "lodash.merge": "^4.6.1", "moment": "^2.24.0", "mousetrap": "^1.6.3", - "query-string": "^6.14.0", "object-hash": "2.1.1", - "cornerstone-math": "0.1.9" + "query-string": "^6.14.0" }, "devDependencies": { "webpack-merge": "5.7.3" diff --git a/platform/core/src/classes/MetadataProvider.js b/platform/core/src/classes/MetadataProvider.js index 7c4cc555ed8..55dff682eaf 100644 --- a/platform/core/src/classes/MetadataProvider.js +++ b/platform/core/src/classes/MetadataProvider.js @@ -4,6 +4,7 @@ import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacin import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData'; import fetchOverlayData from '../utils/metadataProvider/fetchOverlayData'; import DicomMetadataStore from '../services/DicomMetadataStore'; +import { validNumber } from '../utils'; class MetadataProvider { constructor() { @@ -271,7 +272,7 @@ class MetadataProvider { break; case WADO_IMAGE_LOADER_TAGS.VOI_LUT_MODULE: - const { WindowCenter, WindowWidth } = instance; + let { WindowCenter, WindowWidth } = instance; const windowCenter = Array.isArray(WindowCenter) ? WindowCenter @@ -281,15 +282,17 @@ class MetadataProvider { : [WindowWidth]; metadata = { - windowCenter, - windowWidth, + windowCenter: validNumber(windowCenter), + windowWidth: validNumber(windowWidth), }; break; case WADO_IMAGE_LOADER_TAGS.MODALITY_LUT_MODULE: + const rescaleSlope = validNumber(instance.RescaleSlope); + const rescaleIntercept = validNumber(instance.RescaleIntercept); metadata = { - rescaleIntercept: instance.RescaleIntercept, - rescaleSlope: instance.RescaleSlope, + rescaleIntercept, + rescaleSlope, rescaleType: instance.RescaleType, }; break; diff --git a/platform/core/src/utils/index.js b/platform/core/src/utils/index.js index 81355fb713f..135f9380313 100644 --- a/platform/core/src/utils/index.js +++ b/platform/core/src/utils/index.js @@ -20,6 +20,7 @@ import resolveObjectPath from './resolveObjectPath'; import hierarchicalListUtils from './hierarchicalListUtils'; import progressTrackingUtils from './progressTrackingUtils'; import isLowPriorityModality from './isLowPriorityModality'; +import validNumber from './validNumber'; // Commented out unused functionality. // Need to implement new mechanism for dervived displaySets using the displaySetManager. @@ -46,7 +47,8 @@ const utils = { resolveObjectPath, hierarchicalListUtils, progressTrackingUtils, - isLowPriorityModality + isLowPriorityModality, + validNumber, }; export { @@ -69,7 +71,8 @@ export { resolveObjectPath, hierarchicalListUtils, progressTrackingUtils, - isLowPriorityModality + isLowPriorityModality, + validNumber, }; export default utils; diff --git a/platform/core/src/utils/validNumber.js b/platform/core/src/utils/validNumber.js new file mode 100644 index 00000000000..db6178bc7b3 --- /dev/null +++ b/platform/core/src/utils/validNumber.js @@ -0,0 +1,15 @@ +/** + * Validate a number + * + * @param {number} val + * @returns {boolean} boolean indicating wether the number is a valid number + */ +const validNumber = val => { + if (Array.isArray(val)) { + return val.map(v => (v !== undefined ? Number(v) : v)); + } else { + return val !== undefined ? Number(val) : val; + } +}; + +export default validNumber; diff --git a/platform/viewer/package.json b/platform/viewer/package.json index cb85334ca34..8d1d903a18c 100644 --- a/platform/viewer/package.json +++ b/platform/viewer/package.json @@ -58,7 +58,7 @@ "cornerstone-math": "^0.1.8", "cornerstone-tools": "5.1.2", "cornerstone-wado-image-loader": "^3.1.2", - "dcmjs": "0.16.1", + "dcmjs": "0.18.8", "dicom-parser": "^1.8.3", "dotenv-webpack": "^1.7.0", "hammerjs": "^2.0.8", @@ -75,8 +75,8 @@ }, "devDependencies": { "@percy/cypress": "^2.3.0", - "cypress-file-upload": "^3.5.3", "cypress": "^6.4.0", + "cypress-file-upload": "^3.5.3", "identity-obj-proxy": "3.0.x", "lodash": "4.17.15", "terser-webpack-plugin": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 1cb20bb284c..2937266693c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6987,13 +6987,15 @@ dayjs@^1.9.3: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== -dcmjs@0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.1.tgz#76bc61cbdf2c58a2f54990080729ca2d7ee19543" - integrity sha512-t6vsKi5QXzwX1fwnHY2hdU99FfyzmK8aO0OSBDH6XvJNrBp2A6HpoVWbucybOy8eStIPDqs4v0FGP/m39cTCRA== +dcmjs@0.18.8: + version "0.18.8" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.18.8.tgz#fc8e6af03ace9050da49f4108334aa3d0a3bdf3d" + integrity sha512-Z3nNEmzkWdpHQPgjozGqk8XcuhP/hXP1C64Wtg9V+VO9eEc8eNFJFJ6If62f1cuOh0FaRwHqxBT7OzhOAUpFTA== dependencies: "@babel/polyfill" "^7.8.3" "@babel/runtime" "^7.8.4" + gl-matrix "^3.1.0" + lodash.clonedeep "^4.5.0" loglevelnext "^3.0.1" ndarray "^1.0.19" @@ -10173,6 +10175,11 @@ github-slugger@^1.0.0, github-slugger@^1.1.1, github-slugger@^1.2.1: dependencies: emoji-regex ">=6.0.0 <=6.1.1" +gl-matrix@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b" + integrity sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA== + glob-all@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.2.1.tgz#082ca81afd2247cbd3ed2149bb2630f4dc877d95" From 104fe98d0e004ab733f3523dbbb2990626c4eecc Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Tue, 25 May 2021 08:09:46 -0300 Subject: [PATCH 02/11] Resolve format issues with slice thickness --- .../dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js | 4 +++- .../src/viewports/TrackedCornerstoneViewport.js | 4 +++- platform/core/src/classes/MetadataProvider.js | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js b/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js index 9d8d4e74d71..3b8c2218ab5 100644 --- a/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js +++ b/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js @@ -401,7 +401,9 @@ function OHIFCornerstoneSRViewport({ patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', - thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', + thickness: SliceThickness + ? `${Number(SliceThickness).toFixed(2)}mm` + : '', spacing: SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js index 2fd411e881d..a7172c6470e 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js @@ -341,7 +341,9 @@ function TrackedCornerstoneViewport({ patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', - thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', + thickness: SliceThickness + ? `${Number(SliceThickness).toFixed(2)}mm` + : '', spacing: SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` diff --git a/platform/core/src/classes/MetadataProvider.js b/platform/core/src/classes/MetadataProvider.js index 55dff682eaf..1c54667c718 100644 --- a/platform/core/src/classes/MetadataProvider.js +++ b/platform/core/src/classes/MetadataProvider.js @@ -235,7 +235,7 @@ class MetadataProvider { rowCosines, columnCosines, imagePositionPatient: instance.ImagePositionPatient, - sliceThickness: instance.SliceThickness, + sliceThickness: validNumber(instance.SliceThickness), sliceLocation: instance.SliceLocation, pixelSpacing: PixelSpacing, rowPixelSpacing, From e2379f199fa186799e3378da62f56c20e810e2a4 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Thu, 27 May 2021 08:48:24 -0300 Subject: [PATCH 03/11] Resolve promise logic of dcm4chee reject of srs causing dialog to get stuck when deleting sr --- .../src/DicomWebDataSource/dcm4cheeReject.js | 66 ++++++++++--------- .../PanelStudyBrowserTracking.jsx | 2 +- platform/ui/src/components/Dialog/Dialog.jsx | 11 ++-- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/extensions/default/src/DicomWebDataSource/dcm4cheeReject.js b/extensions/default/src/DicomWebDataSource/dcm4cheeReject.js index 542267abefb..66e5522eed0 100644 --- a/extensions/default/src/DicomWebDataSource/dcm4cheeReject.js +++ b/extensions/default/src/DicomWebDataSource/dcm4cheeReject.js @@ -1,35 +1,39 @@ -export default function (wadoRoot) { +/** + * Rejects a given series latest structured report. + * + * @param {string} wadoRoot + * @returns + */ +export default function(wadoRoot) { return { - series: (StudyInstanceUID, SeriesInstanceUID) => { - return new Promise((resolve, reject) => { - // Reject because of Quality. (Seems the most sensible out of the options) - const CodeValueAndCodeSchemeDesignator = `113001%5EDCM`; - - const url = `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/reject/${CodeValueAndCodeSchemeDesignator}`; - - const xhr = new XMLHttpRequest(); - xhr.open('POST', url, true); - - //Send the proper header information along with the request - // TODO -> Auth when we re-add authorization. - - console.log(xhr); - - xhr.onreadystatechange = function () { - //Call a function when the state changes. - if (xhr.readyState == 4) { - switch (xhr.status) { - case 204: - resolve(xhr.responseText); - - break; - case 404: - reject('Your dataSource does not support reject functionality'); + series: (StudyInstanceUID, SeriesInstanceUID) => + new Promise((resolve, reject) => { + try { + /** Reject because of Quality. (Seems the most sensible out of the options) */ + const CodeValueAndCodeSchemeDesignator = `113001%5EDCM`; + const url = `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/reject/${CodeValueAndCodeSchemeDesignator}`; + const xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + switch (xhr.status) { + case 204: + resolve(xhr.responseText); + break; + case 404: + reject( + 'Your dataSource does not support reject functionality' + ); + } } - } - }; - xhr.send(); - }); - }, + if (xhr.readyState === XMLHttpRequest.DONE) { + resolve(); + } + }; + xhr.send(); + } catch (error) { + reject(error); + } + }), }; } diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx index fc4a21944f2..25c4a1823fc 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx @@ -452,8 +452,8 @@ function _mapDisplaySets( onClose: () => UIDialogService.dismiss({ id: 'ds-reject-sr' }), onShow: () => { const yesButton = document.querySelector('.reject-yes-button'); - yesButton.focus(); + debugger }, onSubmit: async ({ action }) => { switch (action.id) { diff --git a/platform/ui/src/components/Dialog/Dialog.jsx b/platform/ui/src/components/Dialog/Dialog.jsx index d1b61500894..5d25ba48b9f 100644 --- a/platform/ui/src/components/Dialog/Dialog.jsx +++ b/platform/ui/src/components/Dialog/Dialog.jsx @@ -30,10 +30,8 @@ const Dialog = ({ const width = 'w-full'; useEffect(() => { - if (onShow) { - onShow(); - } - }, [onShow]); + onShow(); + }, []); return (
@@ -55,11 +53,14 @@ const Dialog = ({ ); }; +const noop = () => {}; + Dialog.propTypes = { title: PropTypes.string, text: PropTypes.string, onClose: PropTypes.func, noCloseButton: PropTypes.bool, + onShow: PropTypes.func, header: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), body: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), footer: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), @@ -80,6 +81,8 @@ Dialog.defaultProps = { footer: Footer, body: Body, value: {}, + onShow: noop, + onClose: noop }; export default Dialog; From 012f13e7fdfcaa19f344d54b58d000a794738d91 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Wed, 30 Jun 2021 13:18:21 -0300 Subject: [PATCH 04/11] Remove debugger --- .../PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx index 79ac8f11391..08151a67ee6 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx @@ -460,7 +460,6 @@ function _mapDisplaySets( onShow: () => { const yesButton = document.querySelector('.reject-yes-button'); yesButton.focus(); - debugger }, onSubmit: async ({ action }) => { switch (action.id) { From 869672aedc23f243f9d76dfeeb41593b15966eac Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Thu, 8 Jul 2021 09:39:32 -0300 Subject: [PATCH 05/11] Add mappings for rectangle and freehand --- extensions/cornerstone/src/init.js | 23 ++++ .../measurementServiceMappings/FreehandRoi.js | 100 +++++++++++++++ .../RectangleRoi.js | 116 ++++++++++++++++++ .../constants/supportedTools.js | 9 +- .../measurementServiceMappingsFactory.js | 55 ++++++++- .../dicom-sr/src/getSopClassHandlerModule.js | 29 +++-- extensions/dicom-sr/src/index.js | 26 +--- extensions/dicom-sr/src/init.js | 9 +- .../dicom-sr/src/tools/DICOMSRDisplayTool.js | 11 +- .../dicom-sr/src/tools/utils/getToolAlias.js | 32 +++++ .../dicom-sr/src/utils/addMeasurement.js | 3 +- .../viewports/OHIFCornerstoneSRViewport.js | 72 +++++------ .../_hydrateStructuredReport.js | 7 +- ...CornerstoneToolStateToMeasurementSchema.js | 24 ++++ .../promptHydrateStructuredReport.js | 1 + .../viewports/TrackedCornerstoneViewport.js | 4 + modes/longitudinal/src/toolbarButtons.js | 5 +- .../MeasurementService/MeasurementService.js | 22 +++- 18 files changed, 462 insertions(+), 86 deletions(-) create mode 100644 extensions/cornerstone/src/utils/measurementServiceMappings/FreehandRoi.js create mode 100644 extensions/cornerstone/src/utils/measurementServiceMappings/RectangleRoi.js create mode 100644 extensions/dicom-sr/src/tools/utils/getToolAlias.js diff --git a/extensions/cornerstone/src/init.js b/extensions/cornerstone/src/init.js index 01171e71cd6..b2f3125226d 100644 --- a/extensions/cornerstone/src/init.js +++ b/extensions/cornerstone/src/init.js @@ -30,6 +30,7 @@ const TOOL_TYPES_WITH_CONTEXT_MENU = [ 'SRBidirectional', 'SRArrowAnnotate', 'SREllipticalRoi', + 'SRRectangleRoi', ]; const _refreshViewports = () => @@ -319,6 +320,8 @@ const _initMeasurementService = (MeasurementService, DisplaySetService) => { Length, Bidirectional, EllipticalRoi, + RectangleRoi, + FreehandRoi, ArrowAnnotate, } = measurementServiceMappingsFactory(MeasurementService, DisplaySetService); const csToolsVer4MeasurementSource = MeasurementService.createSource( @@ -351,6 +354,22 @@ const _initMeasurementService = (MeasurementService, DisplaySetService) => { EllipticalRoi.toMeasurement ); + MeasurementService.addMapping( + csToolsVer4MeasurementSource, + 'RectangleRoi', + RectangleRoi.matchingCriteria, + RectangleRoi.toAnnotation, + RectangleRoi.toMeasurement + ); + + MeasurementService.addMapping( + csToolsVer4MeasurementSource, + 'FreehandRoi', + FreehandRoi.matchingCriteria, + FreehandRoi.toAnnotation, + FreehandRoi.toMeasurement + ); + MeasurementService.addMapping( csToolsVer4MeasurementSource, 'ArrowAnnotate', @@ -520,6 +539,8 @@ const _connectMeasurementServiceToTools = ( const TOOL_TYPE_TO_VALUE_TYPE = { Length: POLYLINE, EllipticalRoi: ELLIPSE, + RectangleRoi: POLYLINE, + FreehandRoi: POLYLINE, Bidirectional: BIDIRECTIONAL, ArrowAnnotate: POINT, }; @@ -527,6 +548,8 @@ const _connectMeasurementServiceToTools = ( const VALUE_TYPE_TO_TOOL_TYPE = { [POLYLINE]: 'Length', [ELLIPSE]: 'EllipticalRoi', + [POLYLINE]: 'RectangleRoi', + [POLYLINE]: 'FreehandRoi', [BIDIRECTIONAL]: 'Bidirectional', [POINT]: 'ArrowAnnotate', }; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/FreehandRoi.js b/extensions/cornerstone/src/utils/measurementServiceMappings/FreehandRoi.js new file mode 100644 index 00000000000..7bf092b7cc3 --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/FreehandRoi.js @@ -0,0 +1,100 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import getHandlesFromPoints from './utils/getHandlesFromPoints'; + +const FreehandRoi = { + toAnnotation: (measurement, definition) => {}, + toMeasurement: ( + csToolsAnnotation, + DisplaySetService, + getValueTypeFromToolType + ) => { + const { element, measurementData } = csToolsAnnotation; + const tool = + csToolsAnnotation.toolType || + csToolsAnnotation.toolName || + measurementData.toolType; + + const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName); + + if (!validToolType(tool)) { + throw new Error('Tool not supported'); + } + + const { + SOPInstanceUID, + FrameOfReferenceUID, + SeriesInstanceUID, + StudyInstanceUID, + } = getSOPInstanceAttributes(element); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + + const { cachedStats, handles } = measurementData; + + const { start, end } = handles; + + const halfXLength = Math.abs(start.x - end.x) / 2; + const halfYLength = Math.abs(start.y - end.y) / 2; + + const points = []; + const center = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 }; + + // To store similar to SR. + if (halfXLength > halfYLength) { + // X-axis major + // Major axis + points.push({ x: center.x - halfXLength, y: center.y }); + points.push({ x: center.x + halfXLength, y: center.y }); + // Minor axis + points.push({ x: center.x, y: center.y - halfYLength }); + points.push({ x: center.x, y: center.y + halfYLength }); + } else { + // Y-axis major + // Major axis + points.push({ x: center.x, y: center.y - halfYLength }); + points.push({ x: center.x, y: center.y + halfYLength }); + // Minor axis + points.push({ x: center.x - halfXLength, y: center.y }); + points.push({ x: center.x + halfXLength, y: center.y }); + } + + let meanSUV; + let stdDevSUV; + + if ( + cachedStats && + cachedStats.meanStdDevSUV && + cachedStats.meanStdDevSUV.mean !== 0 + ) { + const { meanStdDevSUV } = cachedStats; + + meanSUV = meanStdDevSUV.mean; + stdDevSUV = meanStdDevSUV.stdDev; + } + + return { + id: measurementData.id, + SOPInstanceUID, + FrameOfReferenceUID, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: measurementData.label, + description: measurementData.description, + unit: measurementData.unit, + area: cachedStats && cachedStats.area, + mean: cachedStats && cachedStats.mean, + stdDev: cachedStats && cachedStats.stdDev, + meanSUV, + stdDevSUV, + type: getValueTypeFromToolType(tool), + points, + }; + }, +}; + +export default FreehandRoi; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleRoi.js b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleRoi.js new file mode 100644 index 00000000000..7998936cb02 --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleRoi.js @@ -0,0 +1,116 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getHandlesFromPoints from './utils/getHandlesFromPoints'; +import getPointsFromHandles from './utils/getPointsFromHandles'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; + +const RectangleRoi = { + toAnnotation: (measurement, definition) => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} cornerstone Cornerstone event data + * @return {Measurement} Measurement instance + */ + toMeasurement: ( + csToolsAnnotation, + DisplaySetService, + getValueTypeFromToolType + ) => { + const { element, measurementData } = csToolsAnnotation; + const tool = + csToolsAnnotation.toolType || + csToolsAnnotation.toolName || + measurementData.toolType; + + const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName); + + if (!validToolType(tool)) { + throw new Error('Tool not supported'); + } + + const { + SOPInstanceUID, + FrameOfReferenceUID, + SeriesInstanceUID, + StudyInstanceUID, + } = getSOPInstanceAttributes(element); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + + return { + id: measurementData.id, + SOPInstanceUID, + FrameOfReferenceUID, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: measurementData.label, + description: measurementData.description, + unit: measurementData.unit, + length: measurementData.length, + type: getValueTypeFromToolType(tool), + points: getPointsFromHandles(measurementData.handles), + }; + }, +}; + +export default RectangleRoi; + +/** + * { + "data": [ + { + "visible": true, + "active": false, + "invalidated": false, + "handles": { + "start": { + "x": 191.15890083632019, + "y": 108.51135005973717, + "highlight": true, + "active": false + }, + "end": { + "x": 254.77658303464756, + "y": 150.71923536439667, + "highlight": true, + "active": false, + "moving": false + }, + "initialRotation": 0, + "textBox": { + "active": false, + "hasMoved": false, + "movesIndependently": false, + "drawnIndependently": true, + "allowedOutsideImage": true, + "hasBoundingBox": true, + "x": 254.77658303464756, + "y": 129.61529271206692, + "boundingBox": { + "width": 149.6158905029297, + "height": 65, + "left": 625, + "top": 179.39062500000003 + } + } + }, + "uuid": "71509b93-1c6f-4acb-88ba-b67a4debc8a3", + "cachedStats": { + "area": 1364.6373162386578, + "count": 2752, + "mean": -483.00981104651163, + "variance": 169503.0307903713, + "stdDev": 411.7074577784222, + "min": -1024, + "max": 1386 + }, + "unit": "HU" + } + ] + } + */ diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js index a1ca5e83e98..f1de1bf2fc2 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js @@ -1 +1,8 @@ -export default ['Length', 'EllipticalRoi', 'Bidirectional', 'ArrowAnnotate']; +export default [ + 'Length', + 'EllipticalRoi', + 'RectangleRoi', + 'FreehandRoi', + 'Bidirectional', + 'ArrowAnnotate', +]; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js index 069267907fa..8e7129a106f 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js @@ -2,6 +2,8 @@ import Length from './Length'; import Bidirectional from './Bidirectional'; import ArrowAnnotate from './ArrowAnnotate'; import EllipticalRoi from './EllipticalRoi'; +import RectangleRoi from './RectangleRoi'; +import FreehandRoi from './FreehandRoi'; const measurementServiceMappingsFactory = ( MeasurementService, @@ -23,12 +25,14 @@ const measurementServiceMappingsFactory = ( BIDIRECTIONAL, } = MeasurementService.VALUE_TYPES; - // TODO -> I get why this was attemped, but its not nearly flexible enough. + // TODO -> I get why this was attempted, but its not nearly flexible enough. // A single measurement may have an ellipse + a bidirectional measurement, for instances. // You can't define a bidirectional tool as a single type.. const TOOL_TYPE_TO_VALUE_TYPE = { Length: POLYLINE, EllipticalRoi: ELLIPSE, + RectangleRoi: POLYLINE, + FreehandRoi: POLYLINE, Bidirectional: BIDIRECTIONAL, ArrowAnnotate: POINT, }; @@ -88,6 +92,55 @@ const measurementServiceMappingsFactory = ( }, ], }, + FreehandRoi: { + toAnnotation: FreehandRoi.toAnnotation, + toMeasurement: csToolsAnnotation => + FreehandRoi.toMeasurement( + csToolsAnnotation, + DisplaySetService, + _getValueTypeFromToolType + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + properties: ['stdDev'], + // attributes: [ + // { + // codeValue: '386136009', + // codeMeaning: 'Standard Deviation', + // codingSchemeDesignator: 'SCT', + // }, + // ], + }, + ], + }, + RectangleRoi: { + toAnnotation: RectangleRoi.toAnnotation, + toMeasurement: csToolsAnnotation => + RectangleRoi.toMeasurement( + csToolsAnnotation, + DisplaySetService, + _getValueTypeFromToolType + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + points: 2, + }, + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + points: 2, + }, + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + points: 2, + }, + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + points: 2, + }, + ], + }, EllipticalRoi: { toAnnotation: EllipticalRoi.toAnnotation, toMeasurement: csToolsAnnotation => diff --git a/extensions/dicom-sr/src/getSopClassHandlerModule.js b/extensions/dicom-sr/src/getSopClassHandlerModule.js index cc877c409e4..d72e71f47ba 100644 --- a/extensions/dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/dicom-sr/src/getSopClassHandlerModule.js @@ -1,7 +1,9 @@ -import { SOPClassHandlerName, SOPClassHandlerId } from './id'; import { utils, classes } from '@ohif/core'; + +/** Internal imports */ import addMeasurement from './utils/addMeasurement'; import isRehydratable from './utils/isRehydratable'; +import { SOPClassHandlerName, SOPClassHandlerId } from './id'; const { ImageSet } = classes; @@ -217,7 +219,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( for (let j = unloadedMeasurements.length - 1; j >= 0; j--) { const measurement = unloadedMeasurements[j]; - if (_measurmentReferencesSOPInstanceUID(measurement, SOPInstanceUID)) { + if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID)) { addMeasurement( measurement, imageId, @@ -231,7 +233,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( } } -function _measurmentReferencesSOPInstanceUID(measurement, SOPInstanceUID) { +function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID) { const { coords } = measurement; for (let j = 0; j < coords.length; j++) { @@ -526,13 +528,14 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { function _getCoordsFromSCOORDOrSCOORD3D(item) { const { ValueType, RelationshipType, GraphicType, GraphicData } = item; - if (RelationshipType !== RELATIONSHIP_TYPE.INFERRED_FROM) { - console.warn( - `Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM."` - ); + //debugger; + // if (RelationshipType !== RELATIONSHIP_TYPE.INFERRED_FROM) { + // console.warn( + // `Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM."` + // ); - return; - } + // return; + // } const coords = { ValueType, GraphicType, GraphicData }; @@ -569,12 +572,18 @@ function _getLabelFromMeasuredValueSequence( } function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { + const referencedImages = []; + const ImageLibrary = ImagingMeasurementReportContentSequence.find( item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary ); + if (!ImageLibrary.ContentSequence) { + return referencedImages; + } + const ImageLibraryGroup = _getSequenceAsArray( ImageLibrary.ContentSequence ).find( @@ -583,8 +592,6 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { CodeNameCodeSequenceValues.ImageLibraryGroup ); - const referencedImages = []; - _getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => { const { ReferencedSOPSequence } = item; const { diff --git a/extensions/dicom-sr/src/index.js b/extensions/dicom-sr/src/index.js index 7fd9be0ca44..0d0a0ecb177 100644 --- a/extensions/dicom-sr/src/index.js +++ b/extensions/dicom-sr/src/index.js @@ -1,8 +1,11 @@ import React from 'react'; + +/** Internal imports */ import getSopClassHandlerModule from './getSopClassHandlerModule'; import onModeEnter from './onModeEnter'; import id from './id.js'; import init from './init'; +import getToolAlias from './tools/utils/getToolAlias'; const Component = React.lazy(() => { return import('./viewports/OHIFCornerstoneSRViewport'); @@ -70,7 +73,7 @@ export default { } // Set same tool or alt tool - const toolAlias = _getToolAlias(toolName); + const toolAlias = getToolAlias(toolName); cornerstoneTools.setToolActiveForElement(element, toolAlias, { mouseButtonMask: 1, @@ -86,24 +89,3 @@ export default { getSopClassHandlerModule, onModeEnter, }; - -function _getToolAlias(toolName) { - let toolAlias = toolName; - - switch (toolName) { - case 'Length': - toolAlias = 'SRLength'; - break; - case 'Bidirectional': - toolAlias = 'SRBidirectional'; - break; - case 'ArrowAnnotate': - toolAlias = 'SRArrowAnnotate'; - break; - case 'EllipticalRoi': - toolAlias = 'SREllipticalRoi'; - break; - } - - return toolAlias; -} diff --git a/extensions/dicom-sr/src/init.js b/extensions/dicom-sr/src/init.js index d11b2088de5..3c53146ec1e 100644 --- a/extensions/dicom-sr/src/init.js +++ b/extensions/dicom-sr/src/init.js @@ -1,7 +1,8 @@ import cornerstoneTools from 'cornerstone-tools'; -import dicomSRModule from './tools/modules/dicomSRModule'; -import id from './id'; +/** Internal imports */ +import id from './id'; +import dicomSRModule from './tools/modules/dicomSRModule'; import TOOL_NAMES from './constants/toolNames'; const defaultConfig = { @@ -14,9 +15,9 @@ const defaultConfig = { * @param {object} configuration */ export default function init({ configuration = {} }) { - const conifg = Object.assign({}, defaultConfig, configuration); + const config = Object.assign({}, defaultConfig, configuration); - TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = conifg.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL; + TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = config.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL; cornerstoneTools.register('module', id, dicomSRModule); } diff --git a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js index 6e6a19c08a7..2544355d036 100644 --- a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js +++ b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js @@ -1,11 +1,12 @@ import { importInternal, getToolState, toolColors } from 'cornerstone-tools'; import { pixelToCanvas } from 'cornerstone-core'; +/** Internal imports */ import TOOL_NAMES from '../constants/toolNames'; import SCOORD_TYPES from '../constants/scoordTypes'; import id from '../id'; -// Cornerstone 3rd party dev kit imports +/** Cornerstone 3rd party dev kit imports */ const draw = importInternal('drawing/draw'); const drawJoinedLines = importInternal('drawing/drawJoinedLines'); const drawCircle = importInternal('drawing/drawCircle'); @@ -18,6 +19,14 @@ const drawLinkedTextBox = importInternal('drawing/drawLinkedTextBox'); /** * @class DICOMSRDisplayTool - Renders DICOMSR data in a read only manner (i.e. as an overlay). + * + * This is a generic render tool. + * + * A single tool that, given some schema, can render + * POINT, MULTIPOINT, POLYLINE, CIRCLE, and ELLIPSE + * value types for a given imageId. + * + * * @extends cornerstoneTools.BaseTool */ export default class DICOMSRDisplayTool extends BaseTool { diff --git a/extensions/dicom-sr/src/tools/utils/getToolAlias.js b/extensions/dicom-sr/src/tools/utils/getToolAlias.js new file mode 100644 index 00000000000..5460b70ca7a --- /dev/null +++ b/extensions/dicom-sr/src/tools/utils/getToolAlias.js @@ -0,0 +1,32 @@ +/** + * Get cornerstone tool alias. + * + * @param {string} toolName + * @returns tool alias + */ +export default function getToolAlias(toolName) { + let toolAlias = toolName; + + switch (toolName) { + case 'Length': + toolAlias = 'SRLength'; + break; + case 'Bidirectional': + toolAlias = 'SRBidirectional'; + break; + case 'ArrowAnnotate': + toolAlias = 'SRArrowAnnotate'; + break; + case 'EllipticalRoi': + toolAlias = 'SREllipticalRoi'; + break; + case 'FreehandRoi': + toolAlias = 'SRFreehandRoi'; + break; + case 'RectangleRoi': + toolAlias = 'SRRectangleRoi'; + break; + } + + return toolAlias; +} diff --git a/extensions/dicom-sr/src/utils/addMeasurement.js b/extensions/dicom-sr/src/utils/addMeasurement.js index 8d592e7f82c..c8d3a294c48 100644 --- a/extensions/dicom-sr/src/utils/addMeasurement.js +++ b/extensions/dicom-sr/src/utils/addMeasurement.js @@ -1,6 +1,7 @@ import cornerstoneTools from 'cornerstone-tools'; import cornerstoneMath from 'cornerstone-math'; -import cornerstone from 'cornerstone-core'; + +/** Internal imports */ import TOOL_NAMES from '../constants/toolNames'; import SCOORD_TYPES from '../constants/scoordTypes'; diff --git a/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js b/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js index 3b8c2218ab5..cda882d8a8f 100644 --- a/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js +++ b/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js @@ -3,28 +3,28 @@ import PropTypes from 'prop-types'; import cornerstoneTools from 'cornerstone-tools'; import cornerstone from 'cornerstone-core'; import CornerstoneViewport from 'react-cornerstone-viewport'; -import OHIF, { DicomMetadataStore, utils } from '@ohif/core'; -import DICOMSRDisplayTool from './../tools/DICOMSRDisplayTool'; -import ViewportOverlay from './ViewportOverlay'; +import OHIF, { utils } from '@ohif/core'; import { Notification, ViewportActionBar, useViewportGrid, useViewportDialog, } from '@ohif/ui'; + +/** Internal imports */ +import DICOMSRDisplayTool from './../tools/DICOMSRDisplayTool'; +import ViewportOverlay from './ViewportOverlay'; import TOOL_NAMES from './../constants/toolNames'; -import { adapters } from 'dcmjs'; import id from './../id'; +import getToolAlias from '../tools/utils/getToolAlias'; const { formatDate } = utils; -const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex'); -const globalImageIdSpecificToolStateManager = - cornerstoneTools.globalImageIdSpecificToolStateManager; - -const { StackManager, guid } = OHIF.utils; - +const { StackManager } = OHIF.utils; const MEASUREMENT_TRACKING_EXTENSION_ID = 'org.ohif.measurement-tracking'; +/** Cornerstone 3rd party dev kit imports */ +const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex'); + function OHIFCornerstoneSRViewport({ children, dataSource, @@ -33,11 +33,7 @@ function OHIFCornerstoneSRViewport({ servicesManager, extensionManager, }) { - const { - DisplaySetService, - MeasurementService, - ToolBarService, - } = servicesManager.services; + const { DisplaySetService, ToolBarService } = servicesManager.services; const [viewportGrid, viewportGridService] = useViewportGrid(); const [viewportDialogState, viewportDialogApi] = useViewportDialog(); const [measurementSelected, setMeasurementSelected] = useState(0); @@ -98,32 +94,12 @@ function OHIFCornerstoneSRViewport({ isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0; }, [trackedMeasurements]); - function _getToolAlias() { - const primaryToolId = ToolBarService.state.primaryToolId; - let toolAlias = primaryToolId; - - switch (primaryToolId) { - case 'Length': - toolAlias = 'SRLength'; - break; - case 'Bidirectional': - toolAlias = 'SRBidirectional'; - break; - case 'ArrowAnnotate': - toolAlias = 'SRArrowAnnotate'; - break; - case 'EllipticalRoi': - toolAlias = 'SREllipticalRoi'; - break; - } - - return toolAlias; - } - const onElementEnabled = evt => { const eventData = evt.detail; const targetElement = eventData.element; - const toolAlias = _getToolAlias(); // These are 1:1 for built-in only + + const primaryToolId = ToolBarService.state.primaryToolId; + const toolAlias = getToolAlias(primaryToolId); // These are 1:1 for built-in only // ~~ MAGIC cornerstoneTools.addToolForElement(targetElement, DICOMSRDisplayTool); @@ -173,6 +149,26 @@ function OHIFCornerstoneSRViewport({ }, } ); + cornerstoneTools.addToolForElement( + targetElement, + cornerstoneTools.RectangleRoiTool, + { + name: 'SRRectangleRoi', + configuration: { + renderDashed: true, + }, + } + ); + cornerstoneTools.addToolForElement( + targetElement, + cornerstoneTools.FreehandRoiTool, + { + name: 'SRFreehandRoi', + configuration: { + renderDashed: true, + }, + } + ); // ~~ Business as usual cornerstoneTools.setToolActiveForElement(targetElement, 'PanMultiTouch', { diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js index 2baceb80a0a..9ca9cf15cc0 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js @@ -1,12 +1,11 @@ -import cornerstoneTools from 'cornerstone-tools'; import OHIF, { DicomMetadataStore } from '@ohif/core'; +import { adapters } from 'dcmjs'; + +/** Internal imports */ import getLabelFromDCMJSImportedToolData from './utils/getLabelFromDCMJSImportedToolData'; import getCornerstoneToolStateToMeasurementSchema from './getCornerstoneToolStateToMeasurementSchema'; -import { adapters } from 'dcmjs'; const { guid } = OHIF.utils; -const globalImageIdSpecificToolStateManager = - cornerstoneTools.globalImageIdSpecificToolStateManager; /** * diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/getCornerstoneToolStateToMeasurementSchema.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/getCornerstoneToolStateToMeasurementSchema.js index c548a79292e..6fcee1f8e01 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/getCornerstoneToolStateToMeasurementSchema.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/getCornerstoneToolStateToMeasurementSchema.js @@ -22,6 +22,8 @@ export default function getCornerstoneToolStateToMeasurementSchema( const TOOL_TYPE_TO_VALUE_TYPE = { Length: POLYLINE, EllipticalRoi: ELLIPSE, + RectangleRoi: POLYLINE, + FreehandRoi: POLYLINE, Bidirectional: BIDIRECTIONAL, ArrowAnnotate: POINT, }; @@ -63,6 +65,28 @@ export default function getCornerstoneToolStateToMeasurementSchema( DisplaySetService, _getValueTypeFromToolType ); + case 'RectangleRoi': + return measurementData => + RectangleRoi( + measurementData, + SOPInstanceUID, + FrameOfReferenceUID, + SeriesInstanceUID, + StudyInstanceUID, + DisplaySetService, + _getValueTypeFromToolType + ); + case 'FreehandRoi': + return measurementData => + FreehandRoi( + measurementData, + SOPInstanceUID, + FrameOfReferenceUID, + SeriesInstanceUID, + StudyInstanceUID, + DisplaySetService, + _getValueTypeFromToolType + ); case 'ArrowAnnotate': return measurementData => ArrowAnnotate( diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js index 4d017cf10fd..a5800e10d3d 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js @@ -1,3 +1,4 @@ +/** Internal imports */ import hydrateStructuredReport from './_hydrateStructuredReport.js'; const RESPONSE = { diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js index 1476a32984c..a0a21b83764 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js @@ -26,6 +26,8 @@ const { ArrowAnnotateTool, BidirectionalTool, EllipticalRoiTool, + RectangleRoiTool, + FreehandRoiTool, LengthTool, } = cornerstoneTools; @@ -82,6 +84,8 @@ function TrackedCornerstoneViewport(props) { tool instanceof ArrowAnnotateTool || tool instanceof BidirectionalTool || tool instanceof EllipticalRoiTool || + tool instanceof FreehandRoiTool || + tool instanceof RectangleRoiTool || tool instanceof LengthTool ) { const configuration = tool.configuration; diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js index 673235c3fd0..b677f718c75 100644 --- a/modes/longitudinal/src/toolbarButtons.js +++ b/modes/longitudinal/src/toolbarButtons.js @@ -22,7 +22,7 @@ function _createButton(type, id, icon, label, commandName, commandOptions) { label, type, commandName, - commandOptions + commandOptions, }; } @@ -229,6 +229,9 @@ export default [ undefined, { toolName: 'RectangleRoi' } ), + _createToolButton('Freehand', 'tool-move', 'Freehand', undefined, { + toolName: 'FreehandRoi', + }), ], }, }, diff --git a/platform/core/src/services/MeasurementService/MeasurementService.js b/platform/core/src/services/MeasurementService/MeasurementService.js index c4abff9fc92..b1512fc0df1 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.js @@ -1,3 +1,4 @@ +/** Internal imports */ import log from '../../log'; import guid from '../../utils/guid'; import pubSubServiceInterface from '../_shared/pubSubServiceInterface'; @@ -581,10 +582,27 @@ class MeasurementService { /* Criteria Matching */ return sourceMappingsByDefinition.find(({ matchingCriteria }) => { - return ( + if (matchingCriteria.type !== measurement.type) { + return false; + } + + if ( + matchingCriteria.properties && + matchingCriteria.properties.every(name => + measurement.hasOwnProperty(name) + ) + ) { + return true; + } + + if ( measurement.points && measurement.points.length === matchingCriteria.points - ); + ) { + return true; + } + + return false; }); } From 28376032020f81d862fe01fc3254cbdde8853953 Mon Sep 17 00:00:00 2001 From: Bill Wallace Date: Fri, 20 Aug 2021 12:14:19 -0400 Subject: [PATCH 06/11] 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 --- .gitignore | 1 + .../default/src/DicomWebDataSource/index.js | 11 +- .../utils/StaticWadoClient.js | 49 ++++++ .../utils/getWADORSImageId.js | 2 +- .../default/src/Panels/PanelStudyBrowser.jsx | 12 +- .../default/src/getSopClassHandlerModule.js | 4 +- .../getStudiesForPatientByStudyInstanceUID.js | 3 + .../viewports/TrackedCornerstoneViewport.js | 2 +- package.json | 3 + platform/core/src/utils/Queue.test.js | 4 +- .../docs/docs/configuration/data-sources.md | 39 +++++ .../extensions/modules/data-source.md | 12 +- platform/ui/src/assets/styles/styles.css | 4 +- .../components/StudyBrowser/StudyBrowser.jsx | 4 +- .../study-list/OHIFStudyList.spec.js | 37 ++-- platform/viewer/package.json | 4 + platform/viewer/public/config/aws.js | 159 ++++++++++++++++++ .../viewer/public/config/dicomweb-server.js | 4 +- platform/viewer/public/config/local_static.js | 158 +++++++++++++++++ .../viewer/src/routes/DataSourceWrapper.jsx | 10 +- platform/viewer/src/routes/Local/Local.jsx | 4 +- 21 files changed, 484 insertions(+), 42 deletions(-) create mode 100644 extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.js create mode 100644 platform/viewer/public/config/aws.js create mode 100644 platform/viewer/public/config/local_static.js diff --git a/.gitignore b/.gitignore index 7ade4b40b4e..706f5ead678 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ package-lock.json yarn-error.log .DS_Store .env +*.code-workspace # Common Example Data Directories sampledata/ diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index 30b0a59e0cd..d0e7f5ccd3e 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -15,6 +15,7 @@ import { retrieveStudyMetadata, deleteStudyMetadataPromise, } from './retrieveStudyMetadata.js'; +import StaticWadoClient from './utils/StaticWadoClient.js'; const { DicomMetaDictionary, DicomDict } = dcmjs.data; @@ -46,14 +47,14 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { supportsFuzzyMatching, supportsWildcard, supportsReject, - requestOptions, + staticWado, } = dicomWebConfig; const qidoConfig = { url: qidoRoot, + staticWado, headers: UserAuthenticationService.getAuthorizationHeader(), errorInterceptor: errorHandler.getHTTPErrorHandler(), - }; const wadoConfig = { @@ -64,7 +65,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { // TODO -> Two clients sucks, but its better than 1000. // TODO -> We'll need to merge auth later. - const qidoDicomWebClient = new api.DICOMwebClient(qidoConfig); + const qidoDicomWebClient = staticWado ? new StaticWadoClient(qidoConfig) : new api.DICOMwebClient(wadoConfig); const wadoDicomWebClient = new api.DICOMwebClient(wadoConfig); const implementation = { @@ -83,7 +84,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { query: { studies: { mapParams: mapParams.bind(), - search: async function(origParams) { + search: async function (origParams) { const headers = UserAuthenticationService.getAuthorizationHeader(); if (headers) { qidoDicomWebClient.headers = headers; @@ -108,7 +109,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { }, series: { // mapParams: mapParams.bind(), - search: async function(studyInstanceUid) { + search: async function (studyInstanceUid) { const headers = UserAuthenticationService.getAuthorizationHeader(); if (headers) { qidoDicomWebClient.headers = headers; diff --git a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.js b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.js new file mode 100644 index 00000000000..bc2dd60d099 --- /dev/null +++ b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.js @@ -0,0 +1,49 @@ +import { api } from 'dicomweb-client'; + +/** + * An implementation of the static wado client, that fetches data from + * a static response rather than actually doing real queries. This allows + * fast encoding of test data, but because it is static, anything actually + * performing searches doesn't work. This version fixes the query issue + * by manually implementing a query option. + */ +export default class StaticWadoClient extends api.DICOMwebClient { + static filterKeys = { + "StudyInstanceUID": "0020000D", + "PatientName": "00100010", + "00100020": "mrn", + "StudyDescription": "00081030", + "ModalitiesInStudy": "00080061", + }; + + constructor(qidoConfig) { + super(qidoConfig); + this.staticWado = qidoConfig.staticWado; + } + + async searchForStudies(options) { + if (!this.staticWado) return super.searchForStudies(options); + + let searchResult = await super.searchForStudies(options); + const { queryParams } = options; + if (!queryParams) return searchResult; + const filtered = searchResult.filter(study => { + for (const key of Object.keys(StaticWadoClient.filterKeys)) { + if (!this.filterItem(key, queryParams, study)) return false; + } + return true; + }); + return filtered; + } + + filterItem(key, queryParams, study) { + const altKey = StaticWadoClient.filterKeys[key] || key; + if (!queryParams) return true; + const testValue = queryParams[key] || queryParams[altKey]; + if (!testValue) return true; + const valueElem = study[key] || study[altKey]; + if (!valueElem) return false; + const value = valueElem.Value; + return value === testValue || (value.indexOf && value.indexOf(testValue) >= 0); + } +} diff --git a/extensions/default/src/DicomWebDataSource/utils/getWADORSImageId.js b/extensions/default/src/DicomWebDataSource/utils/getWADORSImageId.js index 36058292f1f..4276914ad74 100644 --- a/extensions/default/src/DicomWebDataSource/utils/getWADORSImageId.js +++ b/extensions/default/src/DicomWebDataSource/utils/getWADORSImageId.js @@ -6,7 +6,7 @@ function buildInstanceWadoRsUri(instance, config) { function buildInstanceFrameWadoRsUri(instance, config, frame) { const baseWadoRsUri = buildInstanceWadoRsUri(instance, config); - frame = frame != null || 1; + frame = frame || 1; return `${baseWadoRsUri}/frames/${frame}`; } diff --git a/extensions/default/src/Panels/PanelStudyBrowser.jsx b/extensions/default/src/Panels/PanelStudyBrowser.jsx index e1adb583d6a..aaac606d035 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.jsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.jsx @@ -165,11 +165,11 @@ function PanelStudyBrowser({ ); const updatedExpandedStudyInstanceUIDs = shouldCollapseStudy ? // eslint-disable-next-line prettier/prettier - [ - ...expandedStudyInstanceUIDs.filter( - stdyUid => stdyUid !== StudyInstanceUID - ), - ] + [ + ...expandedStudyInstanceUIDs.filter( + stdyUid => stdyUid !== StudyInstanceUID + ), + ] : [...expandedStudyInstanceUIDs, StudyInstanceUID]; setExpandedStudyInstanceUIDs(updatedExpandedStudyInstanceUIDs); @@ -247,7 +247,7 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) { array.push({ displaySetInstanceUID: ds.displaySetInstanceUID, - description: ds.SeriesDescription, + description: ds.SeriesDescription || '', seriesNumber: ds.SeriesNumber, modality: ds.Modality, seriesDate: ds.SeriesDate, diff --git a/extensions/default/src/getSopClassHandlerModule.js b/extensions/default/src/getSopClassHandlerModule.js index 59e2a3db80b..7bf2d6e89bf 100644 --- a/extensions/default/src/getSopClassHandlerModule.js +++ b/extensions/default/src/getSopClassHandlerModule.js @@ -20,9 +20,9 @@ const makeDisplaySet = instances => { SeriesTime: instance.SeriesTime, SeriesInstanceUID: instance.SeriesInstanceUID, StudyInstanceUID: instance.StudyInstanceUID, - SeriesNumber: instance.SeriesNumber, + SeriesNumber: instance.SeriesNumber || 0, FrameRate: instance.FrameTime, - SeriesDescription: instance.SeriesDescription, + SeriesDescription: instance.SeriesDescription || '', Modality: instance.Modality, isMultiFrame: isMultiFrame(instance), numImageFrames: instances.length, diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getStudiesForPatientByStudyInstanceUID.js b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getStudiesForPatientByStudyInstanceUID.js index ae0ed4a9e33..fb5baa5395d 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getStudiesForPatientByStudyInstanceUID.js +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getStudiesForPatientByStudyInstanceUID.js @@ -21,6 +21,9 @@ async function getStudiesForPatientByStudyInstanceUID( patientId: getStudyResult[0].mrn, }); } + console.log('No mrn found for', getStudyResult); + // The original study we KNOW belongs to the same set, so just return it + return getStudyResult; } export default getStudiesForPatientByStudyInstanceUID; diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js index 1476a32984c..e0bec512414 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js @@ -257,7 +257,7 @@ function TrackedCornerstoneViewport(props) { isLocked: false, isRehydratable: false, studyDate: formatDate(SeriesDate), // TODO: This is series date. Is that ok? - currentSeries: SeriesNumber, + currentSeries: SeriesNumber, // TODO - switch entire currentSeries to be UID based or actual position based seriesDescription: SeriesDescription, modality: Modality, patientInformation: { diff --git a/package.json b/package.json index b143015140a..4dd0dcc045f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "scripts": { "cm": "npx git-cz", "build": "lerna run build:viewer --stream", + "build:dev": "lerna run build:dev --stream", "build:ci": "lerna run build:viewer:ci --stream", "build:qa": "lerna run build:viewer:qa --stream", "build:ui:deploy-preview": "lerna run build:ui:deploy-preview --stream", @@ -26,6 +27,8 @@ "dev": "lerna run dev:viewer --stream", "dev:project": ".scripts/dev.sh", "dev:orthanc": "lerna run dev:orthanc --stream", + "dev:dcm4chee": "lerna run dev:dcm4chee --stream", + "dev:static": "lerna run dev:static --stream", "orthanc:up": "docker-compose -f .docker/Nginx-Orthanc/docker-compose.yml up", "start": "yarn run dev", "test": "yarn run test:unit", diff --git a/platform/core/src/utils/Queue.test.js b/platform/core/src/utils/Queue.test.js index 23b31448d2f..24d3e64cb34 100644 --- a/platform/core/src/utils/Queue.test.js +++ b/platform/core/src/utils/Queue.test.js @@ -25,10 +25,10 @@ describe('Queue', () => { const start = Date.now(); timer(threshold).then(now => { const elapsed = now - start; - expect(elapsed >= threshold && elapsed < 2 * threshold).toBe(true); + expect(elapsed >= threshold && elapsed <= 2 * threshold).toBe(true); }); const end = await timer(threshold); - expect(end - start > 2 * threshold).toBe(true); + expect(end - start >= 2 * threshold).toBe(true); expect(mockedTimeout).toBeCalledTimes(2); }); it('should prevent task execution when queue limit is reached', async () => { diff --git a/platform/docs/docs/configuration/data-sources.md b/platform/docs/docs/configuration/data-sources.md index cd0e8d6aed8..e8e66068db2 100644 --- a/platform/docs/docs/configuration/data-sources.md +++ b/platform/docs/docs/configuration/data-sources.md @@ -189,3 +189,42 @@ below: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/html-templates [config-files]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config + +## Static Files + +There is a binay DICOM to static file generator, which provides easily served +binary files. The files are all compressed in order to reduce space signifcantly, +and are pre-computed for the files required for OHIF, so that the performance +of serving the files is just the read from disk/write to http stream time, without +any extra processing time. + +The project for the static wado files is located here: +[static-wado]: https://github.com/wayfarer3130/static-wado + +It can be compiled with Java and Gradle, and then run against a set of dicom, +in the example located in /dicom/study1 outputting to /dicomweb, and then a +server run against that data, like this: +``` +git clone https://github.com/wayfarer3130/static-wado.git +cd static-wado +./gradlew installDist +StaticWado/build/install/StaticWado/bin/StaticWado -d /dicomweb /dicom/study1 +cd /dicomweb +npx http-server -p 5000 --cors -g +``` + +There is then a dev environment in the platform/viewer directory which can be run +against those files, like this: +``` +cd platform/viewer +yarn dev:static +``` + +Additional studies can be added to the dicomweb by re-running the StaticWado command. +It will create a single studies.gz index file (JSON DICOM file, compressed) +containing an index of all studies created. There is then a small extension +to OHIF which performs client side indexing. + +The StaticWado command also knows how to deploy a client and dicomweb directory +to Amazon s3, which can then server files up directly. There is another +build setup build:aws in the viewer package.json to create such a deployment. diff --git a/platform/docs/docs/platform/extensions/modules/data-source.md b/platform/docs/docs/platform/extensions/modules/data-source.md index 01439d9cd44..48b98fefa41 100644 --- a/platform/docs/docs/platform/extensions/modules/data-source.md +++ b/platform/docs/docs/platform/extensions/modules/data-source.md @@ -76,7 +76,17 @@ function create({ You can take a look at `dicomweb` data source implementation to get an idea `extensions/default/src/DicomWebDataSource/index.js` - +## Static WADO Client + +If the configuration for the data source has the value staticWado set, then it +is assumed that queries for the studies return a super-set of the studies, as +it is assumed to be returning a static list. The StaticWadoClient performs the +search functionality manually, by interpretting the query parameters and then +applying them to the returned response. This functionality may be useful for +other types of DICOMweb back ends, where they are capable of performing queries, +but don't allow for querying certain types of fields. However, that only works +as long as the size of the studies list isn't too large that client side +selectiton isn't too expensive. ## DicomMetadataStore In `OHIF-v3` we have a central location for the metadata of studies and they are located diff --git a/platform/ui/src/assets/styles/styles.css b/platform/ui/src/assets/styles/styles.css index df621a56ecb..dca1088cd20 100644 --- a/platform/ui/src/assets/styles/styles.css +++ b/platform/ui/src/assets/styles/styles.css @@ -1,6 +1,6 @@ /* CUSTOM OHIF SCROLLBAR */ .ohif-scrollbar { - scrollbar-color: #0944b3 transparent; + scrollbar-color: #3E5967 transparent; } .ohif-scrollbar::-webkit-scrollbar { @@ -14,10 +14,12 @@ .ohif-scrollbar::-webkit-scrollbar-thumb { @apply rounded; @apply bg-primary-main; + background-color: #3e5967; } .ohif-scrollbar::-webkit-scrollbar-thumb:window-inactive { @apply bg-primary-main; + background-color: #3e5967; } /* INVISIBLE SCROLLBAR */ diff --git a/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx b/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx index cb366e99f07..bcfab00dedd 100644 --- a/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx +++ b/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx @@ -105,7 +105,7 @@ const StudyBrowser = ({ })}
-
+
{getTabContent()}
@@ -167,7 +167,7 @@ StudyBrowser.propTypes = { ), }; -const noop = () => {}; +const noop = () => { }; StudyBrowser.defaultProps = { onClickTab: noop, diff --git a/platform/viewer/cypress/integration/study-list/OHIFStudyList.spec.js b/platform/viewer/cypress/integration/study-list/OHIFStudyList.spec.js index 2fc0f26068e..c497bf777f7 100644 --- a/platform/viewer/cypress/integration/study-list/OHIFStudyList.spec.js +++ b/platform/viewer/cypress/integration/study-list/OHIFStudyList.spec.js @@ -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()); }); diff --git a/platform/viewer/package.json b/platform/viewer/package.json index a12c1733aec..7a20a35904c 100644 --- a/platform/viewer/package.json +++ b/platform/viewer/package.json @@ -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", diff --git a/platform/viewer/public/config/aws.js b/platform/viewer/public/config/aws.js new file mode 100644 index 00000000000..ccdb7949aea --- /dev/null +++ b/platform/viewer/public/config/aws.js @@ -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'], + }, + ], +}; diff --git a/platform/viewer/public/config/dicomweb-server.js b/platform/viewer/public/config/dicomweb-server.js index bc3b74cd48c..6cb8b2e1aac 100644 --- a/platform/viewer/public/config/dicomweb-server.js +++ b/platform/viewer/public/config/dicomweb-server.js @@ -20,8 +20,8 @@ window.config = { imageRendering: 'wadouri', thumbnailRendering: 'wadouri', enableStudyLazyLoad: true, - supportsFuzzyMatching: true, - supportsWildcard: true, + supportsFuzzyMatching: false, + supportsWildcard: false, }, }, ], diff --git a/platform/viewer/public/config/local_static.js b/platform/viewer/public/config/local_static.js new file mode 100644 index 00000000000..3ca7800a00d --- /dev/null +++ b/platform/viewer/public/config/local_static.js @@ -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'], + }, + ], +}; diff --git a/platform/viewer/src/routes/DataSourceWrapper.jsx b/platform/viewer/src/routes/DataSourceWrapper.jsx index 69ce49f5e1b..bc3a679c016 100644 --- a/platform/viewer/src/routes/DataSourceWrapper.jsx +++ b/platform/viewer/src/routes/DataSourceWrapper.jsx @@ -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(); diff --git a/platform/viewer/src/routes/Local/Local.jsx b/platform/viewer/src/routes/Local/Local.jsx index d594ec94e1a..8a93412c13e 100644 --- a/platform/viewer/src/routes/Local/Local.jsx +++ b/platform/viewer/src/routes/Local/Local.jsx @@ -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) From 7eb288e5b327280cb87796243b86a43b55eec688 Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Tue, 24 Aug 2021 14:13:54 +0200 Subject: [PATCH 07/11] docs: Remove UI components for now, we will use Storybook instead later. Clean up links and introduction. (#2520) --- .netlify/build-deploy-preview.sh | 14 - .webpack/webpack.base.js | 2 +- README.md | 32 +- extensions/default/package.json | 2 +- extensions/default/src/ViewerLayout/index.jsx | 5 +- extensions/measurement-tracking/package.json | 2 +- platform/docs/docs/README.md | 226 ++++------- platform/docs/docs/deployment/index.md | 6 +- .../docs/docs/development/getting-started.md | 3 - platform/docs/docs/faq.md | 9 +- platform/docs/docs/migration.md-todo | 7 - .../platform/component-library/Colors.mdx | 82 ---- .../component-library/_category_.json | 4 - .../components/AboutModal.mdx | 27 -- .../component-library/components/Button.mdx | 373 ------------------ .../components/ButtonGroup.mdx | 111 ------ .../components/CinePlayer.mdx | 38 -- .../components/ContextMenu.mdx | 49 --- .../components/DateRange.mdx | 46 --- .../component-library/components/Dropdown.mdx | 68 ---- .../components/EmptyStudies.mdx | 30 -- .../components/ErrorBoundary.mdx | 59 --- .../components/ExpandableToolbarButton.mdx | 87 ---- .../component-library/components/Input.mdx | 39 -- .../components/InputDateRange.mdx | 44 --- .../components/InputGroup.mdx | 91 ----- .../components/InputMultiSelect.mdx | 49 --- .../components/InputText.mdx | 43 -- .../component-library/components/Label.mdx | 32 -- .../component-library/components/ListMenu.mdx | 82 ---- .../components/MeasurementTable.mdx | 59 --- .../component-library/components/NavBar.mdx | 30 -- .../components/Notification.mdx | 58 --- .../components/SegmentationTable.mdx | 37 -- .../component-library/components/Select.mdx | 70 ---- .../components/SidePanel.mdx | 84 ---- .../components/SplitButton.mdx | 88 ----- .../components/StudyBrowser.mdx | 111 ------ .../components/StudyItem.mdx | 47 --- .../components/StudyListFilter.mdx | 122 ------ .../components/StudyListPagination.mdx | 46 --- .../components/StudyListTable.mdx | 63 --- .../component-library/components/Table.mdx | 78 ---- .../components/Thumbnail.mdx.todo | 40 -- .../components/ThumbnailNoImage.mdx.todo | 37 -- .../components/ThumbnailTracked.mdx.todo | 40 -- .../components/ToolbarButton.mdx | 47 --- .../component-library/components/Tooltip.mdx | 40 -- .../components/TooltipClipboard.mdx | 29 -- .../components/Typography.mdx | 69 ---- .../component-library/components/Viewport.mdx | 55 --- .../components/ViewportActionBar.mdx | 136 ------- .../components/ViewportGrid.mdx | 39 -- .../components/ViewportPane.mdx.todo | 36 -- .../components/WindowLevelMenuItem.mdx | 34 -- .../components/_category_.json | 4 - .../component-library/components/icon.mdx | 50 --- .../context-providers/ModalProvider.mdx | 117 ------ .../ViewportDialogProvider.mdx | 185 --------- .../ViewportDialogProvider.mdx.todo | 178 --------- .../context-providers/_category_.json | 4 - .../example-views/StudyList.mdx | 345 ---------------- .../example-views/Viewer.mdx.todo | 178 --------- .../example-views/_category_.json | 4 - .../component-library/getting-started.mdx | 44 --- .../docs/docs/platform/extensions/index.md | 18 +- .../extensions/modules/layout-template.md | 67 ++-- platform/docs/docs/platform/managers/index.md | 18 +- platform/docs/docs/platform/modes/index.md | 6 +- .../docs/docs/platform/pwa-vs-packaged.md | 3 - platform/docs/docs/platform/services/index.md | 41 +- .../docs/platform/services/ui/cine-service.md | 8 + .../docs/docs/platform/services/ui/index.md | 4 +- ...ridService.md => viewport-grid-service.md} | 0 platform/docs/docs/release-notes.md | 141 +++++++ platform/docs/docusaurus.config.js | 76 ++-- platform/docs/package.json | 9 +- platform/docs/pluginOHIFWebpackConfig.js | 7 +- .../src/components/ComponentPropsTable.jsx | 24 -- .../docs/src/components/HomepageFeatures.js | 64 --- .../components/HomepageFeatures.module.css | 13 - platform/docs/src/css/custom.css | 3 +- platform/docs/{docs => src/pages}/help.md | 12 +- platform/docs/src/pages/index.module.css | 25 -- platform/docs/src/pages/markdown-page.md | 7 - .../react-docgen-props-table/PropsTable.js | 102 ----- .../components/Table.js | 10 - .../components/Tooltip.js | 17 - .../react-docgen-props-table/get-prop-type.js | 23 -- .../react-docgen-props-table/humanize-prop.js | 49 --- .../src/react-docgen-props-table/index.js | 2 - .../docs/src/theme/ReactLiveScope/index.js | 24 -- platform/docs/tailwind.config.js | 1 + .../docs/versioned_docs/version-1.0/README.md | 34 +- .../version-1.0/contributing.md | 39 +- .../versioned_docs/version-1.0/faq/general.md | 9 +- .../lesion-tracker/installation-on-windows.md | 90 +++-- .../versioned_docs/version-2.0/faq/index.md | 13 +- .../docs/versioned_docs/version-2.0/help.md | 8 +- .../OHIFCornerstoneToolbar.spec.js | 56 ++- platform/viewer/package.json | 4 +- platform/viewer/src/index.js | 1 - .../viewer/src/routes/WorkList/WorkList.jsx | 4 +- yarn.lock | 196 +-------- 104 files changed, 538 insertions(+), 4956 deletions(-) delete mode 100644 platform/docs/docs/migration.md-todo delete mode 100644 platform/docs/docs/platform/component-library/Colors.mdx delete mode 100644 platform/docs/docs/platform/component-library/_category_.json delete mode 100644 platform/docs/docs/platform/component-library/components/AboutModal.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Button.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/ButtonGroup.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/CinePlayer.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/ContextMenu.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/DateRange.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Dropdown.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/EmptyStudies.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/ErrorBoundary.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/ExpandableToolbarButton.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Input.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/InputDateRange.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/InputGroup.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/InputMultiSelect.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/InputText.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Label.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/ListMenu.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/MeasurementTable.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/NavBar.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Notification.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/SegmentationTable.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Select.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/SidePanel.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/SplitButton.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/StudyBrowser.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/StudyItem.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/StudyListFilter.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/StudyListPagination.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/StudyListTable.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Table.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Thumbnail.mdx.todo delete mode 100644 platform/docs/docs/platform/component-library/components/ThumbnailNoImage.mdx.todo delete mode 100644 platform/docs/docs/platform/component-library/components/ThumbnailTracked.mdx.todo delete mode 100644 platform/docs/docs/platform/component-library/components/ToolbarButton.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Tooltip.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/TooltipClipboard.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Typography.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/Viewport.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/ViewportActionBar.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/ViewportGrid.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/ViewportPane.mdx.todo delete mode 100644 platform/docs/docs/platform/component-library/components/WindowLevelMenuItem.mdx delete mode 100644 platform/docs/docs/platform/component-library/components/_category_.json delete mode 100644 platform/docs/docs/platform/component-library/components/icon.mdx delete mode 100644 platform/docs/docs/platform/component-library/context-providers/ModalProvider.mdx delete mode 100644 platform/docs/docs/platform/component-library/context-providers/ViewportDialogProvider.mdx delete mode 100644 platform/docs/docs/platform/component-library/context-providers/ViewportDialogProvider.mdx.todo delete mode 100644 platform/docs/docs/platform/component-library/context-providers/_category_.json delete mode 100644 platform/docs/docs/platform/component-library/example-views/StudyList.mdx delete mode 100644 platform/docs/docs/platform/component-library/example-views/Viewer.mdx.todo delete mode 100644 platform/docs/docs/platform/component-library/example-views/_category_.json delete mode 100644 platform/docs/docs/platform/component-library/getting-started.mdx create mode 100644 platform/docs/docs/platform/services/ui/cine-service.md rename platform/docs/docs/platform/services/ui/{ViewportGridService.md => viewport-grid-service.md} (100%) create mode 100644 platform/docs/docs/release-notes.md delete mode 100644 platform/docs/src/components/ComponentPropsTable.jsx delete mode 100644 platform/docs/src/components/HomepageFeatures.js delete mode 100644 platform/docs/src/components/HomepageFeatures.module.css rename platform/docs/{docs => src/pages}/help.md (83%) delete mode 100644 platform/docs/src/pages/index.module.css delete mode 100644 platform/docs/src/pages/markdown-page.md delete mode 100644 platform/docs/src/react-docgen-props-table/PropsTable.js delete mode 100644 platform/docs/src/react-docgen-props-table/components/Table.js delete mode 100644 platform/docs/src/react-docgen-props-table/components/Tooltip.js delete mode 100644 platform/docs/src/react-docgen-props-table/get-prop-type.js delete mode 100644 platform/docs/src/react-docgen-props-table/humanize-prop.js delete mode 100644 platform/docs/src/react-docgen-props-table/index.js delete mode 100644 platform/docs/src/theme/ReactLiveScope/index.js diff --git a/.netlify/build-deploy-preview.sh b/.netlify/build-deploy-preview.sh index 11259021469..cb5d017d37a 100755 --- a/.netlify/build-deploy-preview.sh +++ b/.netlify/build-deploy-preview.sh @@ -17,20 +17,6 @@ mkdir -p ./.netlify/www/pwa mv platform/viewer/dist/* .netlify/www/pwa -v echo 'Web application built and copied' -# Build && Move script output -# yarn run build:package - -# Build && Move Docz Output (for the UI Component Library) -# Using local yarn install to prevent Gatsby from needing to access -# node_modules above the platform/ui folder -cd platform/ui -yarn install -yarn run build -cd ../.. -mkdir -p ./.netlify/www/ui -mv platform/ui/.docz/dist/* .netlify/www/ui -v -echo 'UI Component docs (docz) built and copied' - # Build && Move Docusaurus Output (for the docs themselves) cd platform/docs yarn install diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index b37f3405df8..26171786fdf 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -103,7 +103,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => { 'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''), 'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'), 'process.env.VERSION_NUMBER': JSON.stringify( - process.env.VERSION_NUMBER || PACKAGE.version || '' + process.env.VERSION_NUMBER || PACKAGE.productVersion || '' ), 'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM), /* i18n */ diff --git a/README.md b/README.md index a9ef39699eb..bd3903e0827 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ We offer support through - [Request a Feature 🚀](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Request+%3Ahand%3A&template=---feature-request.md) - [Ask a Question 🤗](https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Community%3A+Question+%3Aquestion%3A&template=---support-question.md) -For commercial support, academic collaberations, and answers to common +For commercial support, academic collaborations, and answers to common questions; please read our [documented FAQ](https://docs.ohif.org/faq/index.html#does-ohif-offer-commercial-support). @@ -173,7 +173,7 @@ also supports a number of commands that can be found in their respective | `dev:project ` | Replace with `core`, `ui`, `i18n`, `cornerstone`, `vtk`, etc. | | `test:unit` | Jest multi-project test runner; overall coverage | | **Deploy** | | -| `build`\* | Builds production output for our PWA Viewer | | +| `build`\* | Builds production output for our PWA Viewer | | \* - For more information on our different builds, check out our [Deploy Docs][deployment-docs] @@ -189,14 +189,14 @@ you'll see the following: . ├── extensions # │ ├── _example # Skeleton of example extension -│ ├── default # +│ ├── default # │ ├── cornerstone # 2D images w/ Cornerstone.js -│ ├── dicom-sr # -│ └── measurement-tracking # +│ ├── dicom-sr # +│ └── measurement-tracking # │ ├── modes # │ ├── _example # Skeleton of example mode -│ └── longitudinal # +│ └── longitudinal # │ ├── platform # │ ├── core # Business Logic @@ -230,14 +230,28 @@ This is a list of Extensions maintained by the OHIF Core team. It's possible to customize and configure these extensions, and you can even create your own. You can [read more about extensions here][ohif-extensions]. -| Name | Description | Links | -| -------------------------------------------------------------- | ------------------------------------------------------- | ---------------------- | -| [@ohif/extension-cornerstone][extension-cornerstone] | 2D image viewing, annotation, and segementation tools | [NPM][cornerstone-npm] | +| Name | Description | Links | +| ---------------------------------------------------- | ----------------------------------------------------- | ---------------------- | +| [@ohif/extension-cornerstone][extension-cornerstone] | 2D image viewing, annotation, and segementation tools | [NPM][cornerstone-npm] | ## Acknowledgments To acknowledge the OHIF Viewer in an academic publication, please cite +> _Open Health Imaging Foundation Viewer: An Extensible Open-Source Framework +> for Building Web-Based Imaging Applications to Support Cancer Research_ +> +> Erik Ziegler, Trinity Urban, Danny Brown, James Petts, Steve D. Pieper, Rob +> Lewis, Chris Hafey, and Gordon J. Harris +> +> _JCO Clinical Cancer Informatics_, no. 4 (2020), 336-345, DOI: +> [10.1200/CCI.19.00131](https://www.doi.org/10.1200/CCI.19.00131) +> +> Open-Access on Pubmed Central: +> https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7259879/ + +or, for v1, please cite: + > _LesionTracker: Extensible Open-Source Zero-Footprint Web Viewer for Cancer > Imaging Research and Clinical Trials_ > diff --git a/extensions/default/package.json b/extensions/default/package.json index f95b11892a6..ae0b10a7ce2 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -13,7 +13,7 @@ "engines": { "node": ">=10", "npm": ">=6", - "yarn": ">=1.19.1" + "yarn": ">=1.18.0" }, "files": [ "dist", diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx index 3e1a9d26ea0..c57f8761515 100644 --- a/extensions/default/src/ViewerLayout/index.jsx +++ b/extensions/default/src/ViewerLayout/index.jsx @@ -101,11 +101,14 @@ function ViewerLayout({ const { show, hide } = useModal(); const { hotkeyDefinitions, hotkeyDefaults } = hotkeysManager; + const versionNumber = process.env.VERSION_NUMBER; + const buildNumber = process.env.BUILD_NUM; + const menuOptions = [ { title: t('Header:About'), icon: 'info', - onClick: () => show({ content: AboutModal, title: 'About OHIF Viewer' }), + onClick: () => show({ content: AboutModal, title: 'About OHIF Viewer', contentProps: { versionNumber, buildNumber } }), }, { title: t('Header:Preferences'), diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index d76b90c167d..4278e9f4402 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -13,7 +13,7 @@ "engines": { "node": ">=10", "npm": ">=6", - "yarn": ">=1.19.1" + "yarn": ">=1.18.0" }, "files": [ "dist", diff --git a/platform/docs/docs/README.md b/platform/docs/docs/README.md index 1a981807499..8c4bdcffd17 100644 --- a/platform/docs/docs/README.md +++ b/platform/docs/docs/README.md @@ -4,167 +4,41 @@ slug: / sidebar_position: 1 --- - - -> ATTENTION! You are looking at the docs for the `OHIF-v3` Viewer (third time is -> always a charm). If you're looking for the our `OHIF-v2` (React stable -> version) or OHIF-v1 (deprecated `Meteor` version) select it's version from the -> dropdown box in the top left corner of this page. - The [Open Health Imaging Foundation][ohif-org] (OHIF) Viewer is an open source, -web-based, medical imaging viewer. It can be configured to connect to Image -Archives that support [DicomWeb][dicom-web], and offers support for mapping to -proprietary API formats. OHIF maintained extensions add support for viewing, -annotating, and reporting on DICOM images in 2D (slices) and 3D (volumes). +web-based, medical imaging platform. It aims to provide a core framework for +building complex imaging applications. + +Key features: + +- Designed to load large radiology studies as quickly as possible. Retrieves + metadata ahead of time and streams in imaging pixel data as needed. +- Leverages [Cornerstone.js](https://cornerstonejs.org/) for decoding, + rendering, and annotating medical images. +- Works out-of-the-box with Image Archives that support [DICOMWeb][dicom-web]. + Offers a Data Source API for communicating with archives over proprietary API + formats. +- Provides a plugin framework for creating task-based workflow modes which can + re-use core functionality. +- Beautiful user interface (UI) designed with extensibility in mind. UI + components available in a reusable component library built with React.js and + Tailwind CSS ![OHIF Viewer Screenshot](./assets/img/OHIF-Viewer.png) - - -  - -## What's new in `OHIF-v3` - -`OHIF-v3` is our second try for a React-based viewer, and is the third version -of our medical image web viewers from the start. The summary of changes include: - -- Addition of workflow modes - - Often, medical imaging use cases involves lots of specific workflows that - re-use functionalities. We have added the capability of workflow modes, that - enable people to customize user interface and configure application for - specific workflow. - - The idea is to re-use the functionalities that extensions provide and create - a workflow. Brain segmentation workflow is different from prostate - segmentation in UI for sure; however, they share the segmentation tools that - can be re-used. - - Our vision is that technical people focus of developing extensions which - provides core functionalities, and experts to build modes by picking the - appropriate functionalities from each extension. - -* Redux store has been removed from the viewer, and a cleaner, more powerful -* Tailwind CSS -* End-to-end test suite - -Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OHIF-v2 functionalitiesOHIF-v3Comment
Rendering of 2D images via Cornerstone
Study List
Series Browser
DICOM JSON
2D Tools via CornerstoneTools
OpenID Connect standard authentication flow for connecting to identity providers
Internationalization
Drag/drop DICOM data into the viewer (see https://viewer.ohif.org/local)
White-labelling: Easily replace the OHIF Logo with your logo
DICOM Whole-slide imaging viewport🔜In Progress
IHE Invoke Image Display - Standard-compliant launching of the viewer (e.g. from PACS or RIS)🔜Not Started
DICOM PDF support🔜Not Started
Displaying non-renderable DICOM as HTML🔜Not Started
Segmentation support🔜Not Started
RT STRUCT support🔜Not Started
DICOM upload to PACS🔜Not Started
Google Cloud adapter🔜Not Started
VTK Extension + MIP / MPR layoutOther plans that involves amazing news soon!
UMD Build (Embedded Viewer). The problem is that this breaks a bunch of extensions that rely on third party scripts (e.g. VTK) which have their own web worker loaders.
+
## Where to next? -The Open Health Imaging Foundation intends to provide a simple general purpose -DICOM Viewer which can be easily extended for specific uses. If you find -yourself unable to extend the viewer for your purposes, please reach out via our -[GitHub issues][gh-issues]. We are actively seeking feedback on ways to improve -our integration and extension points. +The Open Health Imaging Foundation intends to provide an imaging viewer +framework which can be easily extended for specific uses. If you find yourself +unable to extend the viewer for your purposes, please reach out via our [GitHub +issues][gh-issues]. We are actively seeking feedback on ways to improve our +integration and extension points. Check out these helpful links: @@ -172,7 +46,47 @@ Check out these helpful links: [Getting Started Guide](./development/getting-started.md). - We're an active, vibrant community. [Learn how you can be more involved.](./development/contributing.md) -- Feeling lost? Read our [help page](./help.md). +- Feeling lost? Read our [help page](/help). + +## Citing OHIF + +To cite the OHIF Viewer in an academic publication, please cite + +> _Open Health Imaging Foundation Viewer: An Extensible Open-Source Framework +> for Building Web-Based Imaging Applications to Support Cancer Research_ + +> Erik Ziegler, Trinity Urban, Danny Brown, James Petts, Steve D. Pieper, Rob +> Lewis, Chris Hafey, and Gordon J. Harris + +> _JCO Clinical Cancer Informatics_, no. 4 (2020), 336-345, DOI: +> [10.1200/CCI.19.00131](https://www.doi.org/10.1200/CCI.19.00131) + +This article is freely available on Pubmed Central: +https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7259879/ + +or, for Lesion Tracker of OHIF v1, please cite: + +> _LesionTracker: Extensible Open-Source Zero-Footprint Web Viewer for Cancer +> Imaging Research and Clinical Trials_ +> +> Trinity Urban, Erik Ziegler, Rob Lewis, Chris Hafey, Cheryl Sadow, Annick D. +> Van den Abbeele and Gordon J. Harris +> +> _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI: +> [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) + +This article is freely available on Pubmed Central: +https://pubmed.ncbi.nlm.nih.gov/29092955/ + +**Note:** If you use or find this repository helpful, please take the time to +star this repository on Github. This is an easy way for us to assess adoption +and it can help us obtain future funding for the project. + +## License + +MIT © [OHIF](https://github.com/OHIF) + +  [ohif-org]: https://www.ohif.org -[ohif-demo]: http://viewer.ohif.org/ +[ohif-demo]: http://v3-demo.ohif.org/ [dicom-web]: https://en.wikipedia.org/wiki/DICOMweb [gh-issues]: https://github.com/OHIF/Viewers/issues diff --git a/platform/docs/docs/deployment/index.md b/platform/docs/docs/deployment/index.md index 3d8b6b8911b..0b2f8bd6912 100644 --- a/platform/docs/docs/deployment/index.md +++ b/platform/docs/docs/deployment/index.md @@ -17,7 +17,7 @@ your Viewer will display). Our goal is to make deployment as simple and painless as possible; however, there is an inherent amount of complexity in configuring and deploying web applications. If you find yourself a little lost, please don't hesitate to -[reach out for help](/help.md) +[reach out for help](/help) ## Deployment Scenarios @@ -53,7 +53,7 @@ HTML, CSS, JS, Font Files, and Images. We "build" those files from our files publicly accessible by hosting them on a Web Server. If you have not deployed a web application before, this may be a good time to -[reach out for help](/help.md), as these steps assume prior web development and +[reach out for help](/help), as these steps assume prior web development and deployment experience. ##### Part 1 - Build Production Assets @@ -264,7 +264,7 @@ Coming soon - - -import { - Dialog, - Button, - Notification, - ViewportDialogProvider, - useViewportDialog, -} from '@ohif/ui'; - -# Viewport Dialog Provider - -This is a context provider that allow the application to share the modal -component across all application. - -## Sample - -```jsx live - function() { - const ViewportNotification = ({ hide }) => { - return ( - { - if (typeof window !== 'undefined') { - window.alert(value); - } - }} - /> - ); - }; - const ViewportActionButtons = () => { - const [dialogState, dialogApi] = useViewportDialog(); - return ( - - ); - }; - } - - return ( -
-
- -
- - CONTENT 1 -
-
-
-
- -
- - CONTENT 1 -
-
-
-
- ); - }} -``` - -## Example using UIViewportDialogService - -```jsx live - function() { - const ViewportNotification = ({ hide }) => { - return ( - { - if (typeof window !== 'undefined') { - window.alert(value); - } - }} - /> - ); - }; - // Creating servicesManager and register services should be in the root of your app - const servicesManager = new ServicesManager(); - servicesManager.registerServices([UIViewportDialogService]); - // Get service instance - const _UIViewportDialogService = - servicesManager.services.UIViewportDialogService; - console.log(_UIViewportDialogService); - return ( -
-
- - -
-
- -
- CONTENT 0 -
-
- -
- CONTENT 1 -
-
-
-
- ); - }} -``` - -## Properties: - - -```` diff --git a/platform/docs/docs/platform/component-library/context-providers/ViewportDialogProvider.mdx.todo b/platform/docs/docs/platform/component-library/context-providers/ViewportDialogProvider.mdx.todo deleted file mode 100644 index 3425605dd4e..00000000000 --- a/platform/docs/docs/platform/component-library/context-providers/ViewportDialogProvider.mdx.todo +++ /dev/null @@ -1,178 +0,0 @@ ---- -name: ViewportDialogProvider -route: customHooks/viewportDialogProvider ---- - - - - -import UIViewportDialogService from './../../../core/src/services/UIViewportDialogService'; -import ServicesManager from './../../../core/src/services/ServicesManager'; - -import { Dialog, Button, Notification, ViewportDialogProvider, useViewportDialog } from '@ohif/ui'; - -# Viewport Dialog Provider - -This is a context provider that allow the application to share the modal -component across all application. - -## Sample - -```jsx live - {() => { - const ViewportNotification = ({ hide }) => { - return ( - { - if (typeof window !== 'undefined') { - window.alert(value); - } - }} - /> - ); - }; - const ViewportActionButtons = () => { - const [dialogState, dialogApi] = useViewportDialog(); - return ( - - ); - }; - return ( -
-
- -
- - CONTENT 1 -
-
-
-
- -
- - CONTENT 1 -
-
-
-
- ); - }} -``` - -## Example using UIViewportDialogService - -```jsx live - {() => { - const ViewportNotification = ({ hide }) => { - return ( - { - if (typeof window !== 'undefined') { - window.alert(value); - } - }} - /> - ); - }; - // Creating servicesManager and register services should be in the root of your app - const servicesManager = new ServicesManager(); - servicesManager.registerServices([UIViewportDialogService]); - // Get service instance - const _UIViewportDialogService = - servicesManager.services.UIViewportDialogService; - console.log(_UIViewportDialogService); - return ( -
-
- - -
-
- -
- CONTENT 0 -
-
- -
- CONTENT 1 -
-
-
-
- ); - }} -``` - -## Properties: - - diff --git a/platform/docs/docs/platform/component-library/context-providers/_category_.json b/platform/docs/docs/platform/component-library/context-providers/_category_.json deleted file mode 100644 index 1a247737a1e..00000000000 --- a/platform/docs/docs/platform/component-library/context-providers/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Context Providers", - "position": 5 -} diff --git a/platform/docs/docs/platform/component-library/example-views/StudyList.mdx b/platform/docs/docs/platform/component-library/example-views/StudyList.mdx deleted file mode 100644 index be2c08b935e..00000000000 --- a/platform/docs/docs/platform/component-library/example-views/StudyList.mdx +++ /dev/null @@ -1,345 +0,0 @@ ---- -name: Study List -menu: Views -route: examples/studyList ---- - -import { useState } from 'react'; -import { - Icon, - StudyListExpandedRow, - Button, - NavBar, - Svg, - IconButton, - EmptyStudies, - StudyListTable, - StudyListPagination, - StudyListFilter, -} from '@ohif/ui'; -import utils from '../../../../src/utils'; - -import classnames from 'classnames'; -import moment from 'moment'; - -# Study List - -This example shows you how you can build a Study List page using the available -components. - -```jsx live -function () { - const defaultFilterValues = { - patientName: '', - mrn: '', - studyDate: { - startDate: null, - endDate: null, - }, - description: '', - modality: undefined, - accession: '', - sortBy: '', - sortDirection: 'none', - page: 0, - resultsPerPage: 25, - }; - const [filterValues, setFilterValues] = useState(defaultFilterValues); - const [studies, setStudies] = useState([]); - const numOfStudies = studies.length; - const [expandedRows, setExpandedRows] = useState([]); - const filtersMeta = [ - { - name: 'patientName', - displayName: 'Patient Name', - inputType: 'Text', - isSortable: true, - gridCol: 4, - }, - { - name: 'mrn', - displayName: 'MRN', - inputType: 'Text', - isSortable: true, - gridCol: 2, - }, - { - name: 'studyDate', - displayName: 'Study date', - inputType: 'DateRange', - isSortable: true, - gridCol: 5, - }, - { - name: 'description', - displayName: 'Description', - inputType: 'Text', - isSortable: true, - gridCol: 4, - }, - { - name: 'modality', - displayName: 'Modality', - inputType: 'MultiSelect', - inputProps: { - options: [ - { value: 'SEG', label: 'SEG' }, - { value: 'CT', label: 'CT' }, - { value: 'MR', label: 'MR' }, - { value: 'SR', label: 'SR' }, - ], - }, - isSortable: true, - gridCol: 3, - }, - { - name: 'accession', - displayName: 'Accession', - inputType: 'Text', - isSortable: true, - gridCol: 4, - }, - { - name: 'instances', - displayName: 'Instances', - inputType: 'None', - isSortable: true, - gridCol: 2, - }, - ]; - const isFiltering = (filterValues, defaultFilterValues) => { - return Object.keys(defaultFilterValues).some(name => { - return filterValues[name] !== defaultFilterValues[name]; - }); - }; - const tableDataSource = studies.map((study, key) => { - const rowKey = key + 1; - const isExpanded = expandedRows.some(k => k === rowKey); - const { - AccessionNumber, - Modalities, - Instances, - StudyDescription, - PatientId, - PatientName, - StudyDate, - series, - } = study; - const seriesTableColumns = { - description: 'Description', - seriesNumber: 'Series', - modality: 'Modality', - Instances: 'Instances', - }; - const seriesTableDataSource = series.map(seriesItem => { - const { SeriesNumber, Modality, instances } = seriesItem; - return { - description: 'Patient Protocol', - seriesNumber: SeriesNumber, - modality: Modality, - Instances: instances.length, - }; - }); - return { - row: [ - { - key: 'patientName', - content: PatientName, - gridCol: 4, - }, - { - key: 'mrn', - content: PatientId, - gridCol: 2, - }, - { - key: 'studyDate', - content: ( -
- - {moment(StudyDate).format('MMM-DD-YYYY')} - - {moment(StudyDate).format('hh:mm A')} -
- ), - gridCol: 5, - }, - { - key: 'description', - content: StudyDescription, - gridCol: 4, - }, - { - key: 'modality', - content: Modalities, - gridCol: 3, - }, - { - key: 'accession', - content: AccessionNumber, - gridCol: 4, - }, - { - key: 'instances', - content: ( - <> - - {Instances} - - ), - gridCol: 4, - }, - ], - expandedContent: ( - - - - -
- - Feedback text lorem ipsum dolor sit amet -
-
- ), - onClickRow: () => - setExpandedRows(s => - isExpanded ? s.filter(n => rowKey !== n) : [...s, rowKey] - ), - isExpanded, - }; - }); - const handleStudyList = number => { - const studies = utils.getMockedStudies(number); - setStudies(studies); - setCurrentPage(1); - }; - const [currentPage, setCurrentPage] = useState(1); - const [perPage, setPerPage] = useState(25); - const totalPages = Math.floor(numOfStudies / perPage); - const onChangePage = page => { - if (page > totalPages) { - return; - } - setCurrentPage(page); - }; - const onChangePerPage = perPage => { - setPerPage(perPage); - setCurrentPage(1); - }; - const hasStudies = numOfStudies > 0; - return ( -
-
- - - - -
-
- -
-
- -
-
-
- - FOR INVESTIGATIONAL USE ONLY - - {}} - > - - - - - -
-
- setFilterValues(defaultFilterValues)} - isFiltering={isFiltering(filterValues, defaultFilterValues)} - /> - {hasStudies ? ( - <> - - - - ) : ( -
- -
- )} -
-
- ); -} -``` diff --git a/platform/docs/docs/platform/component-library/example-views/Viewer.mdx.todo b/platform/docs/docs/platform/component-library/example-views/Viewer.mdx.todo deleted file mode 100644 index 5fb14845567..00000000000 --- a/platform/docs/docs/platform/component-library/example-views/Viewer.mdx.todo +++ /dev/null @@ -1,178 +0,0 @@ ---- -name: Viewer -menu: Views -route: examples/viewer ---- - -import { useState } from 'react'; - -import { - NavBar, - SidePanel, - Svg, - SegmentationTable, - ButtonGroup, - Button, - Icon, - IconButton, - StudyBrowser, - ViewportActionBar, - Notification, - Viewport, - ViewportGrid, - ViewportPane, - StudySummary, - MeasurementTable, - Header, - DragAndDropProvider, -} from '@ohif/ui'; - -import { tabs } from './studyBrowserMockData'; - -# Viewer - -```jsx live - {() => { - const [activeMeasurementItem, setActiveMeasurementItem] = useState(null); - const descriptionData = { - date: '07-Sep-2010', - modality: 'CT', - description: 'CHEST/ABD/PELVIS W CONTRAST', - }; - const measurementTableData = { - title: 'Measurements', - amount: 10, - data: new Array(10).fill({}).map((el, i) => ({ - id: i + 1, - label: 'Label short description', - displayText: '24.0 x 24.0 mm (S:4, I:22)', - isActive: activeMeasurementItem === i + 1, - })), - onClick: (id) => setActiveMeasurementItem((s) => (s === id ? null : id)), - onEdit: (id) => alert(`Edit: ${id}`), - }; - const [activeViewportIndex, setActiveViewportIndex] = useState(0); - return ( - -
-
- {/* LEFT SIDEPANELS */} - - }} - /> - {/* TOOLBAR + GRID */} -
-
- {/* Secondary Toolbar */} -
- {/* VIEWPORT GRID CONTAINER */} -
- - {[0, 1].map(viewportIndex => ( - {}}> - alert(`Series ${direction}`)} - studyData={{ - label: 'A', - isTracked: true, - isLocked: false, - isRehydratable: false, - studyDate: '07-Sep-2011', - currentSeries: 1, - seriesDescription: - 'Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit ', - modality: 'CT', - patientInformation: { - patientName: 'Smith, Jane', - patientSex: 'F', - patientAge: '59', - MRN: '10000001', - thickness: '5.0mm', - spacing: '1.25mm', - scanner: 'Aquilion', - }, - }} - > -
- CONTENT -
-
-
) - )} -
-
-
- -
- - {}} - onEdit={id => alert(`Edit: ${id}`)} - /> -
-
- - alert('Export')}> - - - - - - - -
- - ) - }} - /> -
- - ); - }} -``` diff --git a/platform/docs/docs/platform/component-library/example-views/_category_.json b/platform/docs/docs/platform/component-library/example-views/_category_.json deleted file mode 100644 index f9601aa2b2b..00000000000 --- a/platform/docs/docs/platform/component-library/example-views/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Example Views", - "position": 6 -} diff --git a/platform/docs/docs/platform/component-library/getting-started.mdx b/platform/docs/docs/platform/component-library/getting-started.mdx deleted file mode 100644 index 80f3704c87d..00000000000 --- a/platform/docs/docs/platform/component-library/getting-started.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: Getting Started ---- - -import { Button } from '@ohif/ui'; - -# Getting Started - -`@ohif/ui` is a collection of components and utilities that power OHIF's -zero-footprint DICOM viewer. - -It's maintained in a monorepo, in the -[OHIF's repository](https://github.com/OHIF/Viewers/). - -## Installation - -Install `@ohif/ui` from your terminal: - -```bash -# with npm -npm i @ohif/ui --save-exact -​ -# with yarn -yarn add @ohif/ui --exact -``` - -## Usage - -```javascript -// Import and use components -import { Button } from '@ohif/ui'; -``` - -```jsx live -
- -
-``` - -## Examples - -In the **Examples** section you can check many examples on how to build your -application using `@ohif/ui` components. diff --git a/platform/docs/docs/platform/extensions/index.md b/platform/docs/docs/platform/extensions/index.md index ea45995a055..9c1f25b46e3 100644 --- a/platform/docs/docs/platform/extensions/index.md +++ b/platform/docs/docs/platform/extensions/index.md @@ -258,7 +258,7 @@ differently. - + LayoutTemplate (NEW) @@ -266,7 +266,7 @@ differently. - + DataSource (NEW) @@ -274,7 +274,7 @@ differently. - + SOPClassHandler @@ -282,7 +282,7 @@ differently. - + Panel @@ -290,7 +290,7 @@ differently. - + Viewport @@ -298,7 +298,7 @@ differently. - + Commands @@ -306,7 +306,7 @@ differently. - + Toolbar @@ -314,7 +314,7 @@ differently. - + Context @@ -322,7 +322,7 @@ differently. - + HangingProtocol diff --git a/platform/docs/docs/platform/extensions/modules/layout-template.md b/platform/docs/docs/platform/extensions/modules/layout-template.md index 7184ae95242..66c839dae1b 100644 --- a/platform/docs/docs/platform/extensions/modules/layout-template.md +++ b/platform/docs/docs/platform/extensions/modules/layout-template.md @@ -2,16 +2,22 @@ sidebar_position: 7 sidebar_label: Layout Template --- + # Module: Layout Template ## Overview -`LayoutTemplates` are a new concept in v3 that modes use to control the layout of a route. -A layout template is a React component that is given a set of managers that define apis to access toolbar state, commands, and hotkeys, as well as props defined by the layout template. -For instance the default LayoutTemplate takes in leftPanels, rightPanels and viewports as props, which it uses to build its view. +`LayoutTemplates` are a new concept in v3 that modes use to control the layout +of a route. A layout template is a React component that is given a set of +managers that define apis to access toolbar state, commands, and hotkeys, as +well as props defined by the layout template. -In addition, `layout template` has complete control over the structure of the application. You could have tools down the left side, or a strict guided workflow with tools set programmatically, the choice is yours for your use case. +For instance the default LayoutTemplate takes in leftPanels, rightPanels and +viewports as props, which it uses to build its view. +In addition, `layout template` has complete control over the structure of the +application. You could have tools down the left side, or a strict guided +workflow with tools set programmatically, the choice is yours for your use case. ```jsx const getLayoutTemplateModule = (/* ... */) => [ @@ -23,28 +29,25 @@ const getLayoutTemplateModule = (/* ... */) => [ ]; ``` -The `props` that are passed to `layoutTemplate` are managers and service, along with -the defined mode left/right panels, mode's defined viewports and OHIF `ViewportGridComp`. -LayoutTemplate leverages extensionManager to grab typed extension module entries: -`*.getModuleEntry(id)` - - - +The `props` that are passed to `layoutTemplate` are managers and service, along +with the defined mode left/right panels, mode's defined viewports and OHIF +`ViewportGridComp`. LayoutTemplate leverages extensionManager to grab typed +extension module entries: `*.getModuleEntry(id)` -A simplified code for `Default extention`'s layout template is: +A simplified code for `Default extension`'s layout template is: ```jsx title="extensions/default/src/ViewerLayout/index.jsx" -import React from 'react' -import { SidePanel } from '@ohif/ui' +import React from 'react'; +import { SidePanel } from '@ohif/ui'; function Toolbar({ servicesManager }) { - const { ToolBarService } = servicesManager.services + const { ToolBarService } = servicesManager.services; return ( <> // ToolBarService.getButtonSection('primary') to get toolbarButtons {toolbarButtons.map((toolDef, index) => { - const { id, Component, componentProps } = toolDef + const { id, Component, componentProps } = toolDef; return ( ToolBarService.recordInteraction(args)} + onInteraction={args => ToolBarService.recordInteraction(args)} /> - ) + ); })} - ) + ); } function ViewerLayout({ @@ -72,9 +75,9 @@ function ViewerLayout({ viewports, ViewportGridComp, }) { - const getPanelData = (id) => { - const entry = extensionManager.getModuleEntry(id) - const content = entry.component + const getPanelData = id => { + const entry = extensionManager.getModuleEntry(id); + const content = entry.component; return { iconName: entry.iconName, @@ -82,21 +85,21 @@ function ViewerLayout({ label: entry.label, name: entry.name, content, - } - } + }; + }; - const getViewportComponentData = (viewportComponent) => { - const entry = extensionManager.getModuleEntry(viewportComponent.namespace) + const getViewportComponentData = viewportComponent => { + const entry = extensionManager.getModuleEntry(viewportComponent.namespace); return { component: entry.component, displaySetsToDisplay: viewportComponent.displaySetsToDisplay, - } - } + }; + }; - const leftPanelComponents = leftPanels.map(getPanelData) - const rightPanelComponents = rightPanels.map(getPanelData) - const viewportComponents = viewports.map(getViewportComponentData) + const leftPanelComponents = leftPanels.map(getPanelData); + const rightPanelComponents = rightPanels.map(getPanelData); + const viewportComponents = viewports.map(getViewportComponentData); return (
@@ -125,7 +128,7 @@ function ViewerLayout({ />
- ) + ); } ``` diff --git a/platform/docs/docs/platform/managers/index.md b/platform/docs/docs/platform/managers/index.md index c2f4a30d8c0..ee6d6b2fc8c 100644 --- a/platform/docs/docs/platform/managers/index.md +++ b/platform/docs/docs/platform/managers/index.md @@ -2,12 +2,14 @@ sidebar_position: 1 sidebar_label: Introduction --- + # Managers ## Overview - -`OHIF` uses `Managers` to accomplish various purposes such as registering new services, dependency injection, and aggregating and exposing `extension` features. +`OHIF` uses `Managers` to accomplish various purposes such as registering new +services, dependency injection, and aggregating and exposing `extension` +features. `OHIF-v3` provides the following managers which we will discuss in depth. @@ -21,7 +23,7 @@ sidebar_label: Introduction - + Extension Manager @@ -31,7 +33,7 @@ sidebar_label: Introduction - + Services Manager @@ -41,7 +43,7 @@ sidebar_label: Introduction - + Commands Manager @@ -51,7 +53,7 @@ sidebar_label: Introduction - + Hotkeys Manager @@ -62,10 +64,6 @@ sidebar_label: Introduction - - - - diff --git a/platform/docs/docs/platform/modes/index.md b/platform/docs/docs/platform/modes/index.md index 28a3e710bc2..187d708fc4f 100644 --- a/platform/docs/docs/platform/modes/index.md +++ b/platform/docs/docs/platform/modes/index.md @@ -99,7 +99,7 @@ export default function mode() { - + onModeEnter @@ -107,7 +107,7 @@ export default function mode() { - + onModeExit @@ -139,7 +139,7 @@ export default function mode() { - + extensions diff --git a/platform/docs/docs/platform/pwa-vs-packaged.md b/platform/docs/docs/platform/pwa-vs-packaged.md index c4a0c66516b..bdc411e6cb1 100644 --- a/platform/docs/docs/platform/pwa-vs-packaged.md +++ b/platform/docs/docs/platform/pwa-vs-packaged.md @@ -10,9 +10,6 @@ processes: ```bash # Static Asset output: For deploying PWAs yarn run build - -# Single `.js` script, for embedding viewer into existing apps -yarn run build:package ``` ## Progressive Web Application (PWA) diff --git a/platform/docs/docs/platform/services/index.md b/platform/docs/docs/platform/services/index.md index 93cfd08660e..e3735c3bc59 100644 --- a/platform/docs/docs/platform/services/index.md +++ b/platform/docs/docs/platform/services/index.md @@ -2,14 +2,16 @@ sidebar_position: 1 sidebar_label: Introduction --- -# Services +# Services ## Overview -Services are "concern-specific" code modules that can be consumed across layers. Services provide -a set of operations, often tied to some shared state, and are made available to -through out the app via the `ServicesManager`. Services are particularly well suited to -address [cross-cutting concerns][cross-cutting-concerns]. + +Services are "concern-specific" code modules that can be consumed across layers. +Services provide a set of operations, often tied to some shared state, and are +made available to through out the app via the `ServicesManager`. Services are +particularly well suited to address [cross-cutting +concerns][cross-cutting-concerns]. Each service should be: @@ -17,13 +19,13 @@ Each service should be: - able to fail and/or be removed without breaking the application - completely interchangeable with another module implementing the same interface - -> In `OHIF-v3` we have added multiple non-UI services and have introduced **pub/sub** pattern to reduce coupling between layers. +> In `OHIF-v3` we have added multiple non-UI services and have introduced +> **pub/sub** pattern to reduce coupling between layers. > > [Read more about Pub/Sub](./pubsub.md) - ## Services + The following services is available in the `OHIF-v3`. @@ -37,7 +39,7 @@ The following services is available in the `OHIF-v3`. @@ -48,7 +50,7 @@ The following services is available in the `OHIF-v3`. @@ -59,7 +61,7 @@ The following services is available in the `OHIF-v3`. @@ -70,7 +72,7 @@ The following services is available in the `OHIF-v3`. @@ -81,7 +83,7 @@ The following services is available in the `OHIF-v3`. @@ -92,7 +94,7 @@ The following services is available in the `OHIF-v3`. @@ -103,7 +105,7 @@ The following services is available in the `OHIF-v3`. @@ -114,7 +116,7 @@ The following services is available in the `OHIF-v3`. @@ -125,7 +127,7 @@ The following services is available in the `OHIF-v3`. @@ -136,7 +138,7 @@ The following services is available in the `OHIF-v3`. @@ -147,7 +149,7 @@ The following services is available in the `OHIF-v3`. @@ -159,7 +161,6 @@ The following services is available in the `OHIF-v3`.
- + DicomMetadataStore (NEW)
- + DisplaySetService (NEW)
- + HangingProtocolService (NEW)
- + MeasurementService (MODIFIED)
- + ToolBarService (NEW)
- + ViewportGridService (NEW)
- + Cine Service (NEW)
- + UIDialogService
- + UIModalService
- + UINotificationService
- + UIViewportDialogService (NEW)
- diff --git a/platform/docs/docs/platform/services/ui/cine-service.md b/platform/docs/docs/platform/services/ui/cine-service.md new file mode 100644 index 00000000000..707b5d97f7f --- /dev/null +++ b/platform/docs/docs/platform/services/ui/cine-service.md @@ -0,0 +1,8 @@ +--- +sidebar_position: 7 +sidebar_label: CINE Service +--- + +# CINE Service + +TODO... diff --git a/platform/docs/docs/platform/services/ui/index.md b/platform/docs/docs/platform/services/ui/index.md index 63ffea903d2..d50cbe56e7e 100644 --- a/platform/docs/docs/platform/services/ui/index.md +++ b/platform/docs/docs/platform/services/ui/index.md @@ -22,8 +22,8 @@ We maintain the following UI Services: - [UI Modal Service](ui-modal-service.md) - [UI Dialog Service](ui-dialog-service.md) - [UI Viewport Dialog Service](ui-viewport-dialog-service.md) -- Cine Service -- [Viewport Grid Service](ViewportGridService.md) +- [CINE Service](cine-service.md) +- [Viewport Grid Service](viewport-grid-service.md)