From bec8f21c0a22c7d4aa21b2f8045851dd4dce6c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elo=C3=ADzio=20Salgado?= Date: Wed, 22 Feb 2017 11:58:49 -0300 Subject: [PATCH] Moving Hanging Protocols updates back to the viewers --- .../client/components/viewer/viewer.js | 3 +- OHIFViewer/client/components/viewer/viewer.js | 2 +- .../both/collections.js | 7 + .../ohif-hanging-protocols/both/testData.js | 1243 +++++++++++++++++ .../client/protocolEngine.js | 122 +- .../client/protocolStore/defaultStrategy.js | 4 +- .../client/OHIFInstanceMetadata.js | 6 +- .../client/OHIFSeriesMetadata.js | 4 +- .../ohif-metadata/client/OHIFStudyMetadata.js | 4 +- .../client/lib/OHIFStudyMetadataSource.js | 4 +- .../client/lib/getStudyPriors.js | 40 +- .../client/lib/getStudyPriorsMap.js | 9 +- .../imageViewerViewport.js | 4 +- .../ohif-viewerbase/client/lib/WLPresets.js | 101 +- .../lib/classes/metadata/InstanceMetadata.js | 42 +- .../client/lib/classes/metadata/Metadata.js | 38 +- .../lib/classes/metadata/SeriesMetadata.js | 27 +- .../lib/classes/metadata/StudyMetadata.js | 69 +- .../lib/classes/metadata/StudySummary.js | 80 +- 19 files changed, 1626 insertions(+), 183 deletions(-) diff --git a/LesionTracker/client/components/viewer/viewer.js b/LesionTracker/client/components/viewer/viewer.js index d09ffc9f2..6b242c67d 100644 --- a/LesionTracker/client/components/viewer/viewer.js +++ b/LesionTracker/client/components/viewer/viewer.js @@ -89,7 +89,7 @@ Template.viewer.onCreated(() => { OHIF.viewer.StudyMetadataList.removeAll(); instance.data.studies.forEach(study => { - const studyMetadata = new OHIF.metadata.StudyMetadata(study); + const studyMetadata = new OHIF.metadata.StudyMetadata(study, study.studyInstanceUid); const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata); studyMetadata.setDisplaySets(displaySets); @@ -125,6 +125,7 @@ Template.viewer.onCreated(() => { return; } + // @TODO: Maybe this should be a setCustomAttribute? study.timepointType = timepoint.timepointType; }); }); diff --git a/OHIFViewer/client/components/viewer/viewer.js b/OHIFViewer/client/components/viewer/viewer.js index 15c0fa03c..84ce5f274 100644 --- a/OHIFViewer/client/components/viewer/viewer.js +++ b/OHIFViewer/client/components/viewer/viewer.js @@ -112,7 +112,7 @@ Template.viewer.onCreated(() => { ViewerData[contentId].studyInstanceUids = []; instance.data.studies.forEach(study => { - const studyMetadata = new OHIF.metadata.StudyMetadata(study); + const studyMetadata = new OHIF.metadata.StudyMetadata(study, study.studyInstanceUid); const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata); studyMetadata.setDisplaySets(displaySets); diff --git a/Packages/ohif-hanging-protocols/both/collections.js b/Packages/ohif-hanging-protocols/both/collections.js index a46245ed8..2262e29f8 100644 --- a/Packages/ohif-hanging-protocols/both/collections.js +++ b/Packages/ohif-hanging-protocols/both/collections.js @@ -12,3 +12,10 @@ HangingProtocols.allow({ return true; } }); + +// @TODO: Remove this after stabilizing ProtocolEngine +if (Meteor.isDevelopment && Meteor.isServer) { + Meteor.startup(() => { + HangingProtocols.remove({}); + }); +} diff --git a/Packages/ohif-hanging-protocols/both/testData.js b/Packages/ohif-hanging-protocols/both/testData.js index 2d6ac5d8c..87dba06e8 100644 --- a/Packages/ohif-hanging-protocols/both/testData.js +++ b/Packages/ohif-hanging-protocols/both/testData.js @@ -106,5 +106,1248 @@ function getMRTwoByTwoTest() { return HP.testProtocol; } +function getDemoProtocols() { + + HP.demoProtocols = []; + + /** + * Demo #1 + */ + HP.demoProtocols.push({ + "id": "demoProtocol1", + "locked": false, + "name": "DFCI-CT-CHEST-COMPARE", + "createdDate": "2017-02-14T16:07:09.033Z", + "modifiedDate": "2017-02-14T16:18:43.930Z", + "availableTo": {}, + "editableBy": {}, + "protocolMatchingRules": [ + { + "id": "7tmuq7KzDMCWFeapc", + "weight": 2, + "required": false, + "attribute": "x00081030", + "constraint": { + "contains": { + "value": "DFCI CT CHEST" + } + } + } + ], + "stages": [ + { + "id": "v5PfGt9F6mffZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [ + { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [ + { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "2.0" + } + } + } + ], + "studyMatchingRules": [] + }, + { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [ + { + "id": "ygz4nb28iJZcJhnYa", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "2.0" + } + } + } + ], + "studyMatchingRules": [ + { + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + } + ] + } + ], + "createdDate": "2017-02-14T16:07:09.033Z" + }, + { + "id": "XTzu8HB3feep3HYKs", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [ + { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [ + { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "3.0" + } + } + } + ], + "studyMatchingRules": [] + }, + { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [ + { + "id": "ygz4nb28iJZcJhnYa", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "3.0" + } + } + } + ], + "studyMatchingRules": [ + { + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + } + ] + } + ], + "createdDate": "2017-02-14T16:07:12.085Z" + }, + { + "id": "3yPYNaeFtr76Qz3jq", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 2, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [ + { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [ + { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 3.0" + } + } + } + ], + "studyMatchingRules": [] + }, + { + "viewportSettings": { + "wlPreset": "Lung" + }, + "imageMatchingRules": [], + "seriesMatchingRules": [ + { + "id": "ygz4nb28iJZcJhnYa", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Lung 3.0" + } + } + } + ], + "studyMatchingRules": [] + }, + { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [ + { + "id": "6vdBRZYnqmmosipph", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 3.0" + } + } + } + ], + "studyMatchingRules": [ + { + "id": "SxfTyhGcMhr56PtPM", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + } + ] + }, + { + "viewportSettings": { + "wlPreset": "Lung" + }, + "imageMatchingRules": [], + "seriesMatchingRules": [ + { + "id": "FTAyChZCPW68yJjXD", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Lung 3.0" + } + } + } + ], + "studyMatchingRules": [ + { + "id": "gMJjfrbsqYNbErPx5", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + } + ] + } + ], + "createdDate": "2017-02-14T16:11:40.489Z" + } + ], + "numberOfPriorsReferenced": 4 + }); + + /** + * Demo #2 + */ + + HP.demoProtocols.push({ + "id": "demoProtocol2", + "locked": false, + "name": "DFCI-CT-CHEST-COMPARE-2", + "createdDate": "2017-02-14T16:07:09.033Z", + "modifiedDate": "2017-02-14T16:18:43.930Z", + "availableTo": {}, + "editableBy": {}, + "protocolMatchingRules": [{ + "id": "7tmuq7KzDMCWFeapc", + "weight": 2, + "required": false, + "attribute": "x00081030", + "constraint": { + "contains": { + "value": "DFCI CT CHEST" + } + } + }], + "stages": [{ + "id": "v5PfGt9F6mffZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "2.0" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "ygz4nb28iJZcJhnYa", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "2.0" + } + } + }], + "studyMatchingRules": [{ + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }, { + "id": "XTzu8HB3feep3HYKs", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 3.0" + } + } + }, { + "id": "mYnsCcNwZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 5.0" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "ygz4nb28iJZcJhnYa", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 3.0" + } + } + }, { + "id": "ygz4nb29iJZcJhnYa", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 5.0" + } + } + }], + "studyMatchingRules": [{ + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }], + "createdDate": "2017-02-14T16:07:12.085Z" + }, { + "id": "3yPYNaeFtr76Qz3jq", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 2, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 3.0" + } + } + }, { + "id": "jXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 5.0" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": { + "wlPreset": "Lung" + }, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "ygz4nb28iJZcJhnYa", + "weight": 2, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Lung 3.0" + } + } + }, { + "id": "ycz4nb28iJZcJhnYa", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Lung 5.0" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "6vdBRZYnqmmosipph", + "weight": 2, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 3.0" + } + } + }, { + "id": "6vdBRFYnqmmosipph", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 5.0" + } + } + }], + "studyMatchingRules": [{ + "id": "SxfTyhGcMhr56PtPM", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }, { + "viewportSettings": { + "wlPreset": "Lung" + }, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "FTAyChZCPW68yJjXD", + "weight": 2, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Lung 3.0" + } + } + }, { + "id": "DTAyChZCPW68yJjXD", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Lung 5.0" + } + } + }], + "studyMatchingRules": [{ + "id": "gMJjfrbsqYNbErPx5", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }], + "createdDate": "2017-02-14T16:11:40.489Z" + }], + "numberOfPriorsReferenced": 1 + }); + + /** + * Demo: screenCT + */ + + HP.demoProtocols.push({ + "id": "screenCT", + "locked": false, + "name": "DFCI-CT-CHEST-SCREEN", + "createdDate": "2017-02-14T16:07:09.033Z", + "modifiedDate": "2017-02-14T16:18:43.930Z", + "availableTo": {}, + "editableBy": {}, + "protocolMatchingRules": [{ + "id": "7tmuq7KzDMCWFeapc", + "weight": 2, + "required": false, + "attribute": "x00081030", + "constraint": { + "contains": { + "value": "DFCI CT CHEST" + } + } + }], + "stages": [{ + "id": "v5PfGt9F6mffZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 1 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "2.0" + } + } + }], + "studyMatchingRules": [] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }, + { + "id": "v5PfGt9F4mffZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 2, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 5.0" + } + } + }, { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 3.0" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Lung 5.0" + } + } + }, { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Lung 3.0" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 4.0" + } + } + }, { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Coronal" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Body 4.0" + } + } + }, { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Sagittal" + } + } + }], + "studyMatchingRules": [] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }], + "numberOfPriorsReferenced": 0 + }); + + /** + * Demo: PETCTSCREEN + */ + + HP.demoProtocols.push({ + "id": "PETCTSCREEN", + "locked": false, + "name": "PETCT-SCREEN", + "createdDate": "2017-02-14T16:07:09.033Z", + "modifiedDate": "2017-02-14T16:18:43.930Z", + "availableTo": {}, + "editableBy": {}, + "protocolMatchingRules": [{ + "id": "7tmuqgKzDMCWFeapc", + "weight": 5, + "required": false, + "attribute": "x00081030", + "constraint": { + "contains": { + "value": "PETCT" + } + } + }], + "stages": [{ + "id": "v5PfGt9F6mFgZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Topogram" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Topogram" + } + } + }, { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x00200011", + "constraint": { + "numericality": { + "greaterThanOrEqualTo": 2 + } + } + }], + "studyMatchingRules": [] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }, { + "id": "v5PfGt9F6mFgZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "PET WB Corrected" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "CT WB" + } + } + }], + "studyMatchingRules": [] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }, { + "id": "v5PfGt9F6mFgZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": { + "invert": "YES" + }, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "PET WB Uncorrected" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "CT Nk" + } + } + }], + "studyMatchingRules": [] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }], + "numberOfPriorsReferenced": 0 + }); + + /** + * Demo: PETCTCOMPARE + */ + + HP.demoProtocols.push({ + "id": "PETCTCOMPARE", + "locked": false, + "name": "PETCT-COMPARE", + "createdDate": "2017-02-14T16:07:09.033Z", + "modifiedDate": "2017-02-14T16:18:43.930Z", + "availableTo": {}, + "editableBy": {}, + "protocolMatchingRules": [{ + "id": "7tmuqgKzDMCWFeapc", + "weight": 5, + "required": false, + "attribute": "x00081030", + "constraint": { + "contains": { + "value": "PETCT" + } + } + }], + "stages": [{ + "id": "v5PfGt9F6mFgZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Topogram" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Topogram" + } + } + }], + "studyMatchingRules": [{ + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }, { + "id": "v5PfGt9F6mFgZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 1, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Topogram" + } + } + }, { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x00200011", + "constraint": { + "numericality": { + "greaterThanOrEqualTo": 2 + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "Topogram" + } + } + }, { + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x00200011", + "constraint": { + "numericality": { + "greaterThanOrEqualTo": 2 + } + } + }], + "studyMatchingRules": [{ + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }, { + "id": "v5PfGt9F6mFgZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 2, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "PET WB Corrected" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "CT WB" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "PET WB Corrected" + } + } + }], + "studyMatchingRules": [{ + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "CT WB" + } + } + }], + "studyMatchingRules": [{ + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }, { + "id": "v5PfGt9F6mFgZPif5", + "name": "oneByOne", + "viewportStructure": { + "type": "grid", + "properties": { + "rows": 2, + "columns": 2 + }, + "layoutTemplateName": "gridLayout" + }, + "viewports": [{ + "viewportSettings": { + "invert": "YES" + }, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "PET WB Uncorrected" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "CT Nk" + } + } + }], + "studyMatchingRules": [] + }, { + "viewportSettings": { + "invert": "YES" + }, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "PET WB Uncorrected" + } + } + }], + "studyMatchingRules": [{ + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }, { + "viewportSettings": {}, + "imageMatchingRules": [], + "seriesMatchingRules": [{ + "id": "mXnsCcNzZL56z7mTZ", + "weight": 1, + "required": false, + "attribute": "x0008103e", + "constraint": { + "contains": { + "value": "CT Nk" + } + } + }], + "studyMatchingRules": [{ + "id": "uDoEgLTvnXTByWnPz", + "weight": 1, + "required": false, + "attribute": "abstractPriorValue", + "constraint": { + "equals": { + "value": 1 + } + } + }] + }], + "createdDate": "2017-02-14T16:07:09.033Z" + }], + "numberOfPriorsReferenced": 1 + }); + +} + getDefaultProtocol(); getMRTwoByTwoTest(); +getDemoProtocols(); diff --git a/Packages/ohif-hanging-protocols/client/protocolEngine.js b/Packages/ohif-hanging-protocols/client/protocolEngine.js index 0326322a9..66be9c773 100644 --- a/Packages/ohif-hanging-protocols/client/protocolEngine.js +++ b/Packages/ohif-hanging-protocols/client/protocolEngine.js @@ -1,5 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { _ } from 'meteor/underscore'; + // OHIF Modules import { OHIF } from 'meteor/ohif:core'; import 'meteor/ohif:viewerbase'; @@ -84,8 +85,8 @@ HP.addCustomViewportSetting = function(settingId, settingName, options, callback }; Meteor.startup(function() { - HP.addCustomViewportSetting('wlPreset', 'Window/Level Preset', Object.keys(OHIF.viewer.wlPresets), function(element, optionValue) { - if (OHIF.viewer.wlPresets.hasOwnProperty(optionValue)) { + HP.addCustomViewportSetting('wlPreset', 'Window/Level Preset', Object.create(null), function(element, optionValue) { + if (optionValue in OHIF.viewer.wlPresets) { OHIF.viewerbase.wlPresets.applyWLPreset(optionValue, element); } }); @@ -93,14 +94,15 @@ Meteor.startup(function() { /** * Match a Metadata instance against rules using Validate.js for validation. - * @param {StudyMetadata|SeriesMetadata|InstanceMetadata} metadataInstance Metadata instance object + * @param {StudySummary|InstanceMetadata} metadataInstance Metadata instance object * @param {Array} rules Array of MatchingRules instances (StudyMatchingRule|SeriesMatchingRule|ImageMatchingRule) for the match * @return {Object} Matching Object with score and details (which rule passed or failed) */ HP.match = function(metadataInstance, rules) { - if (!(metadataInstance instanceof StudyMetadata || metadataInstance instanceof SeriesMetadata || metadataInstance instanceof InstanceMetadata)) { - throw new OHIFError('HP::match metadataInstance must be an instance of StudyMetadata, SeriesMetadata or InstanceMetadata'); + // Make sure the supplied data is valid. + if (!(metadataInstance instanceof StudySummary || metadataInstance instanceof InstanceMetadata)) { + throw new OHIFError('HP::match metadataInstance must be an instance of StudySummary or InstanceMetadata'); } const options = { @@ -111,18 +113,9 @@ HP.match = function(metadataInstance, rules) { passed: [], failed: [] }; - + let requiredFailed = false; let score = 0; - let instance; - - if (metadataInstance instanceof StudyMetadata) { - instance = metadataInstance.getFirstInstance(); - } else if (metadataInstance instanceof SeriesMetadata) { - instance = metadataInstance.getInstanceByIndex(0); - } else { - instance = metadataInstance; - } rules.forEach(rule => { const attribute = rule.attribute; @@ -144,13 +137,18 @@ HP.match = function(metadataInstance, rules) { // Create a single attribute object to be validated, since metadataInstance is an // instance of Metadata (StudyMetadata, SeriesMetadata or InstanceMetadata) - const attributeValue = customAttributeExists ? metadataInstance.getCustomAttribute(attribute) : instance.getRawValue(attribute); + const attributeValue = customAttributeExists ? metadataInstance.getCustomAttribute(attribute) : metadataInstance.getTagValue(attribute); const attributeMap = { - [attribute]: attributeValue + '' + [attribute]: attributeValue }; // Use Validate.js to evaluate the constraints on the specified metadataInstance - const errorMessages = validate(attributeMap, testConstraint, [options]); + let errorMessages; + try { + errorMessages = validate(attributeMap, testConstraint, [options]); + } catch (e) { + errorMessages = [ 'Something went wrong during validation.', e ]; + } if (!errorMessages) { // If no errorMessages were returned, then validation passed. @@ -260,6 +258,7 @@ HP.ProtocolEngine = class ProtocolEngine { OHIF.log.info('ProtocolEngine::findMatchByStudy'); const matched = []; + const studyInstance = study.getFirstInstance(); HP.ProtocolStore.getProtocol().forEach(protocol => { // Clone the protocol's protocolMatchingRules array @@ -271,18 +270,26 @@ HP.ProtocolEngine = class ProtocolEngine { } // Set custom attribute for study metadata - const numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getStudyInstanceUID()); - study.setCustomAttribute('numberOfPriorsReferenced', numberOfPriorsReferenced); + const numberOfPriorsReferenced = this.getNumberOfAvailablePriors(study.getObjectID()); + studyInstance.setCustomAttribute('numberOfPriorsReferenced', numberOfPriorsReferenced); - const rule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', { + const requiredPriorsRule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', { numericality: { greaterThanOrEqualTo: protocol.numberOfPriorsReferenced } }); - rules.push(rule); + rules.push(requiredPriorsRule); - const matchedDetails = HP.match(study, rules); + const exactPriorsDesiredRule = new HP.ProtocolMatchingRule('numberOfPriorsReferenced', { + numericality: { + equalTo: protocol.numberOfPriorsReferenced + }, + }, false, 1); + + rules.push(exactPriorsDesiredRule); + + const matchedDetails = HP.match(studyInstance, rules); if (matchedDetails.score > 0) { matched.push({ @@ -366,25 +373,48 @@ HP.ProtocolEngine = class ProtocolEngine { /** * Get the number of prior studies supplied in the priorStudies map property. * - * @param {String} studyInstanceUID The StudyInstanceUID of the study whose priors are needed + * @param {String} studyObjectID The study object ID of the study whose priors are needed * @returns {number} The number of available prior studies with the same PatientID */ - getNumberOfAvailablePriors(studyInstanceUID) { - const priors = this.getAvailableStudyPriors(studyInstanceUID); + getNumberOfAvailablePriors(studyObjectID) { + const priors = this.getAvailableStudyPriors(studyObjectID); return priors.length; } /** - * Get the array prior studies from a specific study. + * Get the array of prior studies from a specific study. * - * @param {String} studyInstanceUID The StudyInstanceUID of the study whose priors are needed + * @param {String} studyObjectID The study object ID of the study whose priors are needed * @returns {Array} The array of available priors or an empty array */ - getAvailableStudyPriors(studyInstanceUID) { - const priors = this.priorStudies.get(studyInstanceUID); + getAvailableStudyPriors(studyObjectID) { + const priors = this.priorStudies.get(studyObjectID); return priors instanceof Array ? priors : []; } + /** + * Get the array of prior studies based on current protocol matching rules. + * + * @param {String} studyObjectID The study object ID of the study whose priors are needed + * @returns {Array} The array of available priors that match the given rules or an empty array + */ + getPriorsByProtocolMatchingRules(studyObjectID) { + const allPriors = this.getAvailableStudyPriors(studyObjectID); + const protocolMatchingRules = this.protocol.protocolMatchingRules; + + if (protocolMatchingRules instanceof Array && protocolMatchingRules.length > 0) { + return allPriors.filter(prior => { + if (prior instanceof StudyMetadata) { + prior = prior.getFirstInstance(); + } + const matchDetails = HP.match(prior, protocolMatchingRules); + return matchDetails.score > 0; + }); + } + + return allPriors; + } + // Match images given a list of Studies and a Viewport's image matching reqs matchImages(viewport) { OHIF.log.info('ProtocolEngine::matchImages'); @@ -393,14 +423,18 @@ HP.ProtocolEngine = class ProtocolEngine { const matchingScores = []; const currentStudy = this.studies[0]; + const firstInstance = currentStudy.getFirstInstance(); let highestStudyMatchingScore = 0; let highestSeriesMatchingScore = 0; let highestImageMatchingScore = 0; let bestMatch; - // Set custom attribute for study metadata + // Set custom attribute for study metadata and it's first instance currentStudy.setCustomAttribute('abstractPriorValue', 0); + if (firstInstance instanceof InstanceMetadata) { + firstInstance.setCustomAttribute('abstractPriorValue', 0); + } studyMatchingRules.forEach(rule => { if (rule.attribute === 'abstractPriorValue') { @@ -411,7 +445,7 @@ HP.ProtocolEngine = class ProtocolEngine { abstractPriorValue = parseInt(abstractPriorValue, 10); // TODO: Restrict or clarify validators for abstractPriorValue? - const studies = this.getAvailableStudyPriors(currentStudy.getStudyInstanceUID()); + const studies = this.getPriorsByProtocolMatchingRules(currentStudy.getObjectID()); // TODO: Revisit this later: What about two studies with the same // study date? @@ -429,10 +463,10 @@ HP.ProtocolEngine = class ProtocolEngine { return; } - const priorStudyInstanceUID = priorStudy.getStudyInstanceUID(); + const priorStudyObjectID = priorStudy.getObjectID(); // Check if study metadata is already in studies list - if (this.studies.find(study => study.getStudyInstanceUID() === priorStudyInstanceUID)) { + if (this.studies.find(study => study.getObjectID() === priorStudyObjectID)) { return; } @@ -441,6 +475,12 @@ HP.ProtocolEngine = class ProtocolEngine { // Set the custom attribute abstractPriorValue for the study metadata studyMetadata.setCustomAttribute('abstractPriorValue', abstractPriorValue); + // Also add custom attribute + const firstInstance = studyMetadata.getFirstInstance(); + if (firstInstance instanceof InstanceMetadata) { + firstInstance.setCustomAttribute('abstractPriorValue', abstractPriorValue); + } + // Insert the new study metadata this.studies.push(studyMetadata); @@ -448,14 +488,14 @@ HP.ProtocolEngine = class ProtocolEngine { this.updateViewports(); }, error => { OHIF.log.warn(error); - throw new OHIFError(`ProtocolEngine::matchImages could not get study metadata for studyInstanceUID: ${priorStudyInstanceUID}`); + throw new OHIFError(`ProtocolEngine::matchImages could not get study metadata for the Study with the following ObjectID: ${priorStudyObjectID}`); }); } // TODO: Add relative Date / time }); this.studies.forEach(study => { - const studyMatchDetails = HP.match(study, studyMatchingRules); + const studyMatchDetails = HP.match(study.getFirstInstance(), studyMatchingRules); if ((studyMatchingRules.length && !studyMatchDetails.score) || studyMatchDetails.score < highestStudyMatchingScore) { return; @@ -464,7 +504,7 @@ HP.ProtocolEngine = class ProtocolEngine { highestStudyMatchingScore = studyMatchDetails.score; study.forEachSeries(series => { - const seriesMatchDetails = HP.match(series, seriesMatchingRules); + const seriesMatchDetails = HP.match(series.getFirstInstance(), seriesMatchingRules); if ((seriesMatchingRules.length && !seriesMatchDetails.score) || seriesMatchDetails.score < highestSeriesMatchingScore) { return; @@ -478,7 +518,7 @@ HP.ProtocolEngine = class ProtocolEngine { // See https://ohiforg.atlassian.net/browse/LT-227 // sopClassUid = x00080016 // rows = x00280010 - if (!OHIF.viewerbase.isImage(instance.getRawValue('x00080016')) && !instance.getRawValue('x00280010')) { + if (!OHIF.viewerbase.isImage(instance.getTagValue('x00080016')) && !instance.getTagValue('x00280010')) { return; } @@ -509,9 +549,9 @@ HP.ProtocolEngine = class ProtocolEngine { matchDetails: matchDetails, sortingInfo: { score: totalMatchScore, - study: instance.getRawValue('x00080020') + instance.getRawValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030 - series: parseInt(instance.getRawValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011 - instance: parseInt(instance.getRawValue('x00200013')) // TODO: change for acquisitionTime InstanceNumber = x00200013 + study: instance.getTagValue('x00080020') + instance.getTagValue('x00080030'), // StudyDate = x00080020 StudyTime = x00080030 + series: parseInt(instance.getTagValue('x00200011')), // TODO: change for seriesDateTime SeriesNumber = x00200011 + instance: parseInt(instance.getTagValue('x00200013')) // TODO: change for acquisitionTime InstanceNumber = x00200013 } }; diff --git a/Packages/ohif-hanging-protocols/client/protocolStore/defaultStrategy.js b/Packages/ohif-hanging-protocols/client/protocolStore/defaultStrategy.js index 67fd4a407..dc42da2c2 100644 --- a/Packages/ohif-hanging-protocols/client/protocolStore/defaultStrategy.js +++ b/Packages/ohif-hanging-protocols/client/protocolStore/defaultStrategy.js @@ -147,10 +147,12 @@ var defaultStrategy = (function () { Meteor.startup(() => { HP.ProtocolStore.setStrategy(defaultStrategy); - HP.ProtocolStore.onReady(() => { console.log('Inserting default protocols'); HP.ProtocolStore.addProtocol(HP.defaultProtocol); //HP.ProtocolStore.addProtocol(HP.testProtocol); + HP.demoProtocols.forEach(protocol => { + HP.ProtocolStore.addProtocol(protocol); + }); }); }); \ No newline at end of file diff --git a/Packages/ohif-metadata/client/OHIFInstanceMetadata.js b/Packages/ohif-metadata/client/OHIFInstanceMetadata.js index fcc6ddfff..5c3ba72c3 100644 --- a/Packages/ohif-metadata/client/OHIFInstanceMetadata.js +++ b/Packages/ohif-metadata/client/OHIFInstanceMetadata.js @@ -8,8 +8,8 @@ export class OHIFInstanceMetadata extends InstanceMetadata { /** * @param {Object} Instance object. */ - constructor(data, series, study) { - super(data); + constructor(data, series, study, uid) { + super(data, uid); this.init(series, study); } @@ -52,7 +52,7 @@ export class OHIFInstanceMetadata extends InstanceMetadata { } // Override - getRawValue(tagOrProperty, defaultValue, bypassCache) { + getTagValue(tagOrProperty, defaultValue, bypassCache) { // check if this property has been cached... if (tagOrProperty in this._cache && bypassCache !== true) { diff --git a/Packages/ohif-metadata/client/OHIFSeriesMetadata.js b/Packages/ohif-metadata/client/OHIFSeriesMetadata.js index 1b4de4bab..8028815fa 100644 --- a/Packages/ohif-metadata/client/OHIFSeriesMetadata.js +++ b/Packages/ohif-metadata/client/OHIFSeriesMetadata.js @@ -6,8 +6,8 @@ export class OHIFSeriesMetadata extends Viewerbase.metadata.SeriesMetadata { /** * @param {Object} Series object. */ - constructor(data, study) { - super(data); + constructor(data, study, uid) { + super(data, uid); this.init(study); } diff --git a/Packages/ohif-metadata/client/OHIFStudyMetadata.js b/Packages/ohif-metadata/client/OHIFStudyMetadata.js index 1bd1ce2a8..de440beee 100644 --- a/Packages/ohif-metadata/client/OHIFStudyMetadata.js +++ b/Packages/ohif-metadata/client/OHIFStudyMetadata.js @@ -6,8 +6,8 @@ export class OHIFStudyMetadata extends Viewerbase.metadata.StudyMetadata { /** * @param {Object} Study object. */ - constructor(data) { - super(data); + constructor(data, uid) { + super(data, uid); this.init(); } diff --git a/Packages/ohif-study-list/client/lib/OHIFStudyMetadataSource.js b/Packages/ohif-study-list/client/lib/OHIFStudyMetadataSource.js index 6e282c062..c01e88b61 100644 --- a/Packages/ohif-study-list/client/lib/OHIFStudyMetadataSource.js +++ b/Packages/ohif-study-list/client/lib/OHIFStudyMetadataSource.js @@ -43,9 +43,9 @@ export class OHIFStudyMetadataSource extends OHIF.viewerbase.StudyMetadataSource return; } - this.getByInstanceUID(study.getStudyInstanceUID()).then(studyInfo => { + this.getByInstanceUID(studyInstanceUID).then(studyInfo => { // Create study metadata object - const studyMetadata = new OHIF.metadata.StudyMetadata(studyInfo); + const studyMetadata = new OHIF.metadata.StudyMetadata(studyInfo, studyInfo.studyInstanceUid); // Get Study display sets const displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata); diff --git a/Packages/ohif-study-list/client/lib/getStudyPriors.js b/Packages/ohif-study-list/client/lib/getStudyPriors.js index b035b7227..c338bcf3a 100644 --- a/Packages/ohif-study-list/client/lib/getStudyPriors.js +++ b/Packages/ohif-study-list/client/lib/getStudyPriors.js @@ -3,27 +3,30 @@ import 'meteor/ohif:viewerbase'; // Local Dependencies import { OHIFStudySummary } from './OHIFStudySummary'; -const StudyMetadata = OHIF.viewerbase.metadata.StudyMetadata; -const StudySummary = OHIF.viewerbase.metadata.StudySummary; -const PatientID = 'x00100020'; -const StudyDate = 'x00080020'; +const { StudyMetadata, StudySummary } = OHIF.viewerbase.metadata; +const PATIENT_ID = 'x00100020'; +const STUDY_DATE = 'x00080020'; +/** + * Get the priors of a given study + * @param {StudyMetadata|StudySummary} study An instance of StudyMetadata|StudySummary class to get it's priors + * @return {Array} An Array of StudySummary objects representing the study priors for the given study or an empty array if none + */ const getStudyPriors = study => { - const priorStudies = []; - let patientID; - let studyDate; - - if (study instanceof StudyMetadata) { - const instance = study.getFirstInstance(); - patientID = instance.getRawValue(PatientID); // PatientID - studyDate = instance.getRawValue(StudyDate); // StudyDate - } else if (study instanceof StudySummary) { - patientID = study.getTagValue(PatientID); // PatientID - studyDate = study.getTagValue(StudyDate); // StudyDate + if (!(study instanceof StudyMetadata) && !(study instanceof StudySummary)) { + throw new OHIF.viewerbase.OHIFError('getStudyPriors study must be an instance of StudySummary or StudyMetadata'); } - // Find prior studies in global StudyListStudies Minimongo collection... + if (study instanceof StudyMetadata) { + study = study.getFirstInstance(); + } + + const priorStudies = []; + const patientID = study.getTagValue(PATIENT_ID); // PatientID + const studyDate = study.getTagValue(STUDY_DATE); // StudyDate + + // Find prior studies in global StudyListStudies Minimongo collection const cursor = StudyListStudies.find({ patientId: patientID, studyDate: { @@ -35,10 +38,9 @@ const getStudyPriors = study => { } }); - // Create an OHIFStudySummary object for each prior study found... + // Create an OHIFStudySummary object for each prior study found cursor.forEach(study => { - const summary = new OHIFStudySummary(); - summary.addTags(study); + const summary = new OHIFStudySummary(study, null, study.studyInstanceUid); priorStudies.push(summary); }); diff --git a/Packages/ohif-study-list/client/lib/getStudyPriorsMap.js b/Packages/ohif-study-list/client/lib/getStudyPriorsMap.js index d4b062212..184b023e7 100644 --- a/Packages/ohif-study-list/client/lib/getStudyPriorsMap.js +++ b/Packages/ohif-study-list/client/lib/getStudyPriorsMap.js @@ -3,8 +3,7 @@ import 'meteor/ohif:viewerbase'; // Local Dependencies import { getStudyPriors } from './getStudyPriors'; -const StudyMetadata = OHIF.viewerbase.metadata.StudyMetadata; -const StudySummary = OHIF.viewerbase.metadata.StudySummary; +const { StudyMetadata, StudySummary } = OHIF.viewerbase.metadata; /** * Create a Map of study priors where the key of each entry is the StudyInstanceUID and its value is an array of StudySummary instances. @@ -17,10 +16,10 @@ const getStudyPriorsMap = studies => { if (studies instanceof Array) { studies.forEach(study => { if (study instanceof StudyMetadata || study instanceof StudySummary) { - const studyInstanceUID = study.getStudyInstanceUID(); - if (studyInstanceUID) { + const studyObjectID = study.getObjectID(); + if (studyObjectID) { const priors = getStudyPriors(study); - priorsMap.set(studyInstanceUID, priors); + priorsMap.set(studyObjectID, priors); } } }); diff --git a/Packages/ohif-viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js b/Packages/ohif-viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js index cb2460221..53c1f016d 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js +++ b/Packages/ohif-viewerbase/client/components/viewer/imageViewerViewport/imageViewerViewport.js @@ -130,7 +130,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => { seriesInstanceUid, displaySetInstanceUid, currentImageIdIndex, - viewport, + viewport: viewport || data.viewport, viewportIndex }; @@ -235,7 +235,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => { // Call the handler function that represents the end of the image loading phase // (e.g. hide the progress text box) - endLoadingHandler(element); + endLoadingHandler(element, image); // Remove the 'empty' class from the viewport to hide any instruction text element.classList.remove('empty'); diff --git a/Packages/ohif-viewerbase/client/lib/WLPresets.js b/Packages/ohif-viewerbase/client/lib/WLPresets.js index 10276df88..57436c04f 100644 --- a/Packages/ohif-viewerbase/client/lib/WLPresets.js +++ b/Packages/ohif-viewerbase/client/lib/WLPresets.js @@ -1,7 +1,12 @@ import { Meteor } from 'meteor/meteor'; +import { Session } from 'meteor/session'; + import { OHIF } from 'meteor/ohif:core'; import { viewportUtils } from './viewportUtils'; +const WL_PRESET_CUSTOM = 'Custom'; +const WL_PRESET_DEFAULT = 'Default'; + // TODO: add this to a namespace definition Meteor.startup(function() { OHIF.viewer.defaultWLPresets = { @@ -27,30 +32,75 @@ Meteor.startup(function() { } }; - // For now - OHIF.viewer.wlPresets = OHIF.viewer.defaultWLPresets; + setOHIFWLPresets(OHIF.viewer.defaultWLPresets); }); -function applyWLPreset(presetName, element) { - OHIF.log.info('Applying WL Preset: ' + presetName); - if (presetName !== 'Custom') { - const viewport = cornerstone.getViewport(element); +function updateElementWLPresetData(element) { + const wlPresetData = cornerstone.getElementData(element, 'wlPreset'); + const enabledElement = cornerstone.getEnabledElement(element); + const { viewport, image } = enabledElement; + const { windowCenter, windowWidth } = viewport.voi; + let presetName; - if (presetName === 'Default') { - const enabledElement = cornerstone.getEnabledElement(element); - viewport.voi.windowWidth = enabledElement.image.windowWidth; - viewport.voi.windowCenter = enabledElement.image.windowCenter; - } else { - const preset = OHIF.viewer.wlPresets[presetName]; - if(preset) { - viewport.voi.windowWidth = preset.ww; - viewport.voi.windowCenter = preset.wc; + if (windowWidth === image.windowWidth && windowCenter === image.windowCenter) { + presetName = WL_PRESET_DEFAULT; + } else { + const WLPresets = OHIF.viewer.wlPresets; + for (let name in WLPresets) { + const preset = WLPresets[name]; + if (windowCenter === preset.wc && windowWidth === preset.ww) { + presetName = name; + break; } } - - // Update the viewport - cornerstone.setViewport(element, viewport); } + + wlPresetData.name = presetName || WL_PRESET_CUSTOM; + wlPresetData.ww = windowWidth; + wlPresetData.wc = windowCenter; + + if (wlPresetData.name === WL_PRESET_CUSTOM) { + const custom = wlPresetData.custom || (wlPresetData.custom = Object.create(null)); + custom.ww = windowWidth; + custom.wc = windowCenter; + } +} + +/** + * Set specified W/L preset on given element on fallback to default W/L preset if the specified preset is not valid. + * @param {String} presetName The desired W/L preset to be applied + * @param {DOMElement} element An enabled viewport DOM Element. + */ +function applyWLPreset(presetName, element) { + const wlPresets = OHIF.viewer.wlPresets; + const wlPresetData = cornerstone.getElementData(element, 'wlPreset'); + const viewport = cornerstone.getViewport(element); + + if (presetName === WL_PRESET_CUSTOM && wlPresetData.custom) { + viewport.voi.windowWidth = wlPresetData.custom.ww; + viewport.voi.windowCenter = wlPresetData.custom.wc; + } else if (presetName in wlPresets) { + const preset = wlPresets[presetName]; + viewport.voi.windowWidth = preset.ww; + viewport.voi.windowCenter = preset.wc; + } else { + const enabledElement = cornerstone.getEnabledElement(element); + viewport.voi.windowWidth = enabledElement.image.windowWidth; + viewport.voi.windowCenter = enabledElement.image.windowCenter; + presetName = WL_PRESET_DEFAULT; + } + + wlPresetData.name = presetName; + wlPresetData.ww = viewport.voi.windowWidth; + wlPresetData.wc = viewport.voi.windowCenter; + + // Update the viewport + cornerstone.setViewport(element, viewport); + + OHIF.log.info('WLPresets::Applying WL Preset: ' + presetName); + + // Notify other components about W/L Preset changes + Session.set('OHIFWlPresetApplied', presetName); } function applyWLPresetToActiveElement(presetName) { @@ -60,9 +110,6 @@ function applyWLPresetToActiveElement(presetName) { } applyWLPreset(presetName, element); - - // To perform reactivity in other components - Session.set('WLPresetActiveElement', presetName); } /** @@ -70,7 +117,14 @@ function applyWLPresetToActiveElement(presetName) { * @param {Object} wlPresets Object with wlPresets mapping */ function setOHIFWLPresets(wlPresets) { - OHIF.viewer.wlPresets = wlPresets; + const hasOwn = Object.prototype.hasOwnProperty; + const presetMap = Object.create(null); // Objects without prototype have much faster lookup times + for (let name in wlPresets) { + if (hasOwn.call(wlPresets, name)) { + presetMap[name] = wlPresets[name]; + } + } + OHIF.viewer.wlPresets = presetMap; } /** @@ -80,7 +134,8 @@ function setOHIFWLPresets(wlPresets) { const WLPresets = { applyWLPreset, applyWLPresetToActiveElement, - setOHIFWLPresets + setOHIFWLPresets, + updateElementWLPresetData }; export { WLPresets }; diff --git a/Packages/ohif-viewerbase/client/lib/classes/metadata/InstanceMetadata.js b/Packages/ohif-viewerbase/client/lib/classes/metadata/InstanceMetadata.js index ab422214c..c1e424a85 100644 --- a/Packages/ohif-viewerbase/client/lib/classes/metadata/InstanceMetadata.js +++ b/Packages/ohif-viewerbase/client/lib/classes/metadata/InstanceMetadata.js @@ -1,14 +1,21 @@ import { Metadata } from './Metadata'; import { OHIFError } from '../OHIFError'; +/** + * ATTENTION! This class should never depend on StudyMetadata or SeriesMetadata classes as this could + * possibly cause circular dependency issues. + */ + const UNDEFINED = 'undefined'; const NUMBER = 'number'; const STRING = 'string'; +const STUDY_INSTANCE_UID = 'x0020000d'; +const SERIES_INSTANCE_UID = 'x0020000e'; export class InstanceMetadata extends Metadata { - constructor(data) { - super(data); + constructor(data, uid) { + super(data, uid); // Initialize Private Properties Object.defineProperties(this, { _sopInstanceUID: { @@ -60,6 +67,20 @@ export class InstanceMetadata extends Metadata { * Public Methods */ + /** + * Returns the StudyInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call. + */ + getStudyInstanceUID() { + return this.getTagValue(STUDY_INSTANCE_UID, null); + } + + /** + * Returns the SeriesInstanceUID of the current instance. This method is basically a shorthand the full "getTagValue" method call. + */ + getSeriesInstanceUID() { + return this.getTagValue(SERIES_INSTANCE_UID, null); + } + /** * Returns the SOPInstanceUID of the current instance. */ @@ -69,7 +90,7 @@ export class InstanceMetadata extends Metadata { // @TODO: Improve this... (E.g.: blob data) getStringValue(tagOrProperty, index, defaultValue) { - let value = this.getRawValue(tagOrProperty, defaultValue); + let value = this.getTagValue(tagOrProperty, defaultValue); if (typeof value !== STRING && typeof value !== UNDEFINED) { value = value.toString(); @@ -80,7 +101,7 @@ export class InstanceMetadata extends Metadata { // @TODO: Improve this... (E.g.: blob data) getFloatValue(tagOrProperty, index, defaultValue) { - let value = this.getRawValue(tagOrProperty, defaultValue); + let value = this.getTagValue(tagOrProperty, defaultValue); value = InstanceMetadata.getIndexedValue(value, index, defaultValue); if(value instanceof Array) { @@ -96,7 +117,7 @@ export class InstanceMetadata extends Metadata { // @TODO: Improve this... (E.g.: blob data) getIntValue(tagOrProperty, index, defaultValue) { - let value = this.getRawValue(tagOrProperty, defaultValue); + let value = this.getTagValue(tagOrProperty, defaultValue); value = InstanceMetadata.getIndexedValue(value, index, defaultValue); if(value instanceof Array) { @@ -111,13 +132,20 @@ export class InstanceMetadata extends Metadata { } /** - * + * @deprecated Please use getTagValue instead. */ getRawValue(tagOrProperty, defaultValue) { + return this.getTagValue(tagOrProperty, defaultValue); + } + + /** + * This function should be overriden by specialized classes in order to allow client libraries or viewers to take advantage of the Study Metadata API. + */ + getTagValue(tagOrProperty, defaultValue) { /** * Please override this method on a specialized class. */ - throw new OHIFError('InstanceMetadata::getRawValue is not overriden. Please, override it in a specialized class. See OHIFInstanceMetadata for example'); + throw new OHIFError('InstanceMetadata::getTagValue is not overriden. Please, override it in a specialized class. See OHIFInstanceMetadata for example'); } /** diff --git a/Packages/ohif-viewerbase/client/lib/classes/metadata/Metadata.js b/Packages/ohif-viewerbase/client/lib/classes/metadata/Metadata.js index d4c29bfd1..796c61782 100644 --- a/Packages/ohif-viewerbase/client/lib/classes/metadata/Metadata.js +++ b/Packages/ohif-viewerbase/client/lib/classes/metadata/Metadata.js @@ -8,13 +8,17 @@ const NUMBER = 'number'; const FUNCTION = 'function'; const OBJECT = 'object'; +/** + * Class Definition + */ + export class Metadata { /** * Constructor and Instance Methods */ - constructor(data) { + constructor(data, uid) { // Define the main "_data" private property as an immutable property. // IMPORTANT: This property can only be set during instance construction. Object.defineProperty(this, '_data', { @@ -24,7 +28,16 @@ export class Metadata { value: data }); - // Define "_custom" properties as an immutable property + // Define the main "_uid" private property as an immutable property. + // IMPORTANT: This property can only be set during instance construction. + Object.defineProperty(this, '_uid', { + configurable: false, + enumerable: false, + writable: false, + value: uid + }); + + // Define "_custom" properties as an immutable property. // IMPORTANT: This property can only be set during instance construction. Object.defineProperty(this, '_custom', { configurable: false, @@ -47,6 +60,13 @@ export class Metadata { return propertyValue; } + /** + * Get unique object ID + */ + getObjectID() { + return this._uid; + } + /** * Set custom attribute value * @param {String} attribute Custom attribute name @@ -74,6 +94,20 @@ export class Metadata { return attribute in this._custom; } + /** + * Set custom attributes in batch mode. + * @param {Object} attributeMap An object whose own properties will be used as custom attributes. + */ + setCustomAttributes(attributeMap) { + const _hasOwn = Object.prototype.hasOwnProperty; + const _custom = this._custom; + for (let attribute in attributeMap) { + if (_hasOwn.call(attributeMap, attribute)) { + _custom[attribute] = attributeMap[attribute]; + } + } + } + /** * Static Methods */ diff --git a/Packages/ohif-viewerbase/client/lib/classes/metadata/SeriesMetadata.js b/Packages/ohif-viewerbase/client/lib/classes/metadata/SeriesMetadata.js index d2f561a10..9d574e89c 100644 --- a/Packages/ohif-viewerbase/client/lib/classes/metadata/SeriesMetadata.js +++ b/Packages/ohif-viewerbase/client/lib/classes/metadata/SeriesMetadata.js @@ -3,8 +3,8 @@ import { InstanceMetadata } from './InstanceMetadata'; export class SeriesMetadata extends Metadata { - constructor(data) { - super(data); + constructor(data, uid) { + super(data, uid); // Initialize Private Properties Object.defineProperties(this, { _seriesInstanceUID: { @@ -18,6 +18,12 @@ export class SeriesMetadata extends Metadata { enumerable: false, writable: false, value: [] + }, + _firstInstance: { + configurable: false, + enumerable: false, + writable: true, + value: null } }); // Initialize Public Properties @@ -77,6 +83,23 @@ export class SeriesMetadata extends Metadata { return result; } + /** + * Get the first instance of the current series retaining a consistent result across multiple calls. + * @return {InstanceMetadata} An instance of the InstanceMetadata class or null if it does not exist. + */ + getFirstInstance() { + let instance = this._firstInstance; + if (!(instance instanceof InstanceMetadata)) { + instance = null; + const found = this.getInstanceByIndex(0); + if (found instanceof InstanceMetadata) { + this._firstInstance = found; + instance = found; + } + } + return instance; + } + /** * Find an instance by index. * @param {number} index An integer representing a list index. diff --git a/Packages/ohif-viewerbase/client/lib/classes/metadata/StudyMetadata.js b/Packages/ohif-viewerbase/client/lib/classes/metadata/StudyMetadata.js index f43f45c64..0e55ca076 100644 --- a/Packages/ohif-viewerbase/client/lib/classes/metadata/StudyMetadata.js +++ b/Packages/ohif-viewerbase/client/lib/classes/metadata/StudyMetadata.js @@ -1,12 +1,13 @@ import { Metadata } from './Metadata'; import { SeriesMetadata } from './SeriesMetadata'; +import { InstanceMetadata } from './InstanceMetadata'; import { ImageSet } from '../ImageSet'; import { OHIFError } from '../OHIFError'; export class StudyMetadata extends Metadata { - constructor(data) { - super(data); + constructor(data, uid) { + super(data, uid); // Initialize Private Properties Object.defineProperties(this, { _studyInstanceUID: { @@ -26,6 +27,18 @@ export class StudyMetadata extends Metadata { enumerable: false, writable: false, value: [] + }, + _firstSeries: { + configurable: false, + enumerable: false, + writable: true, + value: null + }, + _firstInstance: { + configurable: false, + enumerable: false, + writable: true, + value: null } }); // Initialize Public Properties @@ -232,23 +245,13 @@ export class StudyMetadata extends Metadata { /** * It sorts the series based on display sets order. Each series must be an instance - * of SeriesMetadata and each display sets must be an instance of ImageSet. - * Both array must have the same length, otherwise it throws an error. + * of SeriesMetadata and each display sets must be an instance of ImageSet. * Useful example of usage: * Study data provided by backend does not sort series at all and client-side * needs series sorted by the same criteria used for sorting display sets. */ sortSeriesByDisplaySets() { - // Check if the study is multiframe. If it is, - const firstInstance = this.getFirstInstance(); - const isMultiframe = firstInstance.getRawValue('x00280008') > 1; - // @TODO: Check if is necessary to do a similar check for multiframe studies - // Check if both arrays have same length in case of non-multiframe studies - if (!isMultiframe && this.getSeriesCount() !== this.getDisplaySetCount()) { - throw new OHIFError('StudyMetadata::sortSeriesByDisplaySets series count and display set count does not match'); - } - // Object for mapping display sets' index by seriesInstanceUid const displaySetsMapping = {}; @@ -299,18 +302,40 @@ export class StudyMetadata extends Metadata { } /** - * Get first instance of the first series - * @return {InstanceMetadata} InstanceMetadata class object or undefined if it doenst exist + * Get the first series of the current study retaining a consistent result across multiple calls. + * @return {SeriesMetadata} An instance of the SeriesMetadata class or null if it does not exist. + */ + getFirstSeries() { + let series = this._firstSeries; + if (!(series instanceof SeriesMetadata)) { + series = null; + const found = this.getSeriesByIndex(0); + if (found instanceof SeriesMetadata) { + this._firstSeries = found; + series = found; + } + } + return series; + } + + /** + * Get the first instance of the current study retaining a consistent result across multiple calls. + * @return {InstanceMetadata} An instance of the InstanceMetadata class or null if it does not exist. */ getFirstInstance() { - let firstInstance; - const firstSeries = this.getSeriesByIndex(0); - - if (firstSeries) { - firstInstance = firstSeries.getInstanceByIndex(0); + let instance = this._firstInstance; + if (!(instance instanceof InstanceMetadata)) { + instance = null; + const firstSeries = this.getFirstSeries(); + if (firstSeries instanceof SeriesMetadata) { + const found = firstSeries.getFirstInstance(); + if (found instanceof InstanceMetadata) { + this._firstInstance = found; + instance = found; + } + } } - - return firstInstance; + return instance; } } diff --git a/Packages/ohif-viewerbase/client/lib/classes/metadata/StudySummary.js b/Packages/ohif-viewerbase/client/lib/classes/metadata/StudySummary.js index 8bef2a40a..c88f0ccb2 100644 --- a/Packages/ohif-viewerbase/client/lib/classes/metadata/StudySummary.js +++ b/Packages/ohif-viewerbase/client/lib/classes/metadata/StudySummary.js @@ -1,26 +1,24 @@ +import { Metadata } from './Metadata'; +import { OHIFError } from '../OHIFError'; import { DICOMTagDescriptions } from '../../DICOMTagDescriptions'; -const StudyInstanceUID = 'x0020000d'; +/** + * Constants + */ -export class StudySummary { +const STUDY_INSTANCE_UID = 'x0020000d'; - constructor(tagMap, propertyMap) { +/** + * Class Definition + */ - // Define the main immutable "_data" private property. - Object.defineProperties(this, { - _tags: { - configurable: false, - enumerable: false, - writable: false, - value: Object.create(null) - }, - _properties: { - configurable: false, - enumerable: false, - writable: false, - value: Object.create(null) - } - }); +export class StudySummary extends Metadata { + + constructor(tagMap, attributeMap, uid) { + + // Call the superclass constructor passing an plain object with no prototype to be used as the main "_data" attribute. + const _data = Object.create(null); + super(_data, uid); // Initialize internal tag map if first argument is given. if (tagMap !== void 0) { @@ -28,67 +26,53 @@ export class StudySummary { } // Initialize internal property map if second argument is given. - if (propertyMap !== void 0) { - this.addProperties(addProperties); + if (attributeMap !== void 0) { + this.setCustomAttributes(attributeMap); } } getStudyInstanceUID() { // This method should return null if StudyInstanceUID is not available to keep compatibility StudyMetadata API - return this.getTagValue(StudyInstanceUID) || null; - } - - addProperties(propertyMap) { - const _hasOwn = Object.prototype.hasOwnProperty; - const _properties = this._properties; - for (let property in propertyMap) { - if (_hasOwn.call(propertyMap, property)) { - _properties[property] = propertyMap[property]; - } - } + return this.getTagValue(STUDY_INSTANCE_UID) || null; } + /** + * Append tags to internal tag map. + * @param {Object} tagMap An object whose own properties will be used as tag values and appended to internal tag map. + */ addTags(tagMap) { const _hasOwn = Object.prototype.hasOwnProperty; - const _tags = this._tags; + const _data = this._data; for (let tag in tagMap) { if (_hasOwn.call(tagMap, tag)) { const description = DICOMTagDescriptions.find(tag); // When a description is available, use its tag as internal key... if (description) { - _tags[description.tag] = tagMap[tag]; + _data[description.tag] = tagMap[tag]; } else { - _tags[tag] = tagMap[tag]; + _data[tag] = tagMap[tag]; } } } } tagExists(tagName) { - const _tags = this._tags; + const _data = this._data; const description = DICOMTagDescriptions.find(tagName); if (description) { - return (description.tag in _tags); + return (description.tag in _data); } - return (tagName in _tags); + return (tagName in _data); } getTagValue(tagName) { - const _tags = this._tags; + const _data = this._data; const description = DICOMTagDescriptions.find(tagName); if (description) { - return _tags[description.tag]; + return _data[description.tag]; } - return _tags[tagName]; - } - - propertyExists(propertyName) { - return (propertyName in this._properties); - } - - getPropertyValue(propertyName) { - return this._properties[propertyName]; + return _data[tagName]; } }