Skip to content

Commit

Permalink
feat(nifti): NIFTI data type enhancement (#1219)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanqzsu authored May 28, 2024
1 parent 59613a4 commit 03a2335
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 252 deletions.
11 changes: 9 additions & 2 deletions common/reviews/api/nifti-volume-loader.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@ declare namespace helpers {
export { helpers }

// @public (undocumented)
function makeVolumeMetadata(niftiHeader: any, orientation: any, scalarData: any): Types.Metadata;
function makeVolumeMetadata(niftiHeader: any, orientation: any, scalarData: any, pixelRepresentation: any): {
volumeMetadata: Types.Metadata;
dimensions: Types.Point3;
direction: Types.Mat3;
};

// @public (undocumented)
function modalityScaleNifti(array: Float32Array | Int16Array | Uint8Array, niftiHeader: any): void;
function modalityScaleNifti(niftiHeader: any, niftiImageBuffer: any): {
scalarData: Types.PixelDataTypedArray;
pixelRepresentation: number;
};

// @public (undocumented)
export class NiftiImageVolume extends ImageVolume {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,7 @@ async function createVolumeActor(
volumeActor.getProperty().setIndependentComponents(false);
}

// If the volume is composed of imageIds, we can apply a default VOI based
// on either the metadata or the min/max of the middle slice. Example of other
// types of volumes which might not be composed of imageIds would be e.g., nrrd, nifti
// format volumes
if (imageVolume.imageIds?.length) {
await setDefaultVolumeVOI(volumeActor, imageVolume, useNativeDataType);
}
await setDefaultVolumeVOI(volumeActor, imageVolume, useNativeDataType);

if (callback) {
callback({ volumeActor, volumeId });
Expand Down
77 changes: 38 additions & 39 deletions packages/core/src/RenderingEngine/helpers/setDefaultVolumeVOI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const REQUEST_TYPE = RequestType.Prefetch;
* Finally it sets the VOI on the volumeActor transferFunction
* @param volumeActor - The volume actor
* @param imageVolume - The image volume that we want to set the VOI for.
* @param useNativeDataType - The image data type is native or Float32Array
*/
async function setDefaultVolumeVOI(
volumeActor: VolumeActor,
Expand All @@ -29,27 +30,27 @@ async function setDefaultVolumeVOI(
): Promise<void> {
let voi = getVOIFromMetadata(imageVolume);

if (!voi) {
if (!voi && imageVolume?.imageIds?.length) {
voi = await getVOIFromMinMax(imageVolume, useNativeDataType);
voi = handlePreScaledVolume(imageVolume, voi);
}

if (!voi || voi.lower === undefined || voi.upper === undefined) {
throw new Error(
'Could not get VOI from metadata, nor from the min max of the image middle slice'
);
}

voi = handlePreScaledVolume(imageVolume, voi);
const { lower, upper } = voi;

if (lower === 0 && upper === 0) {
// if (!voi || voi.lower === undefined || voi.upper === undefined) {
// throw new Error(
// 'Could not get VOI from metadata, nor from the min max of the image middle slice'
// );
// }
if (
(voi?.lower === 0 && voi?.upper === 0) ||
voi?.lower === undefined ||
voi?.upper === undefined
) {
return;
}

volumeActor
.getProperty()
.getRGBTransferFunction(0)
.setMappingRange(lower, upper);
.setMappingRange(voi.lower, voi.upper);
}

function handlePreScaledVolume(imageVolume: IImageVolume, voi: VOIRange) {
Expand Down Expand Up @@ -77,35 +78,36 @@ function handlePreScaledVolume(imageVolume: IImageVolume, voi: VOIRange) {
}

/**
* Get the VOI from the metadata of the middle slice of the image volume. It checks
* the metadata for the VOI and if it is not found, it returns null
* Get the VOI from the metadata of the middle slice of the image volume or the metadata of the image volume
* It checks the metadata for the VOI and if it is not found, it returns null
*
* @param imageVolume - The image volume that we want to get the VOI from.
* @returns VOIRange with lower and upper values
*/
function getVOIFromMetadata(imageVolume: IImageVolume): VOIRange {
const { imageIds } = imageVolume;

const imageIdIndex = Math.floor(imageIds.length / 2);
const imageId = imageIds[imageIdIndex];

const voiLutModule = metaData.get('voiLutModule', imageId);

if (voiLutModule && voiLutModule.windowWidth && voiLutModule.windowCenter) {
const { windowWidth, windowCenter } = voiLutModule;

const voi = {
windowWidth: Array.isArray(windowWidth) ? windowWidth[0] : windowWidth,
windowCenter: Array.isArray(windowCenter)
? windowCenter[0]
: windowCenter,
};

const { imageIds, metadata } = imageVolume;
let voi;
if (imageIds.length) {
const imageIdIndex = Math.floor(imageIds.length / 2);
const imageId = imageIds[imageIdIndex];
const voiLutModule = metaData.get('voiLutModule', imageId);
if (voiLutModule && voiLutModule.windowWidth && voiLutModule.windowCenter) {
const { windowWidth, windowCenter } = voiLutModule;
voi = {
windowWidth: Array.isArray(windowWidth) ? windowWidth[0] : windowWidth,
windowCenter: Array.isArray(windowCenter)
? windowCenter[0]
: windowCenter,
};
}
} else {
voi = metadata?.voiLut?.[0];
}
if (voi) {
const { lower, upper } = windowLevel.toLowHighRange(
Number(voi.windowWidth),
Number(voi.windowCenter)
);

return {
lower,
upper,
Expand All @@ -118,6 +120,7 @@ function getVOIFromMetadata(imageVolume: IImageVolume): VOIRange {
* and max pixel values, it calculates the VOI.
*
* @param imageVolume - The image volume that we want to get the VOI from.
* @param useNativeDataType - The image data type is native or Float32Array
* @returns The VOIRange with lower and upper values
*/
async function getVOIFromMinMax(
Expand Down Expand Up @@ -218,19 +221,15 @@ function _getImageScalarDataFromImageVolume(
voxelsPerImage
) {
const { scalarData } = imageVolume;
const { volumeBuffer } = scalarData;
const { buffer } = scalarData;
if (scalarData.BYTES_PER_ELEMENT !== bytePerPixel) {
byteOffset *= scalarData.BYTES_PER_ELEMENT / bytePerPixel;
}

const TypedArray = scalarData.constructor;
const imageScalarData = new TypedArray(voxelsPerImage);

const volumeBufferView = new TypedArray(
volumeBuffer,
byteOffset,
voxelsPerImage
);
const volumeBufferView = new TypedArray(buffer, byteOffset, voxelsPerImage);

imageScalarData.set(volumeBufferView);

Expand Down
5 changes: 1 addition & 4 deletions packages/nifti-volume-loader/examples/niftiBasic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import {
RenderingEngine,
Enums,
init as csInit,
Types,
volumeLoader,
setVolumesForViewports,
} from '@cornerstonejs/core';
import { init as csTools3dInit } from '@cornerstonejs/tools';
import { cornerstoneNiftiImageVolumeLoader } from '@cornerstonejs/nifti-volume-loader';

import { setCtTransferFunctionForVolumeActor } from '../../../../utils/demo/helpers';

// This is for debugging purposes
console.warn(
'Click on index.ts to open source code for this example --------->'
Expand Down Expand Up @@ -90,7 +87,7 @@ async function setup() {

setVolumesForViewports(
renderingEngine,
[{ volumeId, callback: setCtTransferFunctionForVolumeActor }],
[{ volumeId }],
viewportInputArray.map((v) => v.viewportId)
);

Expand Down
7 changes: 2 additions & 5 deletions packages/nifti-volume-loader/examples/niftiWithTools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import {
Enums as NiftiEnums,
} from '@cornerstonejs/nifti-volume-loader';

import {
addDropdownToToolbar,
setCtTransferFunctionForVolumeActor,
} from '../../../../utils/demo/helpers';
import { addDropdownToToolbar } from '../../../../utils/demo/helpers';

const {
LengthTool,
Expand Down Expand Up @@ -276,7 +273,7 @@ async function setup() {

setVolumesForViewports(
renderingEngine,
[{ volumeId, callback: setCtTransferFunctionForVolumeActor }],
[{ volumeId }],
viewportInputArray.map((v) => v.viewportId)
);

Expand Down
Loading

0 comments on commit 03a2335

Please sign in to comment.