diff --git a/packages/core/src/utilities/generateVolumePropsFromImageIds.ts b/packages/core/src/utilities/generateVolumePropsFromImageIds.ts index 814465ffa6..1315c9d51c 100644 --- a/packages/core/src/utilities/generateVolumePropsFromImageIds.ts +++ b/packages/core/src/utilities/generateVolumePropsFromImageIds.ts @@ -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'; diff --git a/packages/dicomImageLoader/src/imageLoader/wadouri/getUncompressedImageFrame.ts b/packages/dicomImageLoader/src/imageLoader/wadouri/getUncompressedImageFrame.ts index acf7087675..afc0857fd6 100644 --- a/packages/dicomImageLoader/src/imageLoader/wadouri/getUncompressedImageFrame.ts +++ b/packages/dicomImageLoader/src/imageLoader/wadouri/getUncompressedImageFrame.ts @@ -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) { diff --git a/packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts b/packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts index cd250d76ea..3ffe53d02e 100644 --- a/packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts +++ b/packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts @@ -10,7 +10,7 @@ async function decodeBigEndian( imageFrame: Types.IImageFrame, pixelData: ByteArray ): Promise { - if (imageFrame.bitsAllocated === 16) { + if (imageFrame.bitsAllocated === 16 || imageFrame.bitsAllocated === 12) { let arrayBuffer = pixelData.buffer; let offset = pixelData.byteOffset; diff --git a/packages/dicomImageLoader/src/shared/decoders/decodeJPEGBaseline12Bit-js.ts b/packages/dicomImageLoader/src/shared/decoders/decodeJPEGBaseline12Bit-js.ts index edaf33548e..ff06db95f2 100644 --- a/packages/dicomImageLoader/src/shared/decoders/decodeJPEGBaseline12Bit-js.ts +++ b/packages/dicomImageLoader/src/shared/decoders/decodeJPEGBaseline12Bit-js.ts @@ -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; diff --git a/packages/dicomImageLoader/src/shared/decoders/decodeJPEGLossless.ts b/packages/dicomImageLoader/src/shared/decoders/decodeJPEGLossless.ts index 69fdc88a68..cc497f120c 100644 --- a/packages/dicomImageLoader/src/shared/decoders/decodeJPEGLossless.ts +++ b/packages/dicomImageLoader/src/shared/decoders/decodeJPEGLossless.ts @@ -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; diff --git a/packages/dicomImageLoader/src/shared/decoders/decodeLittleEndian.ts b/packages/dicomImageLoader/src/shared/decoders/decodeLittleEndian.ts index 7d763c649d..665de4dc6e 100644 --- a/packages/dicomImageLoader/src/shared/decoders/decodeLittleEndian.ts +++ b/packages/dicomImageLoader/src/shared/decoders/decodeLittleEndian.ts @@ -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) { diff --git a/packages/dicomImageLoader/src/shared/decoders/decodeRLE.ts b/packages/dicomImageLoader/src/shared/decoders/decodeRLE.ts index 82737970ee..cf8ae83b3c 100644 --- a/packages/dicomImageLoader/src/shared/decoders/decodeRLE.ts +++ b/packages/dicomImageLoader/src/shared/decoders/decodeRLE.ts @@ -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'); @@ -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;