fix: image slices are displayed in reverse order, if we switch from common layout to 2D MPR (#5022)

This commit is contained in:
arul-trenser authored and GitHub committed 2025-07-17 08:11:26 -04:00
1 parent 8dda7d5f81
commit 6a4ca70836
19 files changed
+202 -102

No files matched your search

+46 -62
View File
@@ -1,15 +1,16 @@
import { Types as csTypes } from '@cornerstonejs/core';
import guid from '../utils/guid.js';
import { Vector3 } from 'cornerstone-math';
import {
instancesSortCriteria,
isValidForPositionSort,
sortImagesByPatientPosition,
} from '../utils/sortStudy';
type Attributes = Record<string, unknown>;
type Image = {
export type Image = {
StudyInstanceUID?: string;
getData(): {
metadata: {
ImagePositionPatient: number[];
ImageOrientationPatient: number[];
};
};
ImagePositionPatient?: csTypes.Point3;
ImageOrientationPatient?: csTypes.Point3;
};
/**
@@ -79,63 +80,46 @@ class ImageSet {
return this.images[index];
}
/**
* Default image sorting. Sorts by the following (in order of priority)
* 1. Image position (if ImagePositionPatient and ImageOrientationPatient are defined)
* 2. Sort by a provided sortingCallback Criteria
* Note: Images are sorted in-place and a reference to the sorted image array is returned.
*
* @returns images - reference to images after sorting
*/
sort(customizationService): Image[] {
// Check instanceSort customization
const customizedSortingCriteria =
customizationService.getCustomization('instanceSortingCriteria');
const combinedSortFunctions = Object.assign(
{},
instancesSortCriteria,
customizedSortingCriteria.sortFunctions
);
const userSpecifiedCriteria = customizedSortingCriteria.defaultSortFunctionName;
// Prefer customized sort function when available
if (typeof combinedSortFunctions[userSpecifiedCriteria] === 'function') {
return this.images.sort(combinedSortFunctions[userSpecifiedCriteria]);
}
// If image position patient is not available, sort by InstanceNumber
if (!isValidForPositionSort(this.images)) {
return this.images.sort(instancesSortCriteria.sortByInstanceNumber);
}
// Do image position patient sorting as default sort
return sortImagesByPatientPosition(this.images);
}
/**
* Sort using the provided callback function.
* Note: Images are sorted in-place and a reference to the sorted image array is returned.
*
* @param sortingCallback - sorting function
* @returns images - reference to images after sorting
*/
sortBy(sortingCallback: (a: Image, b: Image) => number): Image[] {
return this.images.sort(sortingCallback);
}
sortByImagePositionPatient(): void {
const images = this.images;
const referenceImagePositionPatient = _getImagePositionPatient(images[0]);
const refIppVec = new Vector3(
referenceImagePositionPatient[0],
referenceImagePositionPatient[1],
referenceImagePositionPatient[2]
);
const ImageOrientationPatient = _getImageOrientationPatient(images[0]);
const scanAxisNormal = new Vector3(
ImageOrientationPatient[0],
ImageOrientationPatient[1],
ImageOrientationPatient[2]
).cross(
new Vector3(
ImageOrientationPatient[3],
ImageOrientationPatient[4],
ImageOrientationPatient[5]
)
);
const distanceImagePairs = images.map(function (image: Image) {
const ippVec = new Vector3(..._getImagePositionPatient(image));
const positionVector = refIppVec.clone().sub(ippVec);
const distance = positionVector.dot(scanAxisNormal);
return {
distance,
image,
};
});
distanceImagePairs.sort(function (a, b) {
return b.distance - a.distance;
});
const sortedImages = distanceImagePairs.map(a => a.image);
images.sort(function (a, b) {
return sortedImages.indexOf(a) - sortedImages.indexOf(b);
});
}
}
function _getImagePositionPatient(image) {
return image.getData().metadata.ImagePositionPatient;
}
function _getImageOrientationPatient(image) {
return image.getData().metadata.ImageOrientationPatient;
}
export default ImageSet;
@@ -0,0 +1,20 @@
import { vec3 } from 'gl-matrix';
/**
* Calculates the scanAxisNormal based on a image orientation vector extract from a frame
* @param {*} imageOrientation
* @returns
*/
export default function calculateScanAxisNormal(imageOrientation) {
const rowCosineVec = vec3.fromValues(
imageOrientation[0],
imageOrientation[1],
imageOrientation[2]
);
const colCosineVec = vec3.fromValues(
imageOrientation[3],
imageOrientation[4],
imageOrientation[5]
);
return vec3.cross(vec3.create(), rowCosineVec, colCosineVec);
}
+2
View File
@@ -43,6 +43,7 @@ import { createStudyBrowserTabs } from './createStudyBrowserTabs';
import { sopClassDictionary } from './sopClassDictionary';
import * as MeasurementFilters from './measurementFilters';
import getClosestOrientationFromIOP from './getClosestOrientationFromIOP';
import calculateScanAxisNormal from './calculateScanAxisNormal';
// Commented out unused functionality.
// Need to implement new mechanism for derived displaySets using the displaySetManager.
@@ -91,6 +92,7 @@ const utils = {
createStudyBrowserTabs,
MeasurementFilters,
getClosestOrientationFromIOP,
calculateScanAxisNormal,
};
export {
+69 -13
View File
@@ -1,4 +1,6 @@
import { vec3 } from 'gl-matrix';
import isLowPriorityModality from './isLowPriorityModality';
import calculateScanAxisNormal from './calculateScanAxisNormal';
const compareSeriesDateTime = (a, b) => {
const seriesDateA = Date.parse(`${a.seriesDate ?? a.SeriesDate} ${a.seriesTime ?? a.SeriesTime}`);
@@ -41,20 +43,23 @@ const seriesSortCriteria = {
seriesInfoSortingCriteria,
};
const sortByInstanceNumber = (a, b) => {
// Sort by InstanceNumber (0020,0013)
const aInstance = parseInt(a.InstanceNumber) || 0;
const bInstance = parseInt(b.InstanceNumber) || 0;
if (aInstance !== bInstance) {
return (parseInt(a.InstanceNumber) || 0) - (parseInt(b.InstanceNumber) || 0);
}
// Fallback rule to enable consistent sorting
if (a.SOPInstanceUID === b.SOPInstanceUID) {
return 0;
}
return a.SOPInstanceUID < b.SOPInstanceUID ? -1 : 1;
};
const instancesSortCriteria = {
default: (a, b) => {
// Sort by InstanceNumber (0020,0013)
const aInstance = parseInt(a.InstanceNumber) || 0;
const bInstance = parseInt(b.InstanceNumber) || 0;
if (aInstance !== bInstance) {
return (parseInt(a.InstanceNumber) || 0) - (parseInt(b.InstanceNumber) || 0);
}
// Fallback rule to enable consistent sorting
if (a.SOPInstanceUID === b.SOPInstanceUID) {
return 0;
}
return a.SOPInstanceUID < b.SOPInstanceUID ? -1 : 1;
},
default: sortByInstanceNumber,
sortByInstanceNumber,
};
const sortingCriteria = {
@@ -128,6 +133,55 @@ export default function sortStudy(
return study;
}
function isValidForPositionSort(images): boolean {
if (images.length <= 1) {
return false; // No need to sort if there's only one image
}
// Use the first image as a reference
const referenceImagePositionPatient = images[0].ImagePositionPatient;
const imageOrientationPatient = images[0].ImageOrientationPatient;
if (!referenceImagePositionPatient || !imageOrientationPatient) {
return false;
}
return true;
}
/**
* Sort by image position, calculated using imageOrientationPatient and ImagePositionPatient
* If imageOrientationPatient or ImagePositionPatient is not available, Images will be sorted by the provided sortingCriteria
* Note: Images are sorted in-place and a reference to the sorted image array is returned.
*
* @returns images - reference to images after sorting
*/
const sortImagesByPatientPosition = images => {
const referenceImagePositionPatient = images[0].ImagePositionPatient;
const imageOrientationPatient = images[0].ImageOrientationPatient;
// Calculate the scan axis normal using the cross product
const scanAxisNormal = calculateScanAxisNormal(imageOrientationPatient);
// Compute distances from each image to the reference image
const distanceInstancePairs = images.map(image => {
const imagePositionPatient = image.ImagePositionPatient;
const deltaVector = vec3.create();
const distance = vec3.dot(
scanAxisNormal,
vec3.subtract(deltaVector, imagePositionPatient, referenceImagePositionPatient)
);
return { distance, image };
});
// Sort images based on the computed distances
distanceInstancePairs.sort((a, b) => b.distance - a.distance);
// Reorder the images in the original array
for (const [index, item] of distanceInstancePairs.entries()) {
images[index] = item.image;
}
return images;
};
export {
sortStudy,
sortStudySeries,
@@ -135,4 +189,6 @@ export {
sortingCriteria,
seriesSortCriteria,
instancesSortCriteria,
isValidForPositionSort,
sortImagesByPatientPosition,
};