volview 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.browserslistrc +4 -0
- package/.editorconfig +6 -0
- package/.env +6 -0
- package/.eslintrc.js +109 -0
- package/.gitattributes +1 -0
- package/.github/workflows/checks.yml +16 -0
- package/.github/workflows/publish.yml +52 -0
- package/.prettierignore +5 -0
- package/CONTRIBUTING.md +29 -0
- package/LICENSE +202 -0
- package/README.md +44 -0
- package/babel.config.js +3 -0
- package/documentation/config.js +15 -0
- package/documentation/content/KWVolViewLogo.png +0 -0
- package/documentation/content/KWVolViewLogo.svg +35 -0
- package/documentation/content/VolViewLogo.png +0 -0
- package/documentation/content/VolViewLogo.svg +22 -0
- package/documentation/content/doc/index.md +64 -0
- package/documentation/content/doc/loading_data.md +30 -0
- package/documentation/content/doc/mouse_controls.md +34 -0
- package/documentation/content/doc/quick_start_guide.md +75 -0
- package/documentation/content/doc/rendering.md +27 -0
- package/documentation/content/doc/state_files.md +18 -0
- package/documentation/content/doc/toolbar.md +20 -0
- package/documentation/content/doc/welcome_screen.md +23 -0
- package/documentation/content/gallery/01-volview-welcome-notes.jpg +0 -0
- package/documentation/content/gallery/01-volview-welcome.jpg +0 -0
- package/documentation/content/gallery/02-volview-about.jpg +0 -0
- package/documentation/content/gallery/02-volview-notifications.jpg +0 -0
- package/documentation/content/gallery/02-volview-settings.jpg +0 -0
- package/documentation/content/gallery/03-volview-sample-datasets.jpg +0 -0
- package/documentation/content/gallery/04-volview-active-data.jpg +0 -0
- package/documentation/content/gallery/05-volview-select-data-to-delete.jpg +0 -0
- package/documentation/content/gallery/06-volview-corner-annotation.jpg +0 -0
- package/documentation/content/gallery/07-volview-layout-notes.jpg +0 -0
- package/documentation/content/gallery/07-volview-layout.jpg +0 -0
- package/documentation/content/gallery/09-volview-layout-3DPrimary.jpg +0 -0
- package/documentation/content/gallery/10-volview-wl-pan-zoom-notes.jpg +0 -0
- package/documentation/content/gallery/10-volview-wl-pan-zoom.jpg +0 -0
- package/documentation/content/gallery/11-volview-paint-notes.jpg +0 -0
- package/documentation/content/gallery/11-volview-paint.jpg +0 -0
- package/documentation/content/gallery/12-volview-paint-options.jpg +0 -0
- package/documentation/content/gallery/13-volview-crop-notes.jpg +0 -0
- package/documentation/content/gallery/13-volview-crop.jpg +0 -0
- package/documentation/content/gallery/14-volview-ruler.jpg +0 -0
- package/documentation/content/gallery/15-volview-crosshairs.jpg +0 -0
- package/documentation/content/gallery/16-volview-rendering.jpg +0 -0
- package/documentation/content/gallery/17-volview-colormap-notes.jpg +0 -0
- package/documentation/content/gallery/17-volview-colormap.jpg +0 -0
- package/documentation/content/gallery/17-volview-opacity-notes.jpg +0 -0
- package/documentation/content/gallery/17-volview-opacity.jpg +0 -0
- package/documentation/content/gallery/17-volview-opacity2.jpg +0 -0
- package/documentation/content/gallery/18-volview-presets.jpg +0 -0
- package/documentation/content/gallery/19-volview-color.jpg +0 -0
- package/documentation/content/gallery/19-volview-color2.jpg +0 -0
- package/documentation/content/gallery/20-volview-lightfollowcamera0.jpg +0 -0
- package/documentation/content/gallery/20-volview-lightfollowcamera1.jpg +0 -0
- package/documentation/content/gallery/20-volview-lightfollowcamera2.jpg +0 -0
- package/documentation/content/gallery/20-volview-lightfollowcamera3.jpg +0 -0
- package/documentation/content/gallery/21-volview-hybrid-final.jpg +0 -0
- package/documentation/content/gallery/21-volview-hybrid0.0.jpg +0 -0
- package/documentation/content/gallery/21-volview-hybrid0.5.jpg +0 -0
- package/documentation/content/gallery/21-volview-hybrid1.0.jpg +0 -0
- package/documentation/content/gallery/LocalAmbientOcclusion.jpg +0 -0
- package/documentation/content/gallery/Notes.pptx +0 -0
- package/documentation/content/gallery/VolView-Overview.jpg +0 -0
- package/documentation/content/gallery/index.md +53 -0
- package/documentation/content/icon/favicon-16x16.png +0 -0
- package/documentation/content/icon/favicon-196x196.png +0 -0
- package/documentation/content/icon/favicon-32x32.png +0 -0
- package/documentation/content/icon/favicon-96x96.png +0 -0
- package/documentation/content/index.pug +43 -0
- package/documentation/content/logo.png +0 -0
- package/documentation/content/logo.svg +20 -0
- package/documentation/data/menu.yml +3 -0
- package/documentation/themes/navy/layout/partial/footer.swig +22 -0
- package/documentation/tpl/__en__ +31 -0
- package/documentation/tpl/__sidebar__ +11 -0
- package/package.json +96 -0
- package/patches/jsdom-global+3.0.2.patch +12 -0
- package/patches/vue-toastification+1.7.14.patch +41 -0
- package/patches/vuetify+2.6.10.patch +13 -0
- package/prettier.config.js +9 -0
- package/public/favicon.ico +0 -0
- package/public/global.css +20 -0
- package/public/index.html +28 -0
- package/public/pvm-sample.png +0 -0
- package/server/helper.py +147 -0
- package/server/host.js +134 -0
- package/server/serialize.js +111 -0
- package/server/serialize.py +203 -0
- package/server/server.py +84 -0
- package/server/test.py +17 -0
- package/src/assets/KitwareHeadAndNeck.jpg +0 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/logo.svg +20 -0
- package/src/assets/samples/3DUS-Fetus.jpg +0 -0
- package/src/assets/samples/CTA-Head_and_Neck.jpg +0 -0
- package/src/assets/samples/MRA-Head_and_Neck.jpg +0 -0
- package/src/assets/samples/MRI-Cardiac.jpg +0 -0
- package/src/assets/samples/MRI-PROSTATEx.jpg +0 -0
- package/src/components/AboutBox.vue +157 -0
- package/src/components/AiModule.vue +60 -0
- package/src/components/AnnotationsModule.vue +37 -0
- package/src/components/App.vue +539 -0
- package/src/components/ColorFunctionSlider.vue +181 -0
- package/src/components/DataBrowser.vue +173 -0
- package/src/components/DragAndDrop.vue +107 -0
- package/src/components/GroupableItem.vue +30 -0
- package/src/components/ImageDataBrowser.vue +186 -0
- package/src/components/ImageListCard.vue +114 -0
- package/src/components/ItemGroup.vue +74 -0
- package/src/components/LabelmapList.vue +36 -0
- package/src/components/LayoutGrid.vue +96 -0
- package/src/components/MeasurementsList.vue +134 -0
- package/src/components/MessageCenter.vue +189 -0
- package/src/components/MessageItem.vue +83 -0
- package/src/components/MessageNotificationContent.vue +20 -0
- package/src/components/MessageNotifications.vue +84 -0
- package/src/components/ModelBrowser.vue +83 -0
- package/src/components/ModulePanel.vue +137 -0
- package/src/components/PaintControls.vue +174 -0
- package/src/components/PatientBrowser.vue +280 -0
- package/src/components/PatientStudyVolumeBrowser.vue +325 -0
- package/src/components/RenderingModule.vue +49 -0
- package/src/components/ResizableNavDrawer.vue +158 -0
- package/src/components/SampleDataBrowser.vue +198 -0
- package/src/components/SaveSession.vue +57 -0
- package/src/components/Settings.vue +35 -0
- package/src/components/SliceSlider.vue +182 -0
- package/src/components/SpleenModule.vue +101 -0
- package/src/components/ToolButton.vue +58 -0
- package/src/components/ToolStrip.vue +186 -0
- package/src/components/ViewOverlayGrid.vue +84 -0
- package/src/components/VolumePresets.vue +165 -0
- package/src/components/VolumeProperties.vue +171 -0
- package/src/components/VolumeRendering.vue +365 -0
- package/src/components/VtkThreeView.vue +512 -0
- package/src/components/VtkTwoView.vue +665 -0
- package/src/components/__tests__/ResizableNavDrawer.spec.js +32 -0
- package/src/components/__tests__/ToolButton.spec.js +46 -0
- package/src/components/icons/KitwareLogoIcon.vue +54 -0
- package/src/components/icons/VolViewFullLogo.vue +139 -0
- package/src/components/icons/VolViewLogo.vue +78 -0
- package/src/components/styles/utils.css +16 -0
- package/src/components/styles/vtk-view.css +116 -0
- package/src/components/tools/CropTool.vue +61 -0
- package/src/components/tools/CrosshairsTool.vue +93 -0
- package/src/components/tools/MouseManipulatorTool.vue +108 -0
- package/src/components/tools/PaintTool.vue +54 -0
- package/src/components/tools/PanTool.vue +35 -0
- package/src/components/tools/RulerTool.vue +202 -0
- package/src/components/tools/SliceScrollTool.vue +123 -0
- package/src/components/tools/WindowLevelTool.vue +191 -0
- package/src/components/tools/ZoomTool.vue +35 -0
- package/src/components/tools/crop/Crop2D.vue +367 -0
- package/src/components/tools/crop/Crop2DLineHandle.vue +103 -0
- package/src/components/tools/crop/Crop3D.vue +192 -0
- package/src/components/tools/crop/CropControls.vue +38 -0
- package/src/components/tools/crop/types.ts +12 -0
- package/src/components/tools/crosshairs/CrosshairSVG2D.vue +125 -0
- package/src/components/tools/crosshairs/CrosshairsWidget2D.vue +100 -0
- package/src/components/tools/paint/PaintWidget2D.vue +192 -0
- package/src/components/tools/ruler/RulerSVG2D.vue +181 -0
- package/src/components/tools/ruler/RulerWidget2D.vue +154 -0
- package/src/composables/__tests__/useDoubleRecord.spec.ts +57 -0
- package/src/composables/__tests__/useOrientationLabels.spec.ts +29 -0
- package/src/composables/isViewAnimating.ts +25 -0
- package/src/composables/manageVTKSubscription.ts +6 -0
- package/src/composables/onProxyManagerEvent.ts +50 -0
- package/src/composables/proxyManager.ts +13 -0
- package/src/composables/sync/__tests__/createPrimitiveSyncContext.spec.ts +41 -0
- package/src/composables/sync/__tests__/useWindowingSync.spec.ts +51 -0
- package/src/composables/sync/createPrimitiveSyncContext.ts +83 -0
- package/src/composables/sync/useWindowingSync.ts +48 -0
- package/src/composables/useAppLoadingNotifications.ts +73 -0
- package/src/composables/useCameraOrientation.ts +34 -0
- package/src/composables/useCurrentImage.ts +74 -0
- package/src/composables/useCurrentSlice.ts +43 -0
- package/src/composables/useDoubleRecord.ts +77 -0
- package/src/composables/useEventListener.ts +26 -0
- package/src/composables/useGlobalErrorHook.ts +18 -0
- package/src/composables/useMultiSelection.ts +29 -0
- package/src/composables/useOrientationLabels.ts +62 -0
- package/src/composables/usePanels.ts +42 -0
- package/src/composables/usePersistCameraConfig.ts +138 -0
- package/src/composables/useResizeObserver.ts +37 -0
- package/src/composables/useResizeToFit.ts +50 -0
- package/src/composables/useSceneBuilder.ts +96 -0
- package/src/composables/useToast.js +11 -0
- package/src/composables/useVTKCallback.ts +33 -0
- package/src/composables/useVTKWorldToDisplay.ts +141 -0
- package/src/composables/useViewProxy.ts +87 -0
- package/src/composables/useVolumeThumbnailing.ts +173 -0
- package/src/composables/useWebGLWatchdog.ts +32 -0
- package/src/composables/useWidgetManager.ts +41 -0
- package/src/config.ts +182 -0
- package/src/constants.ts +38 -0
- package/src/core/id.ts +18 -0
- package/src/core/provider.ts +31 -0
- package/src/core/proxies.ts +90 -0
- package/src/core/thumbnailers/__tests__/vtk-image.spec.ts +58 -0
- package/src/core/thumbnailers/index.ts +5 -0
- package/src/core/thumbnailers/volume-thumbnailer.ts +130 -0
- package/src/core/thumbnailers/vtk-image.ts +142 -0
- package/src/core/tools/__tests__/paint.spec.ts +101 -0
- package/src/core/tools/__tests__/ruler.spec.ts +73 -0
- package/src/core/tools/paint/brush.ts +20 -0
- package/src/core/tools/paint/ellipse-brush.ts +96 -0
- package/src/core/tools/paint/index.ts +141 -0
- package/src/core/tools/ruler.ts +149 -0
- package/src/global.d.ts +24 -0
- package/src/io/__tests__/io.spec.ts +26 -0
- package/src/io/dicom.ts +187 -0
- package/src/io/index.ts +11 -0
- package/src/io/io.ts +157 -0
- package/src/io/itk/itkConfig.js +8 -0
- package/src/io/itk-dicom/dicom.cpp +327 -0
- package/src/io/itk-dicom/web-build/dicom.js +16 -0
- package/src/io/itk-dicom/web-build/dicom.umd.js +21 -0
- package/src/io/itk-dicom/web-build/dicom.umd.wasm +0 -0
- package/src/io/itk-dicom/web-build/dicom.wasm +0 -0
- package/src/io/manifest.ts +30 -0
- package/src/io/readers.ts +36 -0
- package/src/io/state-file/index.ts +170 -0
- package/src/io/state-file/schema.ts +277 -0
- package/src/io/state-file/utils.ts +33 -0
- package/src/io/types.ts +4 -0
- package/src/io/vtk/async.reader.worker.ts +50 -0
- package/src/io/vtk/async.ts +58 -0
- package/src/io/vtk/async.writer.worker.ts +46 -0
- package/src/io/vtk/common.ts +30 -0
- package/src/io/zip.ts +31 -0
- package/src/main.js +66 -0
- package/src/main.ts +1 -0
- package/src/plugins/vuetify.js +25 -0
- package/src/shims-itk.d.ts +67 -0
- package/src/shims-misc.d.ts +4 -0
- package/src/shims-tsx.d.ts +13 -0
- package/src/shims-vtk.d.ts +1042 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/store/__tests__/datasets.spec.ts +75 -0
- package/src/store/__tests__/messages.spec.ts +74 -0
- package/src/store/__tests__/rulers.spec.ts +77 -0
- package/src/store/__tests__/view-configs.spec.ts +37 -0
- package/src/store/datasets-dicom.ts +398 -0
- package/src/store/datasets-files.ts +35 -0
- package/src/store/datasets-images.ts +118 -0
- package/src/store/datasets-labelmaps.ts +136 -0
- package/src/store/datasets-models.ts +25 -0
- package/src/store/datasets.ts +341 -0
- package/src/store/messages.ts +144 -0
- package/src/store/tools/crop.ts +136 -0
- package/src/store/tools/crosshairs.ts +131 -0
- package/src/store/tools/index.ts +95 -0
- package/src/store/tools/paint.ts +191 -0
- package/src/store/tools/rulers.ts +305 -0
- package/src/store/tools/types.ts +54 -0
- package/src/store/view-configs/camera.ts +51 -0
- package/src/store/view-configs/common.ts +61 -0
- package/src/store/view-configs/slicing.ts +86 -0
- package/src/store/view-configs/types.ts +39 -0
- package/src/store/view-configs/volume-coloring.ts +193 -0
- package/src/store/view-configs/windowing.ts +69 -0
- package/src/store/view-configs.ts +78 -0
- package/src/store/views.ts +130 -0
- package/src/types/index.ts +16 -0
- package/src/types/layout.ts +10 -0
- package/src/types/lps.ts +40 -0
- package/src/types/views.ts +65 -0
- package/src/types/vtk-types.ts +29 -0
- package/src/utils/__tests__/lps.spec.ts +102 -0
- package/src/utils/color.ts +23 -0
- package/src/utils/errors.js +3 -0
- package/src/utils/fetch.ts +101 -0
- package/src/utils/hacks.ts +14 -0
- package/src/utils/index.ts +83 -0
- package/src/utils/lps.ts +129 -0
- package/src/utils/manipulators.ts +35 -0
- package/src/utils/promise-worker.ts +45 -0
- package/src/utils/vtk-helpers.ts +215 -0
- package/src/utils/workerHandler.js +42 -0
- package/src/vtk/ColorMaps.ts +72 -0
- package/src/vtk/CrosshairsWidget/behavior.ts +112 -0
- package/src/vtk/CrosshairsWidget/index.d.ts +22 -0
- package/src/vtk/CrosshairsWidget/index.js +73 -0
- package/src/vtk/CrosshairsWidget/state.ts +51 -0
- package/src/vtk/IJKSliceRepresentationProxy/index.d.ts +9 -0
- package/src/vtk/IJKSliceRepresentationProxy/index.js +59 -0
- package/src/vtk/LPSView2DProxy/index.d.ts +18 -0
- package/src/vtk/LPSView2DProxy/index.js +89 -0
- package/src/vtk/LPSView3DProxy/index.d.ts +16 -0
- package/src/vtk/LPSView3DProxy/index.js +78 -0
- package/src/vtk/LabelMap/index.d.ts +16 -0
- package/src/vtk/LabelMap/index.js +49 -0
- package/src/vtk/LabelMapSliceRepProxy/index.d.ts +6 -0
- package/src/vtk/LabelMapSliceRepProxy/index.js +104 -0
- package/src/vtk/MedicalColorPresets.json +1448 -0
- package/src/vtk/PaintBrushContextRepresentation/index.js +191 -0
- package/src/vtk/PaintWidget/behavior.ts +101 -0
- package/src/vtk/PaintWidget/index.d.ts +28 -0
- package/src/vtk/PaintWidget/index.js +56 -0
- package/src/vtk/PaintWidget/state.ts +41 -0
- package/src/vtk/PiecewiseWidget/index.js +203 -0
- package/src/vtk/RulerWidget/behavior.ts +185 -0
- package/src/vtk/RulerWidget/index.d.ts +25 -0
- package/src/vtk/RulerWidget/index.js +79 -0
- package/src/vtk/RulerWidget/state.ts +63 -0
- package/src/vtk/TreJsonConverter/index.js +150 -0
- package/src/vtk/proxy.js +66 -0
- package/src/vtk/proxyUtils.js +16 -0
- package/src/vtk/webvr-empty.js +1 -0
- package/tests/testUtils.js +12 -0
- package/tsconfig.json +46 -0
- package/vue.config.js +86 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { computed } from '@vue/composition-api';
|
|
2
|
+
import { useDatasetStore } from '../store/datasets';
|
|
3
|
+
import { useDICOMStore } from '../store/datasets-dicom';
|
|
4
|
+
import { defaultImageMetadata, useImageStore } from '../store/datasets-images';
|
|
5
|
+
import { createLPSBounds, getAxisBounds } from '../utils/lps';
|
|
6
|
+
|
|
7
|
+
// Returns a spatially inflated image extent
|
|
8
|
+
export function getImageSpatialExtent(imageID: string | null) {
|
|
9
|
+
const imageStore = useImageStore();
|
|
10
|
+
|
|
11
|
+
if (imageID && imageID in imageStore.metadata) {
|
|
12
|
+
const { lpsOrientation } = imageStore.metadata[imageID];
|
|
13
|
+
const image = imageStore.dataIndex[imageID];
|
|
14
|
+
if (image) {
|
|
15
|
+
const extent = image.getSpatialExtent();
|
|
16
|
+
return {
|
|
17
|
+
Sagittal: getAxisBounds(extent, 'Sagittal', lpsOrientation),
|
|
18
|
+
Coronal: getAxisBounds(extent, 'Coronal', lpsOrientation),
|
|
19
|
+
Axial: getAxisBounds(extent, 'Axial', lpsOrientation),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return createLPSBounds();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useCurrentImage() {
|
|
27
|
+
const dataStore = useDatasetStore();
|
|
28
|
+
const dicomStore = useDICOMStore();
|
|
29
|
+
const imageStore = useImageStore();
|
|
30
|
+
|
|
31
|
+
const currentImageID = computed(() => {
|
|
32
|
+
const { primarySelection } = dataStore;
|
|
33
|
+
const { volumeToImageID } = dicomStore;
|
|
34
|
+
|
|
35
|
+
if (primarySelection?.type === 'image') {
|
|
36
|
+
return primarySelection.dataID;
|
|
37
|
+
}
|
|
38
|
+
if (primarySelection?.type === 'dicom') {
|
|
39
|
+
return volumeToImageID[primarySelection.volumeKey] || null;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const currentImageMetadata = computed(() => {
|
|
45
|
+
const { metadata } = imageStore;
|
|
46
|
+
const imageID = currentImageID.value;
|
|
47
|
+
|
|
48
|
+
if (imageID) {
|
|
49
|
+
return metadata[imageID];
|
|
50
|
+
}
|
|
51
|
+
return defaultImageMetadata();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const currentImageData = computed(() => {
|
|
55
|
+
// assumed to be only images for now
|
|
56
|
+
return dataStore.primaryDataset;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const currentImageExtent = computed(() =>
|
|
60
|
+
getImageSpatialExtent(currentImageID.value)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const isImageLoading = computed(() => {
|
|
64
|
+
return !!dataStore.primarySelection && !dataStore.primaryDataset;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
currentImageData,
|
|
69
|
+
currentImageID,
|
|
70
|
+
currentImageMetadata,
|
|
71
|
+
currentImageExtent,
|
|
72
|
+
isImageLoading,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Vector3 } from '@kitware/vtk.js/types';
|
|
2
|
+
import { computed } from '@vue/composition-api';
|
|
3
|
+
import { MaybeRef } from '@vueuse/core';
|
|
4
|
+
import { useViewConfigStore } from '../store/view-configs';
|
|
5
|
+
import { getLPSAxisFromDir } from '../utils/lps';
|
|
6
|
+
import { useCurrentImage } from './useCurrentImage';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns information about the current slice.
|
|
10
|
+
*
|
|
11
|
+
* axisName: the name of the axis
|
|
12
|
+
* axisIndex: corresponding index in an LPS coordinate array
|
|
13
|
+
* number: slice value
|
|
14
|
+
* planeNormal: slice plane normal
|
|
15
|
+
* planeOrigin: slice plane origin
|
|
16
|
+
* @param viewID
|
|
17
|
+
*/
|
|
18
|
+
export function useCurrentSlice(viewID: MaybeRef<string | null>) {
|
|
19
|
+
const viewConfigStore = useViewConfigStore();
|
|
20
|
+
const currentImage = useCurrentImage();
|
|
21
|
+
const { currentImageMetadata } = currentImage;
|
|
22
|
+
const sliceConfig = viewConfigStore.getComputedSliceConfig(
|
|
23
|
+
viewID,
|
|
24
|
+
currentImage.currentImageID
|
|
25
|
+
);
|
|
26
|
+
return computed(() => {
|
|
27
|
+
const config = sliceConfig.value;
|
|
28
|
+
const { lpsOrientation } = currentImageMetadata.value;
|
|
29
|
+
if (config) {
|
|
30
|
+
const axis = getLPSAxisFromDir(config.axisDirection);
|
|
31
|
+
const planeOrigin = [0, 0, 0] as Vector3;
|
|
32
|
+
planeOrigin[lpsOrientation[axis]] = config.slice;
|
|
33
|
+
return {
|
|
34
|
+
axisName: axis,
|
|
35
|
+
axisIndex: lpsOrientation[axis],
|
|
36
|
+
number: config.slice,
|
|
37
|
+
planeNormal: lpsOrientation[config.axisDirection] as Vector3,
|
|
38
|
+
planeOrigin,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { computed, del, reactive, set, unref } from '@vue/composition-api';
|
|
2
|
+
import { MaybeRef } from '@vueuse/core';
|
|
3
|
+
|
|
4
|
+
export function useDoubleRecord<V>() {
|
|
5
|
+
type Map1 = Record<string, Record<string, V>>;
|
|
6
|
+
type Map2 = Record<string, string>;
|
|
7
|
+
const reactiveRoot = reactive<{ map: Map1 }>({
|
|
8
|
+
map: {},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const _set = (k1: string, k2: string, v: V) => {
|
|
12
|
+
if (!(k1 in reactiveRoot.map)) {
|
|
13
|
+
set(reactiveRoot.map, k1, reactive<Map2>({}));
|
|
14
|
+
}
|
|
15
|
+
set(reactiveRoot.map[k1], k2, v);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const get = (k1: string, k2: string) => {
|
|
19
|
+
return reactiveRoot.map[k1]?.[k2] ?? undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getComputed = (
|
|
23
|
+
k1: MaybeRef<string | null | undefined>,
|
|
24
|
+
k2: MaybeRef<string | null | undefined>
|
|
25
|
+
) => {
|
|
26
|
+
return computed(() => {
|
|
27
|
+
const k1v = unref(k1);
|
|
28
|
+
const k2v = unref(k2);
|
|
29
|
+
const { map } = reactiveRoot;
|
|
30
|
+
if (!k1v || !k2v) return undefined;
|
|
31
|
+
return map[k1v]?.[k2v];
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const has = (k1: string, k2: string) => {
|
|
36
|
+
const { map } = reactiveRoot;
|
|
37
|
+
return k1 in map && k2 in map[k1];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const hasComputed = (
|
|
41
|
+
k1: MaybeRef<string | null | undefined>,
|
|
42
|
+
k2: MaybeRef<string | null | undefined>
|
|
43
|
+
) => {
|
|
44
|
+
return computed(() => {
|
|
45
|
+
const k1v = unref(k1);
|
|
46
|
+
const k2v = unref(k2);
|
|
47
|
+
const { map } = reactiveRoot;
|
|
48
|
+
if (!k1v || !k2v) return undefined;
|
|
49
|
+
return k1v in map && k2v in map[k1v];
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const _delete = (k1: string, k2: string) => {
|
|
54
|
+
if (k1 in reactiveRoot.map) {
|
|
55
|
+
del(reactiveRoot.map[k1], k2);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const deleteFirstKey = (k1: string) => {
|
|
60
|
+
del(reactiveRoot.map, k1);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const deleteSecondKey = (k2: string) => {
|
|
64
|
+
Object.keys(reactiveRoot.map).forEach((k1) => _delete(k1, k2));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
set: _set,
|
|
69
|
+
get,
|
|
70
|
+
getComputed,
|
|
71
|
+
has,
|
|
72
|
+
hasComputed,
|
|
73
|
+
delete: _delete,
|
|
74
|
+
deleteFirstKey,
|
|
75
|
+
deleteSecondKey,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { onUnmounted, Ref, watch } from '@vue/composition-api';
|
|
2
|
+
import { NOOP } from '../constants';
|
|
3
|
+
|
|
4
|
+
export function useEventListener(
|
|
5
|
+
targetRef: Ref<HTMLElement | null | undefined>,
|
|
6
|
+
eventName: string,
|
|
7
|
+
listener: (evt: Event) => void
|
|
8
|
+
) {
|
|
9
|
+
let cleanup = NOOP;
|
|
10
|
+
|
|
11
|
+
watch(
|
|
12
|
+
targetRef,
|
|
13
|
+
(target) => {
|
|
14
|
+
cleanup();
|
|
15
|
+
cleanup = NOOP;
|
|
16
|
+
|
|
17
|
+
if (target) {
|
|
18
|
+
target.addEventListener(eventName, listener);
|
|
19
|
+
cleanup = () => target.removeEventListener(eventName, listener);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{ immediate: true, flush: 'post' }
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
onUnmounted(cleanup);
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted } from '@vue/composition-api';
|
|
2
|
+
import { useMessageStore } from '../store/messages';
|
|
3
|
+
|
|
4
|
+
export function useGlobalErrorHook() {
|
|
5
|
+
const messageStore = useMessageStore();
|
|
6
|
+
|
|
7
|
+
const onError = (event: ErrorEvent) => {
|
|
8
|
+
messageStore.addError('Application error (click for details)', event.error);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
onMounted(() => {
|
|
12
|
+
window.addEventListener('error', onError);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
onBeforeUnmount(() => {
|
|
16
|
+
window.removeEventListener('error', onError);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
Ref,
|
|
4
|
+
ref,
|
|
5
|
+
UnwrapRefSimple,
|
|
6
|
+
watch,
|
|
7
|
+
} from '@vue/composition-api';
|
|
8
|
+
|
|
9
|
+
export function useMultiSelection<T = string>(allItems: Ref<T[]>) {
|
|
10
|
+
const selected = ref<T[]>([]);
|
|
11
|
+
const selectedAll = ref(false);
|
|
12
|
+
const selectedSome = computed(() => selected.value.length > 0);
|
|
13
|
+
|
|
14
|
+
watch(selected, () => {
|
|
15
|
+
selectedAll.value =
|
|
16
|
+
selected.value.length > 0 &&
|
|
17
|
+
selected.value.length === allItems.value.length;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
watch(selectedAll, (yn) => {
|
|
21
|
+
if (yn) {
|
|
22
|
+
selected.value = allItems.value as UnwrapRefSimple<T[]>;
|
|
23
|
+
} else {
|
|
24
|
+
selected.value = [];
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return { selected, selectedAll, selectedSome };
|
|
29
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { computed, Ref, ref } from '@vue/composition-api';
|
|
2
|
+
import { vec3 } from 'gl-matrix';
|
|
3
|
+
import { Vector3 } from '@kitware/vtk.js/types';
|
|
4
|
+
import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
|
|
5
|
+
import { EPSILON } from '../constants';
|
|
6
|
+
import { useVTKCallback } from './useVTKCallback';
|
|
7
|
+
|
|
8
|
+
export function toOrderedLabels(vec: Vector3) {
|
|
9
|
+
return (
|
|
10
|
+
vec
|
|
11
|
+
.map((v, i) => ({ value: v, axis: i }))
|
|
12
|
+
// remove components close to zero, since we won't show those labels
|
|
13
|
+
.filter(({ value }) => Math.abs(value) > EPSILON)
|
|
14
|
+
// sort from largest to smallest component
|
|
15
|
+
.sort((a, b) => Math.abs(b.value) - Math.abs(a.value))
|
|
16
|
+
// apply labels
|
|
17
|
+
.map(({ value, axis }) => (Math.sign(value) === 1 ? 'LPS' : 'RAI')[axis])
|
|
18
|
+
.join('')
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Computes orientation labels for a given view.
|
|
24
|
+
*
|
|
25
|
+
* Labels are with respect to the current camera orientation.
|
|
26
|
+
*
|
|
27
|
+
* vtk.js coordinate coordinate is implied to be LPS, so orientation labels
|
|
28
|
+
* are determined solely from the camera' direction and view-up.
|
|
29
|
+
*/
|
|
30
|
+
export function useOrientationLabels(view: Ref<vtkViewProxy>) {
|
|
31
|
+
const camera = computed(() => view.value.getCamera());
|
|
32
|
+
|
|
33
|
+
const top = ref('');
|
|
34
|
+
const left = ref('');
|
|
35
|
+
const bottom = ref('');
|
|
36
|
+
const right = ref('');
|
|
37
|
+
|
|
38
|
+
function updateAxes() {
|
|
39
|
+
const vup = camera.value.getViewUp();
|
|
40
|
+
const vdir = camera.value.getDirectionOfProjection();
|
|
41
|
+
const vright = [0, 0, 0] as Vector3;
|
|
42
|
+
|
|
43
|
+
// vup and vdir should not be parallel for cameras
|
|
44
|
+
vec3.cross(vright, vdir, vup);
|
|
45
|
+
|
|
46
|
+
const vbottom = vup.map((c) => c * -1) as Vector3;
|
|
47
|
+
const vleft = vright.map((c) => c * -1) as Vector3;
|
|
48
|
+
|
|
49
|
+
top.value = toOrderedLabels(vup);
|
|
50
|
+
right.value = toOrderedLabels(vright);
|
|
51
|
+
bottom.value = toOrderedLabels(vbottom);
|
|
52
|
+
left.value = toOrderedLabels(vleft);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const cameraOnModified = useVTKCallback(
|
|
56
|
+
computed(() => camera.value.onModified)
|
|
57
|
+
);
|
|
58
|
+
cameraOnModified(updateAxes);
|
|
59
|
+
updateAxes();
|
|
60
|
+
|
|
61
|
+
return { top, right, bottom, left };
|
|
62
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { computed, ref, Ref, watch } from '@vue/composition-api';
|
|
2
|
+
|
|
3
|
+
export interface Panel {
|
|
4
|
+
key: string;
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function usePanels(keys: Ref<Set<string>>) {
|
|
9
|
+
const panels = ref<Panel[]>([]);
|
|
10
|
+
|
|
11
|
+
watch(
|
|
12
|
+
keys,
|
|
13
|
+
(newKeys, oldKeys) => {
|
|
14
|
+
// remove deleted
|
|
15
|
+
panels.value = panels.value.filter(({ key }) => newKeys.has(key));
|
|
16
|
+
|
|
17
|
+
const addedKeys = [...newKeys].filter((key) => !oldKeys?.has(key));
|
|
18
|
+
// order in temporal created order, not template order. See https://github.com/vuetifyjs/vuetify/issues/11225
|
|
19
|
+
panels.value = [
|
|
20
|
+
...panels.value,
|
|
21
|
+
...addedKeys.map((key) => ({ key, isOpen: true })),
|
|
22
|
+
];
|
|
23
|
+
},
|
|
24
|
+
// flush: 'post' keeps panels open when deleting patients
|
|
25
|
+
// because panels.value has the same number panels in v-expansion-panels after flush
|
|
26
|
+
{ flush: 'post', immediate: true }
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const handlePanelChange = (changeKey: string) => {
|
|
30
|
+
const panel = panels.value.find(({ key }) => key === changeKey);
|
|
31
|
+
panel!.isOpen = !panel!.isOpen;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const openPanels: Ref<number[]> = computed(() => {
|
|
35
|
+
return panels.value
|
|
36
|
+
.map(({ isOpen }, idx) => ({ isOpen, idx }))
|
|
37
|
+
.filter(({ isOpen }) => isOpen)
|
|
38
|
+
.map(({ idx }) => idx);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return { handlePanelChange, openPanels };
|
|
42
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { manageVTKSubscription } from '@src/composables/manageVTKSubscription';
|
|
2
|
+
import { Ref } from '@vue/composition-api';
|
|
3
|
+
import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
|
|
4
|
+
import { useViewConfigStore } from '../store/view-configs';
|
|
5
|
+
import { CameraConfig } from '../store/view-configs/types';
|
|
6
|
+
|
|
7
|
+
export function usePersistCameraConfig(
|
|
8
|
+
viewID: Ref<string>,
|
|
9
|
+
dataID: Ref<string | null>,
|
|
10
|
+
viewProxy: Ref<vtkViewProxy>,
|
|
11
|
+
...toPersist: (keyof CameraConfig)[]
|
|
12
|
+
) {
|
|
13
|
+
const viewConfigStore = useViewConfigStore();
|
|
14
|
+
let persistCameraConfig = true;
|
|
15
|
+
|
|
16
|
+
// We setup this list of functions to avoid if and indexOf in the onModified
|
|
17
|
+
// call.
|
|
18
|
+
const persist: (() => void)[] = [];
|
|
19
|
+
|
|
20
|
+
if (toPersist.indexOf('position') > -1) {
|
|
21
|
+
persist.push(() => {
|
|
22
|
+
if (dataID.value !== null && persistCameraConfig) {
|
|
23
|
+
viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
|
|
24
|
+
position: viewProxy.value.getCamera().getPosition(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (toPersist.indexOf('viewUp') > -1) {
|
|
30
|
+
persist.push(() => {
|
|
31
|
+
if (dataID.value !== null && persistCameraConfig) {
|
|
32
|
+
viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
|
|
33
|
+
viewUp: viewProxy.value.getCamera().getViewUp(),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (toPersist.indexOf('focalPoint') > -1) {
|
|
39
|
+
persist.push(() => {
|
|
40
|
+
if (dataID.value !== null && persistCameraConfig) {
|
|
41
|
+
viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
|
|
42
|
+
focalPoint: viewProxy.value.getCamera().getFocalPoint(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (toPersist.indexOf('directionOfProjection') > -1) {
|
|
48
|
+
persist.push(() => {
|
|
49
|
+
if (dataID.value !== null && persistCameraConfig) {
|
|
50
|
+
viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
|
|
51
|
+
directionOfProjection: viewProxy.value
|
|
52
|
+
.getCamera()
|
|
53
|
+
.getDirectionOfProjection(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (toPersist.indexOf('parallelScale') > -1) {
|
|
59
|
+
persist.push(() => {
|
|
60
|
+
if (dataID.value !== null && persistCameraConfig) {
|
|
61
|
+
viewConfigStore.updateCameraConfig(viewID.value, dataID.value, {
|
|
62
|
+
parallelScale: viewProxy.value.getCamera().getParallelScale(),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
manageVTKSubscription(
|
|
69
|
+
viewProxy.value.getCamera().onModified(() => {
|
|
70
|
+
persist.forEach((persistFunc) => persistFunc());
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
function blockPersistingCameraConfig(func: () => void) {
|
|
75
|
+
persistCameraConfig = false;
|
|
76
|
+
try {
|
|
77
|
+
func();
|
|
78
|
+
} finally {
|
|
79
|
+
persistCameraConfig = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function restoreCameraConfig(cameraConfig: CameraConfig) {
|
|
84
|
+
blockPersistingCameraConfig(() => {
|
|
85
|
+
toPersist.forEach((key: keyof CameraConfig) => {
|
|
86
|
+
// Parallel scale
|
|
87
|
+
if (key === 'parallelScale' && cameraConfig.parallelScale) {
|
|
88
|
+
viewProxy.value
|
|
89
|
+
.getCamera()
|
|
90
|
+
.setParallelScale(cameraConfig.parallelScale);
|
|
91
|
+
}
|
|
92
|
+
// Position
|
|
93
|
+
else if (key === 'position' && cameraConfig.position) {
|
|
94
|
+
const { position } = cameraConfig;
|
|
95
|
+
viewProxy.value
|
|
96
|
+
.getCamera()
|
|
97
|
+
.setPosition(position[0], position[1], position[2]);
|
|
98
|
+
}
|
|
99
|
+
// Focal point
|
|
100
|
+
else if (key === 'focalPoint' && cameraConfig.focalPoint) {
|
|
101
|
+
const { focalPoint } = cameraConfig;
|
|
102
|
+
viewProxy.value
|
|
103
|
+
.getCamera()
|
|
104
|
+
.setFocalPoint(focalPoint[0], focalPoint[1], focalPoint[2]);
|
|
105
|
+
viewProxy.value
|
|
106
|
+
.getInteractorStyle2D()
|
|
107
|
+
.setCenterOfRotation([...focalPoint]);
|
|
108
|
+
viewProxy.value
|
|
109
|
+
.getInteractorStyle3D()
|
|
110
|
+
.setCenterOfRotation([...focalPoint]);
|
|
111
|
+
}
|
|
112
|
+
// Direction of projection
|
|
113
|
+
else if (
|
|
114
|
+
key === 'directionOfProjection' &&
|
|
115
|
+
cameraConfig.directionOfProjection
|
|
116
|
+
) {
|
|
117
|
+
const { directionOfProjection } = cameraConfig;
|
|
118
|
+
viewProxy.value
|
|
119
|
+
.getCamera()
|
|
120
|
+
.setDirectionOfProjection(
|
|
121
|
+
directionOfProjection[0],
|
|
122
|
+
directionOfProjection[1],
|
|
123
|
+
directionOfProjection[2]
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
// View up
|
|
127
|
+
else if (key === 'viewUp' && cameraConfig.viewUp) {
|
|
128
|
+
const { viewUp } = cameraConfig;
|
|
129
|
+
viewProxy.value
|
|
130
|
+
.getCamera()
|
|
131
|
+
.setViewUp(viewUp[0], viewUp[1], viewUp[2]);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { restoreCameraConfig };
|
|
138
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { onBeforeUnmount, Ref, unref, watch } from '@vue/composition-api';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Invokes a callback whenever an element is resized.
|
|
5
|
+
*/
|
|
6
|
+
export function useResizeObserver(
|
|
7
|
+
targetElRef: Ref<Element | null | undefined>,
|
|
8
|
+
callback: (entry: ResizeObserverEntry) => void
|
|
9
|
+
) {
|
|
10
|
+
const observer = new ResizeObserver((entries) => {
|
|
11
|
+
if (entries.length === 1) {
|
|
12
|
+
callback(entries[0]);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
watch(
|
|
17
|
+
targetElRef,
|
|
18
|
+
(targetEl, prevTarget) => {
|
|
19
|
+
if (prevTarget) {
|
|
20
|
+
observer.unobserve(prevTarget);
|
|
21
|
+
}
|
|
22
|
+
if (targetEl) {
|
|
23
|
+
observer.observe(targetEl);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{ immediate: true }
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
onBeforeUnmount(() => {
|
|
30
|
+
const targetEl = unref(targetElRef);
|
|
31
|
+
if (targetEl) {
|
|
32
|
+
observer.unobserve(targetEl);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return observer;
|
|
37
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ref } from '@vue/composition-api';
|
|
2
|
+
import vtkCamera from '@kitware/vtk.js/Rendering/Core/Camera';
|
|
3
|
+
import { manageVTKSubscription } from '@src/composables/manageVTKSubscription';
|
|
4
|
+
import { vec3 } from 'gl-matrix';
|
|
5
|
+
|
|
6
|
+
export function useResizeToFit(
|
|
7
|
+
camera: vtkCamera,
|
|
8
|
+
initialValue: boolean = false
|
|
9
|
+
) {
|
|
10
|
+
const resizeToFit = ref(initialValue);
|
|
11
|
+
|
|
12
|
+
let trackResizeToFit = true;
|
|
13
|
+
const cachedCameraInfo = {
|
|
14
|
+
position: [0, 0, 0] as vec3,
|
|
15
|
+
parallelScale: 0,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
manageVTKSubscription(
|
|
19
|
+
camera.onModified(() => {
|
|
20
|
+
if (trackResizeToFit && resizeToFit.value) {
|
|
21
|
+
const position = camera.getPosition();
|
|
22
|
+
const parallelScale = camera.getParallelScale();
|
|
23
|
+
if (
|
|
24
|
+
!vec3.equals(position, cachedCameraInfo.position) ||
|
|
25
|
+
parallelScale !== cachedCameraInfo.parallelScale
|
|
26
|
+
) {
|
|
27
|
+
resizeToFit.value = false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
function ignoreResizeToFitTracking(cb: () => void) {
|
|
34
|
+
if (trackResizeToFit) {
|
|
35
|
+
trackResizeToFit = false;
|
|
36
|
+
try {
|
|
37
|
+
cb();
|
|
38
|
+
} finally {
|
|
39
|
+
trackResizeToFit = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resetResizeToFitTracking() {
|
|
45
|
+
cachedCameraInfo.position = camera.getPosition();
|
|
46
|
+
cachedCameraInfo.parallelScale = camera.getParallelScale();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { resizeToFit, ignoreResizeToFitTracking, resetResizeToFitTracking };
|
|
50
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import vtkAbstractRepresentationProxy from '@kitware/vtk.js/Proxy/Core/AbstractRepresentationProxy';
|
|
2
|
+
import { computed, Ref, watch } from '@vue/composition-api';
|
|
3
|
+
import { useViewStore } from '../store/views';
|
|
4
|
+
import { vtkLPSViewProxy } from '../types/vtk-types';
|
|
5
|
+
|
|
6
|
+
interface Scene {
|
|
7
|
+
baseImage?: Ref<string | null>;
|
|
8
|
+
labelmaps?: Ref<string[]>;
|
|
9
|
+
models?: Ref<string[]>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type RepProxy = vtkAbstractRepresentationProxy;
|
|
13
|
+
|
|
14
|
+
export function useSceneBuilder<
|
|
15
|
+
BaseImageType extends RepProxy = RepProxy,
|
|
16
|
+
LabelMapType extends RepProxy = RepProxy,
|
|
17
|
+
ModelType extends RepProxy = RepProxy
|
|
18
|
+
>(viewID: Ref<string>, sceneIDs: Scene) {
|
|
19
|
+
const viewStore = useViewStore();
|
|
20
|
+
const viewProxy = computed(() =>
|
|
21
|
+
viewStore.getViewProxy<vtkLPSViewProxy>(viewID.value)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const baseImageRep = computed(() => {
|
|
25
|
+
const _viewID = viewID.value;
|
|
26
|
+
if (sceneIDs.baseImage) {
|
|
27
|
+
const imageID = sceneIDs.baseImage.value;
|
|
28
|
+
if (imageID) {
|
|
29
|
+
return viewStore.getDataRepresentationForView<BaseImageType>(
|
|
30
|
+
imageID,
|
|
31
|
+
_viewID
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const labelmapReps = computed(() => {
|
|
39
|
+
const _viewID = viewID.value;
|
|
40
|
+
if (sceneIDs.labelmaps) {
|
|
41
|
+
const labelmapIDs = sceneIDs.labelmaps.value;
|
|
42
|
+
if (labelmapIDs) {
|
|
43
|
+
return labelmapIDs
|
|
44
|
+
.map((id) =>
|
|
45
|
+
viewStore.getDataRepresentationForView<LabelMapType>(id, _viewID)
|
|
46
|
+
)
|
|
47
|
+
.filter(Boolean) as LabelMapType[];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [];
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const modelReps = computed(() => {
|
|
54
|
+
const _viewID = viewID.value;
|
|
55
|
+
if (sceneIDs.models) {
|
|
56
|
+
const modelIDs = sceneIDs.models.value;
|
|
57
|
+
if (modelIDs) {
|
|
58
|
+
return modelIDs
|
|
59
|
+
.map((id) =>
|
|
60
|
+
viewStore.getDataRepresentationForView<ModelType>(id, _viewID)
|
|
61
|
+
)
|
|
62
|
+
.filter(Boolean) as ModelType[];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
watch(
|
|
69
|
+
[viewProxy, baseImageRep, labelmapReps, modelReps],
|
|
70
|
+
([view, baseRep, lmReps, mReps]) => {
|
|
71
|
+
if (!view) {
|
|
72
|
+
throw new Error('[useSceneBuilder] No view available');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
view.removeAllRepresentations();
|
|
76
|
+
|
|
77
|
+
if (baseRep) {
|
|
78
|
+
// we control the color mapping range manually
|
|
79
|
+
baseRep.setRescaleOnColorBy(false);
|
|
80
|
+
view.addRepresentation(baseRep);
|
|
81
|
+
}
|
|
82
|
+
lmReps.forEach((rep) => view.addRepresentation(rep));
|
|
83
|
+
mReps.forEach((rep) => view.addRepresentation(rep));
|
|
84
|
+
|
|
85
|
+
// TODO not sure why I need this, but might as well keep
|
|
86
|
+
// the renderer up to date.
|
|
87
|
+
// For reference, this doesn't get invoked when resetting the
|
|
88
|
+
// camera with a supplied bounds, so we manually invoke it here.
|
|
89
|
+
view.getRenderer().computeVisiblePropBounds();
|
|
90
|
+
view.render();
|
|
91
|
+
},
|
|
92
|
+
{ deep: true, immediate: true }
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return { baseImageRep, labelmapReps, modelReps };
|
|
96
|
+
}
|