Skip to content
Merged
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
113 changes: 111 additions & 2 deletions src/lib/data/insertSlice.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import computeImageDataIncrements from './computeImageDataIncrements.js';
import computeIndex from './computeIndex.js';
import cornerstone from 'cornerstone-core';

// insert the slice at the z index location.
export default function insertSlice(imageData, pixels, index) {
export default function insertSlice(imageData, index, image) {
const pixels = image.getPixelData();
const scalingParameters = _calculateScalingParametersForModality(image);

const datasetDefinition = imageData.get('extent', 'spacing', 'origin');
const scalars = imageData.getPointData().getScalars();
const increments = computeImageDataIncrements(imageData, 1); // TODO number of components.
Expand All @@ -20,8 +24,113 @@ export default function insertSlice(imageData, pixels, index) {
increments,
indexXYZ
);
scalarData[destIdx] = pixels[pixelIndex++];
const pixel = pixels[pixelIndex];

scalarData[destIdx] = _getModalityPixelsOrSUV(pixel, scalingParameters);

pixelIndex++;
}
}
imageData.modified();
}

function _calculateScalingParametersForModality(image) {
const patientStudyModule = cornerstone.metaData.get(
'patientStudyModule',
image.imageId
);
const seriesModule = cornerstone.metaData.get(
'generalSeriesModule',
image.imageId
);

if (!patientStudyModule) {
throw new Error('patientStudyModule metadata is required');
}

if (!seriesModule) {
throw new Error('seriesModule metadata is required');
}

const modality = seriesModule.modality;

const scalingParameters = {
slope: image.slope,
intercept: image.intercept,
modality,
};

if (modality === 'PT') {
const patientWeight = patientStudyModule.patientWeight; // In kg

if (!patientWeight) {
throw new Error(
'patientWeight must be present in patientStudyModule for modality PT'
);
}

const petSequenceModule = cornerstone.metaData.get(
'petIsotopeModule',
image.imageId
);

if (!petSequenceModule) {
throw new Error('petSequenceModule metadata is required');
}

// TODO:
// - Update this to match the SUV logic provided here:
// https://github.com/salimkanoun/fijiPlugins/blob/master/Pet_Ct_Viewer/src/SUVDialog.java
// - Test with PET datasets from various providers to ensure SUV is correct
const radiopharmaceuticalInfo = petSequenceModule.radiopharmaceuticalInfo;
const startTime = radiopharmaceuticalInfo.radiopharmaceuticalStartTime;
const totalDose = radiopharmaceuticalInfo.radionuclideTotalDose;
const halfLife = radiopharmaceuticalInfo.radionuclideHalfLife;
const seriesAcquisitionTime = seriesModule.seriesTime;

if (!startTime || !totalDose || !halfLife || !seriesAcquisitionTime) {
throw new Error(
'The required radiopharmaceutical information was not present.'
);
}

const acquisitionTimeInSeconds =
fracToDec(seriesAcquisitionTime.fractionalSeconds || 0) +
seriesAcquisitionTime.seconds +
seriesAcquisitionTime.minutes * 60 +
seriesAcquisitionTime.hours * 60 * 60;
const injectionStartTimeInSeconds =
fracToDec(startTime.fractionalSeconds) +
startTime.seconds +
startTime.minutes * 60 +
startTime.hours * 60 * 60;
const durationInSeconds =
acquisitionTimeInSeconds - injectionStartTimeInSeconds;
const correctedDose =
totalDose * Math.exp((-durationInSeconds * Math.log(2)) / halfLife);

scalingParameters.patientWeight = patientWeight;
scalingParameters.correctedDose = correctedDose;
}

return scalingParameters;
}

function _getModalityPixelsOrSUV(pixel, scalingParameters) {
if (scalingParameters.modality === 'PT') {
const {
slope,
intercept,
patientWeight,
correctedDose,
} = scalingParameters;

const modalityPixelValue = pixel * slope + intercept;
const suv = (1000 * modalityPixelValue * patientWeight) / correctedDose;
return suv;
}

const { slope, intercept } = scalingParameters;

return pixel * slope + intercept;
}
90 changes: 1 addition & 89 deletions src/lib/loadImageData.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,96 +8,8 @@ function loadImageDataProgressively(imageIds, imageData, metaDataMap, zAxis) {
const insertPixelData = image => {
const { imagePositionPatient } = metaDataMap.get(image.imageId);
const sliceIndex = getSliceIndex(zAxis, imagePositionPatient);
const pixels = image.getPixelData();
const { slope, intercept } = image;
const numPixels = pixels.length;

// TODO: Sometimes after scaling from stored pixel value to modality
// pixel value, the result is negative. In this case, we need to use a
// signed array. I've hardcoded Int16 for now but I guess we can try to
// figure out if Int8 is also an option.
const modalityPixelsOrSUV = new Int16Array(numPixels);

const patientStudyModule = cornerstone.metaData.get(
'patientStudyModule',
image.imageId
);
const seriesModule = cornerstone.metaData.get(
'generalSeriesModule',
image.imageId
);

if (!patientStudyModule) {
throw new Error('patientStudyModule metadata is required');
}

if (!seriesModule) {
throw new Error('seriesModule metadata is required');
}

const modality = seriesModule.modality;

if (modality === 'PT') {
const patientWeight = patientStudyModule.patientWeight; // In kg

if (!patientWeight) {
throw new Error(
'patientWeight must be present in patientStudyModule for modality PT'
);
}

const petSequenceModule = cornerstone.metaData.get(
'petIsotopeModule',
image.imageId
);

if (!petSequenceModule) {
throw new Error('petSequenceModule metadata is required');
}

// TODO:
// - Update this to match the SUV logic provided here:
// https://github.com/salimkanoun/fijiPlugins/blob/master/Pet_Ct_Viewer/src/SUVDialog.java
// - Test with PET datasets from various providers to ensure SUV is correct
const radiopharmaceuticalInfo = petSequenceModule.radiopharmaceuticalInfo;
const startTime = radiopharmaceuticalInfo.radiopharmaceuticalStartTime;
const totalDose = radiopharmaceuticalInfo.radionuclideTotalDose;
const halfLife = radiopharmaceuticalInfo.radionuclideHalfLife;
const seriesAcquisitionTime = seriesModule.seriesTime;

if (!startTime || !totalDose || !halfLife || !seriesAcquisitionTime) {
throw new Error(
'The required radiopharmaceutical information was not present.'
);
}

const acquisitionTimeInSeconds =
fracToDec(seriesAcquisitionTime.fractionalSeconds || 0) +
seriesAcquisitionTime.seconds +
seriesAcquisitionTime.minutes * 60 +
seriesAcquisitionTime.hours * 60 * 60;
const injectionStartTimeInSeconds =
fracToDec(startTime.fractionalSeconds) +
startTime.seconds +
startTime.minutes * 60 +
startTime.hours * 60 * 60;
const durationInSeconds =
acquisitionTimeInSeconds - injectionStartTimeInSeconds;
const correctedDose =
totalDose * Math.exp((-durationInSeconds * Math.log(2)) / halfLife);

for (let i = 0; i < numPixels; i++) {
const modalityPixelValue = pixels[i] * slope + intercept;
const suv = (1000 * modalityPixelValue * patientWeight) / correctedDose;
modalityPixelsOrSUV[i] = suv;
}
} else {
for (let i = 0; i < numPixels; i++) {
modalityPixelsOrSUV[i] = pixels[i] * slope + intercept;
}
}

insertSlice(imageData, modalityPixelsOrSUV, sliceIndex);
insertSlice(imageData, sliceIndex, image);
};

loadImagePromises.forEach(promise => {
Expand Down