feat(STOW-RS) Save DICOM Structured Reports from Measurement Table back to archive (#236)
* Small refactor into Table Class setting events as static object into class * feature(STOW-SR) : Initial changes to stow when we have Server. * Improvements into requests and small fix into style * Small fix for jumpToRow without refheshing page * Handling STOW-SR once we don't have server * feature(STOW-SR) Refactor * Merge request with makeRequest and delete old file * Expose wadoproxy to client and update measurement-table to start using wadoproxy.getServerUrl * Move WADOProxy.getServerUrl to Measurement-table package. Fix querystring issue. * Remove proxy:enabled in favour of requestOptions.requestFromBrowser * Bcrypt 2.0.0 to fix Travis
This commit is contained in:
parent
13e4dc25e1
commit
26b14f1dab
@ -4,7 +4,7 @@ import { Template } from 'meteor/templating';
|
||||
import { ReactiveDict } from 'meteor/reactive-dict';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { MeasurementTable, measurementEvents } from 'meteor/ohif:measurement-table';
|
||||
import { MeasurementTable } from 'meteor/ohif:measurement-table';
|
||||
|
||||
import 'meteor/ohif:cornerstone';
|
||||
import 'meteor/ohif:viewerbase';
|
||||
@ -168,7 +168,7 @@ Template.viewer.onRendered(function() {
|
||||
Template.viewer.events( Object.assign({
|
||||
// Viewer Events
|
||||
},
|
||||
measurementEvents
|
||||
MeasurementTable.measurementEvents
|
||||
));
|
||||
|
||||
Template.viewer.onDestroyed(function() {
|
||||
|
||||
440
OHIFViewer/package-lock.json
generated
440
OHIFViewer/package-lock.json
generated
@ -34,6 +34,436 @@
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"bcrypt": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.0.tgz",
|
||||
"integrity": "sha512-gjicxsD4e5U3nH0EqiEb5y+fKpsZ7F52wcnmNfu45nxnolWVAYh7NgbdfilY+5x1v6cLspxmzz4hf+ju2pFxhA==",
|
||||
"requires": {
|
||||
"nan": "2.10.0",
|
||||
"node-pre-gyp": "0.10.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
"bundled": true
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.5",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^2.0.6"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"bundled": true
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"bundled": true
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "1.2.5",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"minipass": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"aproba": "^1.0.3",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"wide-align": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.2",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.23",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ignore-walk": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"bundled": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.3",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"minipass": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.2.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"debug": "^2.1.2",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.10.2",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"detect-libc": "^1.0.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"needle": "^2.2.0",
|
||||
"nopt": "^4.0.1",
|
||||
"npm-packlist": "^1.1.6",
|
||||
"npmlog": "^4.0.2",
|
||||
"rc": "^1.2.7",
|
||||
"rimraf": "^2.6.1",
|
||||
"semver": "^5.3.0",
|
||||
"tar": "^4"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "4.0.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"npm-bundled": {
|
||||
"version": "1.0.3",
|
||||
"bundled": true
|
||||
},
|
||||
"npm-packlist": {
|
||||
"version": "1.1.10",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"ignore-walk": "^3.0.1",
|
||||
"npm-bundled": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"npmlog": {
|
||||
"version": "4.1.2",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"are-we-there-yet": "~1.1.2",
|
||||
"console-control-strings": "~1.1.0",
|
||||
"gauge": "~2.7.3",
|
||||
"set-blocking": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"bundled": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"osenv": {
|
||||
"version": "0.1.5",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"os-homedir": "^1.0.0",
|
||||
"os-tmpdir": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.0.3",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.2",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"glob": "^7.0.5"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"bundled": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"bundled": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"bundled": true
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"chownr": "^1.0.1",
|
||||
"fs-minipass": "^1.2.5",
|
||||
"minipass": "^2.3.3",
|
||||
"minizlib": "^1.1.0",
|
||||
"mkdirp": "^0.5.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.3",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"string-width": "^1.0.2 || 2"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -228,6 +658,11 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@ -298,6 +733,11 @@
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.0.0-beta.51",
|
||||
"bcrypt": "^2.0.0",
|
||||
"loglevel": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -29,6 +29,14 @@ async function makeRequest(url, options) {
|
||||
});
|
||||
}
|
||||
|
||||
if(options.method) {
|
||||
requestOpt.method = options.method;
|
||||
}
|
||||
|
||||
if (options.method === 'POST' && options.body) {
|
||||
requestOpt.body = options.body;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// TODO: Kept getting weird build errors from RegExp
|
||||
const isAbsolute = parsed.href.indexOf('http://') === 0 || parsed.href.indexOf('https://') === 0;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { handleSR, getLatestSRSeries } from "../utils/handleSR";
|
||||
import { retrieveMeasurementFromSR, stowSRFromMeasurements } from '../utils/handleSR';
|
||||
import { getLatestSRSeries } from '../utils/srUtils';
|
||||
|
||||
export const retrieveMeasurements = (patientId, timepointIds) => {
|
||||
OHIF.log.info('retrieveMeasurements');
|
||||
@ -8,7 +9,7 @@ export const retrieveMeasurements = (patientId, timepointIds) => {
|
||||
|
||||
if(!latestSeries) return Promise.resolve({});
|
||||
|
||||
return handleSR(latestSeries).then((value) => {
|
||||
return retrieveMeasurementFromSR(latestSeries).then((value) => {
|
||||
return {
|
||||
length: value
|
||||
}
|
||||
@ -17,11 +18,17 @@ export const retrieveMeasurements = (patientId, timepointIds) => {
|
||||
|
||||
export const storeMeasurements = (measurementData, timepointIds) => {
|
||||
OHIF.log.info('storeMeasurements');
|
||||
|
||||
const server = OHIF.servers.getCurrentServer();
|
||||
if (!server || server.type !== 'dicomWeb') {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
// Here is where we should do any required data transformation and API calls
|
||||
const studyInstanceUid = measurementData[Object.keys(measurementData)[0]][0].studyInstanceUid
|
||||
|
||||
// TODO: Write SR, STOW back to PACS
|
||||
return Promise.resolve();
|
||||
return stowSRFromMeasurements(measurementData).then( () => {
|
||||
OHIF.studies.deleteStudyMetadataPromise(studyInstanceUid);
|
||||
});
|
||||
};
|
||||
|
||||
export const retrieveTimepoints = filter => {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { Session } from 'meteor/session';
|
||||
import { configureApis } from './configuration/configuration'
|
||||
import { $ } from 'meteor/jquery';
|
||||
|
||||
class MeasurementTable {
|
||||
constructor() {
|
||||
@ -9,8 +8,6 @@ class MeasurementTable {
|
||||
|
||||
Session.set('TimepointsReady', false);
|
||||
Session.set('MeasurementsReady', false);
|
||||
|
||||
this.firstMeasurementActivated = false;
|
||||
}
|
||||
|
||||
async onCreated(instance) {
|
||||
@ -38,6 +35,8 @@ class MeasurementTable {
|
||||
|
||||
measurementApi.syncMeasurementsAndToolData();
|
||||
|
||||
this.firstMeasurementActivated = false;
|
||||
|
||||
instance.autorun(() => {
|
||||
if (!Session.get('TimepointsReady') ||
|
||||
!Session.get('MeasurementsReady') ||
|
||||
@ -100,26 +99,25 @@ class MeasurementTable {
|
||||
|
||||
this.firstMeasurementActivated = true;
|
||||
}
|
||||
};
|
||||
|
||||
const measurementEvents = {
|
||||
'cornerstonetoolsmeasurementadded .imageViewerViewport'(event, instance) {
|
||||
const originalEvent = event.originalEvent;
|
||||
OHIF.measurements.MeasurementHandlers.onAdded(originalEvent, instance);
|
||||
},
|
||||
|
||||
'cornerstonetoolsmeasurementmodified .imageViewerViewport'(event, instance) {
|
||||
const originalEvent = event.originalEvent;
|
||||
instance.measurementModifiedHandler(originalEvent, instance);
|
||||
},
|
||||
|
||||
'cornerstonemeasurementremoved .imageViewerViewport'(event, instance) {
|
||||
const originalEvent = event.originalEvent;
|
||||
OHIF.measurements.MeasurementHandlers.onRemoved(originalEvent, instance);
|
||||
}
|
||||
|
||||
static measurementEvents = {
|
||||
'cornerstonetoolsmeasurementadded .imageViewerViewport'(event, instance) {
|
||||
const originalEvent = event.originalEvent;
|
||||
OHIF.measurements.MeasurementHandlers.onAdded(originalEvent, instance);
|
||||
},
|
||||
|
||||
'cornerstonetoolsmeasurementmodified .imageViewerViewport'(event, instance) {
|
||||
const originalEvent = event.originalEvent;
|
||||
instance.measurementModifiedHandler(originalEvent, instance);
|
||||
},
|
||||
|
||||
'cornerstonemeasurementremoved .imageViewerViewport'(event, instance) {
|
||||
const originalEvent = event.originalEvent;
|
||||
OHIF.measurements.MeasurementHandlers.onRemoved(originalEvent, instance);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
MeasurementTable,
|
||||
measurementEvents
|
||||
MeasurementTable
|
||||
}
|
||||
|
||||
@ -1,26 +1,8 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { cornerstone } from 'meteor/ohif:cornerstone';
|
||||
import { getInstanceMetadata } from './srUtils';
|
||||
|
||||
function getInstanceMetadata (displaySets, sopInstanceUid) {
|
||||
let instance;
|
||||
|
||||
// Use Array.some so that this loop stops when the internal loop
|
||||
// has found the correct instance
|
||||
displaySets.some(displaySet => {
|
||||
// Search the display set to find the instance metadata for
|
||||
return displaySet.images.find(instanceMetadata => {
|
||||
if (instanceMetadata._sopInstanceUID === sopInstanceUid) {
|
||||
instance = instanceMetadata;
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
export default function getLengthMeasurementData(lengthMeasurementContent, displaySets) {
|
||||
export default getLengthMeasurementData = (lengthMeasurementContent, displaySets) => {
|
||||
let lengthStates = [];
|
||||
|
||||
lengthMeasurementContent.forEach(groupItemContent => {
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
export default measurementGroupContentItem = (measurement, sopClassUid) => {
|
||||
const {
|
||||
handles,
|
||||
length,
|
||||
frameIndex,
|
||||
sopInstanceUid
|
||||
} = measurement;
|
||||
const measurementContentItem = [
|
||||
{
|
||||
RelationshipType: 'HAS OBS CONTEXT',
|
||||
ValueType: 'TEXT',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '112039',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Tracking Identifier',
|
||||
},
|
||||
TextValue: 'web annotation',
|
||||
},
|
||||
{
|
||||
RelationshipType: 'HAS OBS CONTEXT',
|
||||
ValueType: 'UIDREF',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '112040',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Tracking Unique Identifier',
|
||||
},
|
||||
UID: dcmjs.data.DicomMetaDictionary.uid(),
|
||||
},
|
||||
{
|
||||
RelationshipType: 'CONTAINS',
|
||||
ValueType: 'CODE',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '121071',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Finding',
|
||||
},
|
||||
ConceptCodeSequence: {
|
||||
CodeValue: 'SAMPLEFINDING',
|
||||
CodingSchemeDesignator: '99dcmjs',
|
||||
CodeMeaning: 'Sample Finding',
|
||||
},
|
||||
},
|
||||
{
|
||||
RelationshipType: 'CONTAINS',
|
||||
ValueType: 'NUM',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: 'G-D7FE',
|
||||
CodingSchemeDesignator: 'SRT',
|
||||
CodeMeaning: 'Length',
|
||||
},
|
||||
MeasuredValueSequence: {
|
||||
MeasurementUnitsCodeSequence: {
|
||||
CodeValue: 'mm',
|
||||
CodingSchemeDesignator: 'UCUM',
|
||||
CodingSchemeVersion: '1.4',
|
||||
CodeMeaning: 'millimeter',
|
||||
},
|
||||
NumericValue: length,
|
||||
},
|
||||
ContentSequence: {
|
||||
RelationshipType: 'INFERRED FROM',
|
||||
ValueType: 'SCOORD',
|
||||
GraphicType: 'POLYLINE',
|
||||
GraphicData: [ handles.start.x, handles.start.y, handles.end.x, handles.end.y ],
|
||||
ContentSequence: {
|
||||
RelationshipType: 'SELECTED FROM',
|
||||
ValueType: 'IMAGE',
|
||||
ReferencedSOPSequence: {
|
||||
ReferencedSOPClassUID: sopClassUid,
|
||||
ReferencedSOPInstanceUID: sopInstanceUid,
|
||||
ReferencedFrameNumber: frameIndex,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return measurementContentItem;
|
||||
};
|
||||
@ -1,100 +1,63 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { dcmjs } from 'meteor/ohif:cornerstone';
|
||||
import getLengthMeasurementData from './getLengthMeasurementData';
|
||||
import retrieveDataFromSR from './retrieveDataFromSR';
|
||||
import retrieveDataFromMeasurements from './retrieveDataFromMeasurements';
|
||||
import {
|
||||
multipartEncode
|
||||
} from './srUtils';
|
||||
|
||||
const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22'];
|
||||
|
||||
const toArray = function(x) {
|
||||
return (x.constructor.name === "Array" ? x : [x]);
|
||||
};
|
||||
|
||||
const codeMeaningEquals = (codeMeaningName) => {
|
||||
return (contentItem) => {
|
||||
return contentItem.ConceptNameCodeSequence.CodeMeaning === codeMeaningName;
|
||||
};
|
||||
};
|
||||
|
||||
const imagingMeasurementsToMeasurementData = (dataset, displaySets) => {
|
||||
// Identify the Imaging Measurements
|
||||
const imagingMeasurementContent = toArray(dataset.ContentSequence).find(codeMeaningEquals("Imaging Measurements"));
|
||||
|
||||
// Retrieve the Measurements themselves
|
||||
const measurementGroupContent = toArray(imagingMeasurementContent.ContentSequence).find(codeMeaningEquals("Measurement Group"));
|
||||
|
||||
// For now, bail out if the dataset is not a TID1500 SR with length measurements
|
||||
// TODO: generalize to the various kinds of report
|
||||
// TODO: generalize to the kinds of measurements the Viewer supports
|
||||
if (dataset.ContentTemplateSequence.TemplateIdentifier !== "1500") {
|
||||
OHIF.log.warn("This package can currently only interpret DICOM SR TID 1500");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Filter to find Length measurements in the Structured Report
|
||||
const lengthMeasurementContent = toArray(measurementGroupContent.ContentSequence).filter(codeMeaningEquals("Length"));
|
||||
|
||||
// Retrieve Length Measurement Data
|
||||
return getLengthMeasurementData(lengthMeasurementContent, displaySets);
|
||||
};
|
||||
|
||||
const retrieveDataFromSR = (Part10SRArrayBuffer) => {
|
||||
const allStudies = OHIF.viewer.Studies.all();
|
||||
let allDisplaySets = [];
|
||||
|
||||
allStudies.forEach(study => {
|
||||
allDisplaySets = allDisplaySets.concat(study.displaySets);
|
||||
});
|
||||
|
||||
// get the dicom data as an Object
|
||||
let dicomData = dcmjs.data.DicomMessage.readFile(Part10SRArrayBuffer);
|
||||
let dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
||||
|
||||
// Convert the SR into the kind of object the Measurements package is expecting
|
||||
return imagingMeasurementsToMeasurementData(dataset, allDisplaySets);
|
||||
};
|
||||
|
||||
export const getLatestSRSeries = () => {
|
||||
const allStudies = OHIF.viewer.StudyMetadataList.all();
|
||||
let latestSeries;
|
||||
|
||||
allStudies.forEach(study => {
|
||||
study.getSeries().forEach(series => {
|
||||
const firstInstance = series.getFirstInstance();
|
||||
const sopClassUid = firstInstance._instance.sopClassUid;
|
||||
|
||||
if (supportedSopClassUIDs.includes(sopClassUid)) {
|
||||
if(!latestSeries) {
|
||||
latestSeries = series;
|
||||
} else if (series._data.seriesDate > latestSeries._data.seriesDate ||
|
||||
(series._data.seriesDate === latestSeries._data.seriesDate && series._data.seriesTime > latestSeries._data.seriesTime)) {
|
||||
latestSeries = series;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return latestSeries;
|
||||
};
|
||||
|
||||
export const handleSR = (series) => {
|
||||
const retrieveMeasurementFromSR = async (series) => {
|
||||
const instance = series.getFirstInstance();
|
||||
const options = {
|
||||
method: 'GET',
|
||||
responseType: 'arraybuffer',
|
||||
};
|
||||
const url = instance.getDataProperty('wadouri');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = new XMLHttpRequest();
|
||||
|
||||
request.responseType = 'arraybuffer';
|
||||
request.open('GET', instance.getDataProperty('wadouri'));
|
||||
|
||||
request.onload = function (progressEvent) {
|
||||
const data = retrieveDataFromSR(progressEvent.currentTarget.response);
|
||||
|
||||
resolve(data);
|
||||
};
|
||||
|
||||
request.onerror = function(error) {
|
||||
reject(error);
|
||||
};
|
||||
|
||||
request.send();
|
||||
});
|
||||
try {
|
||||
const result = await DICOMWeb.makeRequest(url, options);
|
||||
const data = await result.arrayBuffer();
|
||||
const measurementData = retrieveDataFromSR(data);
|
||||
return Promise.resolve(measurementData);
|
||||
} catch(error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the DICOMWeb STOW-RS URL for the current server.
|
||||
*
|
||||
* See ftp://medical.nema.org/medical/dicom/current/output/html/part18.html#sect_6.6.1.1
|
||||
*
|
||||
* @return {string|null} The URL, rerouted to the proxy if the WADOProxy is enabled.
|
||||
*/
|
||||
function getStowRsUrl() {
|
||||
const server = OHIF.servers.getCurrentServer();
|
||||
const url = `${server.wadoRoot}/studies`;
|
||||
|
||||
return WADOProxy.convertURL(url, server);
|
||||
}
|
||||
|
||||
const stowSRFromMeasurements = async (measurements) => {
|
||||
const serverUrl = getStowRsUrl();
|
||||
const reportDataset = retrieveDataFromMeasurements(measurements);
|
||||
const boundary = dcmjs.data.DicomMetaDictionary.uid();
|
||||
const options = {
|
||||
method: 'POST',
|
||||
body: multipartEncode(reportDataset, boundary),
|
||||
headers: {
|
||||
'Content-Type': `multipart/related; type=application/dicom; boundary=${boundary}`
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await DICOMWeb.makeRequest(serverUrl, options);
|
||||
return Promise.resolve();
|
||||
} catch(error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
retrieveMeasurementFromSR,
|
||||
stowSRFromMeasurements
|
||||
}
|
||||
|
||||
@ -0,0 +1,199 @@
|
||||
import { dcmjs } from 'meteor/ohif:cornerstone';
|
||||
import { getInstanceMetadata, getAllDisplaySets } from './srUtils';
|
||||
import getLengthMeasurementGroupContentItem from './getLengthMeasurementGroupContentItem';
|
||||
|
||||
|
||||
export default retrieveDataFromMeasurements = (measurements) => {
|
||||
|
||||
const { studyInstanceUid, seriesInstanceUid, sopInstanceUid } = measurements.allTools[0];
|
||||
|
||||
const displaySets = getAllDisplaySets();
|
||||
const instanceMetadata = getInstanceMetadata(displaySets, sopInstanceUid);
|
||||
const sopClassUid = instanceMetadata._data.sopClassUid;
|
||||
|
||||
// TODO: figure out what is needed to make a dcmjs dataset from
|
||||
// information available in the viewer. Apparently the raw dicom is not available
|
||||
// directly.
|
||||
const derivationSourceDataset = {
|
||||
StudyInstanceUID: studyInstanceUid,
|
||||
SeriesInstanceUID: seriesInstanceUid,
|
||||
SOPInstanceUID: sopInstanceUid,
|
||||
SOPClassUID: sopClassUid
|
||||
};
|
||||
report = new dcmjs.derivations.StructuredReport([derivationSourceDataset]);
|
||||
dataset = report.dataset;
|
||||
|
||||
// TODO: what is the correct metaheader
|
||||
// http://dicom.nema.org/medical/Dicom/current/output/chtml/part10/chapter_7.html
|
||||
// TODO: move meta creation to dcmjs
|
||||
const fileMetaInformationVersionArray = new Uint16Array(1);
|
||||
fileMetaInformationVersionArray[0] = 1;
|
||||
dataset._meta = {
|
||||
FileMetaInformationVersion: fileMetaInformationVersionArray.buffer,
|
||||
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
||||
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
||||
TransferSyntaxUID: "1.2.840.10008.1.2.1", // Explicit little endian (always for dcmjs?)
|
||||
ImplementationClassUID: dcmjs.data.DicomMetaDictionary.uid(), // TODO: could be git hash or other valid id
|
||||
ImplementationVersionName: "OHIFViewer"
|
||||
};
|
||||
|
||||
// TODO: factor out a lot of this back into dcmjs for use in both
|
||||
// the example and in the Viewer
|
||||
const measurementGroupContentSequence = [];
|
||||
for (measurement of measurements.allTools) {
|
||||
if (measurement.toolType === 'length') {
|
||||
measurementGroupContentSequence.push.apply(measurementGroupContentSequence, getLengthMeasurementGroupContentItem(measurement, sopClassUid));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make a TID1550 derivation as an SR subclass
|
||||
dataset = Object.assign(dataset, {
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '126000',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Imaging Measurement Report'
|
||||
},
|
||||
ContinuityOfContent: 'SEPARATE',
|
||||
PerformedProcedureCodeSequence: [],
|
||||
CompletionFlag: 'COMPLETE',
|
||||
VerificationFlag: 'UNVERIFIED',
|
||||
ReferencedPerformedProcedureStepSequence: [],
|
||||
InstanceNumber: 1,
|
||||
CurrentRequestedProcedureEvidenceSequence: {
|
||||
StudyInstanceUID: dataset.StudyInstanceUID,
|
||||
ReferencedSeriesSequence: {
|
||||
SeriesInstanceUID: dataset.SeriesInstanceUID,
|
||||
ReferencedSOPSequence: {
|
||||
ReferencedSOPClassUID: derivationSourceDataset.SOPClassUID,
|
||||
ReferencedSOPInstanceUID: derivationSourceDataset.SOPInstanceUID
|
||||
},
|
||||
},
|
||||
},
|
||||
CodingSchemeIdentificationSequence: {
|
||||
CodingSchemeDesignator: "99dcmjs",
|
||||
CodingSchemeName: "Codes used for dcmjs",
|
||||
CodingSchemeVersion: "0",
|
||||
CodingSchemeResponsibleOrganization: "https://github.com/pieper/dcmjs"
|
||||
},
|
||||
ContentTemplateSequence: {
|
||||
MappingResource: 'DCMR',
|
||||
TemplateIdentifier: '1500'
|
||||
},
|
||||
ContentSequence: [
|
||||
{
|
||||
RelationshipType: 'HAS CONCEPT MOD',
|
||||
ValueType: 'CODE',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '121049',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Language of Content Item and Descendants',
|
||||
},
|
||||
ConceptCodeSequence: {
|
||||
CodeValue: 'eng',
|
||||
CodingSchemeDesignator: 'RFC3066',
|
||||
CodeMeaning: 'English',
|
||||
},
|
||||
ContentSequence: {
|
||||
RelationshipType: 'HAS CONCEPT MOD',
|
||||
ValueType: 'CODE',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '121046',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Country of Language',
|
||||
},
|
||||
ConceptCodeSequence: {
|
||||
CodeValue: 'US',
|
||||
CodingSchemeDesignator: 'ISO3166_1',
|
||||
CodeMeaning: 'United States',
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
RelationshipType: 'HAS OBS CONTEXT',
|
||||
ValueType: 'PNAME',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '121008',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Person Observer Name',
|
||||
},
|
||||
PersonName: 'user^web' // TODO: these can be options argument for constructor
|
||||
},
|
||||
{
|
||||
RelationshipType: 'HAS OBS CONTEXT',
|
||||
ValueType: 'TEXT',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: 'RP-100006',
|
||||
CodingSchemeDesignator: '99dcmjs',
|
||||
CodeMeaning: "Person Observer's Login Name"
|
||||
},
|
||||
TextValue: 'user'
|
||||
},
|
||||
{
|
||||
RelationshipType: 'HAS CONCEPT MOD',
|
||||
ValueType: 'CODE',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '121058',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Procedure reported'
|
||||
},
|
||||
ConceptCodeSequence: {
|
||||
CodeValue: '1',
|
||||
CodingSchemeDesignator: '99dcmjs',
|
||||
CodeMeaning: 'Unknown procedure'
|
||||
},
|
||||
},
|
||||
{
|
||||
RelationshipType: 'CONTAINS',
|
||||
ValueType: 'CONTAINER',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '111028',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Image Library',
|
||||
},
|
||||
ContinuityOfContent: 'SEPARATE',
|
||||
ContentSequence: {
|
||||
RelationshipType: 'CONTAINS',
|
||||
ValueType: 'CONTAINER',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '126200',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Image Library Group',
|
||||
},
|
||||
ContinuityOfContent: 'SEPARATE',
|
||||
ContentSequence: {
|
||||
RelationshipType: 'CONTAINS',
|
||||
ValueType: 'IMAGE',
|
||||
ReferencedSOPSequence: {
|
||||
// TODO: this should refer to the UIDs extracted from the toolState / instance info
|
||||
ReferencedSOPClassUID: dataset.SOPClassUID,
|
||||
ReferencedSOPInstanceUID: dataset.SOPInstanceUID,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
RelationshipType: 'CONTAINS',
|
||||
ValueType: 'CONTAINER',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '126010',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Imaging Measurements', // TODO: would be nice to abstract the code sequences (in a dictionary? a service?)
|
||||
},
|
||||
ContinuityOfContent: 'SEPARATE',
|
||||
ContentSequence: {
|
||||
RelationshipType: 'CONTAINS',
|
||||
ValueType: 'CONTAINER',
|
||||
ConceptNameCodeSequence: {
|
||||
CodeValue: '125007',
|
||||
CodingSchemeDesignator: 'DCM',
|
||||
CodeMeaning: 'Measurement Group',
|
||||
},
|
||||
ContinuityOfContent: 'SEPARATE',
|
||||
ContentSequence: measurementGroupContentSequence,
|
||||
},
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
return dataset;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import { dcmjs } from 'meteor/ohif:cornerstone';
|
||||
import getLengthMeasurementData from './getLengthMeasurementData';
|
||||
import { toArray, codeMeaningEquals, getAllDisplaySets } from './srUtils'
|
||||
|
||||
const imagingMeasurementsToMeasurementData = (dataset, displaySets) => {
|
||||
// Identify the Imaging Measurements
|
||||
const imagingMeasurementContent = toArray(dataset.ContentSequence).find(codeMeaningEquals("Imaging Measurements"));
|
||||
|
||||
// Retrieve the Measurements themselves
|
||||
const measurementGroupContent = toArray(imagingMeasurementContent.ContentSequence).find(codeMeaningEquals("Measurement Group"));
|
||||
|
||||
// For now, bail out if the dataset is not a TID1500 SR with length measurements
|
||||
// TODO: generalize to the various kinds of report
|
||||
// TODO: generalize to the kinds of measurements the Viewer supports
|
||||
if (dataset.ContentTemplateSequence.TemplateIdentifier !== "1500") {
|
||||
OHIF.log.warn("This package can currently only interpret DICOM SR TID 1500");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Filter to find Length measurements in the Structured Report
|
||||
const lengthMeasurementContent = toArray(measurementGroupContent.ContentSequence).filter(codeMeaningEquals("Length"));
|
||||
|
||||
// Retrieve Length Measurement Data
|
||||
return getLengthMeasurementData(lengthMeasurementContent, displaySets);
|
||||
};
|
||||
|
||||
export default retrieveDataFromSR = (Part10SRArrayBuffer) => {
|
||||
const allDisplaySets = getAllDisplaySets();
|
||||
|
||||
// get the dicom data as an Object
|
||||
let dicomData = dcmjs.data.DicomMessage.readFile(Part10SRArrayBuffer);
|
||||
let dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
||||
|
||||
// Convert the SR into the kind of object the Measurements package is expecting
|
||||
return imagingMeasurementsToMeasurementData(dataset, allDisplaySets);
|
||||
};
|
||||
105
Packages/ohif-measurement-table/client/utils/srUtils.js
Normal file
105
Packages/ohif-measurement-table/client/utils/srUtils.js
Normal file
@ -0,0 +1,105 @@
|
||||
import { dcmjs } from 'meteor/ohif:cornerstone';
|
||||
|
||||
const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22'];
|
||||
|
||||
const toArray = function(x) {
|
||||
return (x.constructor.name === "Array" ? x : [x]);
|
||||
};
|
||||
|
||||
const codeMeaningEquals = (codeMeaningName) => {
|
||||
return (contentItem) => {
|
||||
return contentItem.ConceptNameCodeSequence.CodeMeaning === codeMeaningName;
|
||||
};
|
||||
};
|
||||
|
||||
const getAllDisplaySets = () => {
|
||||
const allStudies = OHIF.viewer.Studies.all();
|
||||
let allDisplaySets = [];
|
||||
|
||||
allStudies.forEach(study => {
|
||||
allDisplaySets = allDisplaySets.concat(study.displaySets);
|
||||
});
|
||||
|
||||
return allDisplaySets;
|
||||
};
|
||||
|
||||
|
||||
const getInstanceMetadata = (displaySets, sopInstanceUid) => {
|
||||
let instance;
|
||||
|
||||
// Use Array.some so that this loop stops when the internal loop
|
||||
// has found the correct instance
|
||||
displaySets.some(displaySet => {
|
||||
// Search the display set to find the instance metadata for
|
||||
return displaySet.images.find(instanceMetadata => {
|
||||
if (instanceMetadata._sopInstanceUID === sopInstanceUid) {
|
||||
instance = instanceMetadata;
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
const getLatestSRSeries = () => {
|
||||
const allStudies = OHIF.viewer.StudyMetadataList.all();
|
||||
let latestSeries;
|
||||
|
||||
allStudies.forEach(study => {
|
||||
study.getSeries().forEach(series => {
|
||||
const firstInstance = series.getFirstInstance();
|
||||
const sopClassUid = firstInstance._instance.sopClassUid;
|
||||
|
||||
if (supportedSopClassUIDs.includes(sopClassUid)) {
|
||||
if(!latestSeries) {
|
||||
latestSeries = series;
|
||||
} else if (series._data.seriesDate > latestSeries._data.seriesDate ||
|
||||
(series._data.seriesDate === latestSeries._data.seriesDate && series._data.seriesTime > latestSeries._data.seriesTime)) {
|
||||
latestSeries = series;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return latestSeries;
|
||||
};
|
||||
|
||||
const stringToArray = (string) => {
|
||||
return Uint8Array.from(Array.from(string).map(letter => letter.charCodeAt(0)))
|
||||
};
|
||||
|
||||
const multipartEncode = (dataset, boundary) => {
|
||||
|
||||
const denaturalizedMetaheader = dcmjs.data.DicomMetaDictionary.denaturalizeDataset(dataset._meta);
|
||||
const dicomDict = new dcmjs.data.DicomDict(denaturalizedMetaheader);
|
||||
|
||||
dicomDict.dict = dcmjs.data.DicomMetaDictionary.denaturalizeDataset(dataset);
|
||||
|
||||
const part10Buffer = dicomDict.write();
|
||||
|
||||
const header = `\r\n--${boundary}\r\nContent-Type: application/dicom\r\n\r\n`;
|
||||
const footer = `\r\n--${boundary}--`;
|
||||
|
||||
headerArray = stringToArray(header);
|
||||
contentArray = new Uint8Array(part10Buffer);
|
||||
footerArray = stringToArray(footer);
|
||||
|
||||
const multipartArray = new Uint8Array(headerArray.length + contentArray.length + footerArray.length);
|
||||
|
||||
multipartArray.set(headerArray, 0);
|
||||
multipartArray.set(contentArray, headerArray.length);
|
||||
multipartArray.set(footerArray, headerArray.length + contentArray.length);
|
||||
|
||||
return(multipartArray.buffer);
|
||||
};
|
||||
|
||||
export {
|
||||
codeMeaningEquals,
|
||||
getAllDisplaySets,
|
||||
getInstanceMetadata,
|
||||
getLatestSRSeries,
|
||||
multipartEncode,
|
||||
toArray
|
||||
}
|
||||
@ -15,6 +15,7 @@ Package.onUse(function(api) {
|
||||
api.use('ohif:cornerstone-settings');
|
||||
api.use('ohif:viewerbase');
|
||||
api.use('ohif:measurements');
|
||||
api.use('ohif:wadoproxy');
|
||||
|
||||
api.mainModule('client/index.js', 'client');
|
||||
});
|
||||
|
||||
@ -9,4 +9,21 @@
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
{{#if hasAnyMeasurement}}
|
||||
<div class="report-area">
|
||||
{{#if saveEnabled}}
|
||||
{{#if hasUnsavedChanges}}
|
||||
<div class="unsaved-changes-alert">
|
||||
You have unsaved changes
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#form (extend api=instance.api)}}
|
||||
{{#button class='btn p-x-2' action='exportCSV'}}Export CSV{{/button}}
|
||||
{{#if saveEnabled}}
|
||||
{{#button class='btn p-x-2' action='save' disabled=(not hasUnsavedChanges)}}Save to Archive{{/button}}
|
||||
{{/if}}
|
||||
{{/form}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
@ -8,9 +8,68 @@ Template.measurementLightTableView.onCreated(() => {
|
||||
|
||||
instance.data.measurementGroups = new ReactiveVar();
|
||||
|
||||
instance.path = 'viewer.studyViewer.measurements';
|
||||
instance.saveObserver = new Tracker.Dependency();
|
||||
|
||||
instance.api = {
|
||||
save() {
|
||||
|
||||
const successHandler = () => {
|
||||
OHIF.ui.unsavedChanges.clear(`${instance.path}.*`);
|
||||
instance.saveObserver.changed();
|
||||
};
|
||||
|
||||
// Display the error messages
|
||||
const errorHandler = data => {
|
||||
OHIF.ui.showDialog('dialogInfo', Object.assign({ class: 'themed' }, data));
|
||||
};
|
||||
|
||||
const promise = instance.data.measurementApi.storeMeasurements();
|
||||
promise.then(successHandler).catch(errorHandler);
|
||||
OHIF.ui.showDialog('dialogLoading', {
|
||||
promise,
|
||||
text: 'Measurements saved.'
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
exportCSV() {
|
||||
const { measurementApi, timepointApi } = instance.data;
|
||||
OHIF.measurements.exportCSV(measurementApi, timepointApi);
|
||||
}
|
||||
};
|
||||
|
||||
instance.autorun(() => {
|
||||
measurementApi.changeObserver.depend();
|
||||
const data = OHIF.measurements.getMeasurementsGroupedByNumber(measurementApi, timepointApi);
|
||||
instance.data.measurementGroups.set(data);
|
||||
});
|
||||
});
|
||||
|
||||
Template.measurementLightTableView.helpers({
|
||||
hasUnsavedChanges() {
|
||||
const instance = Template.instance();
|
||||
// Run this computation on save or every time any measurement / timepoint suffer changes
|
||||
OHIF.ui.unsavedChanges.depend();
|
||||
instance.saveObserver.depend();
|
||||
|
||||
return OHIF.ui.unsavedChanges.probe('viewer.*') !== 0;
|
||||
},
|
||||
|
||||
hasAnyMeasurement() {
|
||||
const instance = Template.instance();
|
||||
const groups = instance.data.measurementGroups.get();
|
||||
|
||||
if (!groups) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const group = groups.find(item => item.measurementRows.length > 0);
|
||||
return group;
|
||||
},
|
||||
|
||||
saveEnabled() {
|
||||
const server = OHIF.servers.getCurrentServer();
|
||||
return (server && server.type === 'dicomWeb');
|
||||
}
|
||||
});
|
||||
|
||||
@ -11,3 +11,9 @@
|
||||
theme('background-color', '$activeColor')
|
||||
theme('border', '1px solid $uiBorderColorActive')
|
||||
color: #000
|
||||
|
||||
.unsaved-changes-alert
|
||||
padding-bottom: 10px;
|
||||
font-size: 11px;
|
||||
color: #FF0;
|
||||
font-weight: 100;
|
||||
|
||||
@ -205,12 +205,6 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||
const seriesMap = {};
|
||||
const seriesList = [];
|
||||
|
||||
if (!WADOProxy) {
|
||||
WADOProxy = {
|
||||
convertURL: (a => a)
|
||||
};
|
||||
}
|
||||
|
||||
if (!resultData.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -6,11 +6,13 @@ import 'meteor/ohif:viewerbase';
|
||||
// promises and prevent unnecessary subsequent calls to the server
|
||||
const StudyMetaDataPromises = new Map();
|
||||
|
||||
|
||||
/**
|
||||
* Delete the study promise cached to ensure next retrieve will get it retrieved by server call
|
||||
* Delete the cached study metadata retrieval promise to ensure that the browser will
|
||||
* re-retrieve the study metadata when it is next requested
|
||||
*
|
||||
* @param {String} studyInstanceUid The UID of the Study to be removed from cache
|
||||
*
|
||||
*
|
||||
*/
|
||||
OHIF.studies.deleteStudyMetadataPromise = (studyInstanceUid) => {
|
||||
if (StudyMetaDataPromises.has(studyInstanceUid)) {
|
||||
|
||||
18
Packages/ohif-wadoproxy/both/convertURL.js
Normal file
18
Packages/ohif-wadoproxy/both/convertURL.js
Normal file
@ -0,0 +1,18 @@
|
||||
import queryString from 'query-string';
|
||||
|
||||
WADOProxy.convertURL = (url, serverConfiguration) => {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (serverConfiguration.requestOptions &&
|
||||
serverConfiguration.requestOptions.requestFromBrowser === true) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const { settings } = WADOProxy;
|
||||
const serverId = serverConfiguration._id;
|
||||
const query = queryString.stringify({url, serverId});
|
||||
|
||||
return `${settings.uri}?${query}`;
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
Settings = Object.assign({
|
||||
WADOProxy.settings = Object.assign({
|
||||
uri : OHIF.utils.absoluteUrl("/__wado_proxy"),
|
||||
enabled: true
|
||||
}, (Meteor.settings && Meteor.settings.proxy) ? Meteor.settings.proxy : {});
|
||||
}, (Meteor.settings && Meteor.settings.proxy) ? Meteor.settings.proxy : {});
|
||||
1
Packages/ohif-wadoproxy/both/namespace.js
Executable file
1
Packages/ohif-wadoproxy/both/namespace.js
Executable file
@ -0,0 +1 @@
|
||||
WADOProxy = {};
|
||||
@ -5,6 +5,7 @@ Package.describe({
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
'query-string': '5.1.1',
|
||||
'performance-now': '2.1.0'
|
||||
});
|
||||
|
||||
@ -12,17 +13,16 @@ Package.onUse(function(api) {
|
||||
api.versionsFrom('1.4');
|
||||
|
||||
api.use('ecmascript');
|
||||
api.use('standard-app-packages');
|
||||
api.use('clinical:router@2.0.19');
|
||||
|
||||
api.use('ohif:core');
|
||||
api.use('ohif:servers');
|
||||
|
||||
api.addFiles('server/namespace.js', 'server');
|
||||
api.addFiles('server/initialize.js', 'server');
|
||||
api.addFiles('both/namespace.js', ['client', 'server']);
|
||||
api.addFiles('both/convertURL.js', ['client', 'server']);
|
||||
api.addFiles('both/initialize.js', ['client', 'server']);
|
||||
api.addFiles('server/routes.js', 'server');
|
||||
api.addFiles('server/convertURL.js', 'server');
|
||||
|
||||
|
||||
// Global exports
|
||||
api.export('WADOProxy');
|
||||
});
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
const querystring = require("querystring");
|
||||
|
||||
WADOProxy.convertURL = (url, serverConfiguration) => {
|
||||
if (!Settings.enabled) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const serverId = serverConfiguration._id;
|
||||
const query = querystring.stringify({url, serverId});
|
||||
return `${Settings.uri}?${query}`;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
WADOProxy = {};
|
||||
@ -170,4 +170,4 @@ const handleRequest = function() {
|
||||
|
||||
// Setup a Route using Iron Router to avoid Cross-origin resource sharing
|
||||
// (CORS) errors. We only handle this route on the Server.
|
||||
Router.route(Settings.uri.replace(OHIF.utils.absoluteUrl(), ''), handleRequest, { where: 'server' });
|
||||
Router.route(WADOProxy.settings.uri.replace(OHIF.utils.absoluteUrl(), ''), handleRequest, { where: 'server' });
|
||||
|
||||
@ -3,7 +3,7 @@ import { Session } from 'meteor/session';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { ReactiveDict } from 'meteor/reactive-dict';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { MeasurementTable, measurementEvents } from 'meteor/ohif:measurement-table';
|
||||
import { MeasurementTable } from 'meteor/ohif:measurement-table';
|
||||
|
||||
import 'meteor/ohif:viewerbase';
|
||||
import 'meteor/ohif:metadata';
|
||||
@ -133,7 +133,7 @@ Template.viewer.onRendered(function() {
|
||||
Template.viewer.events( Object.assign({
|
||||
// Viewer Events
|
||||
},
|
||||
measurementEvents
|
||||
MeasurementTable.measurementEvents
|
||||
));
|
||||
|
||||
Template.viewer.helpers({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user