UNPKG

@acransac/vtk.js

Version:

Visualization Toolkit for the Web

305 lines (254 loc) 9.48 kB
import macro from 'vtk.js/Sources/macro'; import vtkInteractorStyleTrackballCamera from 'vtk.js/Sources/Interaction/Style/InteractorStyleTrackballCamera'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import { States } from 'vtk.js/Sources/Rendering/Core/InteractorStyle/Constants'; // ---------------------------------------------------------------------------- // vtkInteractorStyleImage methods // ---------------------------------------------------------------------------- function vtkInteractorStyleImage(publicAPI, model) { // Set our className model.classHierarchy.push('vtkInteractorStyleImage'); // Public API methods publicAPI.superHandleMouseMove = publicAPI.handleMouseMove; publicAPI.handleMouseMove = (callData) => { const pos = callData.position; const renderer = callData.pokedRenderer; switch (model.state) { case States.IS_WINDOW_LEVEL: publicAPI.windowLevel(renderer, pos); publicAPI.invokeInteractionEvent({ type: 'InteractionEvent' }); break; case States.IS_SLICE: publicAPI.slice(renderer, pos); publicAPI.invokeInteractionEvent({ type: 'InteractionEvent' }); break; default: break; } publicAPI.superHandleMouseMove(callData); }; //---------------------------------------------------------------------------- publicAPI.superHandleLeftButtonPress = publicAPI.handleLeftButtonPress; publicAPI.handleLeftButtonPress = (callData) => { const pos = callData.position; if (!callData.shiftKey && !callData.controlKey) { model.windowLevelStartPosition[0] = pos.x; model.windowLevelStartPosition[1] = pos.y; // Get the last (the topmost) image publicAPI.setCurrentImageNumber(model.currentImageNumber); const property = model.currentImageProperty; if (property) { model.windowLevelInitial[0] = property.getColorWindow(); model.windowLevelInitial[1] = property.getColorLevel(); } publicAPI.startWindowLevel(); } else if (model.interactionMode === 'IMAGE3D' && callData.shiftKey) { // If shift is held down, do a rotation publicAPI.startRotate(); } else if ( model.interactionMode === 'IMAGE_SLICING' && callData.controlKey ) { // If ctrl is held down in slicing mode, slice the image model.lastSlicePosition = pos.y; publicAPI.startSlice(); } else { // The rest of the button + key combinations remain the same publicAPI.superHandleLeftButtonPress(callData); } }; //-------------------------------------------------------------------------- publicAPI.superHandleLeftButtonRelease = publicAPI.handleLeftButtonRelease; publicAPI.handleLeftButtonRelease = () => { switch (model.state) { case States.IS_WINDOW_LEVEL: publicAPI.endWindowLevel(); break; case States.IS_SLICE: publicAPI.endSlice(); break; default: publicAPI.superHandleLeftButtonRelease(); break; } }; //-------------------------------------------------------------------------- publicAPI.handleStartMouseWheel = (callData) => { publicAPI.startSlice(); publicAPI.handleMouseWheel(callData); }; //-------------------------------------------------------------------------- publicAPI.handleEndMouseWheel = () => { publicAPI.endSlice(); }; //-------------------------------------------------------------------------- publicAPI.handleMouseWheel = (callData) => { const camera = callData.pokedRenderer.getActiveCamera(); let distance = camera.getDistance(); distance += callData.spinY; // clamp the distance to the clipping range const range = camera.getClippingRange(); if (distance < range[0]) { distance = range[0]; } if (distance > range[1]) { distance = range[1]; } camera.setDistance(distance); }; //---------------------------------------------------------------------------- publicAPI.windowLevel = (renderer, position) => { model.windowLevelCurrentPosition[0] = position.x; model.windowLevelCurrentPosition[1] = position.y; const rwi = model.interactor; if (model.currentImageProperty) { const size = rwi.getView().getViewportSize(renderer); const mWindow = model.windowLevelInitial[0]; const level = model.windowLevelInitial[1]; // Compute normalized delta let dx = ((model.windowLevelCurrentPosition[0] - model.windowLevelStartPosition[0]) * 4.0) / size[0]; let dy = ((model.windowLevelStartPosition[1] - model.windowLevelCurrentPosition[1]) * 4.0) / size[1]; // Scale by current values if (Math.abs(mWindow) > 0.01) { dx *= mWindow; } else { dx *= mWindow < 0 ? -0.01 : 0.01; } if (Math.abs(level) > 0.01) { dy *= level; } else { dy *= level < 0 ? -0.01 : 0.01; } // Abs so that direction does not flip if (mWindow < 0.0) { dx *= -1; } if (level < 0.0) { dy *= -1; } // Compute new mWindow level let newWindow = dx + mWindow; const newLevel = level - dy; if (newWindow < 0.01) { newWindow = 0.01; } model.currentImageProperty.setColorWindow(newWindow); model.currentImageProperty.setColorLevel(newLevel); } }; //---------------------------------------------------------------------------- publicAPI.slice = (renderer, position) => { const rwi = model.interactor; const dy = position.y - model.lastSlicePosition; const camera = renderer.getActiveCamera(); const range = camera.getClippingRange(); let distance = camera.getDistance(); // scale the interaction by the height of the viewport let viewportHeight = 0.0; if (camera.getParallelProjection()) { viewportHeight = camera.getParallelScale(); } else { const angle = vtkMath.radiansFromDegrees(camera.getViewAngle()); viewportHeight = 2.0 * distance * Math.tan(0.5 * angle); } const size = rwi.getView().getViewportSize(renderer); const delta = (dy * viewportHeight) / size[1]; distance += delta; // clamp the distance to the clipping range if (distance < range[0]) { distance = range[0] + viewportHeight * 1e-3; } if (distance > range[1]) { distance = range[1] - viewportHeight * 1e-3; } camera.setDistance(distance); model.lastSlicePosition = position.y; }; //---------------------------------------------------------------------------- // This is a way of dealing with images as if they were layers. // It looks through the renderer's list of props and sets the // interactor ivars from the Nth image that it finds. You can // also use negative numbers, i.e. -1 will return the last image, // -2 will return the second-to-last image, etc. publicAPI.setCurrentImageNumber = (i) => { if (i === null) { return; } const renderer = model.interactor.getCurrentRenderer(); if (!renderer) { return; } model.currentImageNumber = i; function propMatch(j, prop, targetIndex) { if ( prop.isA('vtkImageSlice') && j === targetIndex && prop.getPickable() ) { return true; } return false; } const props = renderer.getViewProps(); let targetIndex = i; if (i < 0) { targetIndex += props.length; } let imageProp = null; let foundImageProp = false; for (let j = 0; j < props.length && !foundImageProp; j++) { if (propMatch(j, props[j], targetIndex)) { foundImageProp = true; imageProp = props[j]; } } if (imageProp) { publicAPI.setCurrentImageProperty(imageProp.getProperty()); } }; //---------------------------------------------------------------------------- publicAPI.setCurrentImageProperty = (imageProperty) => { model.currentImageProperty = imageProperty; }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { windowLevelStartPosition: [0, 0], windowLevelCurrentPosition: [0, 0], lastSlicePosition: 0, windowLevelInitial: [1.0, 0.5], currentImageProperty: 0, currentImageNumber: -1, interactionMode: 'IMAGE2D', xViewRightVector: [0, 1, 0], xViewUpVector: [0, 0, -1], yViewRightVector: [1, 0, 0], yViewUpVector: [0, 0, -1], zViewRightVector: [1, 0, 0], zViewUpVector: [0, 1, 0], }; // ---------------------------------------------------------------------------- export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); // Inheritance vtkInteractorStyleTrackballCamera.extend(publicAPI, model, initialValues); // Create get-set macros macro.setGet(publicAPI, model, ['interactionMode']); // For more macro methods, see "Sources/macro.js" // Object specific methods vtkInteractorStyleImage(publicAPI, model); } // ---------------------------------------------------------------------------- export const newInstance = macro.newInstance(extend, 'vtkInteractorStyleImage'); // ---------------------------------------------------------------------------- export default { newInstance, extend };