Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nifti): NIFTI data type enhancement #1219

Merged
merged 11 commits into from
May 28, 2024
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