-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto flipping of disoriented Mammography images on stack viewport
- Loading branch information
1 parent
4f18a21
commit 7763d5b
Showing
15 changed files
with
372 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import { getImageFlips } from './getImageFlips'; | ||
|
||
const orientationDirectionVectorMap = { | ||
L: [1, 0, 0], // Left | ||
R: [-1, 0, 0], // Right | ||
P: [0, 1, 0], // Posterior/ Back | ||
A: [0, -1, 0], // Anterior/ Front | ||
H: [0, 0, 1], // Head/ Superior | ||
F: [0, 0, -1], // Feet/ Inferior | ||
}; | ||
|
||
const getDirectionsFromPatientOrientation = patientOrientation => { | ||
if (typeof patientOrientation === 'string') { | ||
patientOrientation = patientOrientation.split('\\'); | ||
} | ||
|
||
return { | ||
rowDirection: patientOrientation[0], | ||
columnDirection: patientOrientation[1][0], | ||
}; | ||
}; | ||
|
||
const getOrientationStringLPS = vector => { | ||
const sampleVectorDirectionMap = { | ||
'1,0,0': 'L', | ||
'-1,0,0': 'R', | ||
'0,1,0': 'P', | ||
'0,-1,0': 'A', | ||
'0,0,1': 'H', | ||
'0,0,-1': 'F', | ||
}; | ||
|
||
return sampleVectorDirectionMap[vector.toString()]; | ||
}; | ||
|
||
jest.mock('@cornerstonejs/tools ', () => ({ | ||
utilities: { orientation: { getOrientationStringLPS } }, | ||
})); | ||
jest.mock('@ohif/core', () => ({ | ||
defaults: { orientationDirectionVectorMap }, | ||
utils: { getDirectionsFromPatientOrientation }, | ||
})); | ||
|
||
describe('getImageFlips', () => { | ||
test('should return empty object if none of the parameters are provided', () => { | ||
const flipsNeeded = getImageFlips({}); | ||
expect(flipsNeeded).toEqual({}); | ||
}); | ||
|
||
test('should return empty object if ImageOrientationPatient and PatientOrientation is not provided', () => { | ||
const ImageLaterality = 'L'; | ||
const flipsNeeded = getImageFlips({ | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({}); | ||
}); | ||
|
||
test('should return empty object if ImageLaterality is not privided', () => { | ||
const ImageOrientationPatient = [0, 1, 0, 0, 0, 1], | ||
PatientOrientation = ['P', 'H']; | ||
const flipsNeeded = getImageFlips({ | ||
ImageOrientationPatient, | ||
PatientOrientation, | ||
}); | ||
expect(flipsNeeded).toEqual({}); | ||
}); | ||
|
||
test('should return { hFlip: false, vFlip: false } if ImageOrientationPatient is [0, 1, 0, 0, 0, -1] and ImageLaterality is R', () => { | ||
const ImageOrientationPatient = [0, 1, 0, 0, 0, -1], | ||
PatientOrientation = ['P', 'F'], | ||
ImageLaterality = 'R'; | ||
const flipsNeeded = getImageFlips({ | ||
ImageOrientationPatient, | ||
PatientOrientation, | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({ hFlip: false, vFlip: false }); | ||
}); | ||
|
||
test('should return { hFlip: false, vFlip: true } if ImageOrientationPatient is [0, -1, 0, 0, 0, 1] and ImageLaterality is L', () => { | ||
const ImageOrientationPatient = [0, -1, 0, 0, 0, 1], | ||
ImageLaterality = 'L'; | ||
const flipsNeeded = getImageFlips({ | ||
ImageOrientationPatient, | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({ hFlip: false, vFlip: true }); | ||
}); | ||
|
||
test('should return { hFlip: true, vFlip: true } if ImageOrientationPatient is [0, -1, 0, -1, 0, 0] and ImageLaterality is R', () => { | ||
const ImageOrientationPatient = [0, -1, 0, -1, 0, 0], | ||
ImageLaterality = 'R'; | ||
const flipsNeeded = getImageFlips({ | ||
ImageOrientationPatient, | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({ hFlip: true, vFlip: true }); | ||
}); | ||
|
||
test("should return { hFlip: true, vFlip: true } if ImageOrientationPatient is not present, PatientOrientation is ['P', 'H'] and ImageLaterality is L", () => { | ||
const PatientOrientation = ['P', 'H'], | ||
ImageLaterality = 'L'; | ||
const flipsNeeded = getImageFlips({ | ||
PatientOrientation, | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({ hFlip: true, vFlip: true }); | ||
}); | ||
|
||
test("should return { hFlip: true, vFlip: false } if ImageOrientationPatient is not present, PatientOrientation is ['A', 'F'] and ImageLaterality is R", () => { | ||
const PatientOrientation = ['A', 'F'], | ||
ImageLaterality = 'R'; | ||
const flipsNeeded = getImageFlips({ | ||
PatientOrientation, | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({ hFlip: true, vFlip: false }); | ||
}); | ||
|
||
test("should return { hFlip: true, vFlip: false } if ImageOrientationPatient is not present, PatientOrientation is ['A', 'FL'] and ImageLaterality is R", () => { | ||
const PatientOrientation = ['A', 'FL'], | ||
ImageLaterality = 'R'; | ||
const flipsNeeded = getImageFlips({ | ||
PatientOrientation, | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({ hFlip: true, vFlip: false }); | ||
}); | ||
|
||
test("should return { hFlip: true, vFlip: false } if ImageOrientationPatient ans ImageLaterality is not present, PatientOrientation is ['P', 'FL'] and FrameLaterality is L", () => { | ||
const PatientOrientation = ['P', 'FL'], | ||
FrameLaterality = 'L'; | ||
const flipsNeeded = getImageFlips({ | ||
PatientOrientation, | ||
FrameLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({ hFlip: true, vFlip: false }); | ||
}); | ||
|
||
test("should return empty object if ImageOrientationPatient is not present, PatientOrientation is ['H', 'R'] and ImageLaterality is R since the orientation is rotated, not flipped", () => { | ||
const PatientOrientation = ['H', 'R'], | ||
ImageLaterality = 'R'; | ||
const flipsNeeded = getImageFlips({ | ||
PatientOrientation, | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({}); | ||
}); | ||
|
||
test("should return empty object if ImageOrientationPatient is not present, PatientOrientation is ['F', 'L'] and ImageLaterality is L since the orientation is rotated, not flipped", () => { | ||
const PatientOrientation = ['F', 'L'], | ||
ImageLaterality = 'L'; | ||
const flipsNeeded = getImageFlips({ | ||
PatientOrientation, | ||
ImageLaterality, | ||
}); | ||
expect(flipsNeeded).toEqual({}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { defaults, utils } from '@ohif/core'; | ||
import { utilities } from '@cornerstonejs/tools'; | ||
import { vec3 } from 'gl-matrix'; | ||
|
||
const { orientationDirectionVectorMap } = defaults; | ||
const { getOrientationStringLPS } = utilities.orientation; | ||
|
||
type IOP = [number, number, number, number, number, number]; | ||
type PO = [string, string] | string; | ||
type IL = string; | ||
type FL = string; | ||
type Instance = { | ||
ImageOrientationPatient?: IOP; | ||
PatientOrientation?: PO; | ||
ImageLaterality?: IL; | ||
FrameLaterality?: FL; | ||
}; | ||
|
||
/** | ||
* A function to get required flipping to correct the image according to Orientation and Laterality. | ||
* This function does not handle rotated images. | ||
* @param instance Metadata instance of the image. | ||
* @returns vertical and horizontal flipping needed to correct the image if possible. | ||
*/ | ||
export function getImageFlips(instance: Instance): { vFlip?: boolean; hFlip?: boolean } { | ||
const { ImageOrientationPatient, PatientOrientation, ImageLaterality, FrameLaterality } = | ||
instance; | ||
|
||
if (!(ImageOrientationPatient || PatientOrientation) || !(ImageLaterality || FrameLaterality)) { | ||
console.warn( | ||
'Skipping image orientation correction due to missing ImageOrientationPatient/ PatientOrientation or/and ImageLaterality/ FrameLaterality' | ||
); | ||
return {}; | ||
} | ||
|
||
let rowDirectionCurrent, columnDirectionCurrent, rowCosines, columnCosines; | ||
if (ImageOrientationPatient) { | ||
rowCosines = ImageOrientationPatient.slice(0, 3); | ||
columnCosines = ImageOrientationPatient.slice(3, 6); | ||
rowDirectionCurrent = getOrientationStringLPS(rowCosines); | ||
columnDirectionCurrent = getOrientationStringLPS(columnCosines)[0]; | ||
} else { | ||
({ rowDirection: rowDirectionCurrent, columnDirection: columnDirectionCurrent } = | ||
utils.getDirectionsFromPatientOrientation(PatientOrientation)); | ||
|
||
rowCosines = orientationDirectionVectorMap[rowDirectionCurrent]; | ||
columnCosines = orientationDirectionVectorMap[columnDirectionCurrent]; | ||
} | ||
|
||
const scanAxisNormal = vec3.create(); | ||
vec3.cross(scanAxisNormal, rowCosines, columnCosines); | ||
|
||
const scanAxisDirection = getOrientationStringLPS(scanAxisNormal as [number, number, number]); | ||
|
||
if (isImageRotated(rowDirectionCurrent, columnDirectionCurrent)) { | ||
// TODO: Correcting images with rotation is not implemented. | ||
console.warn('Correcting images by rotating is not implemented'); | ||
return {}; | ||
} | ||
|
||
let rowDirectionTarget, columnDirectionTarget; | ||
switch (scanAxisDirection[0]) { | ||
// Sagittal | ||
case 'L': | ||
case 'R': | ||
if ((ImageLaterality || FrameLaterality) === 'L') { | ||
rowDirectionTarget = 'A'; | ||
} else { | ||
rowDirectionTarget = 'P'; | ||
} | ||
columnDirectionTarget = 'F'; | ||
break; | ||
// Coronal | ||
case 'A': | ||
case 'P': | ||
if ((ImageLaterality || FrameLaterality) === 'L') { | ||
rowDirectionTarget = 'R'; | ||
} else { | ||
rowDirectionTarget = 'L'; | ||
} | ||
columnDirectionTarget = 'F'; | ||
break; | ||
// Axial | ||
case 'H': | ||
case 'F': | ||
if ((ImageLaterality || FrameLaterality) === 'L') { | ||
rowDirectionTarget = 'A'; | ||
columnDirectionTarget = 'R'; | ||
} else { | ||
rowDirectionTarget = 'P'; | ||
columnDirectionTarget = 'L'; | ||
} | ||
break; | ||
} | ||
|
||
let hFlip = false, | ||
vFlip = false; | ||
if (rowDirectionCurrent !== rowDirectionTarget) { | ||
hFlip = true; | ||
} | ||
if (columnDirectionCurrent !== columnDirectionTarget) { | ||
vFlip = true; | ||
} | ||
|
||
return { hFlip, vFlip }; | ||
} | ||
|
||
function isImageRotated(rowDirection: string, columnDirection: string): boolean { | ||
const possibleValues: { [key: string]: [string, string] } = { | ||
xDirection: ['L', 'R'], | ||
yDirection: ['P', 'A'], | ||
zDirection: ['H', 'F'], | ||
}; | ||
|
||
if ( | ||
possibleValues.yDirection.includes(columnDirection) || | ||
possibleValues.zDirection.includes(rowDirection) | ||
) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} |
10 changes: 10 additions & 0 deletions
10
extensions/cornerstone/src/utils/isOrientationCorrectionNeeded.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const DEFAULT_AUTO_FLIP_MODALITIES: string[] = ['MG']; | ||
|
||
export default function isOrientationCorrectionNeeded(instance) { | ||
const { Modality } = instance; | ||
|
||
// Check Modality | ||
const isModalityIncluded = DEFAULT_AUTO_FLIP_MODALITIES.includes(Modality); | ||
|
||
return isModalityIncluded; | ||
} |
Oops, something went wrong.