Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/types/IImageFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface ImageFrame {
columns: number;
bitsAllocated: number;
bitsStored: number;
highBit?: number;
pixelRepresentation: number;
smallestPixelValue: number;
largestPixelValue: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Types } from '@cornerstonejs/core';
import normalizeStoredPixelData from '../shared/normalizeStoredPixelData';

function createImageFrame(
pixelData: Types.PixelDataTypedArray,
overrides: Partial<Types.IImageFrame> = {}
): Types.IImageFrame {
return {
bitsAllocated: 16,
bitsStored: 14,
highBit: 13,
pixelRepresentation: 0,
pixelData,
...overrides,
} as Types.IImageFrame;
}

describe('normalizeStoredPixelData', () => {
it('removes unused upper bits from unsigned pixels', () => {
const imageFrame = createImageFrame(new Uint16Array([32768, 41249, 65343]));

normalizeStoredPixelData(imageFrame);

expect(imageFrame.pixelData).toEqual(new Uint16Array([0, 8481, 16191]));
});

it('sign extends signed stored pixels', () => {
const imageFrame = createImageFrame(
new Int16Array([0x3fff, 0x2000, 0x1fff]),
{ pixelRepresentation: 1 }
);

normalizeStoredPixelData(imageFrame);

expect(imageFrame.pixelData).toEqual(new Int16Array([-1, -8192, 8191]));
});

it('uses HighBit to extract a shifted stored field', () => {
const imageFrame = createImageFrame(new Uint16Array([0xfff8, 0x4000]), {
bitsStored: 12,
highBit: 14,
});

normalizeStoredPixelData(imageFrame);

expect(imageFrame.pixelData).toEqual(new Uint16Array([4095, 2048]));
});

it('leaves pixels unchanged when all allocated bits are stored', () => {
const imageFrame = createImageFrame(new Uint16Array([0, 41249, 65535]), {
bitsStored: 16,
highBit: 15,
});

normalizeStoredPixelData(imageFrame);

expect(imageFrame.pixelData).toEqual(new Uint16Array([0, 41249, 65535]));
});
});
16 changes: 2 additions & 14 deletions packages/dicomImageLoader/src/decodeImageFrameWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import getPixelDataTypeFromMinMax, {
validatePixelDataType,
} from './shared/getPixelDataTypeFromMinMax';
import isColorImage from './shared/isColorImage';
import normalizeStoredPixelData from './shared/normalizeStoredPixelData';

const imageUtils = {
bilinear,
Expand All @@ -41,20 +42,7 @@ export function postProcessDecodedPixels(
start,
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++) {
imageFrame.pixelData[i] = (imageFrame.pixelData[i] << shift) >> shift;
}
}
normalizeStoredPixelData(imageFrame);

// Cache the pixelData reference quickly incase we want to set a targetBuffer _and_ scale.
let pixelDataArray = imageFrame.pixelData;
Expand Down
1 change: 1 addition & 0 deletions packages/dicomImageLoader/src/imageLoader/getImageFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function getImageFrame(imageId: string): Types.IImageFrame {
columns: imagePixelModule.columns,
bitsAllocated: imagePixelModule.bitsAllocated,
bitsStored: imagePixelModule.bitsStored,
highBit: imagePixelModule.highBit,
pixelRepresentation: imagePixelModule.pixelRepresentation, // 0 = unsigned,
smallestPixelValue: imagePixelModule.smallestPixelValue,
largestPixelValue: imagePixelModule.largestPixelValue,
Expand Down
43 changes: 43 additions & 0 deletions packages/dicomImageLoader/src/shared/normalizeStoredPixelData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Types } from '@cornerstonejs/core';

/**
* Removes bits outside the stored pixel field and sign extends signed values.
*/
export default function normalizeStoredPixelData(
imageFrame: Types.IImageFrame
): void {
const {
bitsAllocated,
bitsStored,
highBit = bitsStored - 1,
pixelData,
pixelRepresentation,
} = imageFrame;
const lowBit = highBit - bitsStored + 1;

if (
!pixelData ||
bitsStored <= 0 ||
bitsStored >= 32 ||
bitsStored > bitsAllocated ||
lowBit < 0 ||
highBit >= bitsAllocated ||
(bitsStored === bitsAllocated && lowBit === 0)
) {
return;
}

const valueRange = 2 ** bitsStored;
const mask = valueRange - 1;
const signBit = valueRange / 2;
const isSigned = pixelRepresentation === 1;

for (let i = 0; i < pixelData.length; i++) {
const storedValue = (pixelData[i] >>> lowBit) & mask;
pixelData[i] = storedValue;

if (isSigned && storedValue >= signBit) {
pixelData[i] -= valueRange;
}
}
}