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: flip viewports via camera api instead of actor #271

Merged
merged 3 commits into from
Nov 1, 2022
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
155 changes: 92 additions & 63 deletions packages/core/src/RenderingEngine/Viewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,82 +230,111 @@ class Viewport implements IViewport {
return;
}

// In Cornerstone gpu rendering pipeline, the images are positioned
// in the space according to their origin, and direction (even StackViewport
// with one slice only). In order to flip the images, we need to flip them
// around their center axis (either horizontal or vertical). Since the images
// are positioned in the space according to their origin and direction, for a
// proper scaling (flipping), they should be transformed to the origin and
// then flipped. The following code does this transformation.

const origin = imageData.getOrigin();
const direction = imageData.getDirection();
const spacing = imageData.getSpacing();
const size = imageData.getDimensions();

const iVector = direction.slice(0, 3);
const jVector = direction.slice(3, 6);
const kVector = direction.slice(6, 9);

// finding the center of the image
const center = vec3.create();
vec3.scaleAndAdd(center, origin, iVector, (size[0] / 2.0) * spacing[0]);
vec3.scaleAndAdd(center, center, jVector, (size[1] / 2.0) * spacing[1]);
vec3.scaleAndAdd(center, center, kVector, (size[2] / 2.0) * spacing[2]);

let flipHTx, flipVTx;

const transformToOriginTx = vtkMatrixBuilder
.buildFromRadian()
.identity()
.translate(center[0], center[1], center[2])
.rotateFromDirections(jVector, [0, 1, 0])
.rotateFromDirections(iVector, [1, 0, 0]);
const { viewPlaneNormal, viewUp, focalPoint, position } = this.getCamera();

const transformBackFromOriginTx = vtkMatrixBuilder
.buildFromRadian()
.identity()
.rotateFromDirections([1, 0, 0], iVector)
.rotateFromDirections([0, 1, 0], jVector)
.translate(-center[0], -center[1], -center[2]);
const viewRight = vec3.create();
vec3.cross(viewRight, viewPlaneNormal, viewUp);

let viewUpToSet = vec3.create();
vec3.copy(viewUpToSet, viewUp);

let viewPlaneNormalToSet = vec3.create();
viewPlaneNormalToSet = vec3.negate(viewPlaneNormalToSet, viewPlaneNormal);

// for both flip horizontal and vertical we need to move the camera to the
// other side of the image
const distance = vec3.distance(position, focalPoint);

// If the pan has been applied, we need to be able
// apply the pan back
const resetFocalPoint = vec3.create();
const dimensions = imageData.getDimensions();
const middleIJK = dimensions.map((d) => Math.floor(d / 2));

const idx = [middleIJK[0], middleIJK[1], middleIJK[2]];
imageData.indexToWorld(idx, resetFocalPoint);

// what is the difference right now between the rested focal point and
// the current focal point
// Todo: this needs to be retrieved from the function that considers maintainFrame
// just now trying it on stack Viewport
const panDir = vec3.create();
vec3.subtract(panDir, focalPoint, resetFocalPoint);

const panValue = vec3.length(panDir);

const getPanDir = (mirrorVec) => {
const panDirMirror = vec3.create();
vec3.scale(panDirMirror, mirrorVec, 2 * vec3.dot(panDir, mirrorVec));
vec3.subtract(panDirMirror, panDirMirror, panDir);
vec3.normalize(panDirMirror, panDirMirror);

return panDirMirror;
};

// Flipping horizontal mean that the camera should move
// to the other side of the image but looking at the
// same direction and same focal point
if (flipH) {
this.flipHorizontal = flipHorizontal;
flipHTx = vtkMatrixBuilder
.buildFromRadian()
.multiply(transformToOriginTx.getMatrix())
.scale(-1, 1, 1)
.multiply(transformBackFromOriginTx.getMatrix());
// we need to apply the pan value to the new focal point but in the direction
// that is mirrored on the viewUp for the flip horizontal and
// viewRight for the flip vertical
const newFocalPoint = vec3.create();

// mirror the pan direction based on the viewUp
const panDirMirror = getPanDir(viewUpToSet);

// move focal point from the resetFocalPoint to the newFocalPoint
// based on the panDirMirror and panValue
vec3.scaleAndAdd(newFocalPoint, resetFocalPoint, panDirMirror, panValue);

// move the camera position also the same way as the focal point
const newPosition = vec3.create();

vec3.scaleAndAdd(
newPosition,
newFocalPoint,
viewPlaneNormalToSet,
distance
);

this.setCamera({
viewPlaneNormal: viewPlaneNormalToSet as Point3,
position: newPosition as Point3,
focalPoint: newFocalPoint as Point3,
});
}

// Flipping vertical mean that the camera should negate the view up
// and also move to the other side of the image but looking at the
if (flipV) {
this.flipVertical = flipVertical;
flipVTx = vtkMatrixBuilder
.buildFromRadian()
.multiply(transformToOriginTx.getMatrix())
.scale(1, -1, 1)
.multiply(transformBackFromOriginTx.getMatrix());
}
viewUpToSet = vec3.negate(viewUpToSet, viewUp);

const actorEntries = this.getActors();
// we need to apply the pan value to the new focal point but in the direction
const panDirMirror = getPanDir(viewRight);

actorEntries.forEach((actorEntry) => {
const actor = actorEntry.actor;
const newFocalPoint = vec3.create();

const mat = actor.getUserMatrix();
vec3.scaleAndAdd(newFocalPoint, resetFocalPoint, panDirMirror, panValue);

if (flipHTx) {
mat4.multiply(mat, mat, flipHTx.getMatrix());
}
const newPosition = vec3.create();

if (flipVTx) {
mat4.multiply(mat, mat, flipVTx.getMatrix());
}
vec3.scaleAndAdd(
newPosition,
newFocalPoint,
viewPlaneNormalToSet,
distance
);

actor.setUserMatrix(mat);
});
this.setCamera({
focalPoint: newFocalPoint as Point3,
viewPlaneNormal: viewPlaneNormalToSet as Point3,
viewUp: viewUpToSet as Point3,
position: newPosition as Point3,
});
}

this.getRenderingEngine().render();
this.render();
}

private getDefaultImageData(): any {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/test/stackViewport_gpu_render_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ describe('renderingCore -- Stack', () => {
}
});

it('Should be able to flip a stack viewport horizontally and rotate it', function (done) {
it('Should be able to flip a stack viewport vertically and rotate it', function (done) {
const element = createViewport(this.renderingEngine, AXIAL, 256, 256);
this.DOMElements.push(element);

Expand All @@ -971,9 +971,9 @@ describe('renderingCore -- Stack', () => {
rotation: 90,
});

vp.setCamera({ flipHorizontal: true });

vp.render();

vp.setCamera({ flipVertical: true });
});
} catch (e) {
done.fail(e);
Expand Down