orthanc proxy
This commit is contained in:
parent
e354c6c577
commit
94076f6263
15
Packages/orthanc-remote/package.js
Executable file
15
Packages/orthanc-remote/package.js
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
Package.describe({
|
||||||
|
name: "orthancremote",
|
||||||
|
summary: "Orthanc Remote",
|
||||||
|
version: '0.0.1'
|
||||||
|
});
|
||||||
|
|
||||||
|
Package.onUse(function (api) {
|
||||||
|
api.use("http");
|
||||||
|
|
||||||
|
api.addFiles('server/require.js', 'server');
|
||||||
|
api.addFiles('server/namespace.js', 'server');
|
||||||
|
api.addFiles('server/lib.js', 'server');
|
||||||
|
|
||||||
|
api.export("OrthancRemote", 'server');
|
||||||
|
});
|
||||||
115
Packages/orthanc-remote/server/lib.js
Executable file
115
Packages/orthanc-remote/server/lib.js
Executable file
@ -0,0 +1,115 @@
|
|||||||
|
|
||||||
|
OrthancRemote.prototype.findStudies = function(modality, params) {
|
||||||
|
return this.query(modality, 'Study', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.findSeries = function(modality, studyId, params) {
|
||||||
|
params = Object.assign({StudyInstanceUID : studyId || ''}, params);
|
||||||
|
|
||||||
|
return this.query(modality, 'Series', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.findInstances = function(modality, studyId, seriesId, params) {
|
||||||
|
params = Object.assign({StudyInstanceUID : studyId || '', SeriesInstanceUID : seriesId || ''}, params);
|
||||||
|
|
||||||
|
return this.query(modality, 'Instance', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.query = function(modality, level, params) {
|
||||||
|
var url = this.root + '/modalities/' + modality + '/query',
|
||||||
|
postData = {Level : level, Query : params || {}};
|
||||||
|
|
||||||
|
var result = HTTP.call('POST', url, {content : JSON.stringify(postData)});
|
||||||
|
return this.getAnswersFromQuery(result.data['ID']);
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.getAnswersFromQuery = function(id) {
|
||||||
|
var url = this.root + '/queries/' + id + '/answers',
|
||||||
|
result = HTTP.call('GET', url);
|
||||||
|
|
||||||
|
var resultCount = result.data.length, answers = [], i = 0;
|
||||||
|
while (i < resultCount) {
|
||||||
|
var contentUrl = this.root + '/queries/' + id + '/answers/' + result.data[i] + '/content';
|
||||||
|
//fetch the result
|
||||||
|
var contentResult = HTTP.call('GET', contentUrl);
|
||||||
|
answers.push(contentResult.data);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
query : id,
|
||||||
|
length : resultCount,
|
||||||
|
results : answers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.retrieveAnswer = function(query, index) {
|
||||||
|
var retrieveUrl = this.root + '/queries/' + query + '/answers/' + index + '/retrieve';
|
||||||
|
|
||||||
|
var result = HTTP.call('POST', retrieveUrl, {content : this.localAE});
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.retrieveStudy = function(modality, studyInstanceUID) {
|
||||||
|
var id = this.findStudyID(studyInstanceUID);
|
||||||
|
if (id === null) {
|
||||||
|
//retrieve the study
|
||||||
|
console.log('RetrieveStudy', studyInstanceUID);
|
||||||
|
var result = this.findStudies(modality, {"StudyInstanceUID" : studyInstanceUID});
|
||||||
|
if (result.results.length < 1) {
|
||||||
|
throw new Meteor.Error(404, "No such study");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.retrieveAnswer(result.query, 0);
|
||||||
|
//check again
|
||||||
|
id = this.findStudyID(studyInstanceUID);
|
||||||
|
if (id === null) {
|
||||||
|
throw new Meteor.Error(500, "Failed to retrieve remote study");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.retrieveMetadata = function(modality, studyInstanceUID) {
|
||||||
|
var studyId = this.retrieveStudy(modality, studyInstanceUID),
|
||||||
|
instances = this.getInstancesByStudy(studyId);
|
||||||
|
|
||||||
|
var results = [], length = instances.length;
|
||||||
|
for (var i = 0;i < length;i++) {
|
||||||
|
var instanceId = instances[i].ID, metadata = this.getInstanceMetadata(instanceId);
|
||||||
|
metadata['xxxx,0001'] = {Value : instanceId};
|
||||||
|
|
||||||
|
results.push(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.getInstancesByStudy = function(studyId) {
|
||||||
|
var url = this.root + '/studies/' + studyId + '/instances';
|
||||||
|
|
||||||
|
var result = this._getCall(url);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.getInstanceMetadata = function(instanceId) {
|
||||||
|
var url = this.root + '/instances/' + instanceId + '/tags';
|
||||||
|
|
||||||
|
return this._getCall(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype.findStudyID = function(uid) {
|
||||||
|
var url = this.root + '/tools/lookup';
|
||||||
|
|
||||||
|
var result = HTTP.call('POST', url, {content : uid}), len = result.data.length;
|
||||||
|
for (var i = 0;i < len;i++) {
|
||||||
|
if (result.data[i].Type == 'Study') {
|
||||||
|
return result.data[i].ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
OrthancRemote.prototype._getCall = function(url) {
|
||||||
|
return HTTP.call('GET', url).data;
|
||||||
|
}
|
||||||
4
Packages/orthanc-remote/server/namespace.js
Executable file
4
Packages/orthanc-remote/server/namespace.js
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
OrthancRemote = function(root, localAE){
|
||||||
|
this.root = root || 'http://localhost:8042';
|
||||||
|
this.localAE = localAE || 'ORTHANCLOCAL';
|
||||||
|
};
|
||||||
1
Packages/orthanc-remote/server/require.js
Executable file
1
Packages/orthanc-remote/server/require.js
Executable file
@ -0,0 +1 @@
|
|||||||
|
//http = Npm.require("http");
|
||||||
@ -1,4 +1,9 @@
|
|||||||
Services = {};
|
Services = {};
|
||||||
Services.QIDO = {};
|
Services.QIDO = {};
|
||||||
Services.WADO = {};
|
Services.WADO = {};
|
||||||
Services.DIMSE = {};
|
Services.DIMSE = {};
|
||||||
|
Services.REMOTE = {};
|
||||||
|
|
||||||
|
remoteGetValue = function(obj) {
|
||||||
|
return obj ? obj.Value : null;
|
||||||
|
}
|
||||||
|
|||||||
82
Packages/viewerbase/server/services/remote/instances.js
Executable file
82
Packages/viewerbase/server/services/remote/instances.js
Executable file
@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Parses data returned from a QIDO search and transforms it into
|
||||||
|
* an array of series that are present in the study
|
||||||
|
*
|
||||||
|
* @param server The DICOM server
|
||||||
|
* @param studyInstanceUid
|
||||||
|
* @param resultData
|
||||||
|
* @returns {Array} Series List
|
||||||
|
*/
|
||||||
|
function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||||
|
var seriesMap = {};
|
||||||
|
var seriesList = [];
|
||||||
|
|
||||||
|
resultData.forEach(function(instance) {
|
||||||
|
// 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 = remoteGetValue(instance['0020,000e']);
|
||||||
|
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 : parseFloat(remoteGetValue(instance['0020,0011'])),
|
||||||
|
instances: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save this data in the seriesMap cache variable
|
||||||
|
seriesMap[seriesInstanceUid] = series;
|
||||||
|
seriesList.push(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The uri for the dicomweb
|
||||||
|
// NOTE: DCM4CHEE seems to return the data zipped
|
||||||
|
// NOTE: Orthanc returns the data with multi-part mime which cornerstoneWADOImageLoader doesn't
|
||||||
|
// know how to parse yet
|
||||||
|
//var uri = DICOMWeb.getString(instance['00081190']);
|
||||||
|
//uri = uri.replace('wado-rs', 'dicom-web');
|
||||||
|
|
||||||
|
// manually create a WADO-URI from the UIDs
|
||||||
|
// NOTE: Haven't been able to get Orthanc's WADO-URI to work yet - maybe its not configured?
|
||||||
|
var sopInstanceUid = remoteGetValue(instance['0008,0018']);
|
||||||
|
var uri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + "&contentType=application%2Fdicom";
|
||||||
|
|
||||||
|
// Add this instance to the current series
|
||||||
|
series.instances.push({
|
||||||
|
sopClassUid: remoteGetValue(instance['0008,0016']),
|
||||||
|
sopInstanceUid: sopInstanceUid,
|
||||||
|
uri: uri,
|
||||||
|
instanceNumber: parseFloat(remoteGetValue(instance['0020,0013']))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return seriesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a set of instances using a QIDO call
|
||||||
|
* @param server
|
||||||
|
* @param studyInstanceUid
|
||||||
|
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
|
||||||
|
*/
|
||||||
|
Services.REMOTE.Instances = function(server, studyInstanceUid) {
|
||||||
|
var parameters = {
|
||||||
|
PatientName: "",
|
||||||
|
PatientID: "",
|
||||||
|
AccessionNumber: "",
|
||||||
|
SeriesInstanceUID: "",
|
||||||
|
SeriesNumber : "",
|
||||||
|
SOPClassUID : "",
|
||||||
|
InstanceNumber : ""
|
||||||
|
};
|
||||||
|
|
||||||
|
var remote = new OrthancRemote(server.root, server.sourceAE);
|
||||||
|
|
||||||
|
return {
|
||||||
|
wadoUriRoot: server.wadoUriRoot,
|
||||||
|
studyInstanceUid: studyInstanceUid,
|
||||||
|
seriesList: resultDataToStudyMetadata(server, studyInstanceUid, remote.findInstances(server.modality, studyInstanceUid, null, parameters))
|
||||||
|
};
|
||||||
|
};
|
||||||
133
Packages/viewerbase/server/services/remote/retrieveMetadata.js
Executable file
133
Packages/viewerbase/server/services/remote/retrieveMetadata.js
Executable file
@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Parses the SourceImageSequence, if it exists, in order
|
||||||
|
* to return a ReferenceSOPInstanceUID. The ReferenceSOPInstanceUID
|
||||||
|
* is used to refer to this image in any accompanying DICOM-SR documents.
|
||||||
|
*
|
||||||
|
* @param instance
|
||||||
|
* @returns {String} The ReferenceSOPInstanceUID
|
||||||
|
*/
|
||||||
|
function getSourceImageInstanceUid(instance) {
|
||||||
|
// TODO= Parse the whole Source Image Sequence
|
||||||
|
// This is a really poor workaround for now.
|
||||||
|
// Later we should probably parse the whole sequence.
|
||||||
|
var SourceImageSequence = remoteGetValue(instance['0008,2112']);
|
||||||
|
if (SourceImageSequence && SourceImageSequence.Value && SourceImageSequence.Value.length) {
|
||||||
|
return SourceImageSequence.Value[0]['0008,1155'].Value[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses result data from a WADO search into Study MetaData
|
||||||
|
* Returns an object populated with study metadata, including the
|
||||||
|
* series list.
|
||||||
|
*
|
||||||
|
* @param server
|
||||||
|
* @param studyInstanceUid
|
||||||
|
* @param resultData
|
||||||
|
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
|
||||||
|
*/
|
||||||
|
function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
|
||||||
|
var seriesMap = {};
|
||||||
|
var seriesList = [];
|
||||||
|
|
||||||
|
if (!resultData.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var anInstance = resultData[0];
|
||||||
|
if (!anInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var studyData = {
|
||||||
|
seriesList: seriesList,
|
||||||
|
patientName: remoteGetValue(anInstance['0010,0010']),
|
||||||
|
patientId: remoteGetValue(anInstance['0010,0020']),
|
||||||
|
accessionNumber: remoteGetValue(anInstance['0008,0050']),
|
||||||
|
studyDate: remoteGetValue(anInstance['0008,0020']),
|
||||||
|
modalities: remoteGetValue(anInstance['0008,0061']),
|
||||||
|
studyDescription: remoteGetValue(anInstance['0008,1030']),
|
||||||
|
imageCount: remoteGetValue(anInstance['0020,1208']),
|
||||||
|
studyInstanceUid: remoteGetValue(anInstance['0020,000d'])
|
||||||
|
};
|
||||||
|
|
||||||
|
resultData.forEach(function(instance) {
|
||||||
|
var seriesInstanceUid = remoteGetValue(instance['0020,000e']);
|
||||||
|
var series = seriesMap[seriesInstanceUid];
|
||||||
|
if (!series) {
|
||||||
|
series = {
|
||||||
|
seriesDescription: remoteGetValue(instance['0008,103e']),
|
||||||
|
modality: remoteGetValue(instance['0008,0060']),
|
||||||
|
seriesInstanceUid: seriesInstanceUid,
|
||||||
|
seriesNumber: parseFloat(remoteGetValue(instance['0020,0011'])),
|
||||||
|
instances: []
|
||||||
|
};
|
||||||
|
seriesMap[seriesInstanceUid] = series;
|
||||||
|
seriesList.push(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sopInstanceUid = remoteGetValue(instance['0008,0018']);
|
||||||
|
|
||||||
|
var instanceSummary = {
|
||||||
|
imageType: remoteGetValue(instance['0008,0008']),
|
||||||
|
sopClassUid: remoteGetValue(instance['0008,0016']),
|
||||||
|
sopInstanceUid: sopInstanceUid,
|
||||||
|
instanceNumber: parseFloat(remoteGetValue(instance['0020,0013'])),
|
||||||
|
imagePositionPatient: remoteGetValue(instance['0020,0032']),
|
||||||
|
imageOrientationPatient: remoteGetValue(instance['0020,0037']),
|
||||||
|
frameOfReferenceUID: remoteGetValue(instance['0020,0052']),
|
||||||
|
sliceLocation: parseFloat(remoteGetValue(instance['0020,1041'])),
|
||||||
|
samplesPerPixel: parseFloat(remoteGetValue(instance['0028,0002'])),
|
||||||
|
photometricInterpretation: remoteGetValue(instance['0028,0004']),
|
||||||
|
rows: parseFloat(remoteGetValue(instance['0028,0010'])),
|
||||||
|
columns: parseFloat(remoteGetValue(instance['0028,0011'])),
|
||||||
|
pixelSpacing: remoteGetValue(instance['0028,0030']),
|
||||||
|
bitsAllocated: parseFloat(remoteGetValue(instance['0028,0100'])),
|
||||||
|
bitsStored: parseFloat(remoteGetValue(instance['0028,0101'])),
|
||||||
|
highBit: parseFloat(remoteGetValue(instance['0028,0102'])),
|
||||||
|
pixelRepresentation: parseFloat(remoteGetValue(instance['0028,0103'])),
|
||||||
|
windowCenter: remoteGetValue(instance['0028,1050']),
|
||||||
|
windowWidth: remoteGetValue(instance['0028,1051']),
|
||||||
|
rescaleIntercept: parseFloat(remoteGetValue(instance['0028,1052'])),
|
||||||
|
rescaleSlope: parseFloat(remoteGetValue(instance['0028,1053'])),
|
||||||
|
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
|
||||||
|
laterality: remoteGetValue(instance['0020,0062']),
|
||||||
|
viewPosition: remoteGetValue(instance['0018,5101']),
|
||||||
|
numFrames: parseFloat(remoteGetValue(instance['0028,0008'])),
|
||||||
|
frameRate: parseFloat(remoteGetValue(instance['0018,1063']))
|
||||||
|
};
|
||||||
|
|
||||||
|
var iid = instance['xxxx,0001'].Value;
|
||||||
|
if (server.imageRendering === 'wadouri') {
|
||||||
|
instanceSummary.wadouri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + "&contentType=application%2Fdicom";
|
||||||
|
} else if (server.imageRendering == 'orthanc') {
|
||||||
|
instanceSummary.wadouri = server.root + '/instances/' + iid + '/file';
|
||||||
|
} else {
|
||||||
|
instanceSummary.wadorsuri = server.wadoRoot + '/studies/' + studyInstanceUid + '/series/' + seriesInstanceUid + '/instances/' + sopInstanceUid + '/frames/1';
|
||||||
|
}
|
||||||
|
|
||||||
|
series.instances.push(instanceSummary);
|
||||||
|
});
|
||||||
|
console.log(studyData.seriesList[0].instances);
|
||||||
|
return studyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieved Study MetaData from a DICOM server using a WADO call
|
||||||
|
* @param server
|
||||||
|
* @param studyInstanceUid
|
||||||
|
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
|
||||||
|
*/
|
||||||
|
Services.REMOTE.RetrieveMetadata = function(server, studyInstanceUid) {
|
||||||
|
var remote = new OrthancRemote(server.root, server.sourceAE);
|
||||||
|
|
||||||
|
var study = resultDataToStudyMetadata(server, studyInstanceUid, remote.retrieveMetadata(server.modality, studyInstanceUid));
|
||||||
|
if (!study) {
|
||||||
|
study = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
study.wadoUriRoot = server.wadoUriRoot;
|
||||||
|
study.studyInstanceUid = studyInstanceUid;
|
||||||
|
|
||||||
|
return study;
|
||||||
|
};
|
||||||
47
Packages/viewerbase/server/services/remote/studies.js
Executable file
47
Packages/viewerbase/server/services/remote/studies.js
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
function resultDataToStudies(resultData) {
|
||||||
|
var studies = [];
|
||||||
|
|
||||||
|
resultData.forEach(function(study) {
|
||||||
|
studies.push({
|
||||||
|
studyInstanceUid: remoteGetValue(study['0020,000d']),
|
||||||
|
// 00080005 = SpecificCharacterSet
|
||||||
|
studyDate: remoteGetValue(study['0008,0020']),
|
||||||
|
studyTime: remoteGetValue(study['0008,0030']),
|
||||||
|
accessionNumber: remoteGetValue(study['0008,0050']),
|
||||||
|
referringPhysicianName: remoteGetValue(study['0008,0090']),
|
||||||
|
// 00081190 = URL
|
||||||
|
patientName: remoteGetValue(study['0010,0010']),
|
||||||
|
patientId: remoteGetValue(study['0010,0020']),
|
||||||
|
patientBirthdate: remoteGetValue(study['0010,0030']),
|
||||||
|
patientSex: remoteGetValue(study['0010,0040']),
|
||||||
|
studyId: remoteGetValue(study['0020,0010']),
|
||||||
|
numberOfStudyRelatedSeries: parseFloat(remoteGetValue(study['0020,1206'])),
|
||||||
|
numberOfStudyRelatedInstances: parseFloat(remoteGetValue(study['0020,1208'])),
|
||||||
|
studyDescription: remoteGetValue(study['0008,1030']),
|
||||||
|
modalities: remoteGetValue(study['0008,0061'])
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return studies;
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.REMOTE.Studies = function(server, filter) {
|
||||||
|
var parameters = {
|
||||||
|
PatientName: filter.patientName ? filter.patientName : "",
|
||||||
|
PatientID: filter.patientId,
|
||||||
|
AccessionNumber: filter.accessionNumber ? filter.accessionNumber : "",
|
||||||
|
StudyDescription: "",
|
||||||
|
StudyDate : "",
|
||||||
|
StudyTime : "",
|
||||||
|
ReferringPhysicianName : "",
|
||||||
|
PatientBirthDate : "",
|
||||||
|
PatientSex : "",
|
||||||
|
StudyID : "",
|
||||||
|
NumberOfStudyRelatedSeries : "",
|
||||||
|
NumberOfStudyRelatedInstances : "",
|
||||||
|
ModalitiesInStudy : ""
|
||||||
|
};
|
||||||
|
var remote = new OrthancRemote(server.root, server.sourceAE);
|
||||||
|
var data = remote.findStudies(server.modality, parameters);
|
||||||
|
|
||||||
|
return resultDataToStudies(data.results);
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user