The import VTI file does not show circleWidget

Using this demo, I drew a circleWidget on the image, then downloaded it and saved it locally. Then, when it is introduced again, the circleWidget drawn before disappears. How do I display the widgets drawn before?

saved file:


The re-rendered file:

my code:

import {useEffect, useState, useRef} from 'react';
import '@kitware/vtk.js/favicon';
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
import '@kitware/vtk.js/Rendering/Profiles/All';
// Force the loading of HttpDataAccessHelper to support gzip decompression
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper';
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
import vtkRectangleWidget from '@kitware/vtk.js/Widgets/Widgets3D/RectangleWidget';
import vtkEllipseWidget from '@kitware/vtk.js/Widgets/Widgets3D/EllipseWidget';
import vtkInteractorStyleImage from '@kitware/vtk.js/Interaction/Style/InteractorStyleImage';
import vtkHttpDataSetReader from '@kitware/vtk.js/IO/Core/HttpDataSetReader';
import vtkImageMapper from '@kitware/vtk.js/Rendering/Core/ImageMapper';
import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice';
import vtkSphere from '@kitware/vtk.js/Common/DataModel/Sphere';
import vtkXMLImageDataReader from '@kitware/vtk.js/IO/XML/XMLImageDataReader';
import vtkXMLImageDataWriter from '@kitware/vtk.js/IO/XML/XMLImageDataWriter';
import vtkXMLWriter from '@kitware/vtk.js/IO/XML/XMLWriter';
import {
  BehaviorCategory,
  ShapeBehavior,
  TextPosition,
} from '@kitware/vtk.js/Widgets/Widgets3D/ShapeWidget/Constants';
import { VerticalTextAlignment } from '@kitware/vtk.js/Widgets/SVG/SVGLandmarkRepresentation/Constants';
import { ViewTypes } from '@kitware/vtk.js/Widgets/Core/WidgetManager/Constants';
import { vec3 } from 'gl-matrix';
import DeepEqual from 'deep-equal';

import {Select, Slider, Button, Row, Col} from 'antd';
const {Option} = Select;
import 'antd/dist/antd.less';

export default function ShapeWidget() {
    const scene = useRef({});
    const widgets = useRef({});
    const image = useRef({});
    let activeWidget = 'ellipseWidget';

    const [sliceMin, setSliceMin] = useState(0);
    const [sliceMax, setSliceMax] = useState(132);
    const [slice, setSlice] = useState(1);
    const [axis, setAxis] = useState('K');
    const [widget, setWidget] = useState('ellipseWidget');
    const [widgetVisible, setWidgetVisible] = useState(true);
    let selectedWidgetIndex = 0;

    const resetWidgets = () => {
        scene.rectangleHandle.reset();
        scene.ellipseHandle.reset();
        scene.circleHandle.reset();
        const slicingMode = image.imageMapper.getSlicingMode() % 3;
        updateWidgetsVisibility(null, slicingMode);
        scene.widgetManager.grabFocus(widgets[activeWidget]);
    };
    const updateWidgetVisibility = (widget, slicePos, i, widgetIndex) => {
        /* testing if the widget is on the slice and has been placed to modify visibility */
        const widgetVisibility = !scene.widgetManager.getWidgets()[widgetIndex].getPoint1() || widget.getWidgetState().getPoint1Handle().getOrigin()[i] === slicePos[i];
        return widget.setVisibility(widgetVisibility);
    };
    const updateWidgetsVisibility = (slicePos, slicingMode) => {
        updateWidgetVisibility(widgets.rectangleWidget, slicePos, slicingMode, 0);
        updateWidgetVisibility(widgets.ellipseWidget, slicePos, slicingMode, 1);
        updateWidgetVisibility(widgets.circleWidget, slicePos, slicingMode, 2);
    };
    const setCamera = (sliceMode, renderer, data) => {
        const ijk = [0, 0, 0];
        const position = [0, 0, 0];
        const focalPoint = [0, 0, 0];
        const viewUp = sliceMode === 1 ? [0, 0, 1] : [0, 1, 0];
        data.indexToWorld(ijk, focalPoint);
        ijk[sliceMode] = 1;
        data.indexToWorld(ijk, position);
        renderer.getActiveCamera().set({ focalPoint, position, viewUp });
        renderer.resetCamera();
    };
    const updateControlPanel = (im, ds) => {
        const slicingMode = im.getSlicingMode();
        const extent = ds.getExtent();
        setSliceMin(extent[slicingMode * 2]);
        setSliceMax(extent[slicingMode * 2 + 1]);
    };
    const renderScene = () => {
        scene.fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
            rootContainer: document.body,
            background: [0.1, 0.1, 0.1],
        });
        scene.renderer = scene.fullScreenRenderer.getRenderer();
        scene.renderWindow = scene.fullScreenRenderer.getRenderWindow();
        scene.openGLRenderWindow = scene.fullScreenRenderer.getApiSpecificRenderWindow();
        scene.camera = scene.renderer.getActiveCamera();
        // setup 2D view
        scene.camera.setParallelProjection(true);
        scene.iStyle = vtkInteractorStyleImage.newInstance();
        scene.iStyle.setInteractionMode('IMAGE_SLICING');
        scene.renderWindow.getInteractor().setInteractorStyle(scene.iStyle);
        // Widget manager
        scene.widgetManager = vtkWidgetManager.newInstance();
        scene.widgetManager.setRenderer(scene.renderer);
        // Widgets
        widgets.rectangleWidget = vtkRectangleWidget.newInstance({
            resetAfterPointPlacement: true,
        });
        widgets.ellipseWidget = vtkEllipseWidget.newInstance({
            modifierBehavior: {
                None: {
                    [BehaviorCategory.PLACEMENT]: ShapeBehavior[BehaviorCategory.PLACEMENT].CLICK_AND_DRAG,
                    [BehaviorCategory.POINTS]: ShapeBehavior[BehaviorCategory.POINTS].CORNER_TO_CORNER,
                    [BehaviorCategory.RATIO]: ShapeBehavior[BehaviorCategory.RATIO].FREE,
                },
            },
        });
        widgets.circleWidget = vtkEllipseWidget.newInstance({
            modifierBehavior: {
                None: {
                [BehaviorCategory.PLACEMENT]: ShapeBehavior[BehaviorCategory.PLACEMENT].CLICK_AND_DRAG,
                [BehaviorCategory.POINTS]: ShapeBehavior[BehaviorCategory.POINTS].RADIUS,
                [BehaviorCategory.RATIO]: ShapeBehavior[BehaviorCategory.RATIO].FREE,
                },
            },
        });
        // Make a large handle for demo purpose
        widgets.circleWidget.getWidgetState().getPoint1Handle().setScale1(20);
        widgets.circleWidget.getWidgetState().setTextPosition([
            TextPosition.MAX,
            TextPosition.CENTER,
            TextPosition.CENTER,
        ]);
        
        scene.rectangleHandle = scene.widgetManager.addWidget(widgets.rectangleWidget, ViewTypes.SLICE);
        scene.rectangleHandle.setHandleVisibility(false);
        scene.rectangleHandle.setTextProps({
            ...scene.rectangleHandle.getTextProps(),
            'text-anchor': 'middle',
            verticalAlign: VerticalTextAlignment.MIDDLE,
        });
        widgets.rectangleWidget.getWidgetState().setTextPosition([
            TextPosition.CENTER,
            TextPosition.CENTER,
            TextPosition.CENTER,
        ]);
        scene.ellipseHandle = scene.widgetManager.addWidget(widgets.ellipseWidget, ViewTypes.SLICE);
        scene.ellipseHandle.setTextProps({
            ...scene.ellipseHandle.getTextProps(),
            'text-anchor': 'middle',
            verticalAlign: VerticalTextAlignment.MIDDLE,
        });
        
        scene.circleHandle = scene.widgetManager.addWidget(widgets.circleWidget, ViewTypes.SLICE);
        scene.circleHandle.setGlyphResolution(64);
        // 使小部件实例获得焦点;
        scene.widgetManager.grabFocus(widgets.ellipseWidget);
        widgetHandle(activeWidget);

        loadImage();
    };
    const loadImage = async () => {
        image.imageMapper = vtkImageMapper.newInstance();
        image.actor = vtkImageSlice.newInstance();
        // background image pipeline
        image.actor.setMapper(image.imageMapper);

        // const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
        const reader = vtkXMLImageDataReader.newInstance();
        const response = await fetch('http://172.22.150.28:8000/img/LIDC2.vti');
        const arrayBuffer = await response.arrayBuffer();
        reader.parseAsArrayBuffer(arrayBuffer);

        const writer = vtkXMLImageDataWriter.newInstance();
        writer.setFormat(vtkXMLWriter.FormatTypes.BINARY);
        writer.setInputConnection(reader.getOutputPort());

        const writerReader = vtkXMLImageDataReader.newInstance();
        // reader.setUrl(`https://kitware.github.io/vtk-js/data/volume/LIDC2.vti`, { loadData: true }).then(() => {
        debugger
            const data = reader.getOutputData();
            const fileContents = writer.write(data);
            // Try to read it back.
            const textEncoder = new TextEncoder();
            writerReader.parseAsArrayBuffer(textEncoder.encode(fileContents));
            image.data = data;
            // set input data
            image.imageMapper.setInputData(data);
            // add actors to renderers
            scene.renderer.addViewProp(image.actor);
            // default slice orientation/mode and camera view
            const sliceMode = vtkImageMapper.SlicingMode.K;
            image.imageMapper.setSlicingMode(sliceMode);
            image.imageMapper.setSlice(0);
            // set 2D camera position
            setCamera(sliceMode, scene.renderer, image.data);
            updateControlPanel(image.imageMapper, data);
            scene.rectangleHandle.getRepresentations()[1].setDrawBorder(true);
            scene.rectangleHandle.getRepresentations()[1].setDrawFace(false);
            scene.rectangleHandle.getRepresentations()[1].setOpacity(1);
            scene.circleHandle.getRepresentations()[1].setDrawBorder(true);
            scene.circleHandle.getRepresentations()[1].setDrawFace(false);
            scene.circleHandle.getRepresentations()[1].setOpacity(1);
            scene.ellipseHandle.getRepresentations()[1].setDrawBorder(true);
            scene.ellipseHandle.getRepresentations()[1].setDrawFace(false);
            scene.ellipseHandle.getRepresentations()[1].setOpacity(1);

            // set text display callback
            scene.ellipseHandle.onInteractionEvent(() => {
                // const worldBounds = scene.ellipseHandle.getBounds();
                // const { average, minimum, maximum } = image.data.computeHistogram(worldBounds, vtkSphere.isPointIn3DEllipse);
                // const text = `average: ${average.toFixed(0)} \nmin: ${minimum} \nmax: ${maximum} `;
                // widgets.ellipseWidget.getWidgetState().getText().setText(text);
            });

            scene.circleHandle.onInteractionEvent(() => {
                const worldBounds = scene.circleHandle.getBounds();
                const text = `radius: ${(vec3.distance([worldBounds[0], worldBounds[2], worldBounds[4]], [worldBounds[1], worldBounds[3], worldBounds[5]]) / 2).toFixed(2)}`;
                widgets.circleWidget.getWidgetState().getText().setText(text);
            });

            scene.rectangleHandle.onInteractionEvent(() => {
                const worldBounds = scene.rectangleHandle.getBounds();
                const dx = Math.abs(worldBounds[0] - worldBounds[1]);
                const dy = Math.abs(worldBounds[2] - worldBounds[3]);
                const dz = Math.abs(worldBounds[4] - worldBounds[5]);
                const perimeter = 2 * (dx + dy + dz);
                const area = dx * dy + dy * dz + dz * dx;
                const text = `perimeter: ${perimeter.toFixed(1)}mm\narea: ${area.toFixed(1)}mm²`;
                widgets.rectangleWidget.getWidgetState().getText().setText(text);
            });

            const update = () => {
                const slicingMode = image.imageMapper.getSlicingMode() % 3;
                if (slicingMode > -1) {
                    const ijk = [0, 0, 0];
                    const slicePos = [0, 0, 0];
                    // position
                    ijk[slicingMode] = image.imageMapper.getSlice();
                    data.indexToWorld(ijk, slicePos);

                    widgets.rectangleWidget.getManipulator().setUserOrigin(slicePos);
                    widgets.ellipseWidget.getManipulator().setUserOrigin(slicePos);
                    widgets.circleWidget.getManipulator().setUserOrigin(slicePos);

                    updateWidgetsVisibility(slicePos, slicingMode);
                    scene.renderWindow.render();
                    // update UI
                    setSliceMax(data.getDimensions()[slicingMode] - 1);
                }
            };
            image.imageMapper.onModified(update);

            // download
            const blob = new Blob([fileContents], { type: 'text/plain' });
            const a = window.document.createElement('a');
            a.href = window.URL.createObjectURL(blob, { type: 'text/plain' });
            a.download = 'LIDC2.vti';
            a.text = 'Download';
            a.style.position = 'absolute';
            a.style.left = '50%';
            a.style.bottom = '10px';
            document.body.appendChild(a);
            a.style.background = 'white';
            a.style.padding = '5px';
            // trigger initial update
            update();
            readyAll();
        // });
    };
    const readyHandle = (scope, picking = false) => {
        scope.renderer.resetCamera();
        scope.fullScreenRenderer.resize();
        if (picking) {
            scope.widgetManager.enablePicking();
        } else {
            scope.widgetManager.disablePicking();
        }
    };
    const readyAll = () => {
        readyHandle(scene, true);
    };
    // UI
    const sliceHandle = (value: number) => {
        image.imageMapper.setSlice(value);
        setSlice(value);
    }
    const axisHandle = (value: string) => {
        const sliceMode = 'IJKXYZ'.indexOf(value) % 3;
        image.imageMapper.setSlicingMode(sliceMode);

        setCamera(sliceMode, scene.renderer, image.data);
        resetWidgets();
        scene.renderWindow.render();

        setAxis(value);
    };
    const widgetHandle = (value: string) => {
        // For demo purpose, hide ellipse handles when the widget loses focus
        // if (activeWidget === 'ellipseWidget') {
        //     widgets.ellipseWidget.setHandleVisibility(false);
        // }
        scene.widgetManager.grabFocus(widgets[value]);
        activeWidget = value;
        // if (activeWidget === 'ellipseWidget') {
        //     widgets.ellipseWidget.setHandleVisibility(true);
        //     scene.ellipseHandle.updateRepresentationForRender();
        // }

        setWidget(value);
        // let currentHandle = scene.widgetManager.addWidget(widgets[value]);
        // currentHandle.onStartInteractionEvent(() => {
        //     const index = scene.widgetManager.getWidgets().findIndex((cwidget) => {
        //         return DeepEqual(currentHandle.getWidgetState(), cwidget.getWidgetState());
        //     });
        //     setWidgetColor(scene.widgetManager.getWidgets()[selectedWidgetIndex], 0.5);
        //     setWidgetColor(scene.widgetManager.getWidgets()[index], 0.2);
        // });
    };
    const resetHandle = () => {
        resetWidgets();
        scene.renderWindow.render();
    };
    const setWidgetColor = (currentWidget, color) => {
        currentWidget.getWidgetState().getPoint1Handle().setColor(color);
        currentWidget.getWidgetState().getPoint2Handle().setColor(color);
    };
    const delHandle = () => {
        const widgets = scene.widgetManager.getWidgets();
        if (!widgets.length) return;
        scene.widgetManager.removeWidget(widgets[widgets.length - 1]);
        
        // const focusWidget = scene.widgetManager.grabFocus();
        // let selectedWidgetIndex = scene.widgetManager.getWidgets().findIndex((cwidget) => {
        //     return DeepEqual(focusWidget.getWidgetState(), cwidget.getWidgetState());
        // });
        // if (selectedWidgetIndex === -1) {
        //     selectedWidgetIndex = scene.widgetManager.getWidgets().length - 1;
        // }
        // scene.widgetManager.removeWidget(scene.widgetManager.getWidgets()[selectedWidgetIndex]);
    };
    const visibleToggle = (widgetVisible) => {
        const allWidgets = scene.widgetManager.getWidgets();
        allWidgets.forEach(widget => {
            widget.setVisibility(!widgetVisible);
        });
        scene.renderWindow.render();
        setWidgetVisible(!widgetVisible);
    };
    useEffect(() => {
        renderScene();
        
        window.addEventListener('resize', readyAll);
    }, []);
    return (
        <div>
            <div
                style={{
                    position: 'absolute',
                    left: 0,
                    top: 0,
                    backgroundColor: 'white',
                    zIndex: 1,
                    padding: '10px',
                    width: '300px'
                }}
            >
                <Row>
                    <Col span={12}>Slice #</Col>
                    <Col span={12}>
                        <Slider value={slice} step={1} min={sliceMin} max={sliceMax} onChange={sliceHandle}/>
                    </Col>
                </Row>
                <Row>
                    <Col span={12}>Slice Axis</Col>
                    <Col span={12}>
                        <Select value={axis} onChange={axisHandle}>
                            <Option value="I">I</Option>
                            <Option value="J">J</Option>
                            <Option value="K">K</Option>
                        </Select>
                    </Col>
                </Row>
                <Row>
                    <Col span={12}>wdiget</Col>
                    <Col span={12}>
                        <Select value={widget} onChange={widgetHandle}> 
                            <Option value="rectangleWidget">rectangleWidget</Option> 
                            <Option value="ellipseWidget">ellipseWidget</Option> 
                            <Option value="circleWidget">circleWidget</Option> 
                        </Select> 
                    </Col>
                </Row>
                <Row>
                    <Col span={8}>
                        <Button onClick={resetHandle}>Reset</Button>
                    </Col>
                    <Col span={8}>
                        <Button onClick={delHandle}>删除</Button>
                    </Col>
                    <Col span={8}>
                        <Button onClick={() => visibleToggle(widgetVisible)}>{widgetVisible ? '隐藏':'显示'}</Button>
                    </Col>
                </Row>
            </div>
        </div>
    );
}

What you are saving is the original volume (LID2c.vti).
You would need to save the “segmentation” instead. Which I did not see in your screenshot. I only saw a Shape widget (but not the segmentation volume).

This is my first time to use VTK.js. Could you please write an example for me? Thank you very much

Please have a look at the PaintWidget example

I changed the paintWidget Demo to paintWidget and the Widget still doesn’t show up. I hope you can help me find out why. Thank you very much.
my code:

import {useEffect, useState, useRef} from 'react';
import '@kitware/vtk.js/favicon';
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
import '@kitware/vtk.js/Rendering/Profiles/All';

import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
import vtkPaintWidget from '@kitware/vtk.js/Widgets/Widgets3D/PaintWidget';
import vtkRectangleWidget from '@kitware/vtk.js/Widgets/Widgets3D/RectangleWidget';
import vtkEllipseWidget from '@kitware/vtk.js/Widgets/Widgets3D/EllipseWidget';
import vtkSplineWidget from '@kitware/vtk.js/Widgets/Widgets3D/SplineWidget';
import vtkInteractorStyleImage from '@kitware/vtk.js/Interaction/Style/InteractorStyleImage';
import vtkHttpDataSetReader from '@kitware/vtk.js/IO/Core/HttpDataSetReader';
import vtkImageMapper from '@kitware/vtk.js/Rendering/Core/ImageMapper';
import vtkImageSlice from '@kitware/vtk.js/Rendering/Core/ImageSlice';
import vtkPaintFilter from '@kitware/vtk.js/Filters/General/PaintFilter';
import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
import vtkConeSource from '@kitware/vtk.js/Filters/Sources/ConeSource';
import vtkXMLImageDataReader from '@kitware/vtk.js/IO/XML/XMLImageDataReader';
import vtkXMLImageDataWriter from '@kitware/vtk.js/IO/XML/XMLImageDataWriter';
import vtkXMLWriter from '@kitware/vtk.js/IO/XML/XMLWriter';

// Force the loading of HttpDataAccessHelper to support gzip decompression
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper';
import {
  BehaviorCategory,
  ShapeBehavior,
} from '@kitware/vtk.js/Widgets/Widgets3D/ShapeWidget/Constants';
import { ViewTypes } from '@kitware/vtk.js/Widgets/Core/WidgetManager/Constants';
import { vec3 } from 'gl-matrix';
import {Select, Slider, Button} from 'antd';
const {Option} = Select;
import 'antd/dist/antd.less';

export default function PaintWidget() {
    const scene = useRef({});
    const widgets = useRef({});
    const image = useRef({});
    let activeWidget = 'paintWidget';
    // Paint filter
    const painter = vtkPaintFilter.newInstance();

    const [radius, setRadius] = useState(1);
    const [slice, setSlice] = useState(1);
    const [axis, setAxis] = useState('K');
    const [widget, setWidget] = useState('paintWidget');
    const [sliceMin, setSliceMin] = useState(1);
    const [sliceMax, setSliceMax] = useState(132);
    const fileContents = useRef({});

    const initializeHandle = (handle) => {
        handle.onStartInteractionEvent(() => {
            painter.startStroke();
        });
        handle.onEndInteractionEvent(() => {
            painter.endStroke();
        });
    };
    const setCamera = (sliceMode, renderer, data) => {
        const ijk = [0, 0, 0];
        const position = [0, 0, 0];
        const focalPoint = [0, 0, 0];
        data.indexToWorld(ijk, focalPoint);
        ijk[sliceMode] = 1;
        data.indexToWorld(ijk, position);
        renderer.getActiveCamera().set({ focalPoint, position });
        renderer.resetCamera();
    };
    const updateControlPanel = (im, ds) => {
        const slicingMode = im.getSlicingMode();
        const extent = ds.getExtent();
        setSliceMin(extent[slicingMode * 2]);
        setSliceMax(extent[slicingMode * 2 + 1]);
    };
    const renderScene = () => {
        const rootContainer = document.getElementById('rootContainer');
        scene.fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
            rootContainer: rootContainer,
            background: [0.1, 0.1, 0.1],
        });
        scene.renderer = scene.fullScreenRenderer.getRenderer();
        scene.renderWindow = scene.fullScreenRenderer.getRenderWindow();
        scene.apiSpecificRenderWindow = scene.fullScreenRenderer.getInteractor().getView();
        scene.camera = scene.renderer.getActiveCamera();
        // setup 2D view
        scene.camera.setParallelProjection(true);
        scene.iStyle = vtkInteractorStyleImage.newInstance();
        scene.iStyle.setInteractionMode('IMAGE_SLICING');
        scene.renderWindow.getInteractor().setInteractorStyle(scene.iStyle);
        // scene.fullScreenRenderer.addController(controlPanel);
        // Widget manager and vtkPaintFilter
        scene.widgetManager = vtkWidgetManager.newInstance();
        scene.widgetManager.setRenderer(scene.renderer);
        // Widgets
        widgets.paintWidget = vtkPaintWidget.newInstance();
        widgets.rectangleWidget = vtkRectangleWidget.newInstance({
            resetAfterPointPlacement: true,
        });
        widgets.ellipseWidget = vtkEllipseWidget.newInstance({
            resetAfterPointPlacement: true,
        });
        widgets.circleWidget = vtkEllipseWidget.newInstance({
            resetAfterPointPlacement: true,
            modifierBehavior: {
                None: {
                    [BehaviorCategory.PLACEMENT]: ShapeBehavior[BehaviorCategory.PLACEMENT].CLICK_AND_DRAG,
                    [BehaviorCategory.POINTS]: ShapeBehavior[BehaviorCategory.POINTS].RADIUS,
                    [BehaviorCategory.RATIO]: ShapeBehavior[BehaviorCategory.RATIO].FREE,
                },
                Control: {
                    [BehaviorCategory.POINTS]: ShapeBehavior[BehaviorCategory.POINTS].DIAMETER,
                },
            },
        });
        widgets.splineWidget = vtkSplineWidget.newInstance({
            resetAfterPointPlacement: true,
        });
        widgets.polygonWidget = vtkSplineWidget.newInstance({
            resetAfterPointPlacement: true,
            resolution: 1,
        });

        scene.paintHandle = scene.widgetManager.addWidget(widgets.paintWidget, ViewTypes.SLICE);
        scene.rectangleHandle = scene.widgetManager.addWidget(widgets.rectangleWidget, ViewTypes.SLICE);
        scene.ellipseHandle = scene.widgetManager.addWidget(widgets.ellipseWidget, ViewTypes.SLICE);
        scene.circleHandle = scene.widgetManager.addWidget(widgets.circleWidget, ViewTypes.SLICE);
        scene.splineHandle = scene.widgetManager.addWidget(widgets.splineWidget, ViewTypes.SLICE);
        scene.polygonHandle = scene.widgetManager.addWidget(widgets.polygonWidget, ViewTypes.SLICE);

        scene.splineHandle.setOutputBorder(true);
        scene.polygonHandle.setOutputBorder(true);

        // 使小部件实例获得焦点;lossFocus使小部件实例释放焦点;hasFocus如果小部件实例保持焦点,则返回 true,否则返回 false
        scene.widgetManager.grabFocus(widgets.paintWidget);

        // Painting
        scene.paintHandle.onStartInteractionEvent(() => {
            painter.startStroke();
            painter.addPoint(widgets.paintWidget.getWidgetState().getTrueOrigin());
        });
        scene.paintHandle.onInteractionEvent(() => {
            painter.addPoint(widgets.paintWidget.getWidgetState().getTrueOrigin());
        });
        initializeHandle(scene.paintHandle);

        scene.rectangleHandle.onEndInteractionEvent(() => {
            const rectangleHandle = scene.rectangleHandle.getWidgetState().getRectangleHandle();
        
            const origin = rectangleHandle.getOrigin();
            const corner = rectangleHandle.getCorner();
        
            if (origin && corner) {
                painter.paintRectangle(origin, corner);
            }
        });
        initializeHandle(scene.rectangleHandle);
        
        scene.ellipseHandle.onEndInteractionEvent(() => {
            const center = scene.ellipseHandle
            .getWidgetState()
            .getEllipseHandle()
            .getOrigin();
            const point2 = scene.ellipseHandle
            .getWidgetState()
            .getPoint2Handle()
            .getOrigin();
        
            if (center && point2) {
            let corner = [];
            if (
                scene.ellipseHandle.isBehaviorActive(
                BehaviorCategory.RATIO,
                ShapeBehavior[BehaviorCategory.RATIO].FIXED
                )
            ) {
                const radius = vec3.distance(center, point2);
                corner = [radius, radius, radius];
            } else {
                corner = [
                center[0] - point2[0],
                center[1] - point2[1],
                center[2] - point2[2],
                ];
            }
        
            painter.paintEllipse(center, corner);
            }
        });
        initializeHandle(scene.ellipseHandle);
        
        scene.circleHandle.onEndInteractionEvent(() => {
            const center = scene.circleHandle
            .getWidgetState()
            .getEllipseHandle()
            .getOrigin();
            const point2 = scene.circleHandle
            .getWidgetState()
            .getPoint2Handle()
            .getOrigin();
        
            if (center && point2) {
                const radius = vec3.distance(center, point2);
                const corner = [radius, radius, radius];
            
                painter.paintEllipse(center, corner);
            }
        });
        initializeHandle(scene.circleHandle);
        
        scene.splineHandle.onEndInteractionEvent(() => {
            const points = scene.splineHandle.getPoints();
            painter.paintPolygon(points);
        
            scene.splineHandle.updateRepresentationForRender();
        });
        initializeHandle(scene.splineHandle);
        
        scene.polygonHandle.onEndInteractionEvent(() => {
            const points = scene.polygonHandle.getPoints();
            painter.paintPolygon(points);
        
            scene.polygonHandle.updateRepresentationForRender();
        });
        initializeHandle(scene.polygonHandle);

        loadImage();
    };
    const loadImage = async () => {
        image.imageMapper = vtkImageMapper.newInstance();
        image.actor = vtkImageSlice.newInstance();
        const labelMap = {
            imageMapper: vtkImageMapper.newInstance(),
            actor: vtkImageSlice.newInstance(),
            cfun: vtkColorTransferFunction.newInstance(),
            ofun: vtkPiecewiseFunction.newInstance(),
        };
        // background image pipeline
        image.actor.setMapper(image.imageMapper);
        // labelmap pipeline
        labelMap.actor.setMapper(labelMap.imageMapper);
        labelMap.imageMapper.setInputConnection(painter.getOutputPort());
        
        // set up labelMap color and opacity mapping
        labelMap.cfun.addRGBPoint(1, 0, 0, 1); // label "1" will be blue
        labelMap.ofun.addPoint(0, 0); // our background value, 0, will be invisible
        labelMap.ofun.addPoint(1, 1); // all values above 1 will be fully opaque
        
        labelMap.actor.getProperty().setRGBTransferFunction(labelMap.cfun);
        labelMap.actor.getProperty().setPiecewiseFunction(labelMap.ofun);
        // opacity is applied to entire labelmap
        labelMap.actor.getProperty().setOpacity(0.5);
        
        // const reader = vtkHttpDataSetReader.newInstance({ fetchGzip: true });
        const reader = vtkXMLImageDataReader.newInstance();
        const response = await fetch('http://172.22.150.28:8000/img/LIDC2.vti');
        const arrayBuffer = await response.arrayBuffer();
        reader.parseAsArrayBuffer(arrayBuffer);

        const writer = vtkXMLImageDataWriter.newInstance();
        writer.setFormat(vtkXMLWriter.FormatTypes.BINARY);
        writer.setInputConnection(reader.getOutputPort());

        const writerReader = vtkXMLImageDataReader.newInstance();
        // reader.setUrl(`https://kitware.github.io/vtk-js/data/volume/LIDC2.vti`, { loadData: true }).then(() => {
            const data = reader.getOutputData();
            fileContents.current = writer.write(data);
            // Try to read it back.
            const textEncoder = new TextEncoder();
            writerReader.parseAsArrayBuffer(textEncoder.encode(fileContents.current));

            image.data = data;
            // set input data
            image.imageMapper.setInputData(data);
            // add actors to renderers
            scene.renderer.addViewProp(image.actor);
            scene.renderer.addViewProp(labelMap.actor);
            // update paint filter
            painter.setBackgroundImage(image.data);
            // don't set to 0, since that's our empty label color from our pwf
            painter.setLabel(1);
            // set custom threshold
            // painter.setVoxelFunc((bgValue, idx) => bgValue < 145);
            // default slice orientation/mode and camera view
            const sliceMode = vtkImageMapper.SlicingMode.K;
            image.imageMapper.setSlicingMode(sliceMode);
            image.imageMapper.setSlice(0);
            painter.setSlicingMode(sliceMode);
            // set 2D camera position
            setCamera(sliceMode, scene.renderer, image.data);
            updateControlPanel(image.imageMapper, data);
            // set text display callback
            scene.circleHandle.onInteractionEvent(() => {
                const worldBounds = scene.circleHandle.getBounds();
                const text = `radius: ${(
                    vec3.distance(
                    [worldBounds[0], worldBounds[2], worldBounds[4]],
                    [worldBounds[1], worldBounds[3], worldBounds[5]]
                    ) / 2
                ).toFixed(2)}`;
                widgets.circleWidget.getWidgetState().getText().setText(text);
            });

            scene.splineHandle.setHandleSizeInPixels(2 * Math.max(...image.data.getSpacing()));
            scene.splineHandle.setFreehandMinDistance(4 * Math.max(...image.data.getSpacing()));
            scene.polygonHandle.setHandleSizeInPixels(2 * Math.max(...image.data.getSpacing()));
            scene.polygonHandle.setFreehandMinDistance(4 * Math.max(...image.data.getSpacing()));

            const update = () => {
                const slicingMode = image.imageMapper.getSlicingMode() % 3;

                if (slicingMode > -1) {
                    const ijk = [0, 0, 0];
                    const position = [0, 0, 0];
                    // position
                    ijk[slicingMode] = image.imageMapper.getSlice();
                    data.indexToWorld(ijk, position);

                    widgets.paintWidget.getManipulator().setUserOrigin(position);
                    widgets.rectangleWidget.getManipulator().setUserOrigin(position);
                    widgets.ellipseWidget.getManipulator().setUserOrigin(position);
                    widgets.circleWidget.getManipulator().setUserOrigin(position);
                    widgets.splineWidget.getManipulator().setUserOrigin(position);
                    widgets.polygonWidget.getManipulator().setUserOrigin(position);

                    painter.setSlicingMode(slicingMode);

                    scene.paintHandle.updateRepresentationForRender();
                    scene.rectangleHandle.updateRepresentationForRender();
                    scene.ellipseHandle.updateRepresentationForRender();
                    scene.circleHandle.updateRepresentationForRender();
                    scene.splineHandle.updateRepresentationForRender();
                    scene.polygonHandle.updateRepresentationForRender();

                    // update labelMap layer
                    labelMap.imageMapper.set(image.imageMapper.get('slice', 'slicingMode'));

                    // update UI
                    setSliceMax(data.getDimensions()[slicingMode] - 1);
                }
            };
            image.imageMapper.onModified(update);

            // download
            const blob = new Blob([fileContents.current], { type: 'text/plain' });
            const a = window.document.createElement('a');
            a.href = window.URL.createObjectURL(blob, { type: 'text/plain' });
            a.download = 'LIDC2.vti';
            a.text = 'Download';
            a.style.position = 'absolute';
            a.style.left = '50%';
            a.style.bottom = '10px';
            document.body.appendChild(a);
            a.style.background = 'white';
            a.style.padding = '5px';

            // trigger initial update
            update();
            readyAll();
        // });
    };
    const readyHandle = (scope, picking = false) => {
        scope.renderer.resetCamera();
        scope.fullScreenRenderer.resize();
        if (picking) {
            scope.widgetManager.enablePicking();
        } else {
            scope.widgetManager.disablePicking();
        }
    };
    const readyAll = () => {
        readyHandle(scene, true);
    };
    // UI logic
    const radiuHandle = (value: number) => {
        widgets.paintWidget.setRadius(value);
        painter.setRadius(value);
        setRadius(value);
    };
    const sliceHandle = (value: number) => {
        image.imageMapper.setSlice(value);
        setSlice(value);
    };
    const axisHandle = (value: string) => {
        const sliceMode = 'IJKXYZ'.indexOf(value) % 3;
        image.imageMapper.setSlicingMode(sliceMode);
        painter.setSlicingMode(sliceMode);

        const direction = [0, 0, 0];
        direction[sliceMode] = 1;
        scene.paintHandle.getWidgetState().getHandle().setDirection(direction);

        setCamera(sliceMode, scene.renderer, image.data);
        scene.renderWindow.render();
        setAxis(value);
    };
    const widgetHandle = (value: string) => {
        activeWidget = value;
        scene.widgetManager.grabFocus(widgets[activeWidget]);

        scene.paintHandle.setVisibility(activeWidget === 'paintWidget');
        scene.paintHandle.updateRepresentationForRender();

        scene.splineHandle.reset();
        scene.splineHandle.setVisibility(activeWidget === 'splineWidget');
        scene.splineHandle.updateRepresentationForRender();

        scene.polygonHandle.reset();
        scene.polygonHandle.setVisibility(activeWidget === 'polygonWidget');
        scene.polygonHandle.updateRepresentationForRender();

        setWidget(value);
    };
    const focusHandle = () => {
        scene.widgetManager.grabFocus(widgets[activeWidget]);
    };
    const undoHandle = () => {
        painter.undo();
    };
    const redoHandle = () => {
        painter.redo();
    };
    const saveHandle = () => {
        console.log(scene);
        // https://github.com/Kitware/vtk-js/issues/812
        // const stateToSave = {
        //     actors: scene.renderer.getActors().map(a => a.getState()),
        //     camera: scene.renderer.getActiveCamera().getState()
        // };
        // console.log(stateToSave);
        // console.log(scene.renderer.getState());
        // const coneSource = vtkConeSource.newInstance();
        // const source = coneSource.getOutputData().getPointData().getScalars().getData();
        // console.log(source);
        const blob = new Blob([fileContents.current], {type: 'text/plain'});
        const a = window.document.createElement('a');
        a.href = window.URL.createObjectURL(blob, {type: 'text/plain'});
        a.download = 'LIDC2.vti';
        document.body.appendChild(a);
        a.click(); 
        window.URL.revokeObjectURL(a.href);
        document.body.removeChild(a); 
        // const serializedRuler = {
        //     points: widgetState.getHandles().map((handleState) => handleState.getOrigin()),
        //     color: '#ffee00',
        // }
    };
    useEffect(() => {
        renderScene();
        // loadImage();
        // readyAll();
        window.addEventListener('resize', readyAll);
    }, []);
    return (
        <div>
            <div style={{position: 'absolute', top: 0, left: 0, zIndex: 1, background: '#fff'}}>
                <span>Radius Scale</span>
                <Slider value={radius} step={1} min={1} max={200} onChange={radiuHandle} style={{width: '100px', display: 'inline-block'}}/>
                <span>Slice</span>
                <Slider value={slice} step={1} min={sliceMin} max={sliceMax} onChange={sliceHandle} style={{width: '100px', display: 'inline-block'}}/>
                <span>Slice Axis</span>
                <Select value={axis} onChange={axisHandle}>
                    <Option value="I">I</Option>
                    <Option value="J">J</Option>
                    <Option value="K">K</Option>
                </Select>
                <Select value={widget} onChange={widgetHandle}>
                    <Option value="paintWidget">paintWidget</Option>
                    <Option value="rectangleWidget">rectangleWidget</Option>
                    <Option value="ellipseWidget">ellipseWidget</Option>
                    <Option value="circleWidget">circleWidget</Option>
                    <Option value="splineWidget">splineWidget</Option>
                    <Option value="polygonWidget">polygonWidget</Option>
                </Select>
                <Button onClick={focusHandle}>Grab focus</Button>
                <Button onClick={undoHandle}>Undo</Button>
                <Button onClick={redoHandle}>Redo</Button>
                {/* <Button onClick={saveHandle}>Save</Button> */}
            </div>
            <div id="rootContainer"></div>
        </div>
    );
}