Skip to content

Commit

Permalink
fix: improvements for usage of targetBuffer, add convertFloatPixelDat…
Browse files Browse the repository at this point in the history
…aToInt flag (default true)
  • Loading branch information
Punzo committed Dec 7, 2020
1 parent 2b57c08 commit 1f58326
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 26 deletions.
1 change: 1 addition & 0 deletions packages/dicomImageLoader/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
JpxImage: true,
CharLS: true,
OpenJPEG: true,
SharedArrayBuffer: true,
},
rules: {
'accessor-pairs': 'warn',
Expand Down
2 changes: 1 addition & 1 deletion packages/dicomImageLoader/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 49 additions & 11 deletions packages/dicomImageLoader/src/imageLoader/createImage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import isColorImageFn from './isColorImage.js';
import convertColorSpace from './convertColorSpace.js';
import getMinMax from '../shared/getMinMax.js';
import isJPEGBaseline8BitColor from './isJPEGBaseline8BitColor.js';
import { getOptions } from './internal/options.js';

let lastImageIdDrawn = '';

Expand Down Expand Up @@ -83,17 +84,34 @@ function createImage(imageId, pixelData, transferSyntax, options = {}) {
options
);

const { decodeConfig } = getOptions();
const { convertFloatPixelDataToInt } = decodeConfig;

return new Promise((resolve, reject) => {
// eslint-disable-next-line complexity
decodePromise.then(function handleDecodeResponse(imageFrame) {
decodePromise.then(function(imageFrame) {
// If we have a target buffer that was written to in the
// Decode task, point the image to it here.
// We can't have done it within the thread incase it was a SharedArrayBuffer.
let alreadyTyped = false;

if (options.targetBuffer) {
const { arrayBuffer, offset, length, type } = 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 (length === null || length === undefined) {
length = imageFrame.pixelDataLength;
}

if (offset === null || offset === undefined) {
offset = 0;
}

switch (type) {
case 'Uint8Array':
TypedArrayConstructor = Uint8Array;
Expand All @@ -110,13 +128,25 @@ function createImage(imageId, pixelData, transferSyntax, options = {}) {
);
}

const targetArray = new TypedArrayConstructor(
arrayBuffer,
offset,
length
);
if (length !== imageFrame.pixelDataLength) {
throw new Error(
'target array for image does not have the same length as the decoded image 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(imageFrame.pixelData);
}

imageFrame.pixelData = targetArray;
// If need to scale, need to scale correct array.
imageFrame.pixelData = typedArray;
alreadyTyped = true;
}

const imagePlaneModule =
Expand All @@ -132,7 +162,9 @@ function createImage(imageId, pixelData, transferSyntax, options = {}) {
// JPEGBaseline (8 bits) is already returning the pixel data in the right format (rgba)
// because it's using a canvas to load and decode images.
if (!isJPEGBaseline8BitColor(imageFrame, transferSyntax)) {
setPixelDataType(imageFrame);
if (!alreadyTyped) {
setPixelDataType(imageFrame);
}

// convert color space
if (isColorImage) {
Expand Down Expand Up @@ -185,10 +217,16 @@ function createImage(imageId, pixelData, transferSyntax, options = {}) {
: undefined,
decodeTimeInMS: imageFrame.decodeTimeInMS,
floatPixelData: undefined,
imageFrame,
};

// add function to return pixel data
if (imageFrame.pixelData instanceof Float32Array) {
// If pixel data is intrinsically floating 32 array, we convert it to int for
// display in cornerstone. For other cases when pixel data is typed as
// Float32Array for scaling; this conversion is not needed.
if (
imageFrame.pixelData instanceof Float32Array &&
convertFloatPixelDataToInt
) {
const floatPixelData = imageFrame.pixelData;
const results = convertToIntPixelData(floatPixelData);

Expand Down
12 changes: 12 additions & 0 deletions packages/dicomImageLoader/src/imageLoader/imageIdToURI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Removes the data loader scheme from the imageId
*
* @param {string} imageId Image ID
* @returns {string} imageId without the data loader scheme
* @memberof Cache
*/
export default function imageIdToURI(imageId) {
const colonIndex = imageId.indexOf(':');

return imageId.substring(colonIndex + 1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ let options = {
strict: false,
useWebWorkers: true,
decodeConfig: {
convertFloatPixelDataToInt: true,
usePDFJS: false,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ function loadImage(imageId, options) {

const promise = new Promise((resolve, reject) => {
// TODO: load bulk data items that we might need
const mediaType = 'multipart/related; type="application/octet-stream"; transfer-syntax=*'; // 'image/dicom+jp2';
const mediaType =
'multipart/related; type="application/octet-stream"; transfer-syntax=*'; // 'image/dicom+jp2';

// get the pixel data from the server
getPixelData(uri, imageId, mediaType)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
let imageIds = [];
import imageIdToURI from '../imageIdToURI.js';

let metadataByImageURI = [];

function add(imageId, metadata) {
imageIds[imageId] = metadata;
const imageURI = imageIdToURI(imageId);

metadataByImageURI[imageURI] = metadata;
}

function get(imageId) {
return imageIds[imageId];
const imageURI = imageIdToURI(imageId);

return metadataByImageURI[imageURI];
}

function remove(imageId) {
imageIds[imageId] = undefined;
const imageURI = imageIdToURI(imageId);

metadataByImageURI[imageURI] = undefined;
}

function purge() {
imageIds = [];
metadataByImageURI = [];
}

export default {
Expand Down
47 changes: 42 additions & 5 deletions packages/dicomImageLoader/src/shared/decodeImageFrame.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable complexity */
import decodeLittleEndian from './decoders/decodeLittleEndian.js';
import decodeBigEndian from './decoders/decodeBigEndian.js';
import decodeRLE from './decoders/decodeRLE.js';
Expand Down Expand Up @@ -90,12 +91,27 @@ function decodeImageFrame(
// 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.
const { arrayBuffer, offset, length, type } = options.targetBuffer;

({ 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;
Expand All @@ -118,20 +134,41 @@ function decodeImageFrame(
);
}

const typedArray = new TypedArrayConstructor(arrayBuffer, offset, 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) {
if (options.preScale && options.preScale.scalingParameters) {
const { scalingParameters } = options.preScale;
const { rescaleSlope, rescaleIntercept } = scalingParameters;

if (
typeof rescaleSlope === 'number' &&
typeof rescaleIntercept === 'number'
) {
scaleArray(pixelDataArray, scalingParameters);
}
}

scaleArray(pixelDataArray, scalingParameters);
// 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();
Expand Down
7 changes: 4 additions & 3 deletions packages/dicomImageLoader/src/shared/scaling/scaleArray.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
export default function scaleArray(array, scalingParameters) {
const arrayLength = array.length;
const { rescaleSlope, rescaleIntercept, suvbw } = scalingParameters;

if (scalingParameters.modality === 'PT') {
const { rescaleSlope, rescaleIntercept, suvbw } = scalingParameters;
if (typeof suvbw !== 'number') {
return;
}

for (let i = 0; i < arrayLength; i++) {
array[i] = suvbw * (array[i] * rescaleSlope + rescaleIntercept);
}
} else {
const { rescaleSlope, rescaleIntercept } = scalingParameters;

for (let i = 0; i < arrayLength; i++) {
array[i] = array[i] * rescaleSlope + rescaleIntercept;
}
Expand Down

0 comments on commit 1f58326

Please sign in to comment.