ETC

Simple Volume Rendering App - React

leonhong 2021. 12. 11. 19:27

개요

 vtk.js와 bootstrap을 이용하여, 간단히 볼륨템플릿을 바꿔보는 프로그램을 만들어 보겠습니다.

 먼저, 준비사항으로써 본 프로젝트에서 사용할 첨부 파일을 public/leonhong.vti 에 놓습니다.

  ps. pptx 파일은 개인정보가 있어서 첨부하지 않으므로 임으로 pptx 파일을 만들어서 사용하세요.

leonhong.vti
9.82MB

 

파일위치

 

소스코드는 아래와 같습니다. 

import pptFile from './leonhong.pptx'
import 'bootstrap/dist/css/bootstrap.min.css';
import { Container, Button, Row, Col, Card, Navbar } from 'react-bootstrap';
import { useRef, useEffect } from 'react';
 
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
import '@kitware/vtk.js/Rendering/Profiles/Volume';
 
// Force DataAccessHelper to have access to various data source
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HtmlDataAccessHelper';
import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper';
import '@kitware/vtk.js/IO/Core/DataAccessHelper/JSZipDataAccessHelper';
 
import vtkColorTransferFunction     from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction';
import vtkPiecewiseFunction         from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction';
import vtkVolume                    from '@kitware/vtk.js/Rendering/Core/Volume';
import vtkVolumeMapper              from '@kitware/vtk.js/Rendering/Core/VolumeMapper';
import vtkRenderer                  from '@kitware/vtk.js/Rendering/Core/Renderer';
import vtkRenderWindow              from '@kitware/vtk.js/Rendering/Core/RenderWindow';
import vtkOpenGLRenderWindow        from '@kitware/vtk.js/Rendering/OpenGL/RenderWindow';
import xmlImageDataReader           from '@kitware/vtk.js/IO/XML/XMLImageDataReader';
import vtkRenderWindowInteractor    from '@kitware/vtk.js/Rendering/Core/RenderWindowInteractor';
import vtkInteractorStyleTrackballCamera from '@kitware/vtk.js/Interaction/Style/InteractorStyleTrackballCamera';
 
import robot from './logo.svg'
 
function App() {
  const vtkContainerRef = useRef(null);
  const context = useRef(null);
 
  useEffect(() => {
 
    if (!context.current) {
     
      const actor = vtkVolume.newInstance();
      const mapper = vtkVolumeMapper.newInstance();     
      const renderWindow = vtkRenderWindow.newInstance();
      const renderer = vtkRenderer.newInstance({ background: [0.0, 0.0, 0.0] });
      const openglRenderWindow = vtkOpenGLRenderWindow.newInstance();     
      const interactor = vtkRenderWindowInteractor.newInstance();
      const interStyle = vtkInteractorStyleTrackballCamera.newInstance();     
      const xmlReader = xmlImageDataReader.newInstance();
      const ctfun = vtkColorTransferFunction.newInstance();
      const ofun = vtkPiecewiseFunction.newInstance();

      renderWindow.addRenderer(renderer);      
      renderWindow.addView(openglRenderWindow);
 
      vtkContainerRef.current.style.height = '380px';
      openglRenderWindow.setContainer(vtkContainerRef.current);
      openglRenderWindow.setSize(300, 300);
 
      interactor.setView(openglRenderWindow);
      interactor.initialize();
      interactor.bindEvents(vtkContainerRef.current);
      interactor.setInteractorStyle(interStyle);
 
      mapper.setSampleDistance(0.7);
      actor.setMapper(mapper);
 
      // create color and opacity transfer functions   
      ctfun.removeAllPoints();   
      ctfun.addRGBPoint(-3000.0, 0.0, 0.0, 0.0);
      ctfun.addRGBPoint(-1024.0, 0.0, 0.0, 0.0);
      ctfun.addRGBPoint(1016,120,0,0);
      ctfun.addRGBPoint(1603,1,0.56,0.34);
      ctfun.addRGBPoint(2776,1,1,1);            
      ctfun.addRGBPoint(5297,1,1,1);      
      ofun.removeAllPoints();
      ofun.addPoint(-1024,0.000000);
      ofun.addPoint(674,0.000000);
      ofun.addPoint(1372,0.053731);
      ofun.addPoint(3023,0.180000);
      ofun.addPoint(5274,0.343284);

      actor.getProperty().setRGBTransferFunction(0, ctfun);
      actor.getProperty().setScalarOpacity(0, ofun);
      //actor.getProperty().setScalarOpacityUnitDistance(0, 4.5);
      actor.getProperty().setScalarOpacityUnitDistance(0, 1.5);
      actor.getProperty().setInterpolationTypeToLinear();
      actor.getProperty().setUseGradientOpacity(0, true);
      actor.getProperty().setGradientOpacityMinimumValue(0, 15);
      actor.getProperty().setGradientOpacityMinimumOpacity(0, 0.0);
      actor.getProperty().setGradientOpacityMaximumValue(0, 100);
      actor.getProperty().setGradientOpacityMaximumOpacity(0, 1.0);
      actor.getProperty().setShade(false);
      actor.getProperty().setAmbient(0.2);
      actor.getProperty().setDiffuse(0.7);
      actor.getProperty().setSpecular(0.3);
      actor.getProperty().setSpecularPower(8.0);
 
      mapper.setInputConnection(xmlReader.getOutputPort());
 
      xmlReader.setUrl(process.env.PUBLIC_URL+'/leonhong.vti').then(() => {
        xmlReader.loadData().then(() =>{
          renderer.addVolume(actor);
          renderer.resetCamera();
          renderer.getActiveCamera().zoom(1.5);
          renderer.getActiveCamera().elevation(-90);
          renderer.updateLightsGeometryToFollowCamera();
          renderWindow.render();
        });
      });

      context.current = {
        actor,
        mapper,
        renderWindow,
        renderer,
        openglRenderWindow,
        interactor,
        interStyle,
        xmlReader,
        ctfun,
        ofun
      };  

    }

  }, [vtkContainerRef]);
 
  function onClickBone(){
    
    const ctfun = context.current.ctfun;
    ctfun.removeAllPoints();   
    ctfun.addRGBPoint(-3000.0, 0.0, 0.0, 0.0);
    ctfun.addRGBPoint(-1024.0, 0.0, 0.0, 0.0);
    ctfun.addRGBPoint(1007,0.51,0.09,0.07);
    ctfun.addRGBPoint(1181,0.99,0.93,0.76);
    ctfun.addRGBPoint(1350,1,1,1);            
    ctfun.addRGBPoint(2678,1,1,1);      
    const ofun = context.current.ofun;
    ofun.removeAllPoints();
    ofun.addPoint(-1024,0.000000);
    ofun.addPoint(-979,0.000000);
    ofun.addPoint(990,0.000000);
    ofun.addPoint(1106,0.303731);
    ofun.addPoint(1324,1.000000);
    ofun.addPoint(3070,1.000000);
    context.current.actor.getProperty().setShade(true);
    context.current.renderWindow.render();
  }

  function onClickTBone(){    
    const ctfun = context.current.ctfun;
    ctfun.removeAllPoints();   
    ctfun.addRGBPoint(-3000.0, 0.0, 0.0, 0.0);
    ctfun.addRGBPoint(-1024.0, 0.0, 0.0, 0.0);
    ctfun.addRGBPoint(1016,120,0,0);
    ctfun.addRGBPoint(1603,1,0.56,0.34);
    ctfun.addRGBPoint(2776,1,1,1);            
    ctfun.addRGBPoint(5297,1,1,1);      
    const ofun = context.current.ofun;
    ofun.removeAllPoints();
    ofun.addPoint(-1024,0.000000);
    ofun.addPoint(674,0.000000);
    ofun.addPoint(1372,0.053731);
    ofun.addPoint(3023,0.180000);
    ofun.addPoint(5274,0.343284);
    context.current.actor.getProperty().setShade(false);
    context.current.renderWindow.render();
  }

  function onClickSTissue(){
    
    const ctfun = context.current.ctfun;
    ctfun.removeAllPoints();   
    ctfun.addRGBPoint(-3000.0, 0.0, 0.0, 0.0);
    ctfun.addRGBPoint(-1024.0, 0.0, 0.0, 0.0);
    ctfun.addRGBPoint(-661,0,0,0);
    ctfun.addRGBPoint(-627,1,1,1);            
    ctfun.addRGBPoint(2678,1,1,1);      
    const ofun = context.current.ofun;
    ofun.removeAllPoints();
    ofun.addPoint(-1024,0.000000);
    ofun.addPoint(-552,0.000000);
    ofun.addPoint(-461,0.703731);
    ofun.addPoint(-324,1.000000);
    ofun.addPoint(2678,1.000000);
    context.current.actor.getProperty().setShade(true);
    context.current.renderWindow.render();
  }  

  return (
    
    <div className="App d-grid gap-2">
 
    <Navbar bg="primary" variant="dark">
      <Container>
        <Navbar.Brand href="#home">ON3D Cloud Service</Navbar.Brand>
        <Navbar.Toggle />
        <Navbar.Collapse className="justify-content-end">
          <Navbar.Text>
            Signed in as: <a href="#login">leonhong</a>
          </Navbar.Text>
        </Navbar.Collapse>
      </Container>
    </Navbar>
    
    <div/>

    <Container>
      <Row>
        <Col><Button variant="outline-secondary" onClick={onClickBone} > Bone Mode </Button></Col>
        <Col><Button variant="outline-success" onClick={onClickTBone} > Trans. Bone   </Button></Col>
        <Col><Button variant="outline-danger" onClick={onClickSTissue} > Soft Tissue </Button></Col>
      </Row>
    </Container>    

    <div/>

    <div className="mainView" ref={vtkContainerRef}></div>    
    
    <br/>

    <Card>
      <Card.Header as="h5">Report</Card.Header>
      <Card.Body>
        <Card.Title>Diagnosis Information Summary</Card.Title>
        <Card.Text>
          It seems good that the mandible position moves backward, and tooth correction is needed.
        </Card.Text>
        <a href={pptFile} download="leonhong.pptx" target='_blank'>
          <Button variant="primary">Download Detail Report</Button>
        </a>
      </Card.Body>
    </Card>

  </div>
    
  );
}
 

export default App;

 

모바일 웹브라우저로 시연한 영상은 다음과 같습니다.

https://www.youtube.com/watch?v=zPFJaeQmsgA

 

 

'ETC' 카테고리의 다른 글

수학 관련 용어  (0) 2023.04.26
vtkRenderWindow를 div에 연결하기 - Vue  (0) 2022.05.24
BCI 관련 자료들  (0) 2021.12.20
Volume Rendering - React  (0) 2021.12.05
React를 이용한 VTK.js 개발하기  (0) 2021.12.05