LT-67; OHIF-78: Merging server configuration and CINE dialog changes from downstream (Ascend)
This commit is contained in:
parent
da7202de0f
commit
8d5d1ee360
@ -1,3 +1,4 @@
|
||||
accounts-base@1.2.11
|
||||
aldeed:simple-schema@1.5.3
|
||||
aldeed:template-extension@4.0.0
|
||||
allow-deny@1.0.5
|
||||
@ -24,6 +25,7 @@ cornerstone@0.0.1
|
||||
ddp@1.2.5
|
||||
ddp-client@1.3.1
|
||||
ddp-common@1.2.6
|
||||
ddp-rate-limiter@1.0.5
|
||||
ddp-server@1.3.10
|
||||
deps@1.0.12
|
||||
design@0.0.1
|
||||
@ -51,6 +53,7 @@ johdirr:meteor-git-rev@0.0.4
|
||||
jquery@1.11.9
|
||||
launch-screen@1.0.12
|
||||
livedata@1.0.18
|
||||
localstorage@1.0.11
|
||||
logging@1.1.15
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.2.17
|
||||
@ -78,12 +81,14 @@ practicalmeteor:chai@2.1.0_1
|
||||
practicalmeteor:loglevel@1.2.0_2
|
||||
promise@0.8.7
|
||||
random@1.0.10
|
||||
rate-limit@1.0.5
|
||||
reactive-dict@1.1.8
|
||||
reactive-var@1.0.10
|
||||
reload@1.1.10
|
||||
retry@1.0.8
|
||||
routepolicy@1.0.11
|
||||
rwatts:uuid@0.1.0
|
||||
service-configuration@1.0.10
|
||||
session@1.1.6
|
||||
shell-server@0.2.1
|
||||
silentcicero:jszip@0.0.4
|
||||
|
||||
@ -11,19 +11,36 @@
|
||||
Open Health Imaging Foundation
|
||||
</div>
|
||||
</a>
|
||||
{{ #if studyListToggleText }}
|
||||
<a class="btn studyListLinkSection pull-left m-x-1 p-x-2 p-y-0 js-toggle-studyList">
|
||||
{{studyListToggleText}}
|
||||
</a>
|
||||
{{ /if }}
|
||||
<a class="btn pull-right p-y-0"
|
||||
data-toggle="modal"
|
||||
data-target="#aboutModal">
|
||||
About
|
||||
</a>
|
||||
{{#if studyListToggleText}}
|
||||
<a class="btn studyListLinkSection pull-left m-x-1 p-x-2 p-y-0 js-toggle-studyList">
|
||||
{{studyListToggleText}}
|
||||
</a>
|
||||
{{/if}}
|
||||
<span class="pull-right">
|
||||
<div class="dropdown">
|
||||
<div class="dropdown-toggle noselect clearfix" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="pull-left m-r-1">Options</span>
|
||||
<div class="menuButton pull-right"><span class="caret-down"></span></div>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a data-toggle="modal" data-target="#serverInformationModal">
|
||||
<i class="fa fa-server fa-lg"></i> Server Information
|
||||
</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li>
|
||||
<a data-toggle="modal" data-target="#aboutModal">
|
||||
<i class="fa fa-info"></i> About
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{> worklist }}
|
||||
{{> aboutModal }}
|
||||
{{> viewSeriesDetailsModal }}
|
||||
</template>
|
||||
{{>serverInformationModal}}
|
||||
{{>worklist}}
|
||||
{{>aboutModal}}
|
||||
{{>viewSeriesDetailsModal}}
|
||||
</template>
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import { Session } from 'meteor/session';
|
||||
|
||||
const worklistContentId = 'worklistTab';
|
||||
let lastContentId;
|
||||
|
||||
@ -39,7 +42,7 @@ Template.ohifViewer.helpers({
|
||||
return 'Study list';
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onStudyList() {
|
||||
return (Session.get('activeContentId') === 'worklistTab');
|
||||
}
|
||||
|
||||
@ -221,6 +221,10 @@ Template.toolbarSection.onRendered(function() {
|
||||
|
||||
instance.$('#layout').dropdown();
|
||||
|
||||
if (OHIF.uiSettings.showCineDialogOnRendered) {
|
||||
toggleCineDialog();
|
||||
}
|
||||
|
||||
// Set disabled/enabled tool buttons that are set in toolManager
|
||||
const states = toolManager.getToolDefaultStates();
|
||||
const disabledToolButtons = states.disabledToolButtons;
|
||||
|
||||
@ -31,6 +31,9 @@ label.form-group
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
|
||||
.height-auto
|
||||
height: auto !important
|
||||
|
||||
.caret-down
|
||||
display: inline-block
|
||||
width: 0
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { _ } from 'meteor/underscore';
|
||||
|
||||
// Uses NodeJS 'net'
|
||||
// https://nodejs.org/api/net.html
|
||||
var net = Npm.require('net'),
|
||||
Socket = net.Socket;
|
||||
|
||||
var DEFAULT_MAX_PACKAGE_SIZE = 32768;
|
||||
var net = Npm.require('net');
|
||||
var Socket = net.Socket;
|
||||
|
||||
Connection = function(options) {
|
||||
EventEmitter.call(this);
|
||||
@ -16,10 +16,7 @@ Connection = function(options) {
|
||||
}
|
||||
}, options);
|
||||
|
||||
this.peers = {};
|
||||
this.peerSockets = {};
|
||||
this.defaultPeer = null;
|
||||
this.defaultServer = null;
|
||||
this.reset();
|
||||
};
|
||||
|
||||
util.inherits(Connection, EventEmitter);
|
||||
@ -30,15 +27,29 @@ var StoreHandle = function() {
|
||||
|
||||
util.inherits(StoreHandle, EventEmitter);
|
||||
|
||||
Connection.prototype.reset = function() {
|
||||
this.defaultPeer = null;
|
||||
this.defaultServer = null;
|
||||
|
||||
_.each(this.peers, peerInfo => {
|
||||
_.each(peerInfo.sockets, socket => socket.emit('close'));
|
||||
});
|
||||
|
||||
this.peers = {};
|
||||
};
|
||||
|
||||
Connection.prototype.addPeer = function(options) {
|
||||
if (!options.aeTitle || !options.host || !options.port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.peers[options.aeTitle] = {
|
||||
var peer = {
|
||||
host: options.host,
|
||||
port: options.port
|
||||
port: options.port,
|
||||
sockets: {}
|
||||
};
|
||||
|
||||
this.peers[options.aeTitle] = peer;
|
||||
if (options.default) {
|
||||
if (options.server) {
|
||||
this.defaultServer = options.aeTitle;
|
||||
@ -49,22 +60,21 @@ Connection.prototype.addPeer = function(options) {
|
||||
|
||||
if (options.server) {
|
||||
//start listening
|
||||
var server = net.createServer();
|
||||
server.listen(options.port, options.host, function() {
|
||||
peer.server = net.createServer();
|
||||
peer.server.listen(options.port, options.host, function() {
|
||||
console.log('listening on %j', this.address());
|
||||
});
|
||||
server.on('error', function(err) {
|
||||
peer.server.on('error', function(err) {
|
||||
console.log('server error %j', err);
|
||||
});
|
||||
var o = this;
|
||||
server.on('connection', function(nativeSocket) {
|
||||
peer.server.on('connection', nativeSocket => {
|
||||
//incoming connections
|
||||
var socket = new CSocket(nativeSocket, o.options);
|
||||
o.addSocket(options.aeTitle, socket);
|
||||
var socket = new CSocket(nativeSocket, this.options);
|
||||
this.addSocket(options.aeTitle, socket);
|
||||
|
||||
//close server on close socket
|
||||
socket.on('close', function() {
|
||||
server.close();
|
||||
peer.server.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -81,8 +91,8 @@ Connection.prototype.selectPeer = function(aeTitle) {
|
||||
Connection.prototype._sendFile = function(socket, sHandle, file, maxSend, metaLength, list) {
|
||||
var fileNameText = typeof file.file === 'string' ? file.file : 'buffer';
|
||||
console.log('Sending file ' + fileNameText);
|
||||
var useContext = socket.getContextByUID(file.context),
|
||||
self = this;
|
||||
var useContext = socket.getContextByUID(file.context);
|
||||
var self = this;
|
||||
|
||||
PDU.generatePDatas(useContext.id, file.file, maxSend, null, metaLength, function(err, handle) {
|
||||
if (err) {
|
||||
@ -122,13 +132,14 @@ Connection.prototype._sendFile = function(socket, sHandle, file, maxSend, metaLe
|
||||
};
|
||||
|
||||
Connection.prototype.storeInstances = function(fileList) {
|
||||
var contexts = {},
|
||||
read = 0,
|
||||
length = fileList.length,
|
||||
toSend = [],
|
||||
self = this,
|
||||
handle = new StoreHandle();
|
||||
var contexts = {};
|
||||
var read = 0;
|
||||
var length = fileList.length;
|
||||
var toSend = [];
|
||||
var self = this;
|
||||
var handle = new StoreHandle();
|
||||
var lastProcessedMetaLength;
|
||||
|
||||
fileList.forEach(function(bufferOrFile) {
|
||||
var fileNameText = typeof bufferOrFile === 'string' ? bufferOrFile : 'buffer';
|
||||
DicomMessage.readMetaHeader(bufferOrFile, function(err, metaMessage, metaLength) {
|
||||
@ -144,9 +155,9 @@ Connection.prototype.storeInstances = function(fileList) {
|
||||
|
||||
console.log('Dicom file ' + (typeof bufferOrFile === 'string' ? bufferOrFile : 'buffer') + ' found');
|
||||
lastProcessedMetaLength = metaLength;
|
||||
var syntax = metaMessage.getValue(0x00020010),
|
||||
sopClassUID = metaMessage.getValue(0x00020002),
|
||||
instanceUID = metaMessage.getValue(0x00020003);
|
||||
var syntax = metaMessage.getValue(0x00020010);
|
||||
var sopClassUID = metaMessage.getValue(0x00020002);
|
||||
var instanceUID = metaMessage.getValue(0x00020003);
|
||||
|
||||
if (!contexts[sopClassUID]) {
|
||||
contexts[sopClassUID] = [];
|
||||
@ -173,23 +184,22 @@ Connection.prototype.storeInstances = function(fileList) {
|
||||
// Starts to send dcm files
|
||||
sendProcessedFiles = function(self, contexts, toSend, handle, metaLength) {
|
||||
var useContexts = [];
|
||||
for (var context in contexts) {
|
||||
var useSyntaxes = contexts[context];
|
||||
_.each(contexts, (useSyntaxes, context) => {
|
||||
if (useSyntaxes.length > 0) {
|
||||
useContexts.push({
|
||||
context: context,
|
||||
syntaxes: contexts[context]
|
||||
syntaxes: useSyntaxes
|
||||
});
|
||||
} else {
|
||||
throw 'No syntax specified for context ' + context;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.associate({
|
||||
contexts: useContexts
|
||||
}, function(ac) {
|
||||
var maxSend = ac.getMaxSize(),
|
||||
next = toSend.shift();
|
||||
var maxSend = ac.getMaxSize();
|
||||
var next = toSend.shift();
|
||||
self._sendFile(this, handle, next, maxSend, metaLength, toSend);
|
||||
|
||||
});
|
||||
@ -202,8 +212,8 @@ Connection.prototype.storeResponse = function(messageId, msg) {
|
||||
var status = rq.listener[2].call(this, msg);
|
||||
if (status !== undefined && status !== null && rq.command.store) {
|
||||
//store ok, ready to send c-store-rsp
|
||||
var storeSr = rq.command.store,
|
||||
replyMessage = storeSr.replyWith(status);
|
||||
var storeSr = rq.command.store;
|
||||
var replyMessage = storeSr.replyWith(status);
|
||||
replyMessage.setAffectedSOPInstanceUID(this.lastCommand.getSOPInstanceUID());
|
||||
replyMessage.setReplyMessageId(this.lastCommand.messageId);
|
||||
this.sendMessage(replyMessage, null, null, storeSr);
|
||||
@ -215,8 +225,8 @@ Connection.prototype.storeResponse = function(messageId, msg) {
|
||||
|
||||
Connection.prototype.allClosed = function() {
|
||||
var allClosed = true;
|
||||
for (var i in o.peerSockets) {
|
||||
if (Object.keys(o.peerSockets[ae]).length > 0) {
|
||||
for (var i in this.peers) {
|
||||
if (Object.keys(peers[i].sockets).length > 0) {
|
||||
allClosed = false;
|
||||
break;
|
||||
}
|
||||
@ -225,34 +235,30 @@ Connection.prototype.allClosed = function() {
|
||||
return allClosed;
|
||||
};
|
||||
|
||||
Connection.prototype.addSocket = function(ae, socket) {
|
||||
if (!this.peerSockets[ae]) {
|
||||
this.peerSockets[ae] = {};
|
||||
}
|
||||
Connection.prototype.addSocket = function(hostAE, socket) {
|
||||
var peerInfo = this.selectPeer(hostAE);
|
||||
|
||||
this.peerSockets[ae][socket.id] = socket;
|
||||
peerInfo.sockets[socket.id] = socket;
|
||||
|
||||
var o = this;
|
||||
socket.on('close', function() {
|
||||
if (o.peerSockets[ae][this.id]) {
|
||||
delete o.peerSockets[ae][this.id];
|
||||
if (peerInfo.sockets[this.id]) {
|
||||
delete peerInfo.sockets[this.id];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Connection.prototype.associate = function(options, callback) {
|
||||
var hostAE = options.hostAE ? options.hostAE : this.defaultPeer,
|
||||
sourceAE = options.sourceAE ? options.sourceAE : this.defaultServer;
|
||||
var hostAE = options.hostAE ? options.hostAE : this.defaultPeer;
|
||||
var sourceAE = options.sourceAE ? options.sourceAE : this.defaultServer;
|
||||
|
||||
if (!hostAE || !sourceAE) {
|
||||
throw 'Peers not provided or no defaults in settings';
|
||||
}
|
||||
|
||||
var peerInfo = this.selectPeer(hostAE),
|
||||
nativeSocket = new Socket();
|
||||
var peerInfo = this.selectPeer(hostAE);
|
||||
var nativeSocket = new Socket();
|
||||
|
||||
var socket = new CSocket(nativeSocket, this.options),
|
||||
o = this;
|
||||
var socket = new CSocket(nativeSocket, this.options);
|
||||
|
||||
if (callback) {
|
||||
socket.once('associated', callback);
|
||||
@ -268,9 +274,9 @@ Connection.prototype.associate = function(options, callback) {
|
||||
nativeSocket.connect({
|
||||
host: peerInfo.host,
|
||||
port: peerInfo.port
|
||||
}, function() {
|
||||
}, () => {
|
||||
//connected
|
||||
o.addSocket(hostAE, socket);
|
||||
this.addSocket(hostAE, socket);
|
||||
|
||||
if (options.contexts) {
|
||||
socket.setPresentationContexts(options.contexts);
|
||||
@ -282,4 +288,4 @@ Connection.prototype.associate = function(options, callback) {
|
||||
});
|
||||
|
||||
return socket;
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
var Future = Npm.require('fibers/future');
|
||||
|
||||
DIMSE = {};
|
||||
DIMSE = {
|
||||
connection: new Connection({
|
||||
vr: {
|
||||
split: false
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
var conn = new Connection({
|
||||
vr: {
|
||||
split: false
|
||||
}
|
||||
});
|
||||
var conn = DIMSE.connection;
|
||||
|
||||
var getInstanceRetrievalParams = function(studyInstanceUID, seriesInstanceUID) {
|
||||
return {
|
||||
@ -64,35 +66,6 @@ var getInstanceRetrievalParams = function(studyInstanceUID, seriesInstanceUID) {
|
||||
};
|
||||
};
|
||||
|
||||
Meteor.startup(function() {
|
||||
if (Meteor.settings.defaultServiceType !== 'dimse') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Meteor.settings.servers.dimse ||
|
||||
!Meteor.settings.servers.dimse.length) {
|
||||
console.error('dimse-config: ' + 'No DIMSE Servers provided.');
|
||||
throw new Meteor.Error('dimse-config', 'No DIMSE Servers provided.');
|
||||
}
|
||||
|
||||
// TODO: [custom-servers] use active server and check if type is DIMSE
|
||||
var peers = Meteor.settings.servers.dimse[0].peers;
|
||||
if (!peers || !peers.length) {
|
||||
console.error('dimse-config: ' + 'No DIMSE Peers provided.');
|
||||
throw new Meteor.Error('dimse-config', 'No DIMSE Peers provided.');
|
||||
}
|
||||
|
||||
console.log('Adding DIMSE peers');
|
||||
try {
|
||||
peers.forEach(function(peer) {
|
||||
conn.addPeer(peer);
|
||||
});
|
||||
} catch(error) {
|
||||
console.error('dimse-addPeers: ' + error);
|
||||
throw new Meteor.Error('dimse-addPeers', error);
|
||||
}
|
||||
});
|
||||
|
||||
DIMSE.associate = function(contexts, callback, options) {
|
||||
var defaults = {
|
||||
contexts: contexts
|
||||
@ -239,10 +212,10 @@ DIMSE.retrieveInstancesByStudyOnly = function(studyInstanceUID, params, options)
|
||||
0x0020000E: '',
|
||||
0x00200011: ''
|
||||
};
|
||||
var result = this.findSeries(Object.assign(defaultParams, params)),
|
||||
series = [],
|
||||
conn = this,
|
||||
allInstances = [];
|
||||
var result = this.findSeries(Object.assign(defaultParams, params));
|
||||
var series = [];
|
||||
var conn = this;
|
||||
var allInstances = [];
|
||||
|
||||
result.on('result', function(msg) {
|
||||
series.push(msg);
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
|
||||
// Servers describe the DICOM servers configurations
|
||||
Servers = new Mongo.Collection('servers');
|
||||
|
||||
Timepoints = new Mongo.Collection('timepoints');
|
||||
Studies = new Mongo.Collection('studies');
|
||||
Measurements = new Mongo.Collection('measurements');
|
||||
|
||||
@ -1,2 +1 @@
|
||||
Meteor.subscribe('reviewers');
|
||||
Meteor.subscribe('servers');
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
Template.serverInformationList.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
instance.api = {
|
||||
add: () => instance.data.mode.set('create'),
|
||||
edit(server) {
|
||||
instance.data.currentItem.set(server);
|
||||
instance.data.mode.set('edit');
|
||||
},
|
||||
delete(server) {
|
||||
Meteor.call('serverRemove', server._id, error => {
|
||||
// TODO: check for errors: not-authorized, data-write
|
||||
});
|
||||
},
|
||||
use(server) {
|
||||
Meteor.call('serverSetActive', server._id, error => {
|
||||
// TODO: [custom-servers] check for errors: not-authorized, data-write
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Template.serverInformationList.helpers({
|
||||
isActive: function(server) {
|
||||
return server._id === Meteor.user().profile.activeServer;
|
||||
},
|
||||
servers: function() {
|
||||
return Servers.find().fetch();
|
||||
}
|
||||
});
|
||||
@ -203,7 +203,6 @@ Package.onUse(function(api) {
|
||||
|
||||
// Server functions
|
||||
api.addFiles('server/publications.js', 'server');
|
||||
api.addFiles('server/servers.js', 'server');
|
||||
api.addFiles('server/methods.js', [ 'server' ]);
|
||||
api.addFiles('server/reviewers.js', [ 'server' ]);
|
||||
api.addFiles('server/createDemoUser.js', [ 'server' ]);
|
||||
@ -242,23 +241,6 @@ Package.onUse(function(api) {
|
||||
api.addFiles('lib/handleMeasurementModified.js', 'client');
|
||||
api.addFiles('lib/handleMeasurementRemoved.js', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationDicomWeb/serverInformationDicomWeb.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationDicomWeb/serverInformationDicomWeb.js', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationDimse/serverInformationDimse.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationDimse/serverInformationDimse.js', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationForm.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationForm.js', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationFormField.html', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationList/serverInformationList.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationList/serverInformationList.js', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.styl', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.js', 'client');
|
||||
|
||||
// API classes
|
||||
api.addFiles('client/api/timepoint.js');
|
||||
api.addFiles('client/api/measurement.js');
|
||||
@ -299,7 +281,6 @@ Package.onUse(function(api) {
|
||||
api.export('TrialCriteriaTypes', 'client');
|
||||
|
||||
// Export collections spanning both client and server
|
||||
api.export('Servers', [ 'client', 'server' ]);
|
||||
api.export('AdditionalFindings', [ 'client', 'server' ]);
|
||||
api.export('ImageMeasurements', [ 'client', 'server' ]);
|
||||
api.export('Measurements', [ 'client', 'server' ]);
|
||||
|
||||
@ -46,10 +46,6 @@ Meteor.publish('reviewers', function() {
|
||||
return Reviewers.find();
|
||||
});
|
||||
|
||||
Meteor.publish('servers', () => {
|
||||
return Servers.find();
|
||||
});
|
||||
|
||||
// Temporary fix to drop all Collections on server restart
|
||||
// http://stackoverflow.com/questions/23891631/meteor-how-can-i-drop-all-mongo-collections-and-clear-all-data-on-startup
|
||||
Meteor.startup(function() {
|
||||
|
||||
@ -126,7 +126,7 @@ OHIF.mixins.schemaData = new OHIF.Mixin({
|
||||
|
||||
// Stop here if there's no schema data for current key
|
||||
if (!currentSchema) {
|
||||
return;
|
||||
return value;
|
||||
}
|
||||
|
||||
// Check if the schema is a Number
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<template name="cineDialog">
|
||||
{{#form id='cineDialog' class='noselect' schema=instance.schema api=instance.api}}
|
||||
{{>inputHidden key='intervalId'}}
|
||||
<h5>CINE Controls</h5>
|
||||
<div class="cine-navigation">
|
||||
<div class="btn-group">
|
||||
{{#button disabled=(displaySetDisabled false) action='displaySetPrevious' title='Previous display set'}}
|
||||
@ -20,9 +19,6 @@
|
||||
{{#button action='cinePrevious' title='Previous image'}}
|
||||
<i class="fa fa-step-backward"></i>
|
||||
{{/button}}
|
||||
{{#button action='cineSlowDown' title='Slow playback'}}
|
||||
<i class="fa fa-backward"></i>
|
||||
{{/button}}
|
||||
{{#button action='cineToggle' title='Play / Stop' class=(valueIf isPlaying 'active' '')}}
|
||||
{{#if isPlaying}}
|
||||
<i class="fa fa-fw fa-stop"></i>
|
||||
@ -30,9 +26,6 @@
|
||||
<i class="fa fa-fw fa-play"></i>
|
||||
{{/if}}
|
||||
{{/button}}
|
||||
{{#button action='cineSpeedUp' title='Increase playback speed'}}
|
||||
<i class="fa fa-forward"></i>
|
||||
{{/button}}
|
||||
{{#button action='cineNext' title='Next image'}}
|
||||
<i class="fa fa-step-forward"></i>
|
||||
{{/button}}
|
||||
@ -42,14 +35,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="cine-options">
|
||||
{{>inputCheckbox key='loop' labelClass='pull-right m-a-0'}}
|
||||
<div class="fps-section">
|
||||
{{#inputRange key='framesPerSecond' class='p-a-0' labelClass='form-group m-a-0' labelAsDiv=true}}
|
||||
{{#section 'labelAfterText'}}:
|
||||
<span id="fps">{{framerate}}</span>
|
||||
{{/section}}
|
||||
{{/inputRange}}
|
||||
</div>
|
||||
<span id="fps">{{framerate}} fps</span>
|
||||
</div>
|
||||
{{/form}}
|
||||
</template>
|
||||
|
||||
@ -21,7 +21,7 @@ Template.cineDialog.onCreated(() => {
|
||||
},
|
||||
framesPerSecond: {
|
||||
type: Number,
|
||||
label: 'Cine Speed',
|
||||
label: '',
|
||||
defaultValue: 24,
|
||||
min: 1,
|
||||
max: 90,
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
@import "{design}/app.styl"
|
||||
|
||||
#cineDialog
|
||||
theme('border', '1px solid $uiBorderColor', 0.95)
|
||||
theme('border', '2px solid $uiBorderColor', 0.95)
|
||||
theme('background', '$uiGrayDarkest', 0.95)
|
||||
theme('color', '$textSecondaryColor')
|
||||
border-radius: 8px
|
||||
bottom: 3px
|
||||
box-sizing: border-box
|
||||
height: 150px
|
||||
height: 80px
|
||||
left: 50%
|
||||
overflow: hidden
|
||||
padding: 10px
|
||||
position: absolute
|
||||
right: 3px
|
||||
top: 50%
|
||||
top: 2%
|
||||
width: 300px
|
||||
z-index: 1000
|
||||
transform(translateX(-50%) translateY(-50%))
|
||||
@ -36,9 +36,10 @@
|
||||
&:active, &.active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
|
||||
.cine-navigation
|
||||
position: absolute
|
||||
right: 24px
|
||||
right: 4px
|
||||
top: 4px
|
||||
|
||||
.btn
|
||||
@ -49,12 +50,19 @@
|
||||
line-height: 36px
|
||||
|
||||
.cine-controls
|
||||
display: table
|
||||
margin: 0 auto
|
||||
left: 0px
|
||||
|
||||
.cine-options
|
||||
padding: 5px 0
|
||||
overflow: auto
|
||||
padding: 0px 0
|
||||
|
||||
.fps-section
|
||||
width: 175px
|
||||
float: left
|
||||
|
||||
#fps
|
||||
float: right
|
||||
padding: 5px 10px 0 0
|
||||
|
||||
.loop-section
|
||||
padding-right: 12px
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
|
||||
StudyImportStatus = new Mongo.Collection('studyImportStatus');
|
||||
StudyImportStatus = new Mongo.Collection('studyImportStatus');
|
||||
|
||||
// Servers describe the DICOM servers configurations
|
||||
Servers = new Mongo.Collection('servers');
|
||||
|
||||
// CurrentServer is a single document collection to describe which of the Servers is being used
|
||||
CurrentServer = new Mongo.Collection('currentServer');
|
||||
|
||||
14
Packages/worklist/both/lib/getCurrentServer.js
Normal file
14
Packages/worklist/both/lib/getCurrentServer.js
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Retrieves the current server configuration used to retrieve studies
|
||||
*/
|
||||
getCurrentServer = () => {
|
||||
const currentServer = CurrentServer.findOne();
|
||||
|
||||
if (!currentServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serverConfiguration = Servers.findOne({ _id: currentServer.serverId });
|
||||
|
||||
return serverConfiguration;
|
||||
};
|
||||
@ -14,6 +14,12 @@ const serverTypeDefinitions = {
|
||||
optional: true
|
||||
};
|
||||
|
||||
const wadoUriRootDefinitions = {
|
||||
type: String,
|
||||
label: 'WADO URI root',
|
||||
max: 1000
|
||||
};
|
||||
|
||||
export const DICOMWebRequestOptions = new SimpleSchema({
|
||||
auth: {
|
||||
type: String,
|
||||
@ -40,11 +46,7 @@ export const DICOMWebRequestOptions = new SimpleSchema({
|
||||
export const DICOMWebServer = new SimpleSchema({
|
||||
name: serverNameDefinitions,
|
||||
type: serverTypeDefinitions,
|
||||
wadoUriRoot: {
|
||||
type: String,
|
||||
label: 'WADO URI root',
|
||||
max: 1000
|
||||
},
|
||||
wadoUriRoot: wadoUriRootDefinitions,
|
||||
wadoRoot: {
|
||||
type: String,
|
||||
label: 'WADO root',
|
||||
@ -106,7 +108,7 @@ export const DIMSEPeer = new SimpleSchema({
|
||||
},
|
||||
supportsInstanceRetrievalByStudyUid: {
|
||||
type: Boolean,
|
||||
label: 'Supports instance retrieval by StudyUid?',
|
||||
label: 'Supports instance retrieval by StudyUid',
|
||||
defaultValue: true
|
||||
}
|
||||
});
|
||||
@ -114,6 +116,7 @@ export const DIMSEPeer = new SimpleSchema({
|
||||
export const DIMSEServer = new SimpleSchema({
|
||||
name: serverNameDefinitions,
|
||||
type: serverTypeDefinitions,
|
||||
wadoUriRoot: wadoUriRootDefinitions,
|
||||
peers: {
|
||||
type: [DIMSEPeer],
|
||||
label: 'Peer List',
|
||||
@ -141,6 +144,11 @@ export const UISettings = new SimpleSchema({
|
||||
type: Boolean,
|
||||
label: 'The UP/DOWN display set navigation buttons will iterate over all the viewports at once?',
|
||||
defaultValue: false
|
||||
},
|
||||
showCineDialogOnRendered: {
|
||||
type: Boolean,
|
||||
label: 'The Cine dialog will be displayed by default',
|
||||
defaultValue: false
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
2
Packages/worklist/client/collections/subscriptions.js
Normal file
2
Packages/worklist/client/collections/subscriptions.js
Normal file
@ -0,0 +1,2 @@
|
||||
Meteor.subscribe('servers');
|
||||
Meteor.subscribe('currentServer');
|
||||
@ -1,8 +1,15 @@
|
||||
<template name="serverInformationDimse">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
{{>inputText labelClass='form-group' key='wadoUriRoot'}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default panel-body">
|
||||
<div class="clearfix">
|
||||
<h4 class="pull-left">Peer List</h4>
|
||||
<button class="btn btn-primary pull-right js-new-peer">New peer</button>
|
||||
{{#button class='btn btn-primary pull-right'
|
||||
action='newPeer' params=peer
|
||||
}}New peer{{/button}}
|
||||
</div>
|
||||
<hr>
|
||||
{{#group class='row' key='peers' arrayValues=true}}
|
||||
@ -25,16 +32,24 @@
|
||||
<div class="col-lg-12">
|
||||
<strong>Settings</strong>
|
||||
<div class="form-group">
|
||||
<div class="form-control clearfix">
|
||||
<div class="form-control height-auto clearfix">
|
||||
<div class="checkbox pull-left">
|
||||
{{>inputCheckbox key='default'}}
|
||||
</div>
|
||||
<div class="checkbox pull-left">
|
||||
{{>inputCheckbox key='server'}}
|
||||
</div>
|
||||
<div class="checkbox pull-left">
|
||||
{{>inputCheckbox key='supportsInstanceRetrievalByStudyUid'}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 text-right">
|
||||
{{#button class='btn btn-sm btn-danger'
|
||||
action='removePeer' params=@index
|
||||
}}Remove{{/button}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/group}}
|
||||
@ -7,6 +7,23 @@ Template.serverInformationDimse.onCreated(() => {
|
||||
instance.peers.set(currentItem.peers || []);
|
||||
}
|
||||
});
|
||||
const api = instance.data.api;
|
||||
_.extend(api, {
|
||||
newPeer() {
|
||||
const peers = instance.peers.get();
|
||||
peers.push({});
|
||||
instance.peers.set(peers);
|
||||
},
|
||||
removePeer(peerIndex) {
|
||||
const peers = instance.peers.get();
|
||||
peers.splice(peerIndex, 1);
|
||||
instance.peers.set(peers);
|
||||
|
||||
const data = instance.data.currentItem.get();
|
||||
data.peers = peers;
|
||||
Tracker.afterFlush(() => instance.data.form.value(data));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Template.serverInformationDimse.onRendered(() => {
|
||||
@ -19,12 +36,3 @@ Template.serverInformationDimse.onRendered(() => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Template.serverInformationDimse.events({
|
||||
'click .js-new-peer'(event, instance) {
|
||||
event.preventDefault();
|
||||
const peers = instance.peers.get();
|
||||
peers.push({});
|
||||
instance.peers.set(peers);
|
||||
}
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
<template name="serverInformationForm">
|
||||
{{#form class='server-information-form' schema=instance.currentSchema api=instance.api}}
|
||||
{{#form class='server-information-form' schema=instance.currentSchema api=this.api}}
|
||||
{{>inputHidden key='_id'}}
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
@ -1,13 +1,13 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { DICOMWebServer as dicomSchema } from 'meteor/worklist/both/schema';
|
||||
import { DIMSEServer as dimseSchema } from 'meteor/worklist/both/schema';
|
||||
import { DICOMWebServer as dicomSchema } from 'meteor/worklist/both/schema.js';
|
||||
import { DIMSEServer as dimseSchema } from 'meteor/worklist/both/schema.js';
|
||||
|
||||
Template.serverInformationForm.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
instance.api = {
|
||||
instance.data.api = {
|
||||
save() {
|
||||
// Stop here if the form validation fails
|
||||
if (!instance.data.form.validate()) {
|
||||
@ -0,0 +1,47 @@
|
||||
Template.serverInformationList.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
instance.api = {
|
||||
add: () => instance.data.mode.set('create'),
|
||||
edit(server) {
|
||||
instance.data.currentItem.set(server);
|
||||
instance.data.mode.set('edit');
|
||||
},
|
||||
delete(server) {
|
||||
// TODO: Replace this for confirmation dialog after LT-refactor is merged back to master
|
||||
if (!confirm('Are you sure you want to remove this peer?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Meteor.call('serverRemove', server._id, error => {
|
||||
// TODO: check for errors: data-write
|
||||
});
|
||||
},
|
||||
use(server) {
|
||||
Meteor.call('serverSetActive', server._id, error => {
|
||||
// TODO: check for errors: data-write
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Template.serverInformationList.helpers({
|
||||
isActive: function(server) {
|
||||
const currentServer = CurrentServer.findOne();
|
||||
if (!currentServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
return server._id === currentServer.serverId;
|
||||
},
|
||||
servers: function() {
|
||||
const options = {
|
||||
sort: {
|
||||
origin: -1,
|
||||
type: 1,
|
||||
name: 1
|
||||
}
|
||||
};
|
||||
return Servers.find({}, options).fetch();
|
||||
}
|
||||
});
|
||||
@ -34,6 +34,11 @@ switchToTab = function(contentId) {
|
||||
var container;
|
||||
if (contentId === 'worklistTab') {
|
||||
container = $('.tab-content').find('#worklistTab').get(0);
|
||||
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
var worklistContainer = document.createElement('div');
|
||||
worklistContainer.classList.add('worklistContainer');
|
||||
container.appendChild(worklistContainer);
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
|
||||
Worklist = {
|
||||
functions: {},
|
||||
callbacks: {}
|
||||
@ -10,4 +13,19 @@ function dblClickOnStudy(data) {
|
||||
// Use the formatPN template helper to clean up the patient name
|
||||
var title = formatPN(data.patientName);
|
||||
openNewTab(data.studyInstanceUid, title);
|
||||
}
|
||||
}
|
||||
|
||||
let currentServerChangeHandlerFirstRun = true;
|
||||
const currentServerChangeHandler = () => {
|
||||
if (currentServerChangeHandlerFirstRun) {
|
||||
currentServerChangeHandlerFirstRun = false;
|
||||
return;
|
||||
}
|
||||
|
||||
switchToTab('worklistTab');
|
||||
};
|
||||
|
||||
CurrentServer.find().observe({
|
||||
added: currentServerChangeHandler,
|
||||
changed: currentServerChangeHandler
|
||||
});
|
||||
|
||||
@ -16,11 +16,12 @@ Package.onUse(function (api) {
|
||||
api.use('rwatts:uuid');
|
||||
api.use('silentcicero:jszip');
|
||||
api.use('aldeed:simple-schema');
|
||||
api.use('accounts-base');
|
||||
|
||||
// Note: MomentJS appears to be required for Bootstrap3 Datepicker, but not a dependency for some reason
|
||||
api.use('momentjs:moment');
|
||||
|
||||
api.use('gilbertwat:bootstrap3-daterangepicker');
|
||||
api.use('gilbertwat:bootstrap3-daterangepicker');
|
||||
|
||||
// Our custom packages
|
||||
api.use('ohif:core');
|
||||
@ -39,9 +40,13 @@ Package.onUse(function (api) {
|
||||
// console for debugging purposes
|
||||
api.addFiles('log.js');
|
||||
|
||||
api.addFiles('both/collections.js', [ 'client', 'server' ]);
|
||||
api.addFiles('both/schema.js', [ 'client', 'server' ]);
|
||||
|
||||
api.addFiles('both/collections.js', ['client', 'server']);
|
||||
api.addFiles('both/schema.js', ['client', 'server']);
|
||||
api.addFiles('both/lib/getCurrentServer.js', ['client', 'server']);
|
||||
|
||||
// Client collections and subscriptions
|
||||
api.addFiles('client/collections/subscriptions.js', 'client');
|
||||
|
||||
// Components
|
||||
api.addFiles('client/components/worklist.html', 'client');
|
||||
api.addFiles('client/components/worklist.js', 'client');
|
||||
@ -80,7 +85,24 @@ Package.onUse(function (api) {
|
||||
api.addFiles('client/components/seriesDetailsTable/seriesDetailsTable.html', 'client');
|
||||
api.addFiles('client/components/seriesDetailsTable/seriesDetailsTable.styl', 'client');
|
||||
api.addFiles('client/components/seriesDetailsTable/seriesDetailsTable.js', 'client');
|
||||
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationDicomWeb/serverInformationDicomWeb.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationDicomWeb/serverInformationDicomWeb.js', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationDimse/serverInformationDimse.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationDimse/serverInformationDimse.js', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationForm.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationForm.js', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationFormField.html', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationList/serverInformationList.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationList/serverInformationList.js', 'client');
|
||||
|
||||
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.html', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.styl', 'client');
|
||||
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.js', 'client');
|
||||
|
||||
// Client-side library functions
|
||||
api.addFiles('client/lib/getStudyMetadata.js', 'client');
|
||||
api.addFiles('client/lib/getStudiesMetadata.js', 'client');
|
||||
@ -95,6 +117,7 @@ Package.onUse(function (api) {
|
||||
// Server-side functions
|
||||
api.addFiles('server/publications.js', 'server');
|
||||
api.addFiles('server/validateServerConfiguration.js', 'server');
|
||||
api.addFiles('server/servers.js', 'server');
|
||||
api.addFiles('server/lib/remoteGetValue.js', 'server');
|
||||
api.addFiles('server/lib/encodeQueryData.js', 'server');
|
||||
|
||||
@ -113,12 +136,20 @@ Package.onUse(function (api) {
|
||||
api.addFiles('server/services/dimse/instances.js', 'server');
|
||||
api.addFiles('server/services/dimse/studies.js', 'server');
|
||||
api.addFiles('server/services/dimse/retrieveMetadata.js', 'server');
|
||||
api.addFiles('server/services/dimse/setup.js', 'server');
|
||||
|
||||
// Study, instance, and metadata retrieval from remote PACS via Orthanc as a proxy
|
||||
api.addFiles('server/services/remote/instances.js', 'server');
|
||||
api.addFiles('server/services/remote/studies.js', 'server');
|
||||
api.addFiles('server/services/remote/retrieveMetadata.js', 'server');
|
||||
|
||||
// Export Servers and CurrentServer Collections
|
||||
api.export('Servers', ['client', 'server']);
|
||||
api.export('CurrentServer', ['client', 'server']);
|
||||
|
||||
// Export shared lib functions
|
||||
api.export('getCurrentServer', ['client', 'server']);
|
||||
|
||||
api.export('Services', 'server');
|
||||
|
||||
// Export Worklist helper functions for usage in Routes
|
||||
@ -136,4 +167,4 @@ Package.onUse(function (api) {
|
||||
api.export('WorklistTabs', 'client');
|
||||
api.export('WorklistStudies', 'client');
|
||||
api.export('WorklistSelectedStudies', 'client')
|
||||
});
|
||||
});
|
||||
|
||||
@ -6,16 +6,17 @@ Meteor.methods({
|
||||
GetStudyMetadata: function(studyInstanceUid) {
|
||||
log.info('GetStudyMetadata(%s)', studyInstanceUid);
|
||||
|
||||
if (!Meteor.settings.servers.dicomWeb) {
|
||||
throw 'No properly configured server was available over DICOMWeb';
|
||||
// Get the server data. This is user-defined in the config.json files or through servers
|
||||
// configuration modal
|
||||
const server = getCurrentServer();
|
||||
|
||||
if (!server) {
|
||||
throw 'No properly configured server was available over DICOMWeb or DIMSE.';
|
||||
}
|
||||
|
||||
if (Meteor.settings.servers.dicomWeb && Meteor.settings.defaultServiceType === 'dicomWeb') {
|
||||
// Get the server data. This is user-defined in the
|
||||
// config.json files used to run the Meteor server
|
||||
var server = Meteor.settings.servers.dicomWeb[0];
|
||||
if (server.type === 'dicomWeb') {
|
||||
return Services.WADO.RetrieveMetadata(server, studyInstanceUid);
|
||||
} else if (Meteor.settings.servers.dimse && Meteor.settings.defaultServiceType === 'dimse') {
|
||||
} else if (server.type === 'dimse') {
|
||||
return Services.DIMSE.RetrieveMetadata(studyInstanceUid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,8 @@ Meteor.methods({
|
||||
* @returns {boolean}
|
||||
*/
|
||||
importSupported: function() {
|
||||
if (Meteor.settings.servers.dimse && Meteor.settings.defaultServiceType === 'dimse') {
|
||||
const server = getCurrentServer();
|
||||
if (server && server.type === 'dimse') {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
@ -53,13 +54,17 @@ Meteor.methods({
|
||||
return;
|
||||
}
|
||||
|
||||
if (Meteor.settings.servers.dicomWeb && Meteor.settings.defaultServiceType === 'dicomWeb') {
|
||||
const server = getCurrentServer();
|
||||
|
||||
if (!server) {
|
||||
throw 'No properly configured server was available over DICOMWeb or DIMSE.';
|
||||
}
|
||||
|
||||
if (server.type === 'dicomWeb') {
|
||||
//TODO: Support importing studies into dicomWeb
|
||||
console.log('Importing studies into dicomWeb is currently not supported.');
|
||||
} else if (Meteor.settings.servers.dimse && Meteor.settings.defaultServiceType === 'dimse') {
|
||||
} else if (server.type === 'dimse') {
|
||||
importStudiesDIMSE(studiesToImport, studyImportStatusId);
|
||||
} else {
|
||||
throw 'No properly configured server was available over DICOMWeb or DIMSE.';
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
@ -1,19 +1,22 @@
|
||||
Meteor.methods({
|
||||
/**
|
||||
* Use the specified filter to conduct a search from the DICOM server
|
||||
*
|
||||
* @param filter
|
||||
*/
|
||||
WorklistSearch: function(filter) {
|
||||
if (Meteor.settings.servers.dicomWeb && Meteor.settings.defaultServiceType === 'dicomWeb') {
|
||||
// Get the server data. This is user-defined in the
|
||||
// config.json files used to run the Meteor server
|
||||
var server = Meteor.settings.servers.dicomWeb[0];
|
||||
WorklistSearch(filter) {
|
||||
// Get the server data. This is user-defined in the config.json files or through servers
|
||||
// configuration modal
|
||||
const server = getCurrentServer();
|
||||
|
||||
return Services.QIDO.Studies(server, filter);
|
||||
} else if (Meteor.settings.servers.dimse && Meteor.settings.defaultServiceType === 'dimse') {
|
||||
return Services.DIMSE.Studies(filter);
|
||||
} else {
|
||||
if (!server) {
|
||||
throw 'No properly configured server was available over DICOMWeb or DIMSE.';
|
||||
}
|
||||
|
||||
if (server.type === 'dicomWeb') {
|
||||
return Services.QIDO.Studies(server, filter);
|
||||
} else if (server.type === 'dimse') {
|
||||
return Services.DIMSE.Studies(filter);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
Meteor.publish('studyImportStatus', () => {
|
||||
return StudyImportStatus.find();
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.publish('servers', () => {
|
||||
return Servers.find();
|
||||
});
|
||||
|
||||
Meteor.publish('currentServer', () => {
|
||||
return CurrentServer.find();
|
||||
});
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
Meteor.startup(function() {
|
||||
Servers.remove({
|
||||
origin: 'json'
|
||||
});
|
||||
|
||||
_.each(Meteor.settings.servers, function(endpoints, serverType) {
|
||||
_.each(endpoints, function(endpoint) {
|
||||
var server = _.clone(endpoint);
|
||||
@ -10,28 +13,46 @@ Meteor.startup(function() {
|
||||
Servers.insert(server);
|
||||
});
|
||||
});
|
||||
|
||||
ServersControl.resetCurrentServer();
|
||||
});
|
||||
|
||||
class ServersControl {
|
||||
|
||||
static validateUser() {
|
||||
if (!Meteor.userId()) {
|
||||
throw new Meteor.Error('not-authorized');
|
||||
}
|
||||
}
|
||||
|
||||
static writeCallback(error, affected) {
|
||||
if (error) {
|
||||
throw new Meteor.Error('data-write', error);
|
||||
}
|
||||
}
|
||||
|
||||
static resetCurrentServer() {
|
||||
const currentServer = CurrentServer.findOne();
|
||||
if (currentServer && Servers.find({ _id: currentServer.serverId }).count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newServer = Servers.findOne({
|
||||
origin: 'json',
|
||||
type: Meteor.settings.defaultServiceType || 'dicomWeb'
|
||||
});
|
||||
|
||||
if (newServer) {
|
||||
CurrentServer.remove({});
|
||||
CurrentServer.insert({
|
||||
serverId: newServer._id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static find(query) {
|
||||
return Servers.find(query).fetch();
|
||||
}
|
||||
|
||||
static save(serverSettings) {
|
||||
this.validateUser();
|
||||
var query = {
|
||||
const query = {
|
||||
_id: serverSettings._id
|
||||
};
|
||||
var options = {
|
||||
const options = {
|
||||
upsert: true
|
||||
};
|
||||
|
||||
@ -43,29 +64,28 @@ class ServersControl {
|
||||
}
|
||||
|
||||
static setActive(serverId) {
|
||||
this.validateUser();
|
||||
var query = {
|
||||
_id: Meteor.userId()
|
||||
};
|
||||
var data = {
|
||||
$set: {
|
||||
'profile.activeServer': serverId
|
||||
}
|
||||
};
|
||||
Accounts.users.update(query, data, this.writeCallback);
|
||||
CurrentServer.remove({});
|
||||
CurrentServer.insert({
|
||||
serverId: serverId
|
||||
});
|
||||
}
|
||||
|
||||
static remove(serverId) {
|
||||
this.validateUser();
|
||||
var query = {
|
||||
_id: serverId
|
||||
};
|
||||
return Servers.remove(query, this.writeCallback);
|
||||
|
||||
const removeStatus = Servers.remove(query, this.writeCallback);
|
||||
|
||||
ServersControl.resetCurrentServer();
|
||||
|
||||
return removeStatus;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
serverFind: query => ServersControl.find(query),
|
||||
serverSave: serverSettings => ServersControl.save(serverSettings),
|
||||
serverSetActive: serverId => ServersControl.setActive(serverId),
|
||||
serverRemove: serverId => ServersControl.remove(serverId)
|
||||
@ -1,68 +1,66 @@
|
||||
/**
|
||||
* Parses data returned from a study search and transforms it into
|
||||
* an array of series that are present in the study
|
||||
*
|
||||
* @param resultData
|
||||
* @returns {Array} Series List
|
||||
*/
|
||||
function resultDataToStudyMetadata(resultData) {
|
||||
var seriesMap = {};
|
||||
var seriesList = [];
|
||||
|
||||
resultData.forEach(function(instanceRaw) {
|
||||
var instance = instanceRaw.toObject();
|
||||
// Use seriesMap to cache series data
|
||||
// If the series instance UID has already been used to
|
||||
// process series data, continue using that series
|
||||
var seriesInstanceUid = instance[0x0020000E];
|
||||
var series = seriesMap[seriesInstanceUid];
|
||||
|
||||
// If no series data exists in the seriesMap cache variable,
|
||||
// process any available series data
|
||||
if (!series) {
|
||||
series = {
|
||||
seriesInstanceUid: seriesInstanceUid,
|
||||
seriesNumber: instance[0x00200011],
|
||||
instances: []
|
||||
};
|
||||
|
||||
// Save this data in the seriesMap cache variable
|
||||
seriesMap[seriesInstanceUid] = series;
|
||||
seriesList.push(series);
|
||||
}
|
||||
|
||||
// TODO: [custom-servers] Check which peer it should point to
|
||||
var server = Meteor.settings.servers.dimse[0].peers[0];
|
||||
var host = Meteor.settings.servers.dimse.host,
|
||||
port = Meteor.settings.servers.dimse.port;
|
||||
|
||||
var serverRoot = host + ':' + port;
|
||||
|
||||
var sopInstanceUid = instance[0x00080018];
|
||||
var uri = serverRoot + '/studies/' + studyInstanceUid + '/series/' + seriesInstanceUid + '/instances/' + sopInstanceUid + '/frames/1';
|
||||
|
||||
// Add this instance to the current series
|
||||
series.instances.push({
|
||||
sopClassUid: instance[0x00080016],
|
||||
sopInstanceUid: sopInstanceUid,
|
||||
uri: uri,
|
||||
instanceNumber: instance[0x00200013]
|
||||
});
|
||||
});
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of instances using a DIMSE call
|
||||
* @param studyInstanceUid
|
||||
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
|
||||
*/
|
||||
Services.DIMSE.Instances = function(studyInstanceUid) {
|
||||
//var url = buildUrl(server, studyInstanceUid);
|
||||
var result = DIMSE.retrieveInstances(studyInstanceUid);
|
||||
|
||||
return {
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
seriesList: resultDataToStudyMetadata(result)
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Parses data returned from a study search and transforms it into
|
||||
* an array of series that are present in the study
|
||||
*
|
||||
* @param resultData
|
||||
* @returns {Array} Series List
|
||||
*/
|
||||
function resultDataToStudyMetadata(resultData) {
|
||||
var seriesMap = {};
|
||||
var seriesList = [];
|
||||
|
||||
resultData.forEach(function(instanceRaw) {
|
||||
var instance = instanceRaw.toObject();
|
||||
// Use seriesMap to cache series data
|
||||
// If the series instance UID has already been used to
|
||||
// process series data, continue using that series
|
||||
var seriesInstanceUid = instance[0x0020000E];
|
||||
var series = seriesMap[seriesInstanceUid];
|
||||
|
||||
// If no series data exists in the seriesMap cache variable,
|
||||
// process any available series data
|
||||
if (!series) {
|
||||
series = {
|
||||
seriesInstanceUid: seriesInstanceUid,
|
||||
seriesNumber: instance[0x00200011],
|
||||
instances: []
|
||||
};
|
||||
|
||||
// Save this data in the seriesMap cache variable
|
||||
seriesMap[seriesInstanceUid] = series;
|
||||
seriesList.push(series);
|
||||
}
|
||||
|
||||
// TODO: Check which peer it should point to
|
||||
var server = getCurrentServer().peers[0];
|
||||
|
||||
var serverRoot = server.host + ':' + server.port;
|
||||
|
||||
var sopInstanceUid = instance[0x00080018];
|
||||
var uri = serverRoot + '/studies/' + studyInstanceUid + '/series/' + seriesInstanceUid + '/instances/' + sopInstanceUid + '/frames/1';
|
||||
|
||||
// Add this instance to the current series
|
||||
series.instances.push({
|
||||
sopClassUid: instance[0x00080016],
|
||||
sopInstanceUid: sopInstanceUid,
|
||||
uri: uri,
|
||||
instanceNumber: instance[0x00200013]
|
||||
});
|
||||
});
|
||||
return seriesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of instances using a DIMSE call
|
||||
* @param studyInstanceUid
|
||||
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
|
||||
*/
|
||||
Services.DIMSE.Instances = function(studyInstanceUid) {
|
||||
//var url = buildUrl(server, studyInstanceUid);
|
||||
var result = DIMSE.retrieveInstances(studyInstanceUid);
|
||||
|
||||
return {
|
||||
studyInstanceUid: studyInstanceUid,
|
||||
seriesList: resultDataToStudyMetadata(result)
|
||||
};
|
||||
};
|
||||
|
||||
@ -110,8 +110,8 @@ function resultDataToStudyMetadata(studyInstanceUid, resultData) {
|
||||
};
|
||||
|
||||
// Retrieve the actual data over WADO-URI
|
||||
var server = Meteor.settings.servers.dicomWeb[0];
|
||||
instanceSummary.wadouri = WADOProxy.convertURL(server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + "&contentType=application%2Fdicom", server.requestOptions);
|
||||
var server = getCurrentServer();
|
||||
instanceSummary.wadouri = WADOProxy.convertURL(server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + '&contentType=application%2Fdicom', server.requestOptions);
|
||||
|
||||
series.instances.push(instanceSummary);
|
||||
});
|
||||
@ -127,8 +127,8 @@ function resultDataToStudyMetadata(studyInstanceUid, resultData) {
|
||||
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
|
||||
*/
|
||||
Services.DIMSE.RetrieveMetadata = function(studyInstanceUid) {
|
||||
// TODO: Find active server
|
||||
const activeServer = Meteor.settings.servers.dimse[0].peers[0];
|
||||
// TODO: Check which peer it should point to
|
||||
const activeServer = getCurrentServer().peers[0];
|
||||
const supportsInstanceRetrievalByStudyUid = activeServer.supportsInstanceRetrievalByStudyUid;
|
||||
let results;
|
||||
|
||||
|
||||
38
Packages/worklist/server/services/dimse/setup.js
Normal file
38
Packages/worklist/server/services/dimse/setup.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const setupDIMSE = () => {
|
||||
// Terminate existing DIMSE servers and sockets and clean up the connection object
|
||||
DIMSE.connection.reset();
|
||||
|
||||
// Get the new server configuration
|
||||
const server = getCurrentServer();
|
||||
|
||||
// Stop here if the new server is not of DIMSE type
|
||||
if (server.type !== 'dimse') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if peers were defined in the server configuration and throw an error if not
|
||||
const peers = server.peers;
|
||||
if (!peers || !peers.length) {
|
||||
console.error('dimse-config: ' + 'No DIMSE Peers provided.');
|
||||
throw new Meteor.Error('dimse-config', 'No DIMSE Peers provided.');
|
||||
}
|
||||
|
||||
// Add all the DIMSE peers, establishing the connections
|
||||
console.log('Adding DIMSE peers');
|
||||
try {
|
||||
peers.forEach(peer => DIMSE.connection.addPeer(peer));
|
||||
} catch(error) {
|
||||
console.error('dimse-addPeers: ' + error);
|
||||
throw new Meteor.Error('dimse-addPeers', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Setup the DIMSE connections on startup or when the current server is changed
|
||||
Meteor.startup(() => {
|
||||
CurrentServer.find().observe({
|
||||
added: setupDIMSE,
|
||||
changed: setupDIMSE
|
||||
});
|
||||
});
|
||||
@ -17,6 +17,7 @@
|
||||
}],
|
||||
"dimse": [{
|
||||
"name": "DCM4CHEE_DIMSE",
|
||||
"wadoUriRoot": "http://localhost:8080/dcm4chee-arc/aets/DCM4CHEE/wado",
|
||||
"peers": [
|
||||
{
|
||||
"host": "localhost",
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
],
|
||||
"dimse": [{
|
||||
"name": "OrthancDIMSE",
|
||||
"wadoUriRoot" : "http://52.87.58.190:8043/wado",
|
||||
"peers": [
|
||||
{
|
||||
"host" : "localhost",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
],
|
||||
"dimse" : [{
|
||||
"name": "ORTHANC_DIMSE",
|
||||
"wadoUriRoot": "http://dicom-orthanc.medken.com/wado",
|
||||
"peers": [
|
||||
{
|
||||
"host": "208.100.41.100",
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
],
|
||||
"dimse": [{
|
||||
"name": "ORTHANC_DIMSE",
|
||||
"wadoUriRoot": "http://localhost:8042/wado",
|
||||
"peers": [
|
||||
{
|
||||
"host": "localhost",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user