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
package/src/constants.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
|
|
2
|
+
import { ComputedRef, InjectionKey } from '@vue/composition-api';
|
|
3
|
+
|
|
4
|
+
export const NO_PROXY = -1;
|
|
5
|
+
export const NO_SELECTION = -1;
|
|
6
|
+
export const NO_WIDGET = -1;
|
|
7
|
+
|
|
8
|
+
export const EPSILON = 10e-6;
|
|
9
|
+
export const NOOP = () => {};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Retrieves the parent VtkTwoView's widget manager.
|
|
13
|
+
*/
|
|
14
|
+
export const VTKTwoViewWidgetManager: InjectionKey<
|
|
15
|
+
ComputedRef<vtkWidgetManager>
|
|
16
|
+
> = Symbol('VTKTwoViewWidgetManager');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves the parent VtkThreeView's widget manager.
|
|
20
|
+
*/
|
|
21
|
+
export const VTKThreeViewWidgetManager: InjectionKey<
|
|
22
|
+
ComputedRef<vtkWidgetManager>
|
|
23
|
+
> = Symbol('VTKThreeViewWidgetManager');
|
|
24
|
+
|
|
25
|
+
export const DataTypes = {
|
|
26
|
+
Image: 'Image',
|
|
27
|
+
Labelmap: 'Labelmap',
|
|
28
|
+
Dicom: 'DICOM',
|
|
29
|
+
Model: 'Model',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Messages = {
|
|
33
|
+
WebGLLost: {
|
|
34
|
+
title: 'Viewer Error',
|
|
35
|
+
details:
|
|
36
|
+
'Lost the WebGL context! Please reload the webpage. If the problem persists, you may need to restart your web browser.',
|
|
37
|
+
},
|
|
38
|
+
} as const;
|
package/src/core/id.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DICOMIO } from '../io/dicom';
|
|
2
|
+
import IDGenerator from './id';
|
|
3
|
+
import ProxyWrapper from './proxies';
|
|
4
|
+
import PaintTool from './tools/paint';
|
|
5
|
+
import RulerTool from './tools/ruler';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pinia plugin for injecting tool services.
|
|
9
|
+
*/
|
|
10
|
+
export function CorePiniaProviderPlugin({
|
|
11
|
+
rulers,
|
|
12
|
+
paint,
|
|
13
|
+
proxies,
|
|
14
|
+
id,
|
|
15
|
+
dicomIO,
|
|
16
|
+
}: {
|
|
17
|
+
rulers?: RulerTool;
|
|
18
|
+
paint?: PaintTool;
|
|
19
|
+
proxies?: ProxyWrapper;
|
|
20
|
+
id?: IDGenerator;
|
|
21
|
+
dicomIO?: DICOMIO;
|
|
22
|
+
} = {}) {
|
|
23
|
+
const dependencies = {
|
|
24
|
+
$rulers: rulers ?? new RulerTool(),
|
|
25
|
+
$paint: paint ?? new PaintTool(),
|
|
26
|
+
$proxies: proxies,
|
|
27
|
+
$id: id ?? new IDGenerator(),
|
|
28
|
+
$dicomIO: dicomIO ?? new DICOMIO(),
|
|
29
|
+
};
|
|
30
|
+
return () => dependencies;
|
|
31
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { vtkObject } from '@kitware/vtk.js/interfaces';
|
|
2
|
+
import vtkAbstractRepresentationProxy from '@kitware/vtk.js/Proxy/Core/AbstractRepresentationProxy';
|
|
3
|
+
import vtkProxyManager from '@kitware/vtk.js/Proxy/Core/ProxyManager';
|
|
4
|
+
import vtkSourceProxy from '@kitware/vtk.js/Proxy/Core/SourceProxy';
|
|
5
|
+
import vtkViewProxy from '@kitware/vtk.js/Proxy/Core/ViewProxy';
|
|
6
|
+
import vtkProxyObject from '../types/vtk-types';
|
|
7
|
+
|
|
8
|
+
// mapped in proxy.js
|
|
9
|
+
export enum ViewProxyType {
|
|
10
|
+
Volume = 'View3D',
|
|
11
|
+
Slice = 'View2D',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Wrapper around the vtkProxyManager, since we don't need some of
|
|
16
|
+
* its complexities.
|
|
17
|
+
*/
|
|
18
|
+
export default class ProxyWrapper {
|
|
19
|
+
private viewProxies: Map<string, vtkViewProxy>;
|
|
20
|
+
private dataProxies: Map<string, vtkSourceProxy<vtkObject>>;
|
|
21
|
+
private proxyManager: vtkProxyManager;
|
|
22
|
+
|
|
23
|
+
constructor(proxyManager: vtkProxyManager) {
|
|
24
|
+
this.viewProxies = new Map();
|
|
25
|
+
this.dataProxies = new Map();
|
|
26
|
+
this.proxyManager = proxyManager;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
delete() {
|
|
30
|
+
const deleteProxy = (proxy: vtkProxyObject) =>
|
|
31
|
+
this.proxyManager.deleteProxy(proxy);
|
|
32
|
+
|
|
33
|
+
this.viewProxies.forEach(deleteProxy);
|
|
34
|
+
this.dataProxies.forEach(deleteProxy);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
createView(id: string, type: ViewProxyType) {
|
|
38
|
+
if (this.viewProxies.has(id)) {
|
|
39
|
+
throw new Error('Cannot create a view with the same ID');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const proxy = this.proxyManager.createProxy<vtkViewProxy>('Views', type, {
|
|
43
|
+
name: type,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this.viewProxies.set(id, proxy);
|
|
47
|
+
return proxy;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getView<T extends vtkViewProxy>(id: string) {
|
|
51
|
+
return <T | null>this.viewProxies.get(id);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
removeView(id: string) {
|
|
55
|
+
const proxy = this.viewProxies.get(id);
|
|
56
|
+
if (proxy) {
|
|
57
|
+
this.proxyManager.deleteProxy(proxy);
|
|
58
|
+
this.viewProxies.delete(id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
addData<T extends vtkObject>(id: string, data: T) {
|
|
63
|
+
if (this.dataProxies.has(id)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const proxy = this.proxyManager.createProxy<vtkSourceProxy<T>>(
|
|
67
|
+
'Sources',
|
|
68
|
+
'TrivialProducer'
|
|
69
|
+
);
|
|
70
|
+
proxy.setInputData(data);
|
|
71
|
+
this.dataProxies.set(id, proxy);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getData<T extends vtkObject>(id: string) {
|
|
75
|
+
return <vtkSourceProxy<T> | null>(this.dataProxies.get(id) ?? null);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getDataRepresentationForView<T extends vtkAbstractRepresentationProxy>(
|
|
79
|
+
dataID: string,
|
|
80
|
+
viewID: string
|
|
81
|
+
): T | null {
|
|
82
|
+
const dataProxy = this.dataProxies.get(dataID);
|
|
83
|
+
const viewProxy = this.viewProxies.get(viewID);
|
|
84
|
+
if (!dataProxy || !viewProxy) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return this.proxyManager.getRepresentation(dataProxy, viewProxy);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
|
|
2
|
+
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import { ThumbnailSlice } from '..';
|
|
5
|
+
import { createVTKImageThumbnailer } from '../vtk-image';
|
|
6
|
+
|
|
7
|
+
// workaround to get ImageData in the global scope
|
|
8
|
+
global.ImageData = document
|
|
9
|
+
.createElement('canvas')
|
|
10
|
+
.getContext('2d')
|
|
11
|
+
?.createImageData(1, 1).constructor as any;
|
|
12
|
+
|
|
13
|
+
// creates a 3x3x3 image
|
|
14
|
+
function createImageData() {
|
|
15
|
+
const id = vtkImageData.newInstance();
|
|
16
|
+
id.setDimensions([3, 3, 3]);
|
|
17
|
+
const values = new Uint8Array(3 * 3 * 3);
|
|
18
|
+
const da = vtkDataArray.newInstance({
|
|
19
|
+
numberOfComponents: 1,
|
|
20
|
+
values,
|
|
21
|
+
});
|
|
22
|
+
id.getPointData().setScalars(da);
|
|
23
|
+
return { imageData: id, scalars: values };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// index to offset for a 3x3
|
|
27
|
+
function i2o(x: number, y: number, z: number) {
|
|
28
|
+
return z * 9 + y * 3 + x;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('VTK Image Thumbnailer', () => {
|
|
32
|
+
const SLICE_POSITION_WORDS = ['first', 'middle', 'last'];
|
|
33
|
+
const SLICE_POSITIONS = [
|
|
34
|
+
ThumbnailSlice.First,
|
|
35
|
+
ThumbnailSlice.Middle,
|
|
36
|
+
ThumbnailSlice.Last,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
for (let axis: 0 | 1 | 2 = 0; axis < 3; axis++) {
|
|
40
|
+
for (let slice = 0; slice < 3; slice++) {
|
|
41
|
+
const sliceWord = SLICE_POSITION_WORDS[slice];
|
|
42
|
+
const thumbSlice = SLICE_POSITIONS[slice];
|
|
43
|
+
|
|
44
|
+
it(`should thumbnail the ${sliceWord} slice on axis ${axis}`, () => {
|
|
45
|
+
const { imageData, scalars } = createImageData();
|
|
46
|
+
const thumbnailer = createVTKImageThumbnailer();
|
|
47
|
+
|
|
48
|
+
const point: [number, number, number] = [1, 1, 1];
|
|
49
|
+
point[axis] = slice;
|
|
50
|
+
scalars[i2o(...point)] = 255;
|
|
51
|
+
|
|
52
|
+
const im = thumbnailer.generate(imageData, axis, thumbSlice);
|
|
53
|
+
// each pixel is rgba
|
|
54
|
+
expect(im.data[4 * 4]).to.equal(255);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import vtkGenericRenderWindow from '@kitware/vtk.js/Rendering/Misc/GenericRenderWindow';
|
|
2
|
+
import vtkVolume from '@kitware/vtk.js/Rendering/Core/Volume';
|
|
3
|
+
import vtkVolumeMapper from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
|
|
4
|
+
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
|
|
5
|
+
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
|
|
6
|
+
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
7
|
+
import vtkPiecewiseFunctionProxy from '@kitware/vtk.js/Proxy/Core/PiecewiseFunctionProxy';
|
|
8
|
+
import vtkLookupTableProxy from '@kitware/vtk.js/Proxy/Core/LookupTableProxy';
|
|
9
|
+
import { vec3 } from 'gl-matrix';
|
|
10
|
+
import { Vector3 } from '@kitware/vtk.js/types';
|
|
11
|
+
import vtkVolumeProperty from '@kitware/vtk.js/Rendering/Core/VolumeProperty';
|
|
12
|
+
import { getDiagonalLength } from '@kitware/vtk.js/Common/DataModel/BoundingBox';
|
|
13
|
+
|
|
14
|
+
export function createRenderingPipeline() {
|
|
15
|
+
const actor = vtkVolume.newInstance();
|
|
16
|
+
const mapper = vtkVolumeMapper.newInstance();
|
|
17
|
+
const property = actor.getProperty();
|
|
18
|
+
const cfun = vtkColorTransferFunction.newInstance();
|
|
19
|
+
const ofun = vtkPiecewiseFunction.newInstance();
|
|
20
|
+
property.setRGBTransferFunction(0, cfun);
|
|
21
|
+
property.setScalarOpacity(0, ofun);
|
|
22
|
+
actor.setMapper(mapper);
|
|
23
|
+
return {
|
|
24
|
+
actor,
|
|
25
|
+
mapper,
|
|
26
|
+
property,
|
|
27
|
+
cfun,
|
|
28
|
+
ofun,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function updateRenderingProperty(
|
|
33
|
+
prop: vtkVolumeProperty,
|
|
34
|
+
mapper: vtkVolumeMapper,
|
|
35
|
+
image: vtkImageData
|
|
36
|
+
) {
|
|
37
|
+
const scalars = image.getPointData().getScalars();
|
|
38
|
+
const dataRange = scalars.getRange();
|
|
39
|
+
|
|
40
|
+
const sampleDistance =
|
|
41
|
+
0.7 *
|
|
42
|
+
Math.sqrt(
|
|
43
|
+
image
|
|
44
|
+
.getSpacing()
|
|
45
|
+
.map((v) => v * v)
|
|
46
|
+
.reduce((a, b) => a + b, 0)
|
|
47
|
+
);
|
|
48
|
+
mapper.setSampleDistance(sampleDistance * 2 ** (0.4 * 3.0 - 1.5));
|
|
49
|
+
|
|
50
|
+
prop.setScalarOpacityUnitDistance(
|
|
51
|
+
0,
|
|
52
|
+
getDiagonalLength(image.getBounds()) / Math.max(...image.getDimensions())
|
|
53
|
+
);
|
|
54
|
+
prop.setGradientOpacityMinimumValue(0, 0);
|
|
55
|
+
prop.setGradientOpacityMaximumValue(0, (dataRange[1] - dataRange[0]) * 0.05);
|
|
56
|
+
// - Use shading based on gradient
|
|
57
|
+
prop.setShade(true);
|
|
58
|
+
prop.setUseGradientOpacity(0, true);
|
|
59
|
+
// - generic good default
|
|
60
|
+
prop.setGradientOpacityMinimumOpacity(0, 0.0);
|
|
61
|
+
prop.setGradientOpacityMaximumOpacity(0, 1.0);
|
|
62
|
+
prop.setAmbient(0.2);
|
|
63
|
+
prop.setDiffuse(0.7);
|
|
64
|
+
prop.setSpecular(0.3);
|
|
65
|
+
prop.setSpecularPower(8.0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createVolumeThumbnailer(size: number) {
|
|
69
|
+
const container = document.createElement('div');
|
|
70
|
+
container.style.width = `${size}px`;
|
|
71
|
+
container.style.height = `${size}px`;
|
|
72
|
+
|
|
73
|
+
const scene = vtkGenericRenderWindow.newInstance({
|
|
74
|
+
listenWindowResize: false,
|
|
75
|
+
background: [0.2, 0.3, 0.4],
|
|
76
|
+
});
|
|
77
|
+
scene.setContainer(container);
|
|
78
|
+
|
|
79
|
+
const pipeline = createRenderingPipeline();
|
|
80
|
+
const { actor, mapper } = pipeline;
|
|
81
|
+
const renderer = scene.getRenderer();
|
|
82
|
+
|
|
83
|
+
// wrap with proxies for easier usage
|
|
84
|
+
const opacityFuncProxy = vtkPiecewiseFunctionProxy.newInstance({
|
|
85
|
+
piecewiseFunction: pipeline.ofun,
|
|
86
|
+
});
|
|
87
|
+
const colorTransferFuncProxy = vtkLookupTableProxy.newInstance({
|
|
88
|
+
lookupTable: pipeline.cfun,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
scene,
|
|
93
|
+
pipeline,
|
|
94
|
+
opacityFuncProxy,
|
|
95
|
+
colorTransferFuncProxy,
|
|
96
|
+
setInputImage(image: vtkImageData | null) {
|
|
97
|
+
if (image) {
|
|
98
|
+
mapper.setInputData(image);
|
|
99
|
+
updateRenderingProperty(actor.getProperty(), mapper, image);
|
|
100
|
+
if (!renderer.hasViewProp(actor)) {
|
|
101
|
+
renderer.addVolume(actor);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
renderer.removeVolume(actor);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
resetCameraWithOrientation(direction: Vector3, up: Vector3) {
|
|
108
|
+
const image = mapper.getInputData() as vtkImageData | null;
|
|
109
|
+
if (image) {
|
|
110
|
+
const camera = renderer.getActiveCamera();
|
|
111
|
+
const bounds = image.getBounds();
|
|
112
|
+
const center = [
|
|
113
|
+
(bounds[0] + bounds[1]) / 2,
|
|
114
|
+
(bounds[2] + bounds[3]) / 2,
|
|
115
|
+
(bounds[4] + bounds[5]) / 2,
|
|
116
|
+
] as Vector3;
|
|
117
|
+
|
|
118
|
+
const position = vec3.clone(center) as Vector3;
|
|
119
|
+
vec3.sub(position, position, direction);
|
|
120
|
+
camera.setFocalPoint(...center);
|
|
121
|
+
camera.setPosition(...position);
|
|
122
|
+
camera.setDirectionOfProjection(...direction);
|
|
123
|
+
camera.setViewUp(...up);
|
|
124
|
+
renderer.resetCamera();
|
|
125
|
+
// ensure correct lighting post camera manip
|
|
126
|
+
renderer.updateLightsGeometryToFollowCamera();
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
2
|
+
import { TypedArray } from '@kitware/vtk.js/types';
|
|
3
|
+
import { ThumbnailSlice } from '.';
|
|
4
|
+
|
|
5
|
+
function scalarImageToImageData(
|
|
6
|
+
values: TypedArray,
|
|
7
|
+
width: number,
|
|
8
|
+
height: number,
|
|
9
|
+
scaleMin: number,
|
|
10
|
+
scaleMax: number
|
|
11
|
+
) {
|
|
12
|
+
const im = new ImageData(width, height);
|
|
13
|
+
const arr32 = new Uint32Array(im.data.buffer);
|
|
14
|
+
// scale to 1 unsigned byte
|
|
15
|
+
const factor = 255 / (scaleMax - scaleMin);
|
|
16
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
17
|
+
const byte = Math.floor((values[i] - scaleMin) * factor);
|
|
18
|
+
// ABGR order
|
|
19
|
+
// eslint-disable-next-line no-bitwise
|
|
20
|
+
arr32[i] = (255 << 24) | (byte << 16) | (byte << 8) | byte;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return im;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generates a thumbnail given an image data.
|
|
28
|
+
*
|
|
29
|
+
* Assumption: image is comprised of single-component scalars
|
|
30
|
+
*/
|
|
31
|
+
function generateThumbnail(
|
|
32
|
+
imageData: vtkImageData,
|
|
33
|
+
axis: 0 | 1 | 2 = 2,
|
|
34
|
+
whichSlice = ThumbnailSlice.Middle
|
|
35
|
+
) {
|
|
36
|
+
const scalars = imageData.getPointData().getScalars();
|
|
37
|
+
const data = scalars.getData() as TypedArray;
|
|
38
|
+
const dataRange = scalars.getRange();
|
|
39
|
+
const dims = imageData.getDimensions();
|
|
40
|
+
|
|
41
|
+
// ThumbnailSlice.First
|
|
42
|
+
let slice = 0;
|
|
43
|
+
if (whichSlice === ThumbnailSlice.Middle) {
|
|
44
|
+
slice = Math.floor(dims[axis] / 2);
|
|
45
|
+
} else if (whichSlice === ThumbnailSlice.Last) {
|
|
46
|
+
slice = dims[axis] - 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let sliceData: TypedArray;
|
|
50
|
+
let width: number;
|
|
51
|
+
let height: number;
|
|
52
|
+
|
|
53
|
+
if (axis === 0) {
|
|
54
|
+
// work-around for typing data.constructor.
|
|
55
|
+
// data is not necessarily of type Uint8Array.
|
|
56
|
+
sliceData = new (<Uint8ArrayConstructor>data.constructor)(
|
|
57
|
+
dims[1] * dims[2]
|
|
58
|
+
);
|
|
59
|
+
[, width, height] = dims;
|
|
60
|
+
for (let k = 0; k < dims[2]; k++) {
|
|
61
|
+
for (let j = 0; j < dims[1]; j++) {
|
|
62
|
+
const index = slice + j * dims[0] + k * dims[0] * dims[1];
|
|
63
|
+
const offset = k * dims[1] + j;
|
|
64
|
+
sliceData[offset] = data[index];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else if (axis === 1) {
|
|
68
|
+
sliceData = new (<Uint8ArrayConstructor>data.constructor)(
|
|
69
|
+
dims[0] * dims[2]
|
|
70
|
+
);
|
|
71
|
+
[width, , height] = dims;
|
|
72
|
+
for (let k = 0; k < dims[2]; k++) {
|
|
73
|
+
for (let i = 0; i < dims[0]; i++) {
|
|
74
|
+
const index = i + slice * dims[0] + k * dims[0] * dims[1];
|
|
75
|
+
const offset = k * dims[0] + i;
|
|
76
|
+
sliceData[offset] = data[index];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} else if (axis === 2) {
|
|
80
|
+
[width, height] = dims;
|
|
81
|
+
const skip = dims[0] * dims[1];
|
|
82
|
+
const sliceOffset = slice * skip;
|
|
83
|
+
sliceData = Array.isArray(data)
|
|
84
|
+
? data.slice(sliceOffset, sliceOffset + skip)
|
|
85
|
+
: data.subarray(sliceOffset, sliceOffset + skip);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return scalarImageToImageData(
|
|
89
|
+
sliceData!,
|
|
90
|
+
width!,
|
|
91
|
+
height!,
|
|
92
|
+
dataRange[0],
|
|
93
|
+
dataRange[1]
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function createVTKImageThumbnailer() {
|
|
98
|
+
const canvas = document.createElement('canvas');
|
|
99
|
+
const ctx = canvas.getContext('2d');
|
|
100
|
+
|
|
101
|
+
const resizeCanvas = document.createElement('canvas');
|
|
102
|
+
const resizeContext = resizeCanvas.getContext('2d');
|
|
103
|
+
|
|
104
|
+
if (!ctx || !resizeContext) {
|
|
105
|
+
throw new Error('[thumbnailer] Failed to create a 2D context');
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
generate(
|
|
109
|
+
imageData: vtkImageData,
|
|
110
|
+
axis: 0 | 1 | 2 = 2,
|
|
111
|
+
whichSlice = ThumbnailSlice.Middle
|
|
112
|
+
) {
|
|
113
|
+
return generateThumbnail(imageData, axis, whichSlice);
|
|
114
|
+
},
|
|
115
|
+
imageDataToDataURI(
|
|
116
|
+
im: ImageData,
|
|
117
|
+
resizeWidth: number,
|
|
118
|
+
resizeHeight: number
|
|
119
|
+
) {
|
|
120
|
+
canvas.width = im.width;
|
|
121
|
+
canvas.height = im.height;
|
|
122
|
+
ctx.putImageData(im, 0, 0);
|
|
123
|
+
|
|
124
|
+
let { width, height } = im;
|
|
125
|
+
if (im.width > im.height) {
|
|
126
|
+
width = resizeWidth;
|
|
127
|
+
height *= resizeWidth / im.width;
|
|
128
|
+
} else {
|
|
129
|
+
height = resizeHeight;
|
|
130
|
+
width *= resizeHeight / im.height;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
resizeCanvas.width = width;
|
|
134
|
+
resizeCanvas.height = height;
|
|
135
|
+
resizeContext.clearRect(0, 0, width, height);
|
|
136
|
+
resizeContext.scale(width / im.width, height / im.height);
|
|
137
|
+
resizeContext.drawImage(canvas, 0, 0);
|
|
138
|
+
// jpegs are smaller than pngs
|
|
139
|
+
return resizeCanvas.toDataURL('image/jpeg');
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import vtkLabelMap from '@/src/vtk/LabelMap';
|
|
2
|
+
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import PaintTool from '../paint';
|
|
5
|
+
import EllipsePaintBrush from '../paint/ellipse-brush';
|
|
6
|
+
|
|
7
|
+
describe('Paint Tool', () => {
|
|
8
|
+
describe('EllipsePaintBrush', () => {
|
|
9
|
+
it('should rasterize a ellipse', () => {
|
|
10
|
+
const brush = new EllipsePaintBrush();
|
|
11
|
+
|
|
12
|
+
brush.setSize(1);
|
|
13
|
+
brush.setScale([1, 1]);
|
|
14
|
+
expect(brush.getStencil()).to.deep.equal({
|
|
15
|
+
pixels: new Uint8Array([1]),
|
|
16
|
+
size: [1, 1],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
brush.setSize(1);
|
|
20
|
+
brush.setScale([3, 5]);
|
|
21
|
+
expect(brush.getStencil()).to.deep.equal({
|
|
22
|
+
// prettier-ignore
|
|
23
|
+
pixels: new Uint8Array([
|
|
24
|
+
0, 1, 0,
|
|
25
|
+
1, 1, 1,
|
|
26
|
+
1, 1, 1,
|
|
27
|
+
1, 1, 1,
|
|
28
|
+
0, 1, 0,
|
|
29
|
+
]),
|
|
30
|
+
size: [3, 5],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
brush.setSize(4);
|
|
34
|
+
brush.setScale([1.2, 0.8]);
|
|
35
|
+
expect(brush.getStencil()).to.deep.equal({
|
|
36
|
+
// prettier-ignore
|
|
37
|
+
pixels: new Uint8Array([
|
|
38
|
+
0, 1, 1, 1, 0,
|
|
39
|
+
1, 1, 1, 1, 1,
|
|
40
|
+
1, 1, 1, 1, 1,
|
|
41
|
+
0, 1, 1, 1, 0,
|
|
42
|
+
]),
|
|
43
|
+
size: [5, 4],
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('PaintTool', () => {
|
|
49
|
+
describe('setBrushSize', () => {
|
|
50
|
+
it('should update the widget stencil', () => {
|
|
51
|
+
const tool = new PaintTool();
|
|
52
|
+
tool.setBrushSize(5);
|
|
53
|
+
tool.setBrushScale([1, 1]);
|
|
54
|
+
const state = tool.factory.getWidgetState();
|
|
55
|
+
expect(state.getStencil().size).to.deep.equal([5, 5]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('paintLabelmap', () => {
|
|
59
|
+
it('should add paint of a given value to a labelmap', () => {
|
|
60
|
+
const labelmap = vtkLabelMap.newInstance();
|
|
61
|
+
const points = new Uint8Array(4 * 4 * 4);
|
|
62
|
+
labelmap.setDimensions([4, 4, 4]);
|
|
63
|
+
labelmap.getPointData().setScalars(
|
|
64
|
+
vtkDataArray.newInstance({
|
|
65
|
+
numberOfComponents: 1,
|
|
66
|
+
values: points,
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
const brushValue = 8;
|
|
70
|
+
const tool = new PaintTool();
|
|
71
|
+
tool.setBrushValue(brushValue);
|
|
72
|
+
tool.setBrushSize(4);
|
|
73
|
+
tool.paintLabelmap(labelmap, 0, [5, 5, 5]);
|
|
74
|
+
expect(points.every((value) => !value)).to.be.true;
|
|
75
|
+
tool.paintLabelmap(labelmap, 0, [1, 1, 1]);
|
|
76
|
+
// only checks some of the brush, not the entire brush.
|
|
77
|
+
expect(points[1 + 4 * 1 + 16 * 1]).to.equal(brushValue);
|
|
78
|
+
});
|
|
79
|
+
it('should linearly interpolate points', () => {
|
|
80
|
+
const labelmap = vtkLabelMap.newInstance();
|
|
81
|
+
const points = new Uint8Array(4 * 4 * 4);
|
|
82
|
+
labelmap.setDimensions([4, 4, 4]);
|
|
83
|
+
labelmap.getPointData().setScalars(
|
|
84
|
+
vtkDataArray.newInstance({
|
|
85
|
+
numberOfComponents: 1,
|
|
86
|
+
values: points,
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
const brushValue = 8;
|
|
90
|
+
const tool = new PaintTool();
|
|
91
|
+
tool.setBrushValue(brushValue);
|
|
92
|
+
tool.setBrushSize(1);
|
|
93
|
+
tool.paintLabelmap(labelmap, 2, [0, 0, 0], [3, 3, 0]);
|
|
94
|
+
for (let i = 0; i <= 3; i++) {
|
|
95
|
+
const offset = i + 4 * i;
|
|
96
|
+
expect(points[offset]).to.equal(brushValue);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { InteractionState } from '@/src/vtk/RulerWidget/state';
|
|
2
|
+
import { Vector3 } from '@kitware/vtk.js/types';
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import RulerTool, { RulerStateUpdate } from '../ruler';
|
|
5
|
+
|
|
6
|
+
type EventData = {
|
|
7
|
+
id: string;
|
|
8
|
+
update: RulerStateUpdate;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe('Ruler Tool Manager', () => {
|
|
12
|
+
describe('createRuler', () => {
|
|
13
|
+
it('should create a ruler', () => {
|
|
14
|
+
const manager = new RulerTool();
|
|
15
|
+
const factory = manager.createRuler('1');
|
|
16
|
+
expect(factory!.getClassName()).to.equal('vtkRulerWidget');
|
|
17
|
+
|
|
18
|
+
manager.teardown();
|
|
19
|
+
});
|
|
20
|
+
it('should register an event emitter for when state is updated', async () => {
|
|
21
|
+
const manager = new RulerTool();
|
|
22
|
+
const factory = manager.createRuler('1');
|
|
23
|
+
const state = factory.getWidgetState();
|
|
24
|
+
|
|
25
|
+
const promise = new Promise<EventData>((resolve) => {
|
|
26
|
+
manager.events.on('widgetUpdate', (eventData) => {
|
|
27
|
+
resolve(eventData);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
state.getFirstPoint().setOrigin([10, 20, 10]);
|
|
32
|
+
const eventData = await promise;
|
|
33
|
+
|
|
34
|
+
expect(eventData.id).to.equal('1');
|
|
35
|
+
expect(eventData.update.firstPoint).to.deep.equal([10, 20, 10]);
|
|
36
|
+
|
|
37
|
+
manager.teardown();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('updateRuler', () => {
|
|
42
|
+
it('should update the internal widget state', () => {
|
|
43
|
+
const manager = new RulerTool();
|
|
44
|
+
const factory = manager.createRuler('1');
|
|
45
|
+
const state = factory.getWidgetState();
|
|
46
|
+
|
|
47
|
+
const firstPoint = [12, 13, 14] as Vector3;
|
|
48
|
+
const secondPoint = [15, 16, 17] as Vector3;
|
|
49
|
+
|
|
50
|
+
manager.updateRuler('1', {
|
|
51
|
+
firstPoint,
|
|
52
|
+
secondPoint,
|
|
53
|
+
interactionState: InteractionState.Settled,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(state.getFirstPoint().getOrigin()).to.deep.equal(firstPoint);
|
|
57
|
+
expect(state.getFirstPoint().getVisible()).to.be.true;
|
|
58
|
+
expect(state.getSecondPoint().getOrigin()).to.deep.equal(secondPoint);
|
|
59
|
+
expect(state.getSecondPoint().getVisible()).to.be.true;
|
|
60
|
+
expect(state.getInteractionState()).to.equal(InteractionState.Settled);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('removeRuler', () => {
|
|
65
|
+
it('should delete a ruler', () => {
|
|
66
|
+
const manager = new RulerTool();
|
|
67
|
+
manager.createRuler('1');
|
|
68
|
+
manager.removeRuler('1');
|
|
69
|
+
|
|
70
|
+
expect(manager.getFactory('1')).to.be.null;
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|