Fix palette colour display in OHIF v3 (#2731)
* fix(OHIFv3):Fix palette colour data display * PR comments - added params description
This commit is contained in:
parent
3184e5572f
commit
f3822f98a3
@ -162,6 +162,7 @@ const sopClassDictionary = {
|
||||
MRSpectroscopyStorage: '1.2.840.10008.5.1.4.1.1.4.2',
|
||||
EnhancedMRColorImageStorage: '1.2.840.10008.5.1.4.1.1.4.3',
|
||||
LegacyConvertedEnhancedMRImageStorage: '1.2.840.10008.5.1.4.1.1.4.4',
|
||||
UltrasoundImageStorageRET: '1.2.840.10008.5.1.4.1.1.6',
|
||||
UltrasoundImageStorage: '1.2.840.10008.5.1.4.1.1.6.1',
|
||||
EnhancedUSVolumeStorage: '1.2.840.10008.5.1.4.1.1.6.2',
|
||||
SecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7',
|
||||
@ -288,6 +289,7 @@ const sopClassUids = [
|
||||
sopClassDictionary.EnhancedMRColorImageStorage,
|
||||
sopClassDictionary.LegacyConvertedEnhancedMRImageStorage,
|
||||
sopClassDictionary.UltrasoundImageStorage,
|
||||
sopClassDictionary.UltrasoundImageStorageRET,
|
||||
sopClassDictionary.SecondaryCaptureImageStorage,
|
||||
sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage,
|
||||
sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage,
|
||||
|
||||
@ -2,6 +2,7 @@ import queryString from 'query-string';
|
||||
import dicomParser from 'dicom-parser';
|
||||
import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacingInformation';
|
||||
import DicomMetadataStore from '../services/DicomMetadataStore';
|
||||
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
|
||||
|
||||
class MetadataProvider {
|
||||
constructor() {
|
||||
@ -20,46 +21,6 @@ class MetadataProvider {
|
||||
});
|
||||
}
|
||||
|
||||
/*async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) {
|
||||
let dicomJSONDataset;
|
||||
|
||||
// If Arraybuffer, parse to DICOMJSON before naturalizing.
|
||||
if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) {
|
||||
const dicomData = DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer);
|
||||
|
||||
dicomJSONDataset = dicomData.dict;
|
||||
} else {
|
||||
dicomJSONDataset = dicomJSONDatasetOrP10ArrayBuffer;
|
||||
}
|
||||
|
||||
// Check if dataset is already naturalized.
|
||||
|
||||
let naturalizedDataset;
|
||||
|
||||
if (dicomJSONDataset['SeriesInstanceUID'] === undefined) {
|
||||
naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomJSONDataset
|
||||
);
|
||||
} else {
|
||||
naturalizedDataset = dicomJSONDataset;
|
||||
}
|
||||
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
} = naturalizedDataset;
|
||||
|
||||
const study = this._getAndCacheStudy(StudyInstanceUID);
|
||||
const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID);
|
||||
const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID);
|
||||
|
||||
Object.assign(instance, naturalizedDataset);
|
||||
|
||||
await this._checkBulkDataAndInlineBinaries(instance, options.server);
|
||||
|
||||
return instance;
|
||||
}*/
|
||||
|
||||
addImageIdToUIDs(imageId, uids) {
|
||||
// This method is a fallback for when you don't have WADO-URI or WADO-RS.
|
||||
@ -69,40 +30,6 @@ class MetadataProvider {
|
||||
this.imageIdToUIDs.set(imageId, uids);
|
||||
}
|
||||
|
||||
// _getAndCacheStudy(StudyInstanceUID) {
|
||||
// const studies = this.studies;
|
||||
|
||||
// let study = studies.get(StudyInstanceUID);
|
||||
|
||||
// if (!study) {
|
||||
// study = { series: new Map() };
|
||||
// studies.set(StudyInstanceUID, study);
|
||||
// }
|
||||
|
||||
// return study;
|
||||
// }
|
||||
// _getAndCacheSeriesFromStudy(study, SeriesInstanceUID) {
|
||||
// let series = study.series.get(SeriesInstanceUID);
|
||||
|
||||
// if (!series) {
|
||||
// series = { instances: new Map() };
|
||||
// study.series.set(SeriesInstanceUID, series);
|
||||
// }
|
||||
|
||||
// return series;
|
||||
// }
|
||||
|
||||
// _getAndCacheInstanceFromStudy(series, SOPInstanceUID) {
|
||||
// let instance = series.instances.get(SOPInstanceUID);
|
||||
|
||||
// if (!instance) {
|
||||
// instance = {};
|
||||
// series.instances.set(SOPInstanceUID, instance);
|
||||
// }
|
||||
|
||||
// return instance;
|
||||
// }
|
||||
|
||||
_getInstance(imageId) {
|
||||
const uids = this._getUIDsFromImageID(imageId);
|
||||
|
||||
@ -254,14 +181,14 @@ class MetadataProvider {
|
||||
bluePaletteColorLookupTableDescriptor: validNumber(
|
||||
instance.BluePaletteColorLookupTableDescriptor
|
||||
),
|
||||
redPaletteColorLookupTableData: validNumber(
|
||||
instance.RedPaletteColorLookupTableData
|
||||
redPaletteColorLookupTableData: fetchPaletteColorLookupTableData(
|
||||
instance, "RedPaletteColorLookupTableData", "RedPaletteColorLookupTableDescriptor"
|
||||
),
|
||||
greenPaletteColorLookupTableData: validNumber(
|
||||
instance.GreenPaletteColorLookupTableData
|
||||
greenPaletteColorLookupTableData: fetchPaletteColorLookupTableData(
|
||||
instance, "GreenPaletteColorLookupTableData", "GreenPaletteColorLookupTableDescriptor"
|
||||
),
|
||||
bluePaletteColorLookupTableData: validNumber(
|
||||
instance.BluePaletteColorLookupTableData
|
||||
bluePaletteColorLookupTableData: fetchPaletteColorLookupTableData(
|
||||
instance, "BluePaletteColorLookupTableData", "BluePaletteColorLookupTableDescriptor"
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@ -1,165 +1,72 @@
|
||||
import { api } from 'dicomweb-client';
|
||||
import DICOMWeb from '../../DICOMWeb';
|
||||
import str2ab from '../str2ab';
|
||||
|
||||
import errorHandler from '../../errorHandler';
|
||||
|
||||
export default async function fetchPaletteColorLookupTableData(
|
||||
instance,
|
||||
server
|
||||
/**
|
||||
* Gets the palette color data for the specified tag - red/green/blue,
|
||||
* either from the given UID or from the tag itself.
|
||||
* Returns an array if the data is immediately available, or a promise
|
||||
* which resolves to the data if the data needs to be loaded.
|
||||
* Returns undefined if the palette isn't specified.
|
||||
*
|
||||
* @param {*} item containing the palette colour data and description
|
||||
* @param {*} tag is the tag for the palette data
|
||||
* @param {*} descriptorTag is the tag for the descriptor
|
||||
* @returns Array view containing the palette data, or a promise to return one.
|
||||
* Returns undefined if the palette data is absent.
|
||||
*/
|
||||
export default function fetchPaletteColorLookupTableData(
|
||||
item, tag, descriptorTag
|
||||
) {
|
||||
const {
|
||||
PaletteColorLookupTableUID,
|
||||
RedPaletteColorLookupTableDescriptor,
|
||||
GreenPaletteColorLookupTableDescriptor,
|
||||
BluePaletteColorLookupTableDescriptor,
|
||||
RedPaletteColorLookupTableData,
|
||||
GreenPaletteColorLookupTableData,
|
||||
BluePaletteColorLookupTableData,
|
||||
} = instance;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let entry;
|
||||
if (_paletteColorCache.isValidUID(PaletteColorLookupTableUID)) {
|
||||
entry = _paletteColorCache.get(PaletteColorLookupTableUID);
|
||||
|
||||
if (entry) {
|
||||
return resolve(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// no entry in cache... Fetch remote data.
|
||||
const promises = [
|
||||
_getPaletteColor(
|
||||
server,
|
||||
RedPaletteColorLookupTableData,
|
||||
RedPaletteColorLookupTableDescriptor
|
||||
),
|
||||
_getPaletteColor(
|
||||
server,
|
||||
GreenPaletteColorLookupTableData,
|
||||
GreenPaletteColorLookupTableDescriptor
|
||||
),
|
||||
_getPaletteColor(
|
||||
server,
|
||||
BluePaletteColorLookupTableData,
|
||||
BluePaletteColorLookupTableDescriptor
|
||||
),
|
||||
];
|
||||
|
||||
Promise.all(promises).then(
|
||||
([
|
||||
RedPaletteColorLookupTableData,
|
||||
GreenPaletteColorLookupTableData,
|
||||
BluePaletteColorLookupTableData,
|
||||
]) => {
|
||||
// when PaletteColorLookupTableUID is present, the entry can be cached...
|
||||
_paletteColorCache.add({
|
||||
RedPaletteColorLookupTableData,
|
||||
GreenPaletteColorLookupTableData,
|
||||
BluePaletteColorLookupTableData,
|
||||
PaletteColorLookupTableUID,
|
||||
});
|
||||
|
||||
instance.RedPaletteColorLookupTableData = RedPaletteColorLookupTableData;
|
||||
instance.GreenPaletteColorLookupTableData = GreenPaletteColorLookupTableData;
|
||||
instance.BluePaletteColorLookupTableData = BluePaletteColorLookupTableData;
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
const { PaletteColorLookupTableUID } = item;
|
||||
const paletteData = item[tag];
|
||||
if (paletteData === undefined && PaletteColorLookupTableUID === undefined) return;
|
||||
// performance optimization - read UID and cache by UID
|
||||
return _getPaletteColor(
|
||||
item[tag],
|
||||
item[descriptorTag]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple cache schema for retrieved color palettes.
|
||||
*/
|
||||
const _paletteColorCache = {
|
||||
count: 0,
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24h cache?
|
||||
entries: {},
|
||||
isValidUID: function(PaletteColorLookupTableUID) {
|
||||
return (
|
||||
typeof PaletteColorLookupTableUID === 'string' &&
|
||||
PaletteColorLookupTableUID.length > 0
|
||||
);
|
||||
},
|
||||
get: function(PaletteColorLookupTableUID) {
|
||||
let entry = null;
|
||||
if (this.entries.hasOwnProperty(PaletteColorLookupTableUID)) {
|
||||
entry = this.entries[PaletteColorLookupTableUID];
|
||||
// check how the entry is...
|
||||
if (Date.now() - entry.time > this.maxAge) {
|
||||
// entry is too old... remove entry.
|
||||
delete this.entries[PaletteColorLookupTableUID];
|
||||
this.count--;
|
||||
entry = null;
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
},
|
||||
add: function(entry) {
|
||||
if (this.isValidUID(entry.uid)) {
|
||||
let PaletteColorLookupTableUID = entry.uid;
|
||||
if (this.entries.hasOwnProperty(PaletteColorLookupTableUID) !== true) {
|
||||
this.count++; // increment cache entry count...
|
||||
}
|
||||
entry.time = Date.now();
|
||||
this.entries[PaletteColorLookupTableUID] = entry;
|
||||
// @TODO: Add logic to get rid of old entries and reduce memory usage...
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
|
||||
function _getPaletteColor(paletteColorLookupTableData, lutDescriptor) {
|
||||
const numLutEntries = lutDescriptor[0];
|
||||
const bits = lutDescriptor[2];
|
||||
|
||||
if (!paletteColorLookupTableData) return undefined;
|
||||
|
||||
const arrayBufferToPaletteColorLUT = arraybuffer => {
|
||||
const byteArray =
|
||||
bits === 16 ? new Uint16Array(arraybuffer) : new Uint8Array(arraybuffer);
|
||||
const lut = [];
|
||||
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = byteArray[i];
|
||||
}
|
||||
if (bits === 16) {
|
||||
let j = 0;
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = arraybuffer[j++] + arraybuffer[j++] << 8;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numLutEntries; i++) {
|
||||
lut[i] = byteArray[i];
|
||||
}
|
||||
|
||||
}
|
||||
return lut;
|
||||
};
|
||||
|
||||
if (paletteColorLookupTableData.BulkDataURI) {
|
||||
let uri = paletteColorLookupTableData.BulkDataURI;
|
||||
|
||||
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
|
||||
// incorrect bulk data URIs
|
||||
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
|
||||
uri = uri.replace('http', 'https');
|
||||
}
|
||||
|
||||
const config = {
|
||||
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
|
||||
headers: DICOMWeb.getAuthorizationHeader(server),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
const dicomWeb = new api.DICOMwebClient(config);
|
||||
const options = {
|
||||
BulkDataURI: uri,
|
||||
};
|
||||
|
||||
return dicomWeb
|
||||
.retrieveBulkData(options)
|
||||
.then(result => result[0])
|
||||
.then(arrayBufferToPaletteColorLUT);
|
||||
} else if (paletteColorLookupTableData.InlineBinary) {
|
||||
const inlineBinaryData = atob(paletteColorLookupTableData.InlineBinary);
|
||||
const arraybuffer = str2ab(inlineBinaryData);
|
||||
|
||||
return new Promise(resolve => {
|
||||
resolve(arrayBufferToPaletteColorLUT(arraybuffer));
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(
|
||||
arrayBufferToPaletteColorLUT(paletteColorLookupTableData)
|
||||
);
|
||||
if (paletteColorLookupTableData.palette) {
|
||||
return paletteColorLookupTableData.palette;
|
||||
}
|
||||
|
||||
if (paletteColorLookupTableData.InlineBinary) {
|
||||
try {
|
||||
const arraybuffer = Uint8Array.from(atob(paletteColorLookupTableData.InlineBinary), c =>
|
||||
c.charCodeAt(0)
|
||||
);
|
||||
return (paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT(arraybuffer));
|
||||
} catch (e) {
|
||||
console.log("Couldn't decode", paletteColorLookupTableData.InlineBinary, e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (paletteColorLookupTableData.retrieveBulkData) {
|
||||
return paletteColorLookupTableData.retrieveBulkData().then(val =>
|
||||
(paletteColorLookupTableData.palette = arrayBufferToPaletteColorLUT(val)));
|
||||
}
|
||||
|
||||
throw new Error(`No data found for ${paletteColorLookupTableData} palette`)
|
||||
}
|
||||
|
||||
@ -1236,7 +1236,7 @@
|
||||
core-js-pure "^3.20.2"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@7.1.2", "@babel/runtime@7.16.3", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@7.1.2", "@babel/runtime@7.16.3", "@babel/runtime@7.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.16.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
|
||||
integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user