Skip to content
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
20 changes: 10 additions & 10 deletions src/components/VtkThreeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
CommonViewProps,
useVtkView,
useVtkViewCameraOrientation,
giveViewAnnotations,
applyViewAnnotations,
} from '@/src/composables/view/common';
import { useResizeObserver } from '@/src/composables/resizeObserver';
import { watchScene, watchColorBy } from '@/src/composables/scene';
Expand All @@ -42,9 +42,9 @@ export default {

const {
sceneSources,
worldOrientation,
imageParams,
colorBy,
boundsWithSpacing,
extentWithSpacing,
baseImageColorPreset,
baseImage,
slices,
Expand All @@ -56,11 +56,11 @@ export default {
.filter((id) => id in pipelines)
.map((id) => pipelines[id].last);
},
worldOrientation: (state) => state.visualization.worldOrientation,
imageParams: (state) => state.visualization.imageParams,
colorBy: (state, getters) =>
getters.sceneObjectIDs.map((id) => state.visualization.colorBy[id]),
boundsWithSpacing: (_, getters) =>
getters['visualization/boundsWithSpacing'],
extentWithSpacing: (_, getters) =>
getters['visualization/extentWithSpacing'],
baseImageColorPreset: (_, getters) =>
getters['visualization/baseImageColorPreset'],
baseImage(state) {
Expand All @@ -75,7 +75,7 @@ export default {
windowing: (state) => state.visualization.windowing,
});

const spacing = computed(() => worldOrientation.value.spacing);
const spacing = computed(() => imageParams.value.spacing);

const viewRef = useVtkView({
containerRef: vtkContainer,
Expand All @@ -94,7 +94,7 @@ export default {
});

// update scene sources and their colors
watchScene(sceneSources, worldOrientation, viewRef);
watchScene(sceneSources, viewRef);
watchColorBy(colorBy, sceneSources, viewRef);

// prepare view
Expand All @@ -121,14 +121,14 @@ export default {
});

// reset camera whenever bounds changes
watch(boundsWithSpacing, () => {
watch(extentWithSpacing, () => {
const view = unref(viewRef);
if (view) {
view.resetCamera();
}
});

giveViewAnnotations(
applyViewAnnotations(
viewRef,
reactive({
nw: baseImageColorPreset,
Expand Down
130 changes: 99 additions & 31 deletions src/components/VtkTwoView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ import {
import {
CommonViewProps,
useVtkView,
useVtkViewCameraOrientation,
giveViewAnnotations,
applyViewAnnotations,
} from '@/src/composables/view/common';
import {
useOrientationLabels,
use2DMouseControls,
usePixelProbe,
apply2DCameraPlacement,
useIJKAxisCamera,
} from '@/src/composables/view/view2D';
import { useResizeObserver } from '@/src/composables/resizeObserver';
import { watchScene, watchColorBy } from '@/src/composables/scene';
Expand All @@ -48,24 +49,84 @@ import { useSubscription } from '@/src/composables/vtk';
import { useWidgetProvider } from '@/src/composables/widgetProvider';
import { useProxyManager } from '@/src/composables/proxyManager';

import { resize2DCameraToFit } from '@/src/vtk/proxyUtils';
import { DataTypes } from '@/src/constants';

import SliceSlider from '@/src/components/SliceSlider.vue';

/**
* Sets parallel scale of 2D view camera to fit a given bounds.
*
* Assumes the camera is reset, i.e. focused correctly.
*
* Bounds is specified as width/height of orthographic view.
* Renders must be triggered manually.
*/
function resize2DCameraToFit(view, lookAxis, viewUpAxis, bounds) {
const camera = view.getCamera();
const lengths = [
bounds[1] - bounds[0],
bounds[3] - bounds[2],
bounds[5] - bounds[4],
];
const [w, h] = view.getOpenglRenderWindow().getSize();
let bw;
let bh;
/* eslint-disable prefer-destructuring */
if (lookAxis === 0 && viewUpAxis === 1) {
bw = lengths[2];
bh = lengths[1];
} else if (lookAxis === 0 && viewUpAxis === 2) {
bw = lengths[1];
bh = lengths[2];
} else if (lookAxis === 1 && viewUpAxis === 0) {
bw = lengths[2];
bh = lengths[0];
} else if (lookAxis === 1 && viewUpAxis === 2) {
bw = lengths[0];
bh = lengths[2];
} else if (lookAxis === 2 && viewUpAxis === 0) {
bw = lengths[1];
bh = lengths[0];
} else if (lookAxis === 2 && viewUpAxis === 1) {
bw = lengths[0];
bh = lengths[1];
}
/* eslint-enable prefer-destructuring */
const viewAspect = w / h;
const boundsAspect = bw / bh;

let scale = 0;
if (viewAspect >= boundsAspect) {
scale = bh / 2;
} else {
scale = bw / 2 / viewAspect;
}

camera.setParallelScale(scale);
}

/**
* This differs from view.resetCamera() in that we reset the view
* to the specified bounds.
*/
function resetCamera(viewRef, boundsWithSpacing, resizeToFit) {
function resetCamera(viewRef, lookAxis, viewUpAxis, imageParams, resizeToFit) {
const view = unref(viewRef);
if (view) {
const renderer = view.getRenderer();
renderer.computeVisiblePropBounds();
renderer.resetCamera(unref(boundsWithSpacing));
renderer.resetCamera(imageParams.value.bounds);

if (unref(resizeToFit)) {
resize2DCameraToFit(view, unref(boundsWithSpacing));
const { extent, spacing } = imageParams.value;
const extentWithSpacing = extent.map(
(e, i) => e * spacing[Math.floor(i / 2)]
);
resize2DCameraToFit(
view,
unref(lookAxis),
unref(viewUpAxis),
unref(extentWithSpacing)
);
}
}
}
Expand All @@ -80,20 +141,26 @@ export default {
},

setup(props) {
const { viewName, viewType, viewUp, axis, orientation } = toRefs(props);
const { viewName, viewType } = toRefs(props);
const vtkContainer = ref(null);
const resizeToFit = ref(true);

const { axis, orientation, viewUp, viewUpAxis } = useIJKAxisCamera(
viewType
);
const axisLabel = computed(() => 'xyz'[axis.value]);

const store = useStore();
const widgetProvider = useWidgetProvider();
const pxm = useProxyManager();

// currentSlice: VtkTwoView concerns itself only with IJK coords, so
// currentSlice is expected to be in image coords.
const {
sceneSources,
worldOrientation,
imageParams,
colorBy,
boundsWithSpacing,
extentWithSpacing,
baseImage,
currentSlice,
windowing,
Expand All @@ -105,11 +172,11 @@ export default {
.filter((id) => id in pipelines)
.map((id) => pipelines[id].last);
},
worldOrientation: (state) => state.visualization.worldOrientation,
imageParams: (state) => state.visualization.imageParams,
colorBy: (state, getters) =>
getters.sceneObjectIDs.map((id) => state.visualization.colorBy[id]),
boundsWithSpacing: (_, getters) =>
getters['visualization/boundsWithSpacing'],
extentWithSpacing: (_, getters) =>
getters['visualization/extentWithSpacing'],
baseImage(state) {
const { pipelines } = state.visualization;
const { selectedBaseImage } = state;
Expand Down Expand Up @@ -137,18 +204,21 @@ export default {
},
});

const currentSliceSpacing = computed(
() => worldOrientation.value.spacing[axis.value]
);

const viewRef = useVtkView({
containerRef: vtkContainer,
viewName,
viewType,
});

// configure camera orientation
useVtkViewCameraOrientation(viewRef, viewUp, axis, orientation);
apply2DCameraPlacement(
viewRef,
imageParams,
viewUp,
orientation,
axis,
'image'
);

useResizeObserver(vtkContainer, () => {
const view = unref(viewRef);
Expand All @@ -157,17 +227,19 @@ export default {
}
});

watchScene(sceneSources, worldOrientation, viewRef);
watchScene(sceneSources, viewRef);
watchColorBy(colorBy, sceneSources, viewRef);

// reset camera conditions
watch(
[baseImage, boundsWithSpacing],
() => resetCamera(viewRef, boundsWithSpacing, resizeToFit),
[baseImage, extentWithSpacing],
() => resetCamera(viewRef, axis, viewUpAxis, imageParams, resizeToFit),
{ immediate: true }
);
useSubscription(viewRef, (view) =>
view.onResize(() => resetCamera(viewRef, boundsWithSpacing, resizeToFit))
view.onResize(() =>
resetCamera(viewRef, axis, viewUpAxis, imageParams, resizeToFit)
)
);

// setup view
Expand All @@ -193,10 +265,10 @@ export default {
default: windowing.value.level,
}));
const sliceRange = computed(() => {
const { bounds } = unref(worldOrientation);
const { extent } = unref(imageParams);
return {
min: bounds[axis.value * 2],
max: bounds[axis.value * 2 + 1],
min: extent[axis.value * 2],
max: extent[axis.value * 2 + 1],
step: 1,
default: currentSlice.value,
};
Expand Down Expand Up @@ -231,8 +303,7 @@ export default {
if (viewRef.value && baseImage.value) {
const rep = pxm.getRepresentation(baseImage.value, viewRef.value);
if (rep) {
if (rep.setSlice)
rep.setSlice(currentSlice.value * currentSliceSpacing.value);
if (rep.setSlice) rep.setSlice(currentSlice.value);
if (rep.setWindowWidth) rep.setWindowWidth(windowing.value.width);
if (rep.setWindowLevel) rep.setWindowLevel(windowing.value.level);
}
Expand All @@ -243,10 +314,7 @@ export default {
const { pixelProbe } = usePixelProbe(viewRef, baseImage);

// orientation labels
const { leftLabel, upLabel } = useOrientationLabels(
viewRef,
worldOrientation
);
const { left: leftLabel, top: upLabel } = useOrientationLabels(viewRef);

// pixel probe annotation
const pixelAnnotation = computed(() => {
Expand Down Expand Up @@ -285,7 +353,7 @@ export default {
: ''
);

giveViewAnnotations(
applyViewAnnotations(
viewRef,
reactive({
n: upLabel,
Expand Down
9 changes: 2 additions & 7 deletions src/composables/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ import { useProxyManager } from '@/src/composables/proxyManager';
/**
* Updates the scene.
* @param {Ref<vtkSourceProxy[]>} sourcesRef
* @param {Ref<WorldOrientation>} worldOrientationRef
* @param {Ref<vtkViewProxy>} viewRef
*/
export function watchScene(sourcesRef, worldOrientationRef, viewRef) {
export function watchScene(sourcesRef, viewRef) {
const pxm = useProxyManager();

function repopulateScene() {
const view = unref(viewRef);
const sources = unref(sourcesRef);
const worldOrientation = unref(worldOrientationRef);
if (view) {
view
.getRepresentations()
Expand All @@ -23,16 +21,13 @@ export function watchScene(sourcesRef, worldOrientationRef, viewRef) {
sources.forEach((source) => {
const rep = pxm.getRepresentation(source, view);
if (rep) {
if (rep.setTransform) {
rep.setTransform(worldOrientation.worldToIndex);
}
view.addRepresentation(rep);
}
});
}
}

watch([sourcesRef, viewRef, worldOrientationRef], repopulateScene);
watch([sourcesRef, viewRef], repopulateScene);

// trigger this after repopulateScene
watch(sourcesRef, () => {
Expand Down
2 changes: 1 addition & 1 deletion src/composables/view/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function useVtkViewCameraOrientation(
* @param {Reactive<{ [label: string]: Ref<string> }} labels
* @param {Reactive<{ [label: string]: string }} defaults
*/
export function giveViewAnnotations(viewRef, labels, defaults = {}) {
export function applyViewAnnotations(viewRef, labels, defaults = {}) {
const places = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'];

watchEffect(() => {
Expand Down
Loading