Replace the hand-built metadata subset in parseMeta with a VR-aware DICOM-to-DICOM-JSON converter. Window Center/Width, Rescale Intercept/Slope are only emitted when the source element exists; multi-values are preserved in source order. VOI/LUT, Presentation LUT and Pixel Padding tags are preserved and Pixel Data is excluded. - add src/dimse/dicomJson.ts (dictionary-aware converter) - rewrite src/dimse/parseMeta.ts to use the converter - add synthetic non-PHI DICOM writer and metadata tests - add npm test script (node:test + ts-node) and skipLibCheck
115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import dicomParser, { Element } from 'dicom-parser';
|
|
import { convertElementToJson, DicomJsonDataSet, parseDicomToJson } from '../src/dimse/dicomJson';
|
|
import { buildMonochromeImage, Dataset } from './helpers/dicomWriter';
|
|
|
|
const BASE_PIXELS = [0, 1, 2, 3, 4, 5, 6, 7];
|
|
|
|
function crLike(extra: Dataset = [], bufferExtra: Partial<Parameters<typeof buildMonochromeImage>[0]> = {}) {
|
|
return buildMonochromeImage({
|
|
rows: 2,
|
|
columns: 4,
|
|
pixelData: BASE_PIXELS,
|
|
extra,
|
|
...bufferExtra,
|
|
});
|
|
}
|
|
|
|
test('omits Window Center/Width when the source tags are absent', () => {
|
|
const json = parseDicomToJson(crLike());
|
|
assert.equal(json['00281050'], undefined);
|
|
assert.equal(json['00281051'], undefined);
|
|
});
|
|
|
|
test('omits Rescale Intercept/Slope when the source tags are absent', () => {
|
|
const json = parseDicomToJson(crLike());
|
|
assert.equal(json['00281052'], undefined);
|
|
assert.equal(json['00281053'], undefined);
|
|
});
|
|
|
|
test('preserves multi-valued Window Center/Width in source order', () => {
|
|
const json = parseDicomToJson(crLike([
|
|
{ tag: '00281050', vr: 'DS', value: '40\\400' },
|
|
{ tag: '00281051', vr: 'DS', value: '80\\800' },
|
|
]));
|
|
|
|
assert.deepEqual(json['00281050'].Value, [40, 400]);
|
|
assert.deepEqual(json['00281051'].Value, [80, 800]);
|
|
});
|
|
|
|
test('preserves rescale values when present', () => {
|
|
const json = parseDicomToJson(crLike([
|
|
{ tag: '00281052', vr: 'DS', value: '-1024' },
|
|
{ tag: '00281053', vr: 'DS', value: '2' },
|
|
]));
|
|
|
|
assert.deepEqual(json['00281052'].Value, [-1024]);
|
|
assert.deepEqual(json['00281053'].Value, [2]);
|
|
});
|
|
|
|
test('preserves VOI, LUT, presentation LUT and pixel padding metadata', () => {
|
|
const json = parseDicomToJson(crLike([
|
|
{ tag: '00281055', vr: 'LO', value: 'WINDOW' },
|
|
{ tag: '00281056', vr: 'CS', value: 'LINEAR' },
|
|
{ tag: '20500020', vr: 'CS', value: 'IDENTITY' },
|
|
{ tag: '00280120', vr: 'US', value: 5 },
|
|
{ tag: '00280121', vr: 'US', value: 10 },
|
|
{
|
|
tag: '00283010',
|
|
vr: 'SQ',
|
|
items: [
|
|
[
|
|
{ tag: '00283002', vr: 'US', value: [256, 0, 8] },
|
|
{ tag: '00283006', vr: 'US', value: [1, 2, 3] },
|
|
],
|
|
],
|
|
},
|
|
]));
|
|
|
|
assert.deepEqual(json['00281055'].Value, ['WINDOW']);
|
|
assert.deepEqual(json['00281056'].Value, ['LINEAR']);
|
|
assert.deepEqual(json['20500020'].Value, ['IDENTITY']);
|
|
assert.deepEqual(json['00280120'].Value, [5]);
|
|
assert.deepEqual(json['00280121'].Value, [10]);
|
|
|
|
const sequence = json['00283010'];
|
|
assert.equal(sequence.vr, 'SQ');
|
|
const item = (sequence.Value as DicomJsonDataSet[])[0];
|
|
assert.deepEqual(item['00283002'].Value, [256, 0, 8]);
|
|
assert.deepEqual(item['00283006'].Value, [1, 2, 3]);
|
|
});
|
|
|
|
test('never emits Value: [undefined] for absent optional tags', () => {
|
|
const json = parseDicomToJson(crLike());
|
|
for (const [tag, element] of Object.entries(json)) {
|
|
if (Array.isArray(element.Value)) {
|
|
for (const value of element.Value) {
|
|
assert.notEqual(value, undefined, `${tag} contains an undefined value`);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
test('excludes Pixel Data from metadata output', () => {
|
|
const json = parseDicomToJson(crLike());
|
|
assert.equal(json['7FE00010'], undefined);
|
|
});
|
|
|
|
test('falls back to the data dictionary when VR is not explicit', () => {
|
|
const buffer = crLike([
|
|
{ tag: '00281050', vr: 'DS', value: '40' },
|
|
]);
|
|
const dataSet = dicomParser.parseDicom(buffer);
|
|
const explicit = dataSet.elements.x00281050;
|
|
const implicitLike: Element = {
|
|
tag: explicit.tag,
|
|
length: explicit.length,
|
|
dataOffset: explicit.dataOffset,
|
|
};
|
|
|
|
const json = convertElementToJson(dataSet, implicitLike);
|
|
assert.equal(json.vr, 'DS');
|
|
assert.deepEqual(json.Value, [40]);
|
|
});
|