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,20 @@
|
|
|
1
|
+
import { Vector2 } from '@kitware/vtk.js/types';
|
|
2
|
+
|
|
3
|
+
export interface IBrushStencil {
|
|
4
|
+
pixels: Uint8Array;
|
|
5
|
+
size: [number, number];
|
|
6
|
+
}
|
|
7
|
+
export interface IPaintBrush {
|
|
8
|
+
/**
|
|
9
|
+
* Sets the size of the stencil, pre-scaling.
|
|
10
|
+
*/
|
|
11
|
+
setSize(size: number): void;
|
|
12
|
+
/**
|
|
13
|
+
* Sets the scale of the stencil.
|
|
14
|
+
*/
|
|
15
|
+
setScale(scale: Vector2): void;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the stencil with the applied scaling.
|
|
18
|
+
*/
|
|
19
|
+
getStencil(): IBrushStencil;
|
|
20
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Vector2 } from '@kitware/vtk.js/types';
|
|
2
|
+
import { IBrushStencil, IPaintBrush } from './brush';
|
|
3
|
+
|
|
4
|
+
// Adapted from http://members.chello.at/easyfilter/bresenham.html
|
|
5
|
+
function rasterizeEllipse(xdiam: number, ydiam: number) {
|
|
6
|
+
const buffer = new Uint8Array(xdiam * ydiam);
|
|
7
|
+
|
|
8
|
+
const putPixel = (x: number, y: number) => {
|
|
9
|
+
buffer[x + y * xdiam] = 1;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const blitLine = (x0: number, x1: number, y: number) => {
|
|
13
|
+
const start = y * xdiam + x0;
|
|
14
|
+
const end = start + (x1 - x0 + 1);
|
|
15
|
+
buffer.fill(1, start, end);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let x0 = 0;
|
|
19
|
+
let y0 = 0;
|
|
20
|
+
let x1 = xdiam - 1;
|
|
21
|
+
let y1 = ydiam - 1;
|
|
22
|
+
|
|
23
|
+
let a = x1;
|
|
24
|
+
let b = y1;
|
|
25
|
+
// eslint-disable-next-line no-bitwise
|
|
26
|
+
const b1 = b & 1;
|
|
27
|
+
let dx = 4 * (1 - a) * b * b;
|
|
28
|
+
let dy = 4 * (b1 + 1) * a * a;
|
|
29
|
+
let err = dx + dy + b1 * a * a;
|
|
30
|
+
let e2 = 0;
|
|
31
|
+
|
|
32
|
+
y0 += Math.floor((b + 1) / 2);
|
|
33
|
+
y1 = y0 - b1;
|
|
34
|
+
a *= 8 * a;
|
|
35
|
+
b *= 8 * b;
|
|
36
|
+
do {
|
|
37
|
+
blitLine(x0, x1, y0);
|
|
38
|
+
blitLine(x0, x1, y1);
|
|
39
|
+
e2 = 2 * err;
|
|
40
|
+
if (e2 <= dy) {
|
|
41
|
+
y0++;
|
|
42
|
+
y1--;
|
|
43
|
+
dy += a;
|
|
44
|
+
err += dy;
|
|
45
|
+
}
|
|
46
|
+
if (e2 >= dx || 2 * err > dy) {
|
|
47
|
+
x0++;
|
|
48
|
+
x1--;
|
|
49
|
+
dx += b;
|
|
50
|
+
err += dx;
|
|
51
|
+
}
|
|
52
|
+
} while (x0 <= x1);
|
|
53
|
+
while (y0 - y1 < ydiam) {
|
|
54
|
+
putPixel(x0 - 1, y0);
|
|
55
|
+
putPixel(x1 + 1, y0++);
|
|
56
|
+
putPixel(x0 - 1, y1);
|
|
57
|
+
putPixel(x1 + 1, y1--);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return buffer;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default class EllipsePaintBrush implements IPaintBrush {
|
|
64
|
+
private size: number = 1;
|
|
65
|
+
private scale: Vector2 = [1, 1];
|
|
66
|
+
private cachedStencil!: IBrushStencil;
|
|
67
|
+
|
|
68
|
+
constructor() {
|
|
69
|
+
this.recomputeStencil();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setSize(size: number) {
|
|
73
|
+
this.size = Math.max(0, size);
|
|
74
|
+
this.recomputeStencil();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setScale(scale: Vector2) {
|
|
78
|
+
this.scale = scale;
|
|
79
|
+
this.recomputeStencil();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private recomputeStencil() {
|
|
83
|
+
const scaledSize = [
|
|
84
|
+
Math.ceil(this.size * this.scale[0]),
|
|
85
|
+
Math.ceil(this.size * this.scale[1]),
|
|
86
|
+
] as Vector2;
|
|
87
|
+
this.cachedStencil = {
|
|
88
|
+
size: scaledSize,
|
|
89
|
+
pixels: rasterizeEllipse(scaledSize[0], scaledSize[1]),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getStencil() {
|
|
94
|
+
return this.cachedStencil;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import vtkLabelMap from '@/src/vtk/LabelMap';
|
|
2
|
+
import vtkPaintWidget from '@/src/vtk/PaintWidget';
|
|
3
|
+
import { Vector2 } from '@kitware/vtk.js/types';
|
|
4
|
+
import { vec3 } from 'gl-matrix';
|
|
5
|
+
import { IPaintBrush } from './brush';
|
|
6
|
+
import EllipsePaintBrush from './ellipse-brush';
|
|
7
|
+
|
|
8
|
+
export default class PaintTool {
|
|
9
|
+
readonly factory: vtkPaintWidget;
|
|
10
|
+
private brush: IPaintBrush;
|
|
11
|
+
private brushValue: number;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.factory = vtkPaintWidget.newInstance();
|
|
15
|
+
this.brush = new EllipsePaintBrush();
|
|
16
|
+
this.brushValue = 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private updateWidgetStencil() {
|
|
20
|
+
// use unscaled stencil for paint outline
|
|
21
|
+
const stencil = this.brush.getStencil();
|
|
22
|
+
const widgetState = this.factory.getWidgetState();
|
|
23
|
+
widgetState.setStencil(stencil);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setBrushSize(size: number) {
|
|
27
|
+
this.brush.setSize(size);
|
|
28
|
+
this.updateWidgetStencil();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setBrushScale(scale: Vector2) {
|
|
32
|
+
this.brush.setScale(scale);
|
|
33
|
+
this.updateWidgetStencil();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setBrushValue(value: number) {
|
|
37
|
+
this.brushValue = value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Adds paint to a labelmap.
|
|
42
|
+
*
|
|
43
|
+
* If endPoint is specified, then linearly interpolates the brush
|
|
44
|
+
* from the start to the end.
|
|
45
|
+
*
|
|
46
|
+
* Assumption: startPoint and endPoint are on the same slice axis.
|
|
47
|
+
*
|
|
48
|
+
* @param labelmap paint in this labelmap
|
|
49
|
+
* @param sliceAxis Which index-space axis to paint on (0, 1, or 2).
|
|
50
|
+
* @param startPoint start point
|
|
51
|
+
* @param endPoint ending point (optional)
|
|
52
|
+
*/
|
|
53
|
+
paintLabelmap(
|
|
54
|
+
labelmap: vtkLabelMap,
|
|
55
|
+
sliceAxis: 0 | 1 | 2,
|
|
56
|
+
startPoint: vec3,
|
|
57
|
+
endPoint?: vec3
|
|
58
|
+
) {
|
|
59
|
+
const stencil = this.brush.getStencil();
|
|
60
|
+
|
|
61
|
+
const start = [
|
|
62
|
+
// transforms + floating point errors can make zero values occasionally
|
|
63
|
+
// turn into really tiny negative values
|
|
64
|
+
...startPoint.map((val) => Math.round(val)),
|
|
65
|
+
];
|
|
66
|
+
// Assumption: startPoint and endPoint are on the same slice axis.
|
|
67
|
+
const ijkSlice = start[sliceAxis];
|
|
68
|
+
start.splice(sliceAxis, 1);
|
|
69
|
+
|
|
70
|
+
let end = [...start];
|
|
71
|
+
if (endPoint) {
|
|
72
|
+
end = [...endPoint.map((val) => Math.round(val))];
|
|
73
|
+
end.splice(sliceAxis, 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const labelmapPixels = labelmap.getPointData().getScalars().getData();
|
|
77
|
+
const labelmapDims = labelmap.getDimensions();
|
|
78
|
+
const jStride = labelmapDims[0];
|
|
79
|
+
const kStride = labelmapDims[0] * labelmapDims[1];
|
|
80
|
+
|
|
81
|
+
const isInBounds = (point: number[]) =>
|
|
82
|
+
point[0] >= 0 &&
|
|
83
|
+
point[1] >= 0 &&
|
|
84
|
+
point[2] >= 0 &&
|
|
85
|
+
point[0] < labelmapDims[0] &&
|
|
86
|
+
point[1] < labelmapDims[1] &&
|
|
87
|
+
point[2] < labelmapDims[2];
|
|
88
|
+
|
|
89
|
+
const { pixels, size } = stencil;
|
|
90
|
+
const centerX = Math.floor((size[0] - 1) / 2);
|
|
91
|
+
const centerY = Math.floor((size[1] - 1) / 2);
|
|
92
|
+
|
|
93
|
+
const point1 = [...start];
|
|
94
|
+
const point2 = [...end];
|
|
95
|
+
const rounded = [0, 0, 0];
|
|
96
|
+
const curPoint: number[] = [0, 0];
|
|
97
|
+
for (let y = 0; y < size[1]; y++) {
|
|
98
|
+
const ydelta = y - centerY;
|
|
99
|
+
const yoffset = y * size[0];
|
|
100
|
+
for (let x = 0; x < size[0]; x++) {
|
|
101
|
+
const xdelta = x - centerX;
|
|
102
|
+
const pixelOffset = yoffset + x;
|
|
103
|
+
if (pixels[pixelOffset]) {
|
|
104
|
+
point1[0] = start[0] + xdelta;
|
|
105
|
+
point1[1] = start[1] + ydelta;
|
|
106
|
+
point2[0] = end[0] + xdelta;
|
|
107
|
+
point2[1] = end[1] + ydelta;
|
|
108
|
+
|
|
109
|
+
// line between the two points
|
|
110
|
+
const dx = point2[0] - point1[0];
|
|
111
|
+
const dy = point2[1] - point1[1];
|
|
112
|
+
let steps = Math.abs(Math.abs(dx) > Math.abs(dy) ? dx : dy);
|
|
113
|
+
const incX = dx / steps;
|
|
114
|
+
const incY = dy / steps;
|
|
115
|
+
[curPoint[0], curPoint[1]] = point1;
|
|
116
|
+
while (steps-- >= 0) {
|
|
117
|
+
// add slice axis to make a proper 3D index
|
|
118
|
+
curPoint.splice(sliceAxis, 0, ijkSlice);
|
|
119
|
+
rounded[0] = Math.round(curPoint[0]);
|
|
120
|
+
rounded[1] = Math.round(curPoint[1]);
|
|
121
|
+
rounded[2] = Math.round(curPoint[2]);
|
|
122
|
+
|
|
123
|
+
if (isInBounds(rounded)) {
|
|
124
|
+
const offset =
|
|
125
|
+
rounded[0] + rounded[1] * jStride + rounded[2] * kStride;
|
|
126
|
+
labelmapPixels[offset] = this.brushValue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// undo adding the slice axis value
|
|
130
|
+
curPoint.splice(sliceAxis, 1);
|
|
131
|
+
|
|
132
|
+
curPoint[0] += incX;
|
|
133
|
+
curPoint[1] += incY;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
labelmap.modified();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Vector3 } from '@kitware/vtk.js/types';
|
|
2
|
+
import { ImageMetadata } from '@/src/store/datasets-images';
|
|
3
|
+
import {
|
|
4
|
+
InteractionState,
|
|
5
|
+
RulerWidgetState,
|
|
6
|
+
} from '@/src/vtk/RulerWidget/state';
|
|
7
|
+
import vtkRulerWidget from '@/src/vtk/RulerWidget';
|
|
8
|
+
import mitt, { Emitter } from 'mitt';
|
|
9
|
+
import { LPSAxis } from '@/src/types/lps';
|
|
10
|
+
|
|
11
|
+
export interface SerializedRulerV1 {
|
|
12
|
+
version: '1.0';
|
|
13
|
+
/**
|
|
14
|
+
* Index space
|
|
15
|
+
*/
|
|
16
|
+
firstPoint: Vector3;
|
|
17
|
+
/**
|
|
18
|
+
* Index space
|
|
19
|
+
*/
|
|
20
|
+
secondPoint: Vector3;
|
|
21
|
+
viewAxis: LPSAxis;
|
|
22
|
+
/**
|
|
23
|
+
* Index space slice
|
|
24
|
+
*/
|
|
25
|
+
slice: number;
|
|
26
|
+
imageID: string;
|
|
27
|
+
/**
|
|
28
|
+
* imageMetadata is saved as context for where
|
|
29
|
+
* the measurement is located in the world.
|
|
30
|
+
*/
|
|
31
|
+
imageMetadata: Omit<ImageMetadata, 'name'>;
|
|
32
|
+
/**
|
|
33
|
+
* Ruler metadata
|
|
34
|
+
*/
|
|
35
|
+
color: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RulerStateUpdate {
|
|
39
|
+
firstPoint?: Vector3 | null;
|
|
40
|
+
secondPoint?: Vector3 | null;
|
|
41
|
+
interactionState?: InteractionState;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type RulerToolManagerEvents = {
|
|
45
|
+
widgetUpdate: {
|
|
46
|
+
id: string;
|
|
47
|
+
update: RulerStateUpdate;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default class RulerTool {
|
|
52
|
+
private listeners: Map<string, Function>;
|
|
53
|
+
|
|
54
|
+
private factories: Map<string, vtkRulerWidget>;
|
|
55
|
+
|
|
56
|
+
private recurseGuard: boolean = false;
|
|
57
|
+
|
|
58
|
+
public events: Emitter<RulerToolManagerEvents>;
|
|
59
|
+
|
|
60
|
+
constructor() {
|
|
61
|
+
this.listeners = new Map();
|
|
62
|
+
this.factories = new Map();
|
|
63
|
+
this.events = mitt();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
teardown() {
|
|
67
|
+
this.events.all.clear();
|
|
68
|
+
this.listeners.forEach((stop) => stop());
|
|
69
|
+
this.factories.forEach((factory) => factory.delete());
|
|
70
|
+
this.listeners.clear();
|
|
71
|
+
this.factories.clear();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getFactory(id: string) {
|
|
75
|
+
return this.factories.get(id) ?? null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
createRuler(id: string): vtkRulerWidget {
|
|
79
|
+
if (this.factories.has(id)) {
|
|
80
|
+
return this.factories.get(id)!;
|
|
81
|
+
}
|
|
82
|
+
const factory = vtkRulerWidget.newInstance();
|
|
83
|
+
this.listeners.set(
|
|
84
|
+
id,
|
|
85
|
+
this.createStoreUpdater(id, factory.getWidgetState())
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
this.factories.set(id, factory);
|
|
89
|
+
return factory;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
removeRuler(id: string) {
|
|
93
|
+
const factory = this.factories.get(id);
|
|
94
|
+
if (factory) {
|
|
95
|
+
factory.delete();
|
|
96
|
+
this.factories.delete(id);
|
|
97
|
+
}
|
|
98
|
+
const cleanup = this.listeners.get(id);
|
|
99
|
+
if (cleanup) {
|
|
100
|
+
cleanup();
|
|
101
|
+
this.listeners.delete(id);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private createStoreUpdater(id: string, state: RulerWidgetState) {
|
|
106
|
+
const sub = state.onModified(() => {
|
|
107
|
+
if (this.recurseGuard) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const update: RulerStateUpdate = {
|
|
112
|
+
firstPoint: state.getFirstPoint().getOrigin(),
|
|
113
|
+
secondPoint: state.getSecondPoint().getOrigin(),
|
|
114
|
+
interactionState: state.getInteractionState(),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
this.events.emit('widgetUpdate', { id, update });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return () => {
|
|
121
|
+
sub.unsubscribe();
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
updateRuler(id: string, update: RulerStateUpdate) {
|
|
126
|
+
const factory = this.factories.get(id);
|
|
127
|
+
if (factory) {
|
|
128
|
+
this.recurseGuard = true;
|
|
129
|
+
|
|
130
|
+
const state = factory.getWidgetState();
|
|
131
|
+
const first = state.getFirstPoint();
|
|
132
|
+
const second = state.getSecondPoint();
|
|
133
|
+
|
|
134
|
+
if (update.firstPoint) {
|
|
135
|
+
first.setOrigin(update.firstPoint);
|
|
136
|
+
first.setVisible(true);
|
|
137
|
+
}
|
|
138
|
+
if (update.secondPoint) {
|
|
139
|
+
second.setOrigin(update.secondPoint);
|
|
140
|
+
second.setVisible(true);
|
|
141
|
+
}
|
|
142
|
+
if (Number.isInteger(update.interactionState)) {
|
|
143
|
+
state.setInteractionState(update.interactionState!);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.recurseGuard = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import 'pinia';
|
|
2
|
+
import type { Framework } from 'vuetify/types';
|
|
3
|
+
import IDGenerator from './core/id';
|
|
4
|
+
import ProxyWrapper from './core/proxies';
|
|
5
|
+
import PaintTool from './core/tools/paint';
|
|
6
|
+
import RulerTool from './core/tools/ruler';
|
|
7
|
+
import { DICOMIO } from './io/dicom';
|
|
8
|
+
|
|
9
|
+
declare module 'pinia' {
|
|
10
|
+
export interface PiniaCustomProperties {
|
|
11
|
+
// from CorePiniaProviderPlugin
|
|
12
|
+
$rulers: RulerTool;
|
|
13
|
+
$paint: PaintTool;
|
|
14
|
+
$proxies: ProxyWrapper;
|
|
15
|
+
$id: IDGenerator;
|
|
16
|
+
$dicomIO: DICOMIO;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare module 'vue/types/vue' {
|
|
21
|
+
interface Vue {
|
|
22
|
+
$vuetify: Framework;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { retypeFile } from '@src/io';
|
|
3
|
+
|
|
4
|
+
function makeEmptyFile(name: string) {
|
|
5
|
+
return new File([], name);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function makeDicomFile(name: string) {
|
|
9
|
+
const buffer = new Uint8Array(132);
|
|
10
|
+
buffer[128] = 'D'.charCodeAt(0);
|
|
11
|
+
buffer[129] = 'I'.charCodeAt(0);
|
|
12
|
+
buffer[130] = 'C'.charCodeAt(0);
|
|
13
|
+
buffer[131] = 'M'.charCodeAt(0);
|
|
14
|
+
return new File([buffer.buffer], name);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('I/O', () => {
|
|
18
|
+
it('should detect dicom files', async () => {
|
|
19
|
+
expect((await retypeFile(makeDicomFile('file.DCM'))).type).to.equal('dcm');
|
|
20
|
+
expect((await retypeFile(makeDicomFile('somedicom'))).type).to.equal('dcm');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should retype files based on extension', async () => {
|
|
24
|
+
expect((await retypeFile(makeEmptyFile('test.VTI'))).type).to.equal('vti');
|
|
25
|
+
});
|
|
26
|
+
});
|
package/src/io/dicom.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runPipeline,
|
|
3
|
+
TextStream,
|
|
4
|
+
InterfaceTypes,
|
|
5
|
+
readDICOMTags,
|
|
6
|
+
readImageDICOMFileSeries,
|
|
7
|
+
} from 'itk-wasm';
|
|
8
|
+
|
|
9
|
+
export interface TagSpec {
|
|
10
|
+
name: string;
|
|
11
|
+
tag: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// volume ID => file names
|
|
15
|
+
export type VolumesToFilesMap = Record<string, string[]>;
|
|
16
|
+
|
|
17
|
+
export class DICOMIO {
|
|
18
|
+
private webWorker: any;
|
|
19
|
+
private initializeCheck: Promise<void> | null;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this.webWorker = null;
|
|
23
|
+
this.initializeCheck = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async runTask(
|
|
27
|
+
module: string,
|
|
28
|
+
args: any[],
|
|
29
|
+
inputs: any[],
|
|
30
|
+
outputs: any[]
|
|
31
|
+
) {
|
|
32
|
+
return runPipeline(this.webWorker, module, args, outputs, inputs);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Helper that initializes the webworker.
|
|
37
|
+
*
|
|
38
|
+
* @async
|
|
39
|
+
* @throws Error initialization failed
|
|
40
|
+
*/
|
|
41
|
+
private async initialize() {
|
|
42
|
+
if (!this.initializeCheck) {
|
|
43
|
+
this.initializeCheck = new Promise<void>((resolve, reject) => {
|
|
44
|
+
this.runTask('dicom', [], [], [])
|
|
45
|
+
.then((result) => {
|
|
46
|
+
if (result.webWorker) {
|
|
47
|
+
this.webWorker = result.webWorker;
|
|
48
|
+
resolve();
|
|
49
|
+
} else {
|
|
50
|
+
reject(new Error('Could not initialize webworker'));
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
.catch(reject);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return this.initializeCheck;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Categorize files
|
|
62
|
+
* @async
|
|
63
|
+
* @param {File[]} files
|
|
64
|
+
* @returns volumeID => file names mapping
|
|
65
|
+
*/
|
|
66
|
+
async categorizeFiles(files: File[]): Promise<VolumesToFilesMap> {
|
|
67
|
+
await this.initialize();
|
|
68
|
+
|
|
69
|
+
const inputs = await Promise.all(
|
|
70
|
+
files.map(async (file) => {
|
|
71
|
+
const buffer = await file.arrayBuffer();
|
|
72
|
+
return {
|
|
73
|
+
type: InterfaceTypes.BinaryFile,
|
|
74
|
+
data: {
|
|
75
|
+
path: file.name,
|
|
76
|
+
data: new Uint8Array(buffer),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const args = [
|
|
83
|
+
'--action',
|
|
84
|
+
'categorize',
|
|
85
|
+
'--memory-io',
|
|
86
|
+
'0',
|
|
87
|
+
'--files',
|
|
88
|
+
...inputs.map((fd) => fd.data.path),
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const outputs = [{ type: InterfaceTypes.TextStream }];
|
|
92
|
+
|
|
93
|
+
const result = await this.runTask(
|
|
94
|
+
// module
|
|
95
|
+
'dicom',
|
|
96
|
+
args,
|
|
97
|
+
inputs,
|
|
98
|
+
outputs
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
return JSON.parse((result.outputs[0].data as TextStream).data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reads a list of tags out from a given file.
|
|
106
|
+
*
|
|
107
|
+
* @param {File} file
|
|
108
|
+
* @param {[]Tag} tags
|
|
109
|
+
*/
|
|
110
|
+
async readTags<T extends TagSpec[]>(
|
|
111
|
+
file: File,
|
|
112
|
+
tags: T
|
|
113
|
+
): Promise<Record<T[number]['name'], string>> {
|
|
114
|
+
const tagsArgs = tags.map((t) => t.tag);
|
|
115
|
+
|
|
116
|
+
const result = await readDICOMTags(this.webWorker, file, tagsArgs);
|
|
117
|
+
const tagValues = result.tags;
|
|
118
|
+
|
|
119
|
+
return tags.reduce((info, t) => {
|
|
120
|
+
const { tag, name } = t;
|
|
121
|
+
if (tagValues.has(tag)) {
|
|
122
|
+
return { ...info, [name]: tagValues.get(tag) };
|
|
123
|
+
}
|
|
124
|
+
return info;
|
|
125
|
+
}, {} as Record<T[number]['name'], string>);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Retrieves a slice of a volume.
|
|
130
|
+
* @async
|
|
131
|
+
* @param {File} file containing the slice
|
|
132
|
+
* @param {Boolean} asThumbnail cast image to unsigned char. Defaults to false.
|
|
133
|
+
* @returns ItkImage
|
|
134
|
+
*/
|
|
135
|
+
async getVolumeSlice(file: File, asThumbnail: boolean = false) {
|
|
136
|
+
await this.initialize();
|
|
137
|
+
|
|
138
|
+
const buffer = await file.arrayBuffer();
|
|
139
|
+
|
|
140
|
+
const inputs = [
|
|
141
|
+
{
|
|
142
|
+
type: InterfaceTypes.BinaryFile,
|
|
143
|
+
data: {
|
|
144
|
+
path: file.name,
|
|
145
|
+
data: new Uint8Array(buffer),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const args = [
|
|
151
|
+
'--action',
|
|
152
|
+
'getSliceImage',
|
|
153
|
+
'--thumbnail',
|
|
154
|
+
asThumbnail.toString(),
|
|
155
|
+
'--file',
|
|
156
|
+
file.name,
|
|
157
|
+
'--memory-io',
|
|
158
|
+
'0',
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const outputs = [{ type: InterfaceTypes.Image }];
|
|
162
|
+
|
|
163
|
+
const result = await this.runTask(
|
|
164
|
+
// module
|
|
165
|
+
'dicom',
|
|
166
|
+
args,
|
|
167
|
+
inputs,
|
|
168
|
+
outputs
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
return result.outputs[0].data;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Builds a volume for a set of files.
|
|
176
|
+
* @async
|
|
177
|
+
* @param {File[]} files the set of files to build volume from
|
|
178
|
+
* @returns ItkImage
|
|
179
|
+
*/
|
|
180
|
+
async buildVolume(files: File[]) {
|
|
181
|
+
await this.initialize();
|
|
182
|
+
|
|
183
|
+
const result = await readImageDICOMFileSeries(files);
|
|
184
|
+
|
|
185
|
+
return result.image;
|
|
186
|
+
}
|
|
187
|
+
}
|
package/src/io/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { vtkObject } from '@kitware/vtk.js/interfaces';
|
|
2
|
+
|
|
3
|
+
export * from './io';
|
|
4
|
+
|
|
5
|
+
export type ReaderType = (file: File) => vtkObject | Promise<vtkObject>;
|
|
6
|
+
export type FileReaderMap = Map<string, ReaderType>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A map of the currently registered file readers.
|
|
10
|
+
*/
|
|
11
|
+
export const FILE_READERS: FileReaderMap = new Map();
|