/* eslint-disable complexity */
import decodeLittleEndian from './decoders/decodeLittleEndian.js';
import decodeBigEndian from './decoders/decodeBigEndian.js';
import decodeRLE from './decoders/decodeRLE.js';
import decodeJPEGBaseline8Bit from './decoders/decodeJPEGBaseline8Bit.js';
// import decodeJPEGBaseline12Bit from './decoders/decodeJPEGBaseline12Bit.js';
import decodeJPEGBaseline12Bit from './decoders/decodeJPEGBaseline12Bit-js.js';
import decodeJPEGLossless from './decoders/decodeJPEGLossless.js';
import decodeJPEGLS from './decoders/decodeJPEGLS.js';
import decodeJPEG2000 from './decoders/decodeJPEG2000.js';
import decodeHTJ2K from './decoders/decodeHTJ2K.js';
import scaleArray from './scaling/scaleArray.js';
/**
* Decodes the provided image frame.
* This is an async function return the result, or you can provide an optional
* callbackFn that is called with the results.
*/
async function decodeImageFrame(
imageFrame,
transferSyntax,
pixelData,
decodeConfig,
options,
callbackFn
) {
const start = new Date().getTime();
let decodePromise = null;
let opts;
switch (transferSyntax) {
case '1.2.840.10008.1.2':
case '1.2.840.10008.1.2.1':
// Implicit or Explicit VR Little Endian
decodePromise = decodeLittleEndian(imageFrame, pixelData);
break;
case '1.2.840.10008.1.2.2':
// Explicit VR Big Endian (retired)
decodePromise = decodeBigEndian(imageFrame, pixelData);
break;
case '1.2.840.10008.1.2.1.99':
// Deflate transfer syntax (deflated by dicomParser)
decodePromise = decodeLittleEndian(imageFrame, pixelData);
break;
case '1.2.840.10008.1.2.5':
// RLE Lossless
decodePromise = decodeRLE(imageFrame, pixelData);
break;
case '1.2.840.10008.1.2.4.50':
// JPEG Baseline lossy process 1 (8 bit)
opts = {
...imageFrame,
};
decodePromise = decodeJPEGBaseline8Bit(pixelData, opts);
break;
case '1.2.840.10008.1.2.4.51':
// JPEG Baseline lossy process 2 & 4 (12 bit)
// opts = {
// ...imageFrame,
// };
// decodePromise = decodeJPEGBaseline12Bit(pixelData, opts);
//throw new Error('Currently unsupported: 1.2.840.10008.1.2.4.51');
decodePromise = decodeJPEGBaseline12Bit(imageFrame, pixelData);
break;
case '1.2.840.10008.1.2.4.57':
// JPEG Lossless, Nonhierarchical (Processes 14)
decodePromise = decodeJPEGLossless(imageFrame, pixelData);
break;
case '1.2.840.10008.1.2.4.70':
// JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1])
decodePromise = decodeJPEGLossless(imageFrame, pixelData);
break;
case '1.2.840.10008.1.2.4.80':
// JPEG-LS Lossless Image Compression
opts = {
signed: imageFrame.pixelRepresentation === 1, // imageFrame.signed,
// shouldn't need...
bytesPerPixel: imageFrame.bitsAllocated <= 8 ? 1 : 2,
...imageFrame,
};
decodePromise = decodeJPEGLS(pixelData, opts);
break;
case '1.2.840.10008.1.2.4.81':
// JPEG-LS Lossy (Near-Lossless) Image Compression
opts = {
signed: false, // imageFrame.signed,
// shouldn't need...
bytesPerPixel: imageFrame.bitsAllocated <= 8 ? 1 : 2,
...imageFrame,
};
decodePromise = decodeJPEGLS(pixelData, opts);
break;
case '1.2.840.10008.1.2.4.90':
opts = {
...imageFrame,
};
// JPEG 2000 Lossless
// imageFrame, pixelData, decodeConfig, options
decodePromise = decodeJPEG2000(pixelData, opts);
break;
case '1.2.840.10008.1.2.4.91':
// JPEG 2000 Lossy
opts = {
...imageFrame,
};
// JPEG 2000 Lossy
// imageFrame, pixelData, decodeConfig, options
decodePromise = decodeJPEG2000(pixelData, opts);
break;
case '3.2.840.10008.1.2.4.96':
// HTJ2K
opts = {
...imageFrame,
};
decodePromise = decodeHTJ2K(pixelData, opts);
break;
default:
throw new Error(`no decoder for transfer syntax ${transferSyntax}`);
}
/* Don't know if these work...
// JPEG 2000 Part 2 Multicomponent Image Compression (Lossless Only)
else if(transferSyntax === "1.2.840.10008.1.2.4.92")
{
return decodeJPEG2000(dataSet, frame);
}
// JPEG 2000 Part 2 Multicomponent Image Compression
else if(transferSyntax === "1.2.840.10008.1.2.4.93")
{
return decodeJPEG2000(dataSet, frame);
}
*/
if (!decodePromise) {
throw new Error('decodePromise not defined');
}
const decodedFrame = await decodePromise;
const postProcessed = postProcessDecodedPixels(
decodedFrame,
options,
start,
decodeConfig
);
// Call the callbackFn to agree with older arguments
callbackFn?.(postProcessed);
return postProcessed;
}
function postProcessDecodedPixels(imageFrame, options, start, decodeConfig) {
const { use16BitDataType } = decodeConfig || {};
const shouldShift =
imageFrame.pixelRepresentation !== undefined &&
imageFrame.pixelRepresentation === 1;
const shift =
shouldShift && imageFrame.bitsStored !== undefined
? 32 - imageFrame.bitsStored
: undefined;
if (shouldShift && shift !== undefined) {
for (let i = 0; i < imageFrame.pixelData.length; i++) {
// eslint-disable-next-line no-bitwise
imageFrame.pixelData[i] = (imageFrame.pixelData[i] << shift) >> shift;
}
}
// Cache the pixelData reference quickly incase we want to set a targetBuffer _and_ scale.
let pixelDataArray = imageFrame.pixelData;
imageFrame.pixelDataLength = imageFrame.pixelData.length;
if (options.targetBuffer) {
let offset, length;
// If we have a target buffer, write to that instead. This helps reduce memory duplication.
({ offset, length } = options.targetBuffer);
const { arrayBuffer, type } = options.targetBuffer;
let TypedArrayConstructor;
if (offset === null || offset === undefined) {
offset = 0;
}
if ((length === null || length === undefined) && offset !== 0) {
length = imageFrame.pixelDataLength - offset;
} else if (length === null || length === undefined) {
length = imageFrame.pixelDataLength;
}
switch (type) {
case 'Uint8Array':
TypedArrayConstructor = Uint8Array;
break;
case use16BitDataType && 'Uint16Array':
TypedArrayConstructor = Uint16Array;
break;
case use16BitDataType && 'Int16Array':
TypedArrayConstructor = Int16Array;
break;
case 'Float32Array':
TypedArrayConstructor = Float32Array;
break;
default:
throw new Error('target array for image does not have a valid type.');
}
const imageFramePixelData = imageFrame.pixelData;
if (length !== imageFramePixelData.length) {
throw new Error(
`target array for image does not have the same length (${length}) as the decoded image length (${imageFramePixelData.length}).`
);
}
// TypedArray.Set is api level and ~50x faster than copying elements even for
// Arrays of different types, which aren't simply memcpy ops.
let typedArray;
if (arrayBuffer) {
typedArray = new TypedArrayConstructor(arrayBuffer, offset, length);
} else {
typedArray = new TypedArrayConstructor(length);
}
typedArray.set(imageFramePixelData, 0);
// If need to scale, need to scale correct array.
pixelDataArray = typedArray;
}
if (options.preScale.enabled) {
const scalingParameters = options.preScale.scalingParameters;
if (!scalingParameters) {
throw new Error(
'options.preScale.scalingParameters must be defined if preScale.enabled is true, and scalingParameters cannot be derived from the metadata providers.'
);
}
const { rescaleSlope, rescaleIntercept } = scalingParameters;
if (
typeof rescaleSlope === 'number' &&
typeof rescaleIntercept === 'number'
) {
if (scaleArray(pixelDataArray, scalingParameters)) {
imageFrame.preScale = {
...options.preScale,
scaled: true,
};
}
}
}
// Handle cases where the targetBuffer is not backed by a SharedArrayBuffer
if (
options.targetBuffer &&
(!options.targetBuffer.arrayBuffer ||
options.targetBuffer.arrayBuffer instanceof ArrayBuffer)
) {
imageFrame.pixelData = pixelDataArray;
}
const end = new Date().getTime();
imageFrame.decodeTimeInMS = end - start;
return imageFrame;
}
export default decodeImageFrame;