Skip to content

Instantly share code, notes, and snippets.

@agirault
Last active February 5, 2021 15:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save agirault/1779e12ad36ae8bca7da526fbbd772a8 to your computer and use it in GitHub Desktop.
Save agirault/1779e12ad36ae8bca7da526fbbd772a8 to your computer and use it in GitHub Desktop.
import vtkImageMapper from 'vtk.js/Sources/Rendering/Core/ImageMapper';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
const { SlicingMode } = vtkImageMapper;
const ScreenSide = {
top: Symbol('top'),
bottom: Symbol('bottom'),
left: Symbol('left'),
right: Symbol('right'),
close: Symbol('close'),
far: Symbol('far'),
};
function LPSCoordinatesOfScreenSide(screenSide) {
switch (screenSide) {
case ScreenSide.top: return [0, -1, 0];
case ScreenSide.bottom: return [0, 1, 0];
case ScreenSide.left: return [-1, 0, 0];
case ScreenSide.right: return [1, 0, 0];
case ScreenSide.close: return [0, 0, -1];
case ScreenSide.far: return [0, 0, 1];
default: return [];
}
}
const Label = {
left: Symbol('Left'),
right: Symbol('Right'),
posterior: Symbol('Posterior'),
anterior: Symbol('Anterior'),
superior: Symbol('Superior'),
inferior: Symbol('Inferior'),
};
function LPSCoordinatesOfLabel(label) {
switch (label) {
case Label.left: return [1, 0, 0];
case Label.right: return [-1, 0, 0];
case Label.posterior: return [0, 1, 0];
case Label.anterior: return [0, -1, 0];
case Label.superior: return [0, 0, 1];
case Label.inferior: return [0, 0, -1];
default: return [];
}
}
function labelFor(screenSide, mapper) {
// Method for image space slicing (I, J, K)
function labelForImageSpaceSlicing(mat3x3) {
// Create a 3x3 matrix transformation from the image space
// (IJK, JKI or KIJ) to world space (LPS)
const dir9 = mapper.getInputData().getDirection();
const dir3x3 = [
[dir9[0], dir9[3], dir9[6]],
[dir9[1], dir9[4], dir9[7]],
[dir9[2], dir9[5], dir9[8]],
];
vtkMath.multiply3x3_mat3(dir3x3, mat3x3, mat3x3);
// Apply the transformation to the coordinate of the side
let outVec = [0, 0, 0];
const inVec = LPSCoordinatesOfScreenSide(screenSide);
vtkMath.multiply3x3_vect3(mat3x3, inVec, outVec);
// Restrict to the unit vectors
outVec = outVec.map((val) => {
const threshold = Math.sqrt(0.5);
if (val < -threshold) {
return -1;
}
if (val > threshold) {
return 1;
}
return 0;
});
// Find matching label
const labels = Object.values(Label);
return labels.find((label) => vtkMath.areMatricesEqual(outVec, LPSCoordinatesOfLabel(label)));
}
switch (mapper.getSlicingMode()) {
case SlicingMode.I: {
// JKI -> IJK
const mat3x3 = [[0, 1, 0], [0, 0, -1], [-1, 0, 0]];
return labelForImageSpaceSlicing(mat3x3);
}
case SlicingMode.J: {
// KIJ -> IJK
const mat3x3 = [[1, 0, 0], [0, 0, -1], [0, 1, 0]];
return labelForImageSpaceSlicing(mat3x3);
}
case SlicingMode.K: {
// IJK -> IJK
const mat3x3 = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
return labelForImageSpaceSlicing(mat3x3);
}
case SlicingMode.X:
switch (screenSide) {
case ScreenSide.top: return Label.superior;
case ScreenSide.bottom: return Label.inferior;
case ScreenSide.left: return Label.anterior;
case ScreenSide.right: return Label.posterior;
case ScreenSide.close: return Label.left;
case ScreenSide.far: return Label.right;
default: return null;
}
case SlicingMode.Y:
switch (side) {
case ScreenSide.top: return Label.superior;
case ScreenSide.bottom: return Label.inferior;
case ScreenSide.left: return Label.right;
case ScreenSide.right: return Label.left;
case ScreenSide.close: return Label.anterior;
case ScreenSide.far: return Label.posterior;
default: return null;
}
case SlicingMode.Z:
switch (screenSide) {
case ScreenSide.top: return Label.anterior;
case ScreenSide.bottom: return Label.posterior;
case ScreenSide.left: return Label.right;
case ScreenSide.right: return Label.left;
case ScreenSide.close: return Label.inferior;
case ScreenSide.far: return Label.superior;
default: return null;
}
default: return null;
}
}
export default {
ScreenSide,
labelFor,
};
@finetjul
Copy link

finetjul commented Feb 2, 2021

I find it odd that Side and LPSCoordinatesOfSide start with top/bottom but Label starts with left/right.

I also find odd that top is [0, -1, 0] and not [0, 1, 0]

@agirault
Copy link
Author

agirault commented Feb 3, 2021

I find it odd that Side and LPSCoordinatesOfSide start with top/bottom but Label starts with left/right.

They're two distinct things though L/R for Side is left and right of the screen. L/R for Label is anatomical orientations. I don't think the order matters here since there isn't a 1-1 match.

I also find odd that top is [0, -1, 0] and not [0, 1, 0]

So, the logic here is a bit hard to explain (meaning I could do a better job/it should be refactored another way for clarity), but I'll try to show my thinking here: if you take a DICOM scan (IJ coordinates, slices along K) that is axial (direction cosines -> identity in LPS), then the top of the screen points to "Anterior" which is [0, -1, 0] in LPS.

It does work (I've tested with data acquired in any arbitrary direction), but I am pretty sure there is a better way to explain/implement the conversion from screen coordinates (Side) to anatomical coordinates (Label)

@finetjul
Copy link

finetjul commented Feb 3, 2021

Not sure to fully understand, but it seems that you know what you are doing :-)

As a side note, I did not realize that Side was for screen positions, maybe ScreenSide would be more meaningful...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment