# @lunit/insight-viewer2

> dcm viewer component library

Latest version **0.2.5** (published 2019-08-26) · MIT license · 0 weekly downloads

> **Deprecated.** This package is deprecated.

## Install

```sh
npm install @lunit/insight-viewer2
pnpm add @lunit/insight-viewer2
yarn add @lunit/insight-viewer2
bun add @lunit/insight-viewer2
```

## Health

**Score 10/100 (F)** — status: deprecated.

Negative: deprecated.

## Facts

| | |
|---|---|
| Version | 0.2.5 |
| Published | 2019-08-26 |
| First published | 2019-08-10 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 12 |
| Unpacked size | 2.4 MB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Seoyeon Lee |
| Maintainers | lunit |

## Links

- npm: https://www.npmjs.com/package/@lunit/insight-viewer2
- Repository: https://github.com/lunit-io/opt-tool-frontend
- Homepage: https://github.com/lunit-io/opt-tool-frontend#readme
- Issues: https://github.com/lunit-io/opt-tool-frontend/issues
- npm.io page: https://npm.io/package/@lunit/insight-viewer2

## Dependencies (12)

- [rxjs](https://npm.io/package/rxjs.md) ^6.5.2
- [react](https://npm.io/package/react.md) >=16.8.0
- [polylabel](https://npm.io/package/polylabel.md) ^1.0.2
- [dicom-parser](https://npm.io/package/dicom-parser.md) ^1.8.3
- [@lunit/heatmap](https://npm.io/package/@lunit/heatmap.md) ^0.2.2
- [cornerstone-core](https://npm.io/package/cornerstone-core.md) ^2.3.0
- [point-in-polygon](https://npm.io/package/point-in-polygon.md) ^1.0.1
- [@material-ui/core](https://npm.io/package/@material-ui/core.md) ^4.3.1
- [styled-components](https://npm.io/package/styled-components.md) ^4.3.2
- [@lunit/is-complex-polygon](https://npm.io/package/@lunit/is-complex-polygon.md) ^0.1.0
- [cornerstone-wado-image-loader](https://npm.io/package/cornerstone-wado-image-loader.md) ^2.2.3
- [@lunit/is-polygon-area-greater-than-area](https://npm.io/package/@lunit/is-polygon-area-greater-than-area.md) ^0.1.0

## Recent versions

- 0.2.5 (latest) — 2019-08-26
- 0.2.4 — 2019-08-26
- 0.2.3 — 2019-08-26
- 0.2.2 — 2019-08-26
- 0.2.1 — 2019-08-26
- 0.2.0 — 2019-08-26
- 0.1.1 — 2019-08-10

## README

cornerstone 등 OPT 기능을 구현하기 위한 여러 graphics layer를 구현한다.

# Install

```sh
npm install @lunit/insight-viewer2
```

```json
{
  "scripts": {
    "app:build": "zeroconfig-webapp-scripts build app --static-file-packages @lunit/insight-viewer2",
    "app:start": "zeroconfig-webapp-scripts start app --static-file-packages @lunit/insight-viewer2" 
  }
}
```

Package 내부에 `public/cornerstoneWADOImageLoaderCodecs.min.js` 파일과 `public/cornerstoneWADOImageLoaderWebWorker.min.js` 파일을 가지고 있고, 이를 App 빌드에 적용하기 위해서는 위와 같이 `--static-file-packages` 옵션을 추가해줘야 한다. 

# Sample Codes

[Storybook](https://lunit-io.github.io/opt-tool-frontend)

## Stories

<!-- import **/*.stories.{ts,tsx} --title-tag h3 -->
<h3>\_\_stories\_\_/InsightViewer.stories.tsx</h3>
```tsx
import { withOptTheme } from '@lunit/opt-theme';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { InsightViewer } from '../components/InsightViewer';
import { useInsightViewerSync } from '../hooks/useInsightViewerSync';
import { CornerstoneSingleImage } from '../image/CornerstoneSingleImage';
import { CornerstoneImage } from '../image/types';
import { installWADOImageLoader, unloadWADOImage } from '../installWADO';
import { useController, withTestController } from './decorators/withTestController';

// cornerstoneWADOImageLoader 초기화
installWADOImageLoader();

// <InsightViewer resetTime={}>을 변경하면 Viewport 등 cornerstone-core 관련 속성들이 초기화 된다
const resetTime: number = Date.now();

// unload 옵션은 위에 선언된 installWADOImageLoader()와 함께 동작한다
// CornerstoneImage 객체를 unload 할때 wado image loader의 unload 동작을 하게 된다
const image: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000010.dcm`, {unload: unloadWADOImage});

function Sample() {
  // addDecorator(withTestController())의 값을 받는다
  const {
    width,
    height,
    control,
    wheel,
    invert,
    flip,
  } = useController();
  
  const {
    updateCornerstoneRenderData,
  } = useInsightViewerSync();
  
  return (
    <InsightViewer width={width}
                   height={height}
                   invert={invert}
                   flip={flip}
                   pan={control === 'pan'}
                   adjust={control === 'adjust'}
                   zoom={wheel === 'zoom'}
                   resetTime={resetTime}
                   image={image}
                   updateCornerstoneRenderData={updateCornerstoneRenderData}/>
  );
}

storiesOf('insight-viewer2', module)
  .addDecorator(withOptTheme)
  .addDecorator(withTestController({
    width: [600, 400, 1000],
    height: [700, 400, 1000],
    control: ['pan', ['none', 'pan', 'adjust']],
    wheel: ['zoom', ['none', 'zoom']],
    flip: false,
    invert: false,
  }))
  .add('<InsightViewer>', () => <Sample/>);
```
<h3>\_\_stories\_\_/MachineHeatmapViewer.stories.tsx</h3>
```tsx
import { MachineHeatmapViewer } from '@lunit/insight-viewer2';
import { withOptTheme } from '@lunit/opt-theme';
import { storiesOf } from '@storybook/react';
import React, { useState } from 'react';
import { InsightViewer } from '../components/InsightViewer';
import { InsightViewerContainer } from '../components/InsightViewerContainer';
import { ProgressViewer } from '../components/ProgressViewer';
import { UserContourDrawer } from '../components/UserContourDrawer';
import { UserContourViewer } from '../components/UserContourViewer';
import { useInsightViewerSync } from '../hooks/useInsightViewerSync';
import { useUserContour } from '../hooks/useUserContour';
import { CornerstoneSingleImage } from '../image/CornerstoneSingleImage';
import { CornerstoneImage } from '../image/types';
import { installWADOImageLoader, unloadWADOImage } from '../installWADO';
import { useController, withTestController } from './decorators/withTestController';
import data from './posMap.sample.json';

const {engine_result: {engine_result: {pos_map: posMap}}} = data;

installWADOImageLoader();

const resetTime: number = Date.now();
const image: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000010.dcm`, {unload: unloadWADOImage});

function Sample() {
  const {
    width,
    height,
    control,
    wheel,
    invert,
    flip,
  } = useController();
  
  const [interactionElement, setInteractionElement] = useState<HTMLElement | null>(null);
  
  const {
    cornerstoneRenderData,
    updateCornerstoneRenderData,
  } = useInsightViewerSync();
  
  const {
    contours,
    focusedContour,
    addContour,
    removeContour,
    focusContour,
  } = useUserContour();
  
  return (
    <InsightViewerContainer ref={setInteractionElement} width={width} height={height}>
      <InsightViewer width={width}
                     height={height}
                     invert={invert}
                     flip={flip}
                     pan={control === 'pan' && interactionElement}
                     adjust={control === 'adjust' && interactionElement}
                     zoom={wheel === 'zoom' && interactionElement}
                     resetTime={resetTime}
                     image={image}
                     updateCornerstoneRenderData={updateCornerstoneRenderData}/>
      {/*
      engineResult.posMap을 그리는 Layer
      현재 Heatmap Spec (number[][])에 맞춰서 개발되었기 때문에
      Spec이 다르다면 새로운 Viewer를 만들어야 한다
      */}
      <MachineHeatmapViewer width={width}
                            height={height}
                            posMap={posMap}
                            threshold={0.1}
                            cornerstoneRenderData={cornerstoneRenderData}/>
      {
        contours &&
        contours.length > 0 &&
        cornerstoneRenderData &&
        <UserContourViewer width={width}
                           height={height}
                           contours={contours}
                           focusedContour={focusedContour}
                           cornerstoneRenderData={cornerstoneRenderData}/>
      }
      {
        contours &&
        cornerstoneRenderData &&
        control === 'pen' &&
        <UserContourDrawer width={width}
                           height={height}
                           contours={contours}
                           draw={control === 'pen' && interactionElement}
                           onFocus={focusContour}
                           onAdd={contour => addContour(contour, 0)}
                           onRemove={removeContour}
                           cornerstoneRenderData={cornerstoneRenderData}/>
      }
      <ProgressViewer image={image}
                      width={width}
                      height={height}/>
    </InsightViewerContainer>
  );
}

storiesOf('insight-viewer2', module)
  .addDecorator(withOptTheme)
  .addDecorator(withTestController({
    width: [600, 400, 1000],
    height: [700, 400, 1000],
    control: ['pan', ['none', 'pen', 'pan', 'adjust']],
    wheel: ['zoom', ['none', 'zoom']],
    flip: false,
    invert: false,
  }))
  .add('<MachineHeatmapViewer>', () => <Sample/>);
```
<h3>\_\_stories\_\_/ProgressCollector.stories.tsx</h3>
```tsx
import { withOptTheme } from '@lunit/opt-theme';
import { storiesOf } from '@storybook/react';
import React, { CSSProperties } from 'react';
import { InsightViewer } from '../components/InsightViewer';
import { InsightViewerContainer } from '../components/InsightViewerContainer';
import {
  ProgressCollector,
  ProgressViewer,
  useContainerStyleOfProgressViewersInactivity,
  useProgressViewersActivity,
} from '../components/ProgressViewer';
import { useInsightViewerSync } from '../hooks/useInsightViewerSync';
import { CornerstoneSeriesImage } from '../image/CornerstoneSeriesImage';
import { CornerstoneSingleImage } from '../image/CornerstoneSingleImage';
import { CornerstoneImage } from '../image/types';
import { installWADOImageLoader, unloadWADOImage } from '../installWADO';
import { useController, withTestController } from './decorators/withTestController';
import series from './series.json';

installWADOImageLoader();

const resetTime: number = Date.now();
const image1: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000010.dcm`, {unload: unloadWADOImage});
const image2: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000020.dcm`, {unload: unloadWADOImage});
const image3: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000030.dcm`, {unload: unloadWADOImage});
const image4: CornerstoneImage = new CornerstoneSeriesImage(series.map(p => `wadouri:samples/series/${p}`), {unload: unloadWADOImage});

function Sample() {
  // <ProgressCollector>는 하위의 <ProgressViewer>들의 상태를 수집한다
  return (
    <ProgressCollector>
      <Container/>
    </ProgressCollector>
  );
}

function Container() {
  // <ProgressCollector>에서 수집한 정보를 얻을 수 있다
  const progressActivity: boolean = useProgressViewersActivity();
  // 혹은 <ProgressViewer>가 동작중일때 비활성을 처리할 Style을 만들 수 있다
  const containerDisabledStyle: CSSProperties = useContainerStyleOfProgressViewersInactivity({pointerEvents: 'none'});
  
  return (
    <div style={containerDisabledStyle}>
      <div style={{display: 'flex'}}>
        <Component image={image1}/>
        <Component image={image2}/>
        <Component image={image3}/>
        <Component image={image4}/>
      </div>
      <p>
        {
          progressActivity
            ? '<ProgressViewer>가 하나 이상 작동 중입니다!!!'
            : '동작중인 <ProgressViewer>가 없습니다!!!'
        }
      </p>
    </div>
  );
}

function Component({image}: {image: CornerstoneImage}) {
  const {
    width,
    height,
    control,
    wheel,
    invert,
    flip,
  } = useController();
  
  const {updateCornerstoneRenderData} = useInsightViewerSync();
  
  return (
    <InsightViewerContainer width={width} height={height}>
      <InsightViewer width={width}
                     height={height}
                     invert={invert}
                     flip={flip}
                     pan={control === 'pan'}
                     adjust={control === 'adjust'}
                     zoom={wheel === 'zoom'}
                     resetTime={resetTime}
                     image={image}
                     updateCornerstoneRenderData={updateCornerstoneRenderData}/>
      {/* 이 <ProgressViewer>가 <ProgressCollector>에 수집된다 */}
      <ProgressViewer image={image}
                      width={width}
                      height={height}/>
    </InsightViewerContainer>
  );
}

storiesOf('insight-viewer2', module)
  .addDecorator(withOptTheme)
  .addDecorator(withTestController({
    width: [300, 200, 500],
    height: [400, 300, 600],
    control: ['pan', ['none', 'pen', 'pan', 'adjust']],
    wheel: ['zoom', ['none', 'zoom']],
    flip: false,
    invert: false,
  }))
  .add('<ProgressCollector>', () => <Sample/>);
```
<h3>\_\_stories\_\_/SeriesImage.stories.tsx</h3>
```tsx
import { UserContourDrawer, UserContourViewer, useUserContour } from '@lunit/insight-viewer2';
import { withOptTheme } from '@lunit/opt-theme';
import { storiesOf } from '@storybook/react';
import React, { useState } from 'react';
import { InsightViewer } from '../components/InsightViewer';
import { InsightViewerContainer } from '../components/InsightViewerContainer';
import { ProgressViewer } from '../components/ProgressViewer';
import { useBulkImageScroll } from '../hooks/useBulkImageScroll';
import { useInsightViewerSync } from '../hooks/useInsightViewerSync';
import { CornerstoneSeriesImage } from '../image/CornerstoneSeriesImage';
import { CornerstoneBulkImage } from '../image/types';
import { installWADOImageLoader, unloadWADOImage } from '../installWADO';
import { withSeriesImageController } from './decorators/withSeriesImageController';
import { useController, withTestController } from './decorators/withTestController';
import series from './series.json';

installWADOImageLoader();

const resetTime: number = Date.now();

// CornerstoneSeriesImage는 여러장의 dcm 이미지를 받는다
const image: CornerstoneBulkImage = new CornerstoneSeriesImage(series.map(p => `wadouri:samples/series/${p}`), {unload: unloadWADOImage});

function Component() {
  const {
    width,
    height,
    control,
    wheel,
    invert,
    flip,
  } = useController();
  
  const [interactionElement, setInteractionElement] = useState<HTMLElement | null>(null);
  
  const {
    cornerstoneRenderData,
    updateCornerstoneRenderData,
  } = useInsightViewerSync();
  
  const {
    contours,
    focusedContour,
    addContour,
    removeContour,
    focusContour,
  } = useUserContour();
  
  // CornerstoneBulkImage를 Wheel과 연결 시킨다
  useBulkImageScroll({
    image,
    element: interactionElement,
    enabled: wheel === 'scroll',
  });
  
  return (
    <InsightViewerContainer ref={setInteractionElement} width={width} height={height}>
      <InsightViewer width={width}
                     height={height}
                     invert={invert}
                     flip={flip}
                     pan={control === 'pan' && interactionElement}
                     adjust={control === 'adjust' && interactionElement}
                     zoom={wheel === 'zoom' && interactionElement}
                     resetTime={resetTime}
                     image={image}
                     updateCornerstoneRenderData={updateCornerstoneRenderData}/>
      {
        contours &&
        contours.length > 0 &&
        cornerstoneRenderData &&
        <UserContourViewer width={width}
                           height={height}
                           contours={contours}
                           focusedContour={focusedContour}
                           cornerstoneRenderData={cornerstoneRenderData}/>
      }
      {
        contours &&
        cornerstoneRenderData &&
        control === 'pen' &&
        <UserContourDrawer width={width}
                           height={height}
                           contours={contours}
                           draw={control === 'pen' && interactionElement}
                           onFocus={focusContour}
                           onAdd={contour => addContour(contour, 0)}
                           onRemove={removeContour}
                           cornerstoneRenderData={cornerstoneRenderData}/>
      }
      <ProgressViewer image={image}
                      width={width}
                      height={height}/>
    </InsightViewerContainer>
  );
}

storiesOf('insight-viewer2', module)
  .addDecorator(withOptTheme)
  .addDecorator(withTestController({
    width: [600, 400, 1000],
    height: [700, 400, 1000],
    control: ['pan', ['none', 'pen', 'pan', 'adjust']],
    wheel: ['scroll', ['none', 'zoom', 'scroll']],
    flip: false,
    invert: false,
  }))
  .addDecorator(withSeriesImageController(image))
  .add('Series Image', () => <Component/>);
```
<h3>\_\_stories\_\_/UserContourViewer.stories.tsx</h3>
```tsx
import { withOptTheme } from '@lunit/opt-theme';
import { storiesOf } from '@storybook/react';
import React, { useState } from 'react';
import { InsightViewer } from '../components/InsightViewer';
import { InsightViewerContainer } from '../components/InsightViewerContainer';
import { ProgressViewer } from '../components/ProgressViewer';
import { UserContourDrawer } from '../components/UserContourDrawer';
import { UserContourViewer } from '../components/UserContourViewer';
import { useInsightViewerSync } from '../hooks/useInsightViewerSync';
import { useUserContour } from '../hooks/useUserContour';
import { CornerstoneSingleImage } from '../image/CornerstoneSingleImage';
import { CornerstoneImage } from '../image/types';
import { installWADOImageLoader, unloadWADOImage } from '../installWADO';
import { useController, withTestController } from './decorators/withTestController';

installWADOImageLoader();

const resetTime: number = Date.now();
const image: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000010.dcm`, {unload: unloadWADOImage});

function Sample() {
  const {
    width,
    height,
    control,
    wheel,
    invert,
    flip,
  } = useController();
  
  // pan, adjust, zoom은
  // pan={true}로 설정하면 내부 Element를 사용해서 MouseEvent를 처리하게 되고,
  // pan={HTMLElement}로 설정하면 해당 Element를 사용해서 MouseEvent를 처리하게 된다.
  // MouseEvent를 처리하는 Layer가 여러개 중첩될 때, 하위 Layer의 MouseEvent가 막히는 현상을 해결해준다.
  const [interactionElement, setInteractionElement] = useState<HTMLElement | null>(null);
  
  // <InsightViewer> 내부의 Canvas Render를 외부의 다른 Component들에 동기화 시키기 위한 Hook
  const {
    cornerstoneRenderData,
    updateCornerstoneRenderData,
  } = useInsightViewerSync();
  
  // Annotation (사용자 Contour)를 다루기 위한 Hook
  const {
    contours,
    focusedContour,
    addContour,
    removeContour,
    focusContour,
  } = useUserContour();
  
  return (
    <InsightViewerContainer ref={setInteractionElement} width={width} height={height}>
      <InsightViewer width={width}
                     height={height}
                     invert={invert}
                     flip={flip}
                     pan={control === 'pan' && interactionElement}
                     adjust={control === 'adjust' && interactionElement}
                     zoom={wheel === 'zoom' && interactionElement}
                     resetTime={resetTime}
                     image={image}
                     updateCornerstoneRenderData={updateCornerstoneRenderData}/>
      {
        // 사용자가 그린 Annotation을 보여준다
        // contours가 있는 경우에만 출력
        contours &&
        contours.length > 0 &&
        cornerstoneRenderData &&
        <UserContourViewer width={width}
                           height={height}
                           contours={contours}
                           focusedContour={focusedContour}
                           cornerstoneRenderData={cornerstoneRenderData}/>
      }
      {
        // Annotation을 그리고, 지우게 해준다
        // control === 'pen' 인 경우에만 출력
        contours &&
        cornerstoneRenderData &&
        control === 'pen' &&
        <UserContourDrawer width={width}
                           height={height}
                           contours={contours}
                           draw={control === 'pen' && interactionElement}
                           onFocus={focusContour}
                           onAdd={contour => addContour(contour, 0)}
                           onRemove={removeContour}
                           cornerstoneRenderData={cornerstoneRenderData}/>
      }
      <ProgressViewer image={image}
                      width={width}
                      height={height}/>
    </InsightViewerContainer>
  );
}

storiesOf('insight-viewer2', module)
  .addDecorator(withOptTheme)
  .addDecorator(withTestController({
    width: [600, 400, 1000],
    height: [700, 400, 1000],
    control: ['pan', ['none', 'pen', 'pan', 'adjust']],
    wheel: ['zoom', ['none', 'zoom']],
    flip: false,
    invert: false,
  }))
  .add('<UserContourViewer>', () => <Sample/>);
```
<h3>\_\_stories\_\_/UserContourViewerCanvasStyle.stories.tsx</h3>
```tsx
import { withOptTheme } from '@lunit/opt-theme';
import { storiesOf } from '@storybook/react';
import React, { useState } from 'react';
import { InsightViewer } from '../components/InsightViewer';
import { InsightViewerContainer } from '../components/InsightViewerContainer';
import { ProgressViewer } from '../components/ProgressViewer';
import { UserContourDrawer } from '../components/UserContourDrawer';
import { UserContourViewer } from '../components/UserContourViewer';
import { useInsightViewerSync } from '../hooks/useInsightViewerSync';
import { useUserContour } from '../hooks/useUserContour';
import { CornerstoneSingleImage } from '../image/CornerstoneSingleImage';
import { CornerstoneImage } from '../image/types';
import { installWADOImageLoader, unloadWADOImage } from '../installWADO';
import { useController, withTestController } from './decorators/withTestController';

installWADOImageLoader();

const resetTime: number = Date.now();
const image: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000010.dcm`, {unload: unloadWADOImage});

function Sample() {
  const {
    width,
    height,
    control,
    wheel,
    invert,
    flip,
  } = useController();
  
  const [interactionElement, setInteractionElement] = useState<HTMLElement | null>(null);
  
  const {
    cornerstoneRenderData,
    updateCornerstoneRenderData,
  } = useInsightViewerSync();
  
  const {
    contours,
    focusedContour,
    addContour,
    removeContour,
    focusContour,
  } = useUserContour();
  
  return (
    <InsightViewerContainer ref={setInteractionElement} width={width} height={height}>
      <InsightViewer width={width}
                     height={height}
                     invert={invert}
                     flip={flip}
                     pan={control === 'pan' && interactionElement}
                     adjust={control === 'adjust' && interactionElement}
                     zoom={wheel === 'zoom' && interactionElement}
                     resetTime={resetTime}
                     image={image}
                     updateCornerstoneRenderData={updateCornerstoneRenderData}/>
      {
        contours &&
        contours.length > 0 &&
        cornerstoneRenderData &&
        // Canvas Style을 변경할 수 있다
        <UserContourViewer width={width}
                           height={height}
                           contours={contours}
                           focusedContour={focusedContour}
                           cornerstoneRenderData={cornerstoneRenderData}
        
                           canvasStrokeLineWidth={10}
                           canvasStrokeStyle="blue"
                           canvasFillStyle="rgba(0, 0, 255, 0.3)"
                           canvasFontStyle="normal normal 600 40px proximanova"
                           canvasFocusedStrokeLineWidth={20}
                           canvasFocusedStrokeStyle="red"
                           canvasFocusedFillStyle="rgba(255, 0, 0, 0.3)"
                           canvasFocusedFontStyle="normal normal 600 50px proximanova"/>
      }
      {
        contours &&
        cornerstoneRenderData &&
        control === 'pen' &&
        // Canvas Style을 변경할 수 있다
        <UserContourDrawer width={width}
                           height={height}
                           contours={contours}
                           draw={control === 'pen' && interactionElement}
                           onFocus={focusContour}
                           onAdd={contour => addContour(contour, 0)}
                           onRemove={removeContour}
                           cornerstoneRenderData={cornerstoneRenderData}
        
                           canvasStokeLineWidght={8}
                           canvasStokeStyle="purple"
                           canvasFillStyle="rgba(255, 255, 255, 0.4)"/>
      }
      <ProgressViewer image={image}
                      width={width}
                      height={height}/>
    </InsightViewerContainer>
  );
}

storiesOf('insight-viewer2', module)
  .addDecorator(withOptTheme)
  .addDecorator(withTestController({
    width: [600, 400, 1000],
    height: [700, 400, 1000],
    control: ['pan', ['none', 'pen', 'pan', 'adjust']],
    wheel: ['zoom', ['none', 'zoom']],
    flip: false,
    invert: false,
  }))
  .add('<UserContourViewer canvasStokeStyle="{}">', () => <Sample/>);
```
<h3>\_\_stories\_\_/useResizeObserver.stories.tsx</h3>
```tsx
import { InsightViewerContainer } from '@lunit/insight-viewer2';
import { withOptTheme } from '@lunit/opt-theme';
import { storiesOf } from '@storybook/react';
import React from 'react';
import useResizeObserver from 'use-resize-observer';
import { InsightViewer } from '../components/InsightViewer';
import { useInsightViewerSync } from '../hooks/useInsightViewerSync';
import { CornerstoneSingleImage } from '../image/CornerstoneSingleImage';
import { CornerstoneImage } from '../image/types';
import { installWADOImageLoader, unloadWADOImage } from '../installWADO';

installWADOImageLoader();

const resetTime: number = Date.now();
const image: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000010.dcm`, {unload: unloadWADOImage});

function Sample() {
  const {
    updateCornerstoneRenderData,
  } = useInsightViewerSync();
  
  // 특정 Element의 width, height를 지속적으로 감지한다
  // flex 등 layout으로 처리된 <div> Element의 width, height를 useResizeObserver()로 받아서
  // <InsightViewer width={width} height={height}> 로 넘길 수 있다
  const [resizeRef, width, height] = useResizeObserver();
  
  console.log('useResizeObserver.stories.tsx..Sample()', width, height);
  
  return (
    <div ref={resizeRef} style={{width: '50vw', height: '80vh'}}>
      <InsightViewerContainer width={width} height={height}>
        <InsightViewer width={width}
                       height={height}
                       invert={false}
                       flip={false}
                       pan
                       adjust={false}
                       zoom={false}
                       resetTime={resetTime}
                       image={image}
                       updateCornerstoneRenderData={updateCornerstoneRenderData}/>
      </InsightViewerContainer>
    </div>
  );
}

storiesOf('insight-viewer2', module)
  .addDecorator(withOptTheme)
  .add('useResizeObserver', () => <Sample/>);
```
<h3>\_\_stories\_\_/useViewportMirroring.stories.tsx</h3>
```tsx
import {
  CornerstoneBulkImage,
  useInsightViewerSync,
  UserContourDrawer,
  UserContourViewer,
  useUserContour,
} from '@lunit/insight-viewer2';
import { useBulkImageScroll } from '@lunit/insight-viewer2/hooks/useBulkImageScroll';
import { useViewportMirroring } from '@lunit/insight-viewer2/hooks/useViewportMirroring';
import { withOptTheme } from '@lunit/opt-theme';
import { storiesOf } from '@storybook/react';
import React, { RefObject, useRef, useState } from 'react';
import { InsightViewer } from '../components/InsightViewer';
import { InsightViewerContainer } from '../components/InsightViewerContainer';
import { CornerstoneSeriesImage } from '../image/CornerstoneSeriesImage';
import { CornerstoneSingleImage } from '../image/CornerstoneSingleImage';
import { CornerstoneImage } from '../image/types';
import { installWADOImageLoader, unloadWADOImage } from '../installWADO';
import { useController, withTestController } from './decorators/withTestController';
import series from './series.json';

installWADOImageLoader();

const resetTime: number = Date.now();
const image1: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000010.dcm`, {unload: unloadWADOImage});
const image2: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000020.dcm`, {unload: unloadWADOImage});
const image3: CornerstoneImage = new CornerstoneSingleImage(`wadouri:samples/series/CT000030.dcm`, {unload: unloadWADOImage});
const image4: CornerstoneBulkImage = new CornerstoneSeriesImage(series.map(p => `wadouri:samples/series/${p}`), {unload: unloadWADOImage});

function Sample() {
  const {
    width,
    height,
    invert,
    flip,
  } = useController();
  
  // Viewport Mirroring의 대상에게 부여할 RefObject 들을 만든다
  const viewer2: RefObject<InsightViewer> = useRef(null);
  const viewer3: RefObject<InsightViewer> = useRef(null);
  const viewer4: RefObject<InsightViewer> = useRef(null);
  
  // updateMasterRenderData()가 실행되면 viewer2, viewer3, viewer4에 Viewport가 Mirroring 된다
  const {updateMasterRenderData} = useViewportMirroring(viewer2, viewer3, viewer4);
  
  // 3번째 화면을 위한 처리
  const [interactionElement3, setInteractionElement3] = useState<HTMLElement | null>(null);
  
  const {
    cornerstoneRenderData,
    updateCornerstoneRenderData,
  } = useInsightViewerSync();
  
  const {
    contours,
    focusedContour,
    addContour,
    removeContour,
    focusContour,
  } = useUserContour();
  
  // 4번째 화면을 위한 처리
  const [interactionElement4, setInteractionElement4] = useState<HTMLElement | null>(null);
  
  useBulkImageScroll({
    image: image4,
    element: interactionElement4,
    enabled: true,
  });
  
  return (
    <div style={{display: 'flex'}}>
      <InsightViewerContainer width={width} height={height}>
        <InsightViewer width={width}
                       height={height}
                       invert={invert}
                       flip={flip}
                       pan
                       adjust={false}
                       zoom
                       resetTime={resetTime}
                       image={image1}
                       updateCornerstoneRenderData={updateMasterRenderData}/>
      </InsightViewerContainer>
      
      <InsightViewerContainer width={width} height={height}>
        <InsightViewer ref={viewer2}
                       width={width}
                       height={height}
                       invert={invert}
                       flip={flip}
                       pan={false}
                       adjust
                       zoom={false}
                       resetTime={resetTime}
                       image={image2}
                       updateCornerstoneRenderData={renderData => console.log('#2', renderData)}/>
      </InsightViewerContainer>
      
      <InsightViewerContainer ref={setInteractionElement3} width={width} height={height}>
        <InsightViewer ref={viewer3}
                       width={width}
                       height={height}
                       invert={invert}
                       flip={flip}
                       pan={false}
                       adjust={false}
                       zoom={false}
                       resetTime={resetTime}
                       image={image3}
                       updateCornerstoneRenderData={updateCornerstoneRenderData}/>
        {
          contours &&
          contours.length > 0 &&
          cornerstoneRenderData &&
          <UserContourViewer width={width}
                             height={height}
                             contours={contours}
                             focusedContour={focusedContour}
                             cornerstoneRenderData={cornerstoneRenderData}/>
        }
        {
          contours &&
          cornerstoneRenderData &&
          <UserContourDrawer width={width}
                             height={height}
                             contours={contours}
                             draw={interactionElement3}
                             onFocus={focusContour}
                             onAdd={contour => addContour(contour, 0)}
                             onRemove={removeContour}
                             cornerstoneRenderData={cornerstoneRenderData}/>
        }
      </InsightViewerContainer>
      
      <InsightViewerContainer ref={setInteractionElement4} width={width} height={height}>
        <InsightViewer ref={viewer4}
                       width={width}
                       height={height}
                       invert={invert}
                       flip={flip}
                       pan={false}
                       adjust={false}
                       zoom={false}
                       resetTime={resetTime}
                       image={image4}
                       updateCornerstoneRenderData={renderData => console.log('#4', renderData)}/>
      </InsightViewerContainer>
    </div>
  );
}

storiesOf('insight-viewer2', module)
  .addDecorator(withOptTheme)
  .addDecorator(withTestController({
    width: [300, 200, 500],
    height: [400, 300, 600],
    control: ['none', ['none']],
    wheel: ['none', ['none']],
    flip: false,
    invert: false,
  }))
  .add('useViewportMirroring()', () => <Sample/>);
```
<!-- importend -->

## Tests

<!-- import __tests__/*.{ts,tsx} --title-tag h3 -->
<!-- importend -->

---
_Source: https://npm.io/package/@lunit/insight-viewer2 · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
