Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ function _determineDataType(
return 'Uint8Array';

case 16:
case 12:
// Temporary fix for 16 bit images to use Float32
if (canRenderFloat && floatAfterScale) {
return 'Float32Array';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ function getUncompressedImageFrame(
return new Uint8Array(
dataSet.byteArray.buffer.slice(frameOffset, frameOffset + pixelsPerFrame)
);
} else if (bitsAllocated === 12) {
// Packed 12-bit: 2 pixels = 3 bytes
const bytesPerFrame = Math.ceil((pixelsPerFrame * 12) / 8);
frameOffset = pixelDataOffset + frameIndex * bytesPerFrame;

if (frameOffset + bytesPerFrame > dataSet.byteArray.length) {
throw new Error('frame exceeds size of pixelData');
}

const pixelData = new Uint16Array(pixelsPerFrame);
let byteOffset = frameOffset;
let pixelIndex = 0;

while (pixelIndex < pixelsPerFrame) {
const byte0 = dataSet.byteArray[byteOffset++] || 0;
const byte1 = dataSet.byteArray[byteOffset++] || 0;
const byte2 = dataSet.byteArray[byteOffset++] || 0;

// First pixel: lower 12 bits from bytes 0-1
// Byte0: bits 0-7, Byte1 bits 0-3 (lower nibble)
pixelData[pixelIndex++] = byte0 | ((byte1 & 0x0f) << 8);

// Second pixel: upper 12 bits from bytes 1-2
// Byte1 bits 4-7 (upper nibble), Byte2: bits 0-7
if (pixelIndex < pixelsPerFrame) {
pixelData[pixelIndex++] = (byte1 >> 4) | (byte2 << 4);
}
}

return new Uint8Array(pixelData.buffer);
} else if (bitsAllocated === 16) {
frameOffset = pixelDataOffset + frameIndex * pixelsPerFrame * 2;
if (frameOffset >= dataSet.byteArray.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async function decodeBigEndian(
imageFrame: Types.IImageFrame,
pixelData: ByteArray
): Promise<Types.IImageFrame> {
if (imageFrame.bitsAllocated === 16) {
if (imageFrame.bitsAllocated === 16 || imageFrame.bitsAllocated === 12) {
let arrayBuffer = pixelData.buffer;

let offset = pixelData.byteOffset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ async function decodeJPEGBaseline12BitAsync(
imageFrame.pixelData = jpeg.getData(imageFrame.columns, imageFrame.rows);

return imageFrame;
} else if (imageFrame.bitsAllocated === 16) {
} else if (
imageFrame.bitsAllocated === 16 ||
imageFrame.bitsAllocated === 12
) {
imageFrame.pixelData = jpeg.getData16(imageFrame.columns, imageFrame.rows);

return imageFrame;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async function decodeJPEGLossless(
);

if (imageFrame.pixelRepresentation === 0) {
if (imageFrame.bitsAllocated === 16) {
if (imageFrame.bitsAllocated === 16 || imageFrame.bitsAllocated === 12) {
imageFrame.pixelData = new Uint16Array(decompressedData.buffer);

return imageFrame;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async function decodeLittleEndian(
let offset = pixelData.byteOffset;
const length = pixelData.length;

if (imageFrame.bitsAllocated === 16) {
if (imageFrame.bitsAllocated === 16 || imageFrame.bitsAllocated === 12) {
// if pixel data is not aligned on even boundary, shift it so we can create the 16 bit array
// buffers on it
if (offset % 2) {
Expand Down
52 changes: 52 additions & 0 deletions packages/dicomImageLoader/src/shared/decoders/decodeRLE.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ async function decodeRLE(
return decode8(imageFrame, pixelData);
} else if (imageFrame.bitsAllocated === 16) {
return decode16(imageFrame, pixelData);
} else if (imageFrame.bitsAllocated === 12) {
return decode12(imageFrame, pixelData);
}

throw new Error('unsupported pixel format for RLE');
Expand Down Expand Up @@ -172,4 +174,54 @@ function decode16(imageFrame: Types.IImageFrame, pixelData: ByteArray) {
return imageFrame;
}

function decode12(imageFrame: Types.IImageFrame, pixelData: ByteArray) {
const frameData = pixelData;
const frameSize = imageFrame.rows * imageFrame.columns;
const outFrame = new ArrayBuffer(frameSize * imageFrame.samplesPerPixel * 2);

const header = new DataView(frameData.buffer, frameData.byteOffset);
const data = new Int8Array(frameData.buffer, frameData.byteOffset);
const out = new Int8Array(outFrame);

const numSegments = header.getInt32(0, true);

for (let s = 0; s < numSegments; ++s) {
let outIndex = 0;
const highByte = s === 0 ? 1 : 0;

let inIndex = header.getInt32((s + 1) * 4, true);

let maxIndex = header.getInt32((s + 2) * 4, true);

if (maxIndex === 0) {
maxIndex = frameData.length;
}

while (inIndex < maxIndex) {
const n = data[inIndex++];

if (n >= 0 && n <= 127) {
for (let i = 0; i < n + 1 && outIndex < frameSize; ++i) {
out[outIndex * 2 + highByte] = data[inIndex++];
outIndex++;
}
} else if (n <= -1 && n >= -127) {
const value = data[inIndex++];

for (let j = 0; j < -n + 1 && outIndex < frameSize; ++j) {
out[outIndex * 2 + highByte] = value;
outIndex++;
}
}
}
}
if (imageFrame.pixelRepresentation === 0) {
imageFrame.pixelData = new Uint16Array(outFrame);
} else {
imageFrame.pixelData = new Int16Array(outFrame);
}

return imageFrame;
}

export default decodeRLE;