Skip to content

Commit 62ddef1

Browse files
authored
Merge pull request #29 from KitwareMedical/oriented-views
Oriented views
2 parents 3acabf0 + d09d2d2 commit 62ddef1

14 files changed

Lines changed: 408 additions & 203 deletions

File tree

src/components/VtkThreeView.vue

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
CommonViewProps,
2525
useVtkView,
2626
useVtkViewCameraOrientation,
27-
giveViewAnnotations,
27+
applyViewAnnotations,
2828
} from '@/src/composables/view/common';
2929
import { useResizeObserver } from '@/src/composables/resizeObserver';
3030
import { watchScene, watchColorBy } from '@/src/composables/scene';
@@ -42,9 +42,9 @@ export default {
4242
4343
const {
4444
sceneSources,
45-
worldOrientation,
45+
imageParams,
4646
colorBy,
47-
boundsWithSpacing,
47+
extentWithSpacing,
4848
baseImageColorPreset,
4949
baseImage,
5050
slices,
@@ -56,11 +56,11 @@ export default {
5656
.filter((id) => id in pipelines)
5757
.map((id) => pipelines[id].last);
5858
},
59-
worldOrientation: (state) => state.visualization.worldOrientation,
59+
imageParams: (state) => state.visualization.imageParams,
6060
colorBy: (state, getters) =>
6161
getters.sceneObjectIDs.map((id) => state.visualization.colorBy[id]),
62-
boundsWithSpacing: (_, getters) =>
63-
getters['visualization/boundsWithSpacing'],
62+
extentWithSpacing: (_, getters) =>
63+
getters['visualization/extentWithSpacing'],
6464
baseImageColorPreset: (_, getters) =>
6565
getters['visualization/baseImageColorPreset'],
6666
baseImage(state) {
@@ -75,7 +75,7 @@ export default {
7575
windowing: (state) => state.visualization.windowing,
7676
});
7777
78-
const spacing = computed(() => worldOrientation.value.spacing);
78+
const spacing = computed(() => imageParams.value.spacing);
7979
8080
const viewRef = useVtkView({
8181
containerRef: vtkContainer,
@@ -94,7 +94,7 @@ export default {
9494
});
9595
9696
// update scene sources and their colors
97-
watchScene(sceneSources, worldOrientation, viewRef);
97+
watchScene(sceneSources, viewRef);
9898
watchColorBy(colorBy, sceneSources, viewRef);
9999
100100
// prepare view
@@ -121,14 +121,14 @@ export default {
121121
});
122122
123123
// reset camera whenever bounds changes
124-
watch(boundsWithSpacing, () => {
124+
watch(extentWithSpacing, () => {
125125
const view = unref(viewRef);
126126
if (view) {
127127
view.resetCamera();
128128
}
129129
});
130130
131-
giveViewAnnotations(
131+
applyViewAnnotations(
132132
viewRef,
133133
reactive({
134134
nw: baseImageColorPreset,

src/components/VtkTwoView.vue

Lines changed: 99 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ import {
3333
import {
3434
CommonViewProps,
3535
useVtkView,
36-
useVtkViewCameraOrientation,
37-
giveViewAnnotations,
36+
applyViewAnnotations,
3837
} from '@/src/composables/view/common';
3938
import {
4039
useOrientationLabels,
4140
use2DMouseControls,
4241
usePixelProbe,
42+
apply2DCameraPlacement,
43+
useIJKAxisCamera,
4344
} from '@/src/composables/view/view2D';
4445
import { useResizeObserver } from '@/src/composables/resizeObserver';
4546
import { watchScene, watchColorBy } from '@/src/composables/scene';
@@ -48,24 +49,84 @@ import { useSubscription } from '@/src/composables/vtk';
4849
import { useWidgetProvider } from '@/src/composables/widgetProvider';
4950
import { useProxyManager } from '@/src/composables/proxyManager';
5051
51-
import { resize2DCameraToFit } from '@/src/vtk/proxyUtils';
5252
import { DataTypes } from '@/src/constants';
5353
5454
import SliceSlider from '@/src/components/SliceSlider.vue';
5555
56+
/**
57+
* Sets parallel scale of 2D view camera to fit a given bounds.
58+
*
59+
* Assumes the camera is reset, i.e. focused correctly.
60+
*
61+
* Bounds is specified as width/height of orthographic view.
62+
* Renders must be triggered manually.
63+
*/
64+
function resize2DCameraToFit(view, lookAxis, viewUpAxis, bounds) {
65+
const camera = view.getCamera();
66+
const lengths = [
67+
bounds[1] - bounds[0],
68+
bounds[3] - bounds[2],
69+
bounds[5] - bounds[4],
70+
];
71+
const [w, h] = view.getOpenglRenderWindow().getSize();
72+
let bw;
73+
let bh;
74+
/* eslint-disable prefer-destructuring */
75+
if (lookAxis === 0 && viewUpAxis === 1) {
76+
bw = lengths[2];
77+
bh = lengths[1];
78+
} else if (lookAxis === 0 && viewUpAxis === 2) {
79+
bw = lengths[1];
80+
bh = lengths[2];
81+
} else if (lookAxis === 1 && viewUpAxis === 0) {
82+
bw = lengths[2];
83+
bh = lengths[0];
84+
} else if (lookAxis === 1 && viewUpAxis === 2) {
85+
bw = lengths[0];
86+
bh = lengths[2];
87+
} else if (lookAxis === 2 && viewUpAxis === 0) {
88+
bw = lengths[1];
89+
bh = lengths[0];
90+
} else if (lookAxis === 2 && viewUpAxis === 1) {
91+
bw = lengths[0];
92+
bh = lengths[1];
93+
}
94+
/* eslint-enable prefer-destructuring */
95+
const viewAspect = w / h;
96+
const boundsAspect = bw / bh;
97+
98+
let scale = 0;
99+
if (viewAspect >= boundsAspect) {
100+
scale = bh / 2;
101+
} else {
102+
scale = bw / 2 / viewAspect;
103+
}
104+
105+
camera.setParallelScale(scale);
106+
}
107+
56108
/**
57109
* This differs from view.resetCamera() in that we reset the view
58110
* to the specified bounds.
59111
*/
60-
function resetCamera(viewRef, boundsWithSpacing, resizeToFit) {
112+
function resetCamera(viewRef, lookAxis, viewUpAxis, imageParams, resizeToFit) {
61113
const view = unref(viewRef);
62114
if (view) {
63115
const renderer = view.getRenderer();
64116
renderer.computeVisiblePropBounds();
65-
renderer.resetCamera(unref(boundsWithSpacing));
117+
renderer.resetCamera(imageParams.value.bounds);
66118
67119
if (unref(resizeToFit)) {
68-
resize2DCameraToFit(view, unref(boundsWithSpacing));
120+
const { extent, spacing } = imageParams.value;
121+
const extentWithSpacing = extent.map(
122+
(e, i) => e * spacing[Math.floor(i / 2)]
123+
);
124+
resize2DCameraToFit(
125+
view,
126+
unref(lookAxis),
127+
unref(viewUpAxis),
128+
unref(extentWithSpacing)
129+
);
69130
}
70131
}
71132
}
@@ -80,20 +141,26 @@ export default {
80141
},
81142
82143
setup(props) {
83-
const { viewName, viewType, viewUp, axis, orientation } = toRefs(props);
144+
const { viewName, viewType } = toRefs(props);
84145
const vtkContainer = ref(null);
85146
const resizeToFit = ref(true);
147+
148+
const { axis, orientation, viewUp, viewUpAxis } = useIJKAxisCamera(
149+
viewType
150+
);
86151
const axisLabel = computed(() => 'xyz'[axis.value]);
87152
88153
const store = useStore();
89154
const widgetProvider = useWidgetProvider();
90155
const pxm = useProxyManager();
91156
157+
// currentSlice: VtkTwoView concerns itself only with IJK coords, so
158+
// currentSlice is expected to be in image coords.
92159
const {
93160
sceneSources,
94-
worldOrientation,
161+
imageParams,
95162
colorBy,
96-
boundsWithSpacing,
163+
extentWithSpacing,
97164
baseImage,
98165
currentSlice,
99166
windowing,
@@ -105,11 +172,11 @@ export default {
105172
.filter((id) => id in pipelines)
106173
.map((id) => pipelines[id].last);
107174
},
108-
worldOrientation: (state) => state.visualization.worldOrientation,
175+
imageParams: (state) => state.visualization.imageParams,
109176
colorBy: (state, getters) =>
110177
getters.sceneObjectIDs.map((id) => state.visualization.colorBy[id]),
111-
boundsWithSpacing: (_, getters) =>
112-
getters['visualization/boundsWithSpacing'],
178+
extentWithSpacing: (_, getters) =>
179+
getters['visualization/extentWithSpacing'],
113180
baseImage(state) {
114181
const { pipelines } = state.visualization;
115182
const { selectedBaseImage } = state;
@@ -137,18 +204,21 @@ export default {
137204
},
138205
});
139206
140-
const currentSliceSpacing = computed(
141-
() => worldOrientation.value.spacing[axis.value]
142-
);
143-
144207
const viewRef = useVtkView({
145208
containerRef: vtkContainer,
146209
viewName,
147210
viewType,
148211
});
149212
150213
// configure camera orientation
151-
useVtkViewCameraOrientation(viewRef, viewUp, axis, orientation);
214+
apply2DCameraPlacement(
215+
viewRef,
216+
imageParams,
217+
viewUp,
218+
orientation,
219+
axis,
220+
'image'
221+
);
152222
153223
useResizeObserver(vtkContainer, () => {
154224
const view = unref(viewRef);
@@ -157,17 +227,19 @@ export default {
157227
}
158228
});
159229
160-
watchScene(sceneSources, worldOrientation, viewRef);
230+
watchScene(sceneSources, viewRef);
161231
watchColorBy(colorBy, sceneSources, viewRef);
162232
163233
// reset camera conditions
164234
watch(
165-
[baseImage, boundsWithSpacing],
166-
() => resetCamera(viewRef, boundsWithSpacing, resizeToFit),
235+
[baseImage, extentWithSpacing],
236+
() => resetCamera(viewRef, axis, viewUpAxis, imageParams, resizeToFit),
167237
{ immediate: true }
168238
);
169239
useSubscription(viewRef, (view) =>
170-
view.onResize(() => resetCamera(viewRef, boundsWithSpacing, resizeToFit))
240+
view.onResize(() =>
241+
resetCamera(viewRef, axis, viewUpAxis, imageParams, resizeToFit)
242+
)
171243
);
172244
173245
// setup view
@@ -193,10 +265,10 @@ export default {
193265
default: windowing.value.level,
194266
}));
195267
const sliceRange = computed(() => {
196-
const { bounds } = unref(worldOrientation);
268+
const { extent } = unref(imageParams);
197269
return {
198-
min: bounds[axis.value * 2],
199-
max: bounds[axis.value * 2 + 1],
270+
min: extent[axis.value * 2],
271+
max: extent[axis.value * 2 + 1],
200272
step: 1,
201273
default: currentSlice.value,
202274
};
@@ -231,8 +303,7 @@ export default {
231303
if (viewRef.value && baseImage.value) {
232304
const rep = pxm.getRepresentation(baseImage.value, viewRef.value);
233305
if (rep) {
234-
if (rep.setSlice)
235-
rep.setSlice(currentSlice.value * currentSliceSpacing.value);
306+
if (rep.setSlice) rep.setSlice(currentSlice.value);
236307
if (rep.setWindowWidth) rep.setWindowWidth(windowing.value.width);
237308
if (rep.setWindowLevel) rep.setWindowLevel(windowing.value.level);
238309
}
@@ -243,10 +314,7 @@ export default {
243314
const { pixelProbe } = usePixelProbe(viewRef, baseImage);
244315
245316
// orientation labels
246-
const { leftLabel, upLabel } = useOrientationLabels(
247-
viewRef,
248-
worldOrientation
249-
);
317+
const { left: leftLabel, top: upLabel } = useOrientationLabels(viewRef);
250318
251319
// pixel probe annotation
252320
const pixelAnnotation = computed(() => {
@@ -285,7 +353,7 @@ export default {
285353
: ''
286354
);
287355
288-
giveViewAnnotations(
356+
applyViewAnnotations(
289357
viewRef,
290358
reactive({
291359
n: upLabel,

src/composables/scene.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ import { useProxyManager } from '@/src/composables/proxyManager';
55
/**
66
* Updates the scene.
77
* @param {Ref<vtkSourceProxy[]>} sourcesRef
8-
* @param {Ref<WorldOrientation>} worldOrientationRef
98
* @param {Ref<vtkViewProxy>} viewRef
109
*/
11-
export function watchScene(sourcesRef, worldOrientationRef, viewRef) {
10+
export function watchScene(sourcesRef, viewRef) {
1211
const pxm = useProxyManager();
1312

1413
function repopulateScene() {
1514
const view = unref(viewRef);
1615
const sources = unref(sourcesRef);
17-
const worldOrientation = unref(worldOrientationRef);
1816
if (view) {
1917
view
2018
.getRepresentations()
@@ -23,16 +21,13 @@ export function watchScene(sourcesRef, worldOrientationRef, viewRef) {
2321
sources.forEach((source) => {
2422
const rep = pxm.getRepresentation(source, view);
2523
if (rep) {
26-
if (rep.setTransform) {
27-
rep.setTransform(worldOrientation.worldToIndex);
28-
}
2924
view.addRepresentation(rep);
3025
}
3126
});
3227
}
3328
}
3429

35-
watch([sourcesRef, viewRef, worldOrientationRef], repopulateScene);
30+
watch([sourcesRef, viewRef], repopulateScene);
3631

3732
// trigger this after repopulateScene
3833
watch(sourcesRef, () => {

src/composables/view/common.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export function useVtkViewCameraOrientation(
132132
* @param {Reactive<{ [label: string]: Ref<string> }} labels
133133
* @param {Reactive<{ [label: string]: string }} defaults
134134
*/
135-
export function giveViewAnnotations(viewRef, labels, defaults = {}) {
135+
export function applyViewAnnotations(viewRef, labels, defaults = {}) {
136136
const places = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'];
137137

138138
watchEffect(() => {

0 commit comments

Comments
 (0)