Improving new study browser and moving studies services to ohif:studies

This commit is contained in:
Bruno Alves de Faria committed 2017-10-25 22:09:08 -02:00
1 parent 0b51a53976
commit eb372bed03
54 files changed
+560 -495

No files matched your search

@@ -1,5 +1,6 @@
import './list.html';
import './list.js';
import './item.html';
import './item.js';
import './item.styl';
import './series.html';
import './series.js';
@@ -1,5 +1,5 @@
<template name="studyBrowserItem">
<div class="study-browser-item {{this.settings.studyItemClass}} {{#if isActive}}active{{/if}} {{#if isLoading}}loading{{/if}}" data-uid="{{this.studyInformation.studyInstanceUid}}">
<div class="study-browser-item {{this.studyItemClass}} {{#if isActive}}active{{/if}} {{#if isLoading}}loading{{/if}}" data-uid="{{this.studyInformation.studyInstanceUid}}">
<div class="study-item">
{{#if isLoading}}
{{>loadingText}}
@@ -14,8 +14,8 @@
</div>
</div>
</div>
{{#if this.settings.studyTemplate}}
{{>Template.dynamic template=this.settings.studyTemplate data=(clone this)}}
{{#if this.studyChildTemplate}}
{{>Template.dynamic template=this.studyChildTemplate data=(clone this)}}
{{/if}}
</div>
</template>
@@ -1,26 +1,52 @@
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
Template.studyBrowserItem.onCreated(() => {
const instance = Template.instance();
instance.loaded = false;
instance.loading = new ReactiveVar(false);
const modality = instance.data.studyInformation.modality || 'UN';
instance.modalities = modality.replace(/\\/g, ' ');
});
Template.studyBrowserItem.events({
'click .study-browser-item'(event, instance) {
const element = event.currentTarget;
'click .study-item'(event, instance) {
if (instance.loading.get()) return;
const { studyClickCallback, studyInformation } = instance.data;
const element = event.currentTarget.parentElement;
const $element = $(element);
$element.trigger('ohif.studies.study.click', instance.data.studyInformation);
const { settings, studyInformation } = instance.data;
if (settings && typeof settings.studyClickCallback) {
settings.studyClickCallback(studyInformation, element);
$element.trigger('ohif.studies.study.click', studyInformation);
const triggerClickCallback = () => {
if (typeof studyClickCallback === 'function') {
studyClickCallback(studyInformation, element);
}
};
if (instance.loaded) {
triggerClickCallback();
} else {
instance.loading.set(true);
OHIF.studies.retrieveStudyMetadata(studyInformation.studyInstanceUid).then(() => {
instance.loaded = true;
instance.loading.set(false);
$element.trigger('ohif.studies.study.load', studyInformation);
triggerClickCallback();
});
}
}
});
Template.studyBrowserItem.helpers({
isLoading() {
return Template.instance().loading.get();
},
modalityStyle() {
// Responsively styles the Modality Acronyms for studies
// with more than one modality
@@ -46,13 +46,25 @@ $spacerY = 12px
theme('background-color', '$activeColor')
border-color: $boxActiveBorderColor
&:not(.active) .studyTimepointThumbnails
&:not(.active) .study-browser-series
max-height: 0 !important
.studyTimepointThumbnails
.study-series-container
opacity: 0
transform(translateY(-100%))
.study-browser-series
overflow: hidden
transform(translateY(0))
transform-origin(50% 0%)
transition($sidebarTransition)
.study-series-container
opacity: 1
transition($sidebarTransition)
transform(translateY(0))
transform-origin(50% 0%)
.study-item-box
border: 1px solid $boxBorderColor
border-radius: 12px
@@ -1,5 +1,5 @@
<template name="studyBrowserList">
<div class="study-browser-list {{this.settings.studyListClass}}">
<div class="study-browser-list {{this.class}}">
{{#each studyInformation in studiesInformation}}
{{>studyBrowserItem (clone this studyInformation=studyInformation)}}
{{/each}}
Whitespace-only changes.
@@ -0,0 +1,9 @@
<template name="studyBrowserSeries">
<div class="study-browser-series">
<div class="study-series-container">
{{#each thumbnail in instance.thumbnails.get}}
{{>thumbnailEntry (clone '' thumbnail=thumbnail)}}
{{/each}}
</div>
</div>
</template>
@@ -0,0 +1,40 @@
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import { OHIF } from 'meteor/ohif:core';
Template.studyBrowserSeries.onCreated(() => {
const instance = Template.instance();
const { studyInformation } = instance.data;
const { studyInstanceUid } = studyInformation;
instance.thumbnails = new ReactiveVar([]);
// Get the study metadata and update the study thumbnails
instance.autorun(() => {
const studyMetadata = OHIF.viewer.Studies.findBy({ studyInstanceUid });
// Defines the resulting thumbnails list
const thumbnails = [];
studyMetadata.displaySets.forEach((stack, thumbnailIndex) => {
thumbnails.push({
thumbnailIndex,
stack
});
});
instance.thumbnails.set(thumbnails);
});
});
Template.studyBrowserSeries.onRendered(() => {
const instance = Template.instance();
// Run this computation every time the thumbnails are changed
instance.autorun(() => {
instance.thumbnails.get();
Tracker.afterFlush(() => {
instance.$('.study-browser-series').adjustMax('height');
});
});
});
@@ -0,0 +1,2 @@
import './retrieveStudiesMetadata';
import './retrieveStudyMetadata';
@@ -0,0 +1,32 @@
import { OHIF } from 'meteor/ohif:core';
/**
* Retrieves metaData for multiple studies at once.
*
* This function calls retrieveStudyMetadata several times, asynchronously,
* and waits for all of the results to be returned.
*
* @param studyInstanceUids The UIDs of the Studies to be retrieved
* @return Promise
*/
OHIF.studies.retrieveStudiesMetadata = (studyInstanceUids, seriesInstanceUids) => {
// Create an empty array to store the Promises for each metaData retrieval call
const promises = [];
// Loop through the array of studyInstanceUids
studyInstanceUids.forEach(function(studyInstanceUid) {
// Send the call and resolve or reject the related promise based on its outcome
const promise = OHIF.studies.retrieveStudyMetadata(studyInstanceUid, seriesInstanceUids);
// Add the current promise to the array of promises
promises.push(promise);
});
// When all of the promises are complete, this callback runs
const promise = Promise.all(promises);
// Warn the error on console if some retrieval failed
promise.catch(error => OHIF.log.warn(error));
return promise;
};
@@ -0,0 +1,3 @@
import './lib';
import './methods';
import './services';
@@ -0,0 +1,18 @@
/**
* Converts the properties to URL query parameters. Based on:
* http://stackoverflow.com/questions/111529/create-query-parameters-in-javascript
*
* @param data
* @returns {string}
*/
encodeQueryData = function(data) {
var ret = [];
for (var d in data) {
if (data[d]) {
ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
}
}
return ret.join('&');
};
@@ -0,0 +1,2 @@
import './remoteGetValue.js';
import './encodeQueryData.js';
@@ -0,0 +1,14 @@
export const parseFloatArray = function(obj) {
var result = [];
if (!obj) {
return result;
}
var objs = obj.split("\\");
for (var i = 0; i < objs.length; i++) {
result.push(parseFloat(objs[i]));
}
return result;
};
@@ -0,0 +1,3 @@
export const remoteGetValue = function(obj) {
return obj ? obj.Value : null;
};
@@ -0,0 +1,32 @@
import { Meteor } from 'meteor/meteor';
import { OHIF } from 'meteor/ohif:core';
Meteor.methods({
/**
* Retrieves Study metadata given a Study Instance UID
* This Meteor method is available from both the client and the server
*/
GetStudyMetadata: function(studyInstanceUid) {
OHIF.log.info('GetStudyMetadata(%s)', studyInstanceUid);
// Get the server data. This is user-defined in the config.json files or through servers
// configuration modal
const server = OHIF.servers.getCurrentServer();
if (!server) {
throw new Meteor.Error('improper-server-config', 'No properly configured server was available over DICOMWeb or DIMSE.');
}
try {
if (server.type === 'dicomWeb') {
return OHIF.studies.services.WADO.RetrieveMetadata(server, studyInstanceUid);
} else if (server.type === 'dimse') {
return OHIF.studies.services.DIMSE.RetrieveMetadata(studyInstanceUid);
}
} catch (error) {
OHIF.log.trace();
throw error;
}
}
});
@@ -0,0 +1 @@
import './getStudyMetadata.js';
@@ -0,0 +1,68 @@
import { OHIF } from 'meteor/ohif:core';
/**
* 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, studyInstanceUid) {
const seriesMap = {};
const seriesList = [];
resultData.forEach(function(instanceRaw) {
const 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
const seriesInstanceUid = instance[0x0020000E];
let 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
const server = OHIF.servers.getCurrentServer().peers[0];
const serverRoot = server.host + ':' + server.port;
const sopInstanceUid = instance[0x00080018];
const uri = serverRoot + '/studies/' + studyInstanceUid + '/series/' + seriesInstanceUid + '/instances/' + sopInstanceUid + '/frames/1';
// Add this instance to the current series
series.instances.push({
sopClassUid: instance[0x00080016],
sopInstanceUid,
uri,
instanceNumber: instance[0x00200013]
});
});
return seriesList;
}
/**
* Retrieve a set of instances using a DIMSE call
* @param studyInstanceUid
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
*/
OHIF.studies.services.DIMSE.Instances = function(studyInstanceUid) {
//var url = buildUrl(server, studyInstanceUid);
const result = DIMSE.retrieveInstances(studyInstanceUid);
return {
studyInstanceUid: studyInstanceUid,
seriesList: resultDataToStudyMetadata(result, studyInstanceUid)
};
};
@@ -0,0 +1,169 @@
import { OHIF } from 'meteor/ohif:core';
import { parseFloatArray } from 'meteor/ohif:studies/imports/server/lib/parseFloatArray';
/**
* Returns the value of the element (e.g. '00280009')
*
* @param element - The group/element of the element (e.g. '00280009')
* @param defaultValue - The default value to return if the element does not exist
* @returns {*}
*/
function getValue(element, defaultValue) {
if (!element || !element.value) {
return defaultValue;
}
return element.value;
}
/**
* 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.
const SourceImageSequence = instance[0x00082112];
if (SourceImageSequence && SourceImageSequence.length) {
return SourceImageSequence[0][0x00081155];
}
}
/**
* Parses result data from a DIMSE search into Study MetaData
* Returns an object populated with study metadata, including the
* series list.
*
* @param studyInstanceUid
* @param resultData
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
*/
function resultDataToStudyMetadata(studyInstanceUid, resultData) {
OHIF.log.info('resultDataToStudyMetadata');
const seriesMap = {};
const seriesList = [];
if (!resultData.length) {
return;
}
const anInstance = resultData[0].toObject();
if (!anInstance) {
return;
}
const studyData = {
seriesList: seriesList,
patientName: anInstance[0x00100010],
patientId: anInstance[0x00100020],
patientBirthDate: anInstance[0x00100030],
patientSex: anInstance[0x00100040],
accessionNumber: anInstance[0x00080050],
studyDate: anInstance[0x00080020],
modalities: anInstance[0x00080061],
studyDescription: anInstance[0x00081030],
imageCount: anInstance[0x00201208],
studyInstanceUid: anInstance[0x0020000D],
institutionName: anInstance[0x00080080]
};
resultData.forEach(function(instanceRaw) {
const instance = instanceRaw.toObject();
const seriesInstanceUid = instance[0x0020000E];
let series = seriesMap[seriesInstanceUid];
if (!series) {
series = {
seriesDescription: instance[0x0008103E],
modality: instance[0x00080060],
seriesInstanceUid: seriesInstanceUid,
seriesNumber: parseFloat(instance[0x00200011]),
instances: []
};
seriesMap[seriesInstanceUid] = series;
seriesList.push(series);
}
const sopInstanceUid = instance[0x00080018];
const instanceSummary = {
imageType: instance[0x00080008],
sopClassUid: instance[0x00080016],
modality: instance[0x00080060],
sopInstanceUid: sopInstanceUid,
instanceNumber: parseFloat(instance[0x00200013]),
imagePositionPatient: instance[0x00200032],
imageOrientationPatient: instance[0x00200037],
frameOfReferenceUID: instance[0x00200052],
sliceThickness: parseFloat(instance[0x00180050]),
sliceLocation: parseFloat(instance[0x00201041]),
tablePosition: parseFloat(instance[0x00189327]),
samplesPerPixel: parseFloat(instance[0x00280002]),
photometricInterpretation: instance[0x00280004],
planarConfiguration: parseFloat(instance[0x00280006]),
rows: parseFloat(instance[0x00280010]),
columns: parseFloat(instance[0x00280011]),
pixelSpacing: instance[0x00280030],
bitsAllocated: parseFloat(instance[0x00280100]),
bitsStored: parseFloat(instance[0x00280101]),
highBit: parseFloat(instance[0x00280102]),
pixelRepresentation: parseFloat(instance[0x00280103]),
windowCenter: instance[0x00281050],
windowWidth: instance[0x00281051],
rescaleIntercept: parseFloat(instance[0x00281052]),
rescaleSlope: parseFloat(instance[0x00281053]),
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
laterality: instance[0x00200062],
viewPosition: instance[0x00185101],
acquisitionDateTime: instance[0x0008002A],
numberOfFrames: parseFloat(instance[0x00280008]),
frameIncrementPointer: getValue(instance[0x00280009]),
frameTime: parseFloat(instance[0x00181063]),
frameTimeVector: parseFloatArray(instance[0x00181065]),
lossyImageCompression: instance[0x00282110],
derivationDescription: instance[0x00282111],
lossyImageCompressionRatio: instance[0x00282112],
lossyImageCompressionMethod: instance[0x00282114],
spacingBetweenSlices: instance[0x00180088],
echoNumber: instance[0x00180086],
contrastBolusAgent: instance[0x00180010]
};
// Retrieve the actual data over WADO-URI
const server = OHIF.servers.getCurrentServer();
const wadouri = `${server.wadoUriRoot}?requestType=WADO&studyUID=${studyInstanceUid}&seriesUID=${seriesInstanceUid}&objectUID=${sopInstanceUid}&contentType=application%2Fdicom`;
instanceSummary.wadouri = WADOProxy.convertURL(wadouri, server);
series.instances.push(instanceSummary);
});
studyData.studyInstanceUid = studyInstanceUid;
return studyData;
}
/**
* Retrieved Study MetaData from a DICOM server using DIMSE
* @param studyInstanceUid
* @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
*/
OHIF.studies.services.DIMSE.RetrieveMetadata = function(studyInstanceUid) {
// TODO: Check which peer it should point to
const activeServer = OHIF.servers.getCurrentServer().peers[0];
const supportsInstanceRetrievalByStudyUid = activeServer.supportsInstanceRetrievalByStudyUid;
let results;
// Check explicitly for a value of false, since this property
// may be left undefined in config files
if (supportsInstanceRetrievalByStudyUid === false) {
results = DIMSE.retrieveInstancesByStudyOnly(studyInstanceUid);
} else {
results = DIMSE.retrieveInstances(studyInstanceUid);
}
return resultDataToStudyMetadata(studyInstanceUid, results);
};
@@ -0,0 +1,40 @@
import { Meteor } from 'meteor/meteor';
import { OHIF } from 'meteor/ohif:core';
import { CurrentServer } from 'meteor/ohif:servers/both/collections';
const setupDIMSE = () => {
// Terminate existing DIMSE servers and sockets and clean up the connection object
DIMSE.connection.reset();
// Get the new server configuration
const server = OHIF.servers.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) {
OHIF.log.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
OHIF.log.info('Adding DIMSE peers');
try {
peers.forEach(peer => DIMSE.connection.addPeer(peer));
} catch(error) {
OHIF.log.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
});
});
@@ -0,0 +1,59 @@
import { OHIF } from 'meteor/ohif:core';
/**
* Parses resulting data from a QIDO call into a set of Study MetaData
*
* @param resultData
* @returns {Array} An array of Study MetaData objects
*/
function resultDataToStudies(resultData) {
var studies = [];
resultData.forEach(function(studyRaw) {
var study = studyRaw.toObject();
studies.push({
studyInstanceUid: study[0x0020000D],
// 00080005 = SpecificCharacterSet
studyDate: study[0x00080020],
studyTime: study[0x00080030],
accessionNumber: study[0x00080050],
referringPhysicianName: study[0x00080090],
// 00081190 = URL
patientName: study[0x00100010],
patientId: study[0x00100020],
patientBirthdate: study[0x00100030],
patientSex: study[0x00100040],
imageCount: study[0x00201208],
studyId: study[0x00200010],
studyDescription: study[0x00081030],
modalities: study[0x00080061]
});
});
return studies;
}
OHIF.studies.services.DIMSE.Studies = function(filter) {
OHIF.log.info('Services.DIMSE.Studies');
let filterStudyDate = '';
if (filter.studyDateFrom && filter.studyDateTo) {
const convertDate = date => moment(date, 'MM/DD/YYYY').format('YYYYMMDD');
const dateFrom = convertDate(filter.studyDateFrom);
const dateTo = convertDate(filter.studyDateTo);
filterStudyDate = `${dateFrom}-${dateTo}`;
}
var parameters = {
0x00100010: filter.patientName,
0x00100020: filter.patientId,
0x00080050: filter.accessionNumber,
0x00080020: filterStudyDate,
0x00081030: filter.studyDescription,
0x00100040: '',
0x00201208: '',
0x00080061: filter.modalitiesInStudy
};
var results = DIMSE.retrieveStudies(parameters);
return resultDataToStudies(results);
};
@@ -0,0 +1,17 @@
import './namespace.js';
// DICOMWeb instance, study, and metadata retrieval
import './qido/instances.js';
import './qido/studies.js';
import './wado/retrieveMetadata.js';
// DIMSE instance, study, and metadata retrieval
import './dimse/instances.js';
import './dimse/studies.js';
import './dimse/retrieveMetadata.js';
import './dimse/setup.js';
// Study, instance, and metadata retrieval from remote PACS via Orthanc as a proxy
import './remote/instances.js';
import './remote/studies.js';
import './remote/retrieveMetadata.js';
@@ -0,0 +1,13 @@
import { OHIF } from 'meteor/ohif:core';
const Services = {};
Services.QIDO = {};
Services.WADO = {};
Services.DIMSE = {};
Services.REMOTE = {};
OHIF.studies.services = Services;
remoteGetValue = function(obj) {
return obj ? obj.Value : null;
};
@@ -0,0 +1,94 @@
import { OHIF } from 'meteor/ohif:core';
/**
* Creates a QIDO URL given the server settings and a study instance UID
* @param server
* @param studyInstanceUid
* @returns {string} URL to be used for QIDO calls
*/
function buildUrl(server, studyInstanceUid) {
return server.qidoRoot + '/studies/' + studyInstanceUid + '/instances';
}
/**
* 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 = DICOMWeb.getString(instance['0020000E']);
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: DICOMWeb.getString(instance['00200011']),
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 = DICOMWeb.getString(instance['00080018']);
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: DICOMWeb.getString(instance['00080016']),
sopInstanceUid: sopInstanceUid,
uri: uri,
instanceNumber: DICOMWeb.getString(instance['00200013'])
});
});
return seriesList;
}
/**
* Retrieve a set of instances using a QIDO call
* @param server
* @param studyInstanceUid
* @throws ECONNREFUSED
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
*/
OHIF.studies.services.QIDO.Instances = function(server, studyInstanceUid) {
var url = buildUrl(server, studyInstanceUid);
try {
var result = DICOMWeb.getJSON(url, server.requestOptions);
return {
wadoUriRoot: server.wadoUriRoot,
studyInstanceUid: studyInstanceUid,
seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
};
} catch (error) {
OHIF.log.trace();
throw error;
}
};
@@ -0,0 +1,106 @@
import { OHIF } from 'meteor/ohif:core';
/**
* Creates a QIDO date string for a date range query
* Assumes the year is positive, at most 4 digits long.
*
* @param date The Date object to be formatted
* @returns {string} The formatted date string
*/
function dateToString(date) {
if(!date) return "";
var year = date.getFullYear().toString();
var month = (date.getMonth() + 1).toString();
var day = date.getDate().toString();
year = "0".repeat(4-year.length).concat(year);
month= "0".repeat(2-month.length).concat(month);
day = "0".repeat(2-day.length).concat(day);
return "".concat(year,month,day);
}
/**
* Produces a QIDO URL given server details and a set of specified search filter
* items
*
* @param server
* @param filter
* @returns {string} The URL with encoded filter query data
*/
function filterToQIDOURL(server, filter) {
var commaSeparatedFields = [
'00081030', // Study Description
'00080060' //Modality
// Add more fields here if you want them in the Study List
].join(',');
var parameters = {
PatientName: filter.patientName,
PatientID: filter.patientId,
AccessionNumber: filter.accessionNumber,
StudyDescription: filter.studyDescription,
ModalitiesInStudy: filter.modalitiesInStudy,
limit: filter.limit,
includefield: server.qidoSupportsIncludeField ? 'all' : commaSeparatedFields
};
// build the StudyDate range parameter
if (filter.studyDateFrom || filter.studyDateTo) {
var date = "".concat(dateToString(new Date(filter.studyDateFrom)), "-", dateToString(new Date(filter.studyDateTo)));
parameters.StudyDate = date;
}
return server.qidoRoot + '/studies?' + encodeQueryData(parameters);
}
/**
* Parses resulting data from a QIDO call into a set of Study MetaData
*
* @param resultData
* @returns {Array} An array of Study MetaData objects
*/
function resultDataToStudies(resultData) {
var studies = [];
if (!resultData || !resultData.length) {
return;
}
resultData.forEach(function(study) {
studies.push({
studyInstanceUid: DICOMWeb.getString(study['0020000D']),
// 00080005 = SpecificCharacterSet
studyDate: DICOMWeb.getString(study['00080020']),
studyTime: DICOMWeb.getString(study['00080030']),
accessionNumber: DICOMWeb.getString(study['00080050']),
referringPhysicianName: DICOMWeb.getString(study['00080090']),
// 00081190 = URL
patientName: DICOMWeb.getName(study['00100010']),
patientId: DICOMWeb.getString(study['00100020']),
patientBirthdate: DICOMWeb.getString(study['00100030']),
patientSex: DICOMWeb.getString(study['00100040']),
studyId: DICOMWeb.getString(study['00200010']),
numberOfStudyRelatedSeries: DICOMWeb.getString(study['00201206']),
numberOfStudyRelatedInstances: DICOMWeb.getString(study['00201208']),
studyDescription: DICOMWeb.getString(study['00081030']),
// modality: DICOMWeb.getString(study['00080060']),
// modalitiesInStudy: DICOMWeb.getString(study['00080061']),
modalities: DICOMWeb.getString(DICOMWeb.getModalities(study['00080060'], study['00080061']))
});
});
return studies;
}
OHIF.studies.services.QIDO.Studies = function(server, filter) {
var url = filterToQIDOURL(server, filter);
try {
var result = DICOMWeb.getJSON(url, server.requestOptions);
return resultDataToStudies(result.data);
} catch (error) {
OHIF.log.trace();
throw error;
}
};
@@ -0,0 +1,85 @@
import { OHIF } from 'meteor/ohif:core';
import { remoteGetValue } from '../../lib/remoteGetValue';
/**
* 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}}
*/
OHIF.studies.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))
};
};
@@ -0,0 +1,142 @@
import { OHIF } from 'meteor/ohif:core';
import { remoteGetValue } from '../../lib/remoteGetValue';
import { parseFloatArray } from '../../lib/parseFloatArray';
/**
* 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']),
acquisitionDateTime: remoteGetValue(instance['0008,002A']),
numberOfFrames: parseFloat(remoteGetValue(instance['0028,0008'])),
frameIncrementPointer: remoteGetValue(instance['0028,0009']),
frameTime: parseFloat(remoteGetValue(instance['0018,1063'])),
frameTimeVector: parseFloatArray(remoteGetValue(instance['0018,1065'])),
echoNumber: remoteGetValue(instance['0018,0086']),
contrastBolusAgent: remoteGetValue(instance['0018,0010'])
};
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: *}}
*/
OHIF.studies.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;
};
@@ -0,0 +1,50 @@
import { OHIF } from 'meteor/ohif:core';
import { remoteGetValue } from '../../lib/remoteGetValue';
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;
}
OHIF.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);
};
@@ -0,0 +1,360 @@
import { OHIF } from 'meteor/ohif:core';
import { parseFloatArray } from '../../lib/parseFloatArray';
/**
* Simple cache schema for retrieved color palettes.
*/
const paletteColorCache = {
count: 0,
maxAge: 24 * 60 * 60 * 1000, // 24h cache?
entries: {},
isValidUID: function (paletteUID) {
return typeof paletteUID === 'string' && paletteUID.length > 0;
},
get: function (paletteUID) {
let entry = null;
if (this.entries.hasOwnProperty(paletteUID)) {
entry = this.entries[paletteUID];
// check how the entry is...
if ((Date.now() - entry.time) > this.maxAge) {
// entry is too old... remove entry.
delete this.entries[paletteUID];
this.count--;
entry = null;
}
}
return entry;
},
add: function (entry) {
if (this.isValidUID(entry.uid)) {
let paletteUID = entry.uid;
if (this.entries.hasOwnProperty(paletteUID) !== true) {
this.count++; // increment cache entry count...
}
entry.time = Date.now();
this.entries[paletteUID] = entry;
// @TODO: Add logic to get rid of old entries and reduce memory usage...
}
}
};
/**
* Creates a URL for a WADO search
*
* @param server
* @param studyInstanceUid
* @returns {string}
*/
function buildUrl(server, studyInstanceUid) {
return server.wadoRoot + '/studies/' + studyInstanceUid + '/metadata';
}
/** Returns a WADO url for an instance
*
* @param studyInstanceUid
* @param seriesInstanceUid
* @param sopInstanceUid
* @returns {string}
*/
function buildInstanceWadoUrl(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid) {
const params = [];
params.push('requestType=WADO');
params.push(`studyUID=${studyInstanceUid}`);
params.push(`seriesUID=${seriesInstanceUid}`);
params.push(`objectUID=${sopInstanceUid}`);
params.push('contentType=application%2Fdicom');
params.push('transferSyntax=*');
return `${server.wadoUriRoot}?${params.join('&')}`;
}
function buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid) {
return `${server.wadoRoot}/studies/${studyInstanceUid}/series/${seriesInstanceUid}/instances/${sopInstanceUid}`
}
function buildInstanceFrameWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame) {
const baseWadoRsUri = buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
frame = frame != null || 1;
return `${baseWadoRsUri}/frames/${frame}`
}
/**
* 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 = instance['00082112'];
if (SourceImageSequence && SourceImageSequence.Value && SourceImageSequence.Value.length) {
return SourceImageSequence.Value[0]['00081155'].Value[0];
}
}
function getPaletteColor(server, instance, tag, lutDescriptor) {
const lut = [];
const numLutEntries = lutDescriptor[0];
const bits = lutDescriptor[2];
const uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server)
const data = DICOMWeb.getBulkData(uri);
for (var i = 0; i < numLutEntries; i++) {
if(bits === 16) {
lut[i] = data.readUInt16LE(i*2);
} else {
lut[i] = data.readUInt8(i);
}
}
return lut;
}
/**
* Fetch palette colors for instances with "PALETTE COLOR" photometricInterpretation.
*
* @param server {Object} Current server;
* @param instance {Object} The retrieved instance metadata;
* @returns {String} The ReferenceSOPInstanceUID
*/
function getPaletteColors(server, instance, lutDescriptor) {
let entry = null,
paletteUID = DICOMWeb.getString(instance['00281199']);
if (paletteColorCache.isValidUID(paletteUID)) {
entry = paletteColorCache.get(paletteUID);
} else {
paletteUID = null;
}
if (!entry) {
// no entry on cache... Fetch remote data.
try {
let r, g, b;
r = getPaletteColor(server, instance, '00281201', lutDescriptor);
g = getPaletteColor(server, instance, '00281202', lutDescriptor);;
b = getPaletteColor(server, instance, '00281203', lutDescriptor);;
entry = { red: r, green: g, blue: b };
if (paletteUID !== null) {
// when paletteUID is present, the entry can be cached...
entry.uid = paletteUID;
paletteColorCache.add(entry);
}
} catch (error) {
OHIF.log.error(`(${error.name}) ${error.message}`);
}
}
return entry;
}
function getFrameIncrementPointer(element) {
const frameIncrementPointerNames = {
'00181065': 'frameTimeVector',
'00181063': 'frameTime'
};
if(!element || !element.Value || !element.Value.length) {
return;
}
const value = element.Value[0];
return frameIncrementPointerNames[value];
}
function getRadiopharmaceuticalInfo(instance) {
const modality = DICOMWeb.getString(instance['00080060']);
if (modality !== 'PT') {
return;
}
const radiopharmaceuticalInfo = instance['00540016'];
if ((radiopharmaceuticalInfo === undefined) || !radiopharmaceuticalInfo.Value || !radiopharmaceuticalInfo.Value.length) {
return;
}
const firstPetRadiopharmaceuticalInfo = radiopharmaceuticalInfo.Value[0];
return {
radiopharmaceuticalStartTime: DICOMWeb.getString(firstPetRadiopharmaceuticalInfo['00181072']),
radionuclideTotalDose: DICOMWeb.getNumber(firstPetRadiopharmaceuticalInfo['00181074']),
radionuclideHalfLife: DICOMWeb.getNumber(firstPetRadiopharmaceuticalInfo['00181075'])
};
}
/**
* 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: DICOMWeb.getName(anInstance['00100010']),
patientId: DICOMWeb.getString(anInstance['00100020']),
patientAge: DICOMWeb.getNumber(anInstance['00101010']),
patientSize: DICOMWeb.getNumber(anInstance['00101020']),
patientWeight: DICOMWeb.getNumber(anInstance['00101030']),
accessionNumber: DICOMWeb.getString(anInstance['00080050']),
studyDate: DICOMWeb.getString(anInstance['00080020']),
modalities: DICOMWeb.getString(anInstance['00080061']),
studyDescription: DICOMWeb.getString(anInstance['00081030']),
imageCount: DICOMWeb.getString(anInstance['00201208']),
studyInstanceUid: DICOMWeb.getString(anInstance['0020000D']),
institutionName: DICOMWeb.getString(anInstance['00080080'])
};
resultData.forEach(function(instance) {
var seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
var series = seriesMap[seriesInstanceUid];
if (!series) {
series = {
seriesDescription: DICOMWeb.getString(instance['0008103E']),
modality: DICOMWeb.getString(instance['00080060']),
seriesInstanceUid: seriesInstanceUid,
seriesNumber: DICOMWeb.getNumber(instance['00200011']),
seriesDate: DICOMWeb.getString(instance['00080021']),
seriesTime: DICOMWeb.getString(instance['00080031']),
instances: []
};
seriesMap[seriesInstanceUid] = series;
seriesList.push(series);
}
var sopInstanceUid = DICOMWeb.getString(instance['00080018']);
const wadouri = buildInstanceWadoUrl(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
const baseWadoRsUri = buildInstanceWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
const wadorsuri = buildInstanceFrameWadoRsUri(server, studyInstanceUid, seriesInstanceUid, sopInstanceUid);
var instanceSummary = {
imageType: DICOMWeb.getString(instance['00080008']),
sopClassUid: DICOMWeb.getString(instance['00080016']),
modality: DICOMWeb.getString(instance['00080060']),
sopInstanceUid: sopInstanceUid,
instanceNumber: DICOMWeb.getNumber(instance['00200013']),
imagePositionPatient: DICOMWeb.getString(instance['00200032']),
imageOrientationPatient: DICOMWeb.getString(instance['00200037']),
frameOfReferenceUID: DICOMWeb.getString(instance['00200052']),
sliceLocation: DICOMWeb.getNumber(instance['00201041']),
samplesPerPixel: DICOMWeb.getNumber(instance['00280002']),
photometricInterpretation: DICOMWeb.getString(instance['00280004']),
planarConfiguration: DICOMWeb.getNumber(instance['00280006']),
rows: DICOMWeb.getNumber(instance['00280010']),
columns: DICOMWeb.getNumber(instance['00280011']),
pixelSpacing: DICOMWeb.getString(instance['00280030']),
pixelAspectRatio: DICOMWeb.getString(instance['00280034']),
bitsAllocated: DICOMWeb.getNumber(instance['00280100']),
bitsStored: DICOMWeb.getNumber(instance['00280101']),
highBit: DICOMWeb.getNumber(instance['00280102']),
pixelRepresentation: DICOMWeb.getNumber(instance['00280103']),
smallestPixelValue: DICOMWeb.getNumber(instance['00280106']),
largestPixelValue: DICOMWeb.getNumber(instance['00280107']),
windowCenter: DICOMWeb.getString(instance['00281050']),
windowWidth: DICOMWeb.getString(instance['00281051']),
rescaleIntercept: DICOMWeb.getNumber(instance['00281052']),
rescaleSlope: DICOMWeb.getNumber(instance['00281053']),
rescaleType: DICOMWeb.getNumber(instance['00281054']),
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
laterality: DICOMWeb.getString(instance['00200062']),
viewPosition: DICOMWeb.getString(instance['00185101']),
acquisitionDateTime: DICOMWeb.getString(instance['0008002A']),
numberOfFrames: DICOMWeb.getNumber(instance['00280008']),
frameIncrementPointer: getFrameIncrementPointer(instance['00280009']),
frameTime: DICOMWeb.getNumber(instance['00181063']),
frameTimeVector: parseFloatArray(DICOMWeb.getString(instance['00181065'])),
sliceThickness: DICOMWeb.getNumber(instance['00180050']),
lossyImageCompression: DICOMWeb.getString(instance['00282110']),
derivationDescription: DICOMWeb.getString(instance['00282111']),
lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']),
lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']),
echoNumber: DICOMWeb.getString(instance['00180086']),
contrastBolusAgent: DICOMWeb.getString(instance['00180010']),
radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance),
baseWadoRsUri: baseWadoRsUri,
wadouri: WADOProxy.convertURL(wadouri, server),
wadorsuri: WADOProxy.convertURL(wadorsuri, server),
imageRendering: server.imageRendering,
thumbnailRendering: server.thumbnailRendering
};
// Get additional information if the instance uses "PALETTE COLOR" photometric interpretation
if (instanceSummary.photometricInterpretation === 'PALETTE COLOR') {
const redPaletteColorLookupTableDescriptor = parseFloatArray(DICOMWeb.getString(instance['00281101']));
const greenPaletteColorLookupTableDescriptor = parseFloatArray(DICOMWeb.getString(instance['00281102']));
const bluePaletteColorLookupTableDescriptor = parseFloatArray(DICOMWeb.getString(instance['00281103']));
const palettes = getPaletteColors(server, instance, redPaletteColorLookupTableDescriptor);
if (palettes) {
if (palettes.uid) {
instanceSummary.paletteColorLookupTableUID = palettes.uid;
}
instanceSummary.redPaletteColorLookupTableData = palettes.red;
instanceSummary.greenPaletteColorLookupTableData = palettes.green;
instanceSummary.bluePaletteColorLookupTableData = palettes.blue;
instanceSummary.redPaletteColorLookupTableDescriptor = redPaletteColorLookupTableDescriptor;
instanceSummary.greenPaletteColorLookupTableDescriptor = greenPaletteColorLookupTableDescriptor;
instanceSummary.bluePaletteColorLookupTableDescriptor = bluePaletteColorLookupTableDescriptor;
}
}
series.instances.push(instanceSummary);
});
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: *}}
*/
OHIF.studies.services.WADO.RetrieveMetadata = function(server, studyInstanceUid) {
var url = buildUrl(server, studyInstanceUid);
try {
var result = DICOMWeb.getJSON(url, server.requestOptions);
var study = resultDataToStudyMetadata(server, studyInstanceUid, result.data);
if (!study) {
study = {};
}
study.wadoUriRoot = server.wadoUriRoot;
study.studyInstanceUid = studyInstanceUid;
return study;
} catch (error) {
OHIF.log.trace();
throw error;
}
};