ohif-viewer/Packages/ohif-study-list/server/services/wado/retrieveMetadata.js

354 lines
14 KiB
JavaScript

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) {
console.log(`(${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: *}}
*/
Services.WADO.RetrieveMetadata = function(server, studyInstanceUid) {
var url = buildUrl(server, studyInstanceUid);
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;
};