dicomweb-proxy
Version:
A proxy to translate between dicomweb and dimse
143 lines (132 loc) • 7.03 kB
text/typescript
import { LoggerSingleton } from '../utils/logger';
import { ConfParams, config } from '../utils/config';
import { fileExists, waitForCachedInstancePath } from '../utils/fileHelper';
import { get_element } from '@iwharris/dicom-data-dictionary';
import dicomParser from 'dicom-parser';
import fs from 'fs';
interface ValueType {
Value?: string[] | number[] | unknown[];
BulkDataURI?: string;
vr: string;
}
type ElementType = Record<string, ValueType>;
function parseFile(filename: string): Promise<ElementType> {
const logger = LoggerSingleton.Instance;
return new Promise<ElementType>((resolve, reject) => {
fileExists(filename).then((success: boolean) => {
if (!success) {
logger.error(`file does not exist: ${filename}`);
return reject();
}
fs.promises.readFile(filename).then((data: Uint8Array) => {
const dataset = dicomParser.parseDicom(data);
// parse additional needed attributes
const patientName = dataset.string('x00100010');
const patentID = dataset.string('x00100020');
const studyInstanceUID = dataset.string('x0020000d');
const studyDate = dataset.string('x00080020');
const studyTime = dataset.string('x00080030');
const seriesInstanceUID = dataset.string('x0020000e');
const seriesNumber = dataset.string('x00200011');
const sopInstanceUID = dataset.string('x00080018');
const sopClassUID = dataset.string('x00080016');
const bitsAllocated = dataset.uint16('x00280100');
const bitsStored = dataset.uint16('x00280101');
const highBit = dataset.uint16('x00280102');
const rows = dataset.uint16('x00280010');
const cols = dataset.uint16('x00280011');
const pixelSpacingString = dataset.string('x00280030');
const pixelSpacing = pixelSpacingString ? pixelSpacingString.split('\\').map((e: string) => parseFloat(e)) : [1, 1];
const modality = dataset.string('x00080060');
const samplesPerPixel = dataset.uint16('x00280002');
const photometricInterpretation = dataset.string('x00280004');
const pixelRepresentation = dataset.uint16('x00280103');
const windowCenter = dataset.string('x00281050');
const wc = windowCenter ? parseFloat(windowCenter.split('\\')[0]) : 40;
const windowWidth = dataset.string('x00281051');
const ww = windowWidth ? parseFloat(windowWidth.split('\\')[0]) : 80;
const rescaleIntercept = parseFloat(dataset.string('x00281052') || '1');
const rescaleSlope = parseFloat(dataset.string('x00281053') || '1');
const iopString = dataset.string('x00200037');
const iop = iopString ? iopString.split('\\').map((e: string) => parseFloat(e)) : null;
const ippString = dataset.string('x00200032');
const ipp = ippString ? ippString.split('\\').map((e: string) => parseFloat(e)) : null;
const instanceNumber = dataset.string('x00200013');
const sliceThickness = dataset.string('x00180050');
const sliceLocation = dataset.string('x00201041');
const encDocumentValue = dataset.elements.x00420011;
const encapsulatedDocument = encDocumentValue
? Buffer.from(dataset.byteArray.buffer, encDocumentValue.dataOffset, encDocumentValue.length).toString('base64')
: null;
// append to all results
const resultStatic: ElementType = {
'00100010': { Value: [{ Alphabetic: patientName }], vr: 'PN' },
'00100020': { Value: [patentID], vr: 'LO' },
'0020000D': { Value: [studyInstanceUID], vr: 'UI' },
...(studyDate && { '00080020': { Value: [studyDate], vr: 'DA' } }),
...(studyTime && { '00080030': { Value: [studyTime], vr: 'TM' } }),
'0020000E': { Value: [seriesInstanceUID], vr: 'UI' },
...(seriesNumber && { '00200011': { Value: [seriesNumber], vr: 'IS' } }),
'00080018': { Value: [sopInstanceUID], vr: 'UI' },
'00080016': { Value: [sopClassUID], vr: 'UI' },
...(modality && { '00080060': { Value: [modality], vr: 'CS' } }),
'00280002': { Value: [samplesPerPixel], vr: 'US' },
...(photometricInterpretation && { '00280004': { Value: [photometricInterpretation], vr: 'CS' } }),
'00280010': { Value: [rows], vr: 'US' },
'00280011': { Value: [cols], vr: 'US' },
'00280030': { Value: pixelSpacing, vr: 'DS' },
'00280100': { Value: [bitsAllocated], vr: 'US' },
'00280101': { Value: [bitsStored], vr: 'US' },
'00280102': { Value: [highBit], vr: 'US' },
'00280103': { Value: [pixelRepresentation], vr: 'US' },
'00281050': { Value: [wc], vr: 'DS' },
'00281051': { Value: [ww], vr: 'DS' },
'00281052': { Value: [rescaleIntercept], vr: 'DS' },
'00281053': { Value: [rescaleSlope], vr: 'DS' },
...(iop && { '00200037': { Value: iop, vr: 'DS' } }),
...(ipp && { '00200032': { Value: ipp, vr: 'DS' } }),
...(instanceNumber && { '00200013': { Value: [instanceNumber], vr: 'IS' } }),
...(sliceThickness && { '00180050': { Value: [sliceThickness], vr: 'DS' } }),
...(sliceLocation && { '00201041': { Value: [sliceLocation], vr: 'DS' } }),
//...( encapsulatedDocument && {'00420011': { Value: [encapsulatedDocument], vr: 'OB' }}), // unfortunately this does not work
...(encapsulatedDocument && { '00420011': { BulkDataURI: `series/${seriesInstanceUID}/bulkdata/${sopInstanceUID}`, vr: 'OB' } }),
};
const keys: string[] = [];
for (const propertyName in dataset.elements) {
keys.push(propertyName);
}
keys.sort();
let resultDynamic: ElementType = {};
for (let k = 0; k < keys.length; k++) {
const propertyName = keys[k];
const element = dataset.elements[propertyName];
const tag = element.tag.substring(1).toUpperCase();
const vr = element.vr || get_element(tag)?.vr;
const tagValue = dataset.string(element.tag);
if (tagValue) {
resultDynamic = Object.assign(resultDynamic, {
[tag]: {
Value: [tagValue],
vr,
},
});
}
}
resolve({ ...resultDynamic, ...resultStatic });
});
});
});
}
export function parseMeta(json: object, studyInstanceUID: string, seriesInstanceUID: string): Promise<unknown> {
const logger = LoggerSingleton.Instance;
logger.info(`parsing series ${seriesInstanceUID}`);
const parsing = new Array<Promise<ElementType>>();
const storagePath = config.get(ConfParams.STORAGE_PATH) as string;
for (const [key] of Object.entries(json)) {
const sopInstanceUid = json[key]['00080018'].Value[0];
parsing.push(
waitForCachedInstancePath(storagePath, studyInstanceUID, sopInstanceUid).then((pathname) => parseFile(pathname))
);
}
return Promise.all(parsing);
}