Compare commits
1 Commits
main
...
feat/test-
Author | SHA1 | Date | |
---|---|---|---|
|
779e1383c6 |
|
@ -0,0 +1,185 @@
|
||||||
|
import * as cornerstoneTools from '@cornerstonejs/tools';
|
||||||
|
import type { Types } from '@cornerstonejs/tools';
|
||||||
|
|
||||||
|
const {
|
||||||
|
LengthTool,
|
||||||
|
StackScrollMouseWheelTool,
|
||||||
|
StackScrollTool,
|
||||||
|
PanTool,
|
||||||
|
ZoomTool,
|
||||||
|
TrackballRotateTool,
|
||||||
|
Enums: csToolsEnums,
|
||||||
|
} = cornerstoneTools;
|
||||||
|
|
||||||
|
const { MouseBindings, KeyboardBindings } = csToolsEnums;
|
||||||
|
|
||||||
|
let registered = false;
|
||||||
|
|
||||||
|
export type ToolBinding = {
|
||||||
|
// A base tool to register. Should only be defined once per tool
|
||||||
|
tool?: any;
|
||||||
|
// The tool name to base this on
|
||||||
|
baseTool?: string;
|
||||||
|
// The configuration to register with
|
||||||
|
configuration?: Record<string, any>;
|
||||||
|
// Sets to passive initially
|
||||||
|
passive?: boolean;
|
||||||
|
// Initial bindings
|
||||||
|
bindings?: Types.IToolBinding[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds navigation bindings to the given tool group. Registers the basic
|
||||||
|
* tool with CS Tools if register is true.
|
||||||
|
*
|
||||||
|
* Adds:
|
||||||
|
* * Pan on Right or Primary+Ctrl
|
||||||
|
* * Zoom on Middle, Primary+Shift
|
||||||
|
* * Stack Scroll on Mouse Wheel, Primary+Alt
|
||||||
|
* * Length Tool on fourth button
|
||||||
|
*
|
||||||
|
* Also allows registering other tools by having them in the options.toolMap with configuration values.
|
||||||
|
*/
|
||||||
|
export default function addManipulationBindings(
|
||||||
|
toolGroup,
|
||||||
|
options: {
|
||||||
|
enableShiftClickZoom?: boolean;
|
||||||
|
is3DViewport?: boolean;
|
||||||
|
toolMap?: Map<string, ToolBinding>;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
const zoomBindings: Types.IToolBinding[] = [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Secondary,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const {
|
||||||
|
is3DViewport = false,
|
||||||
|
enableShiftClickZoom = false,
|
||||||
|
toolMap = new Map(),
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
if (enableShiftClickZoom === true) {
|
||||||
|
zoomBindings.push({
|
||||||
|
mouseButton: MouseBindings.Primary, // Shift Left Click
|
||||||
|
modifierKey: KeyboardBindings.Shift,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registered) {
|
||||||
|
cornerstoneTools.addTool(StackScrollMouseWheelTool);
|
||||||
|
cornerstoneTools.addTool(PanTool);
|
||||||
|
cornerstoneTools.addTool(ZoomTool);
|
||||||
|
cornerstoneTools.addTool(TrackballRotateTool);
|
||||||
|
cornerstoneTools.addTool(LengthTool);
|
||||||
|
cornerstoneTools.addTool(StackScrollTool);
|
||||||
|
for (const [, config] of toolMap) {
|
||||||
|
if (config.tool) {
|
||||||
|
cornerstoneTools.addTool(config.tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registered = true;
|
||||||
|
|
||||||
|
toolGroup.addTool(PanTool.toolName);
|
||||||
|
// Allow significant zooming to occur
|
||||||
|
toolGroup.addTool(ZoomTool.toolName, {
|
||||||
|
minZoomScale: 0.001,
|
||||||
|
maxZoomScale: 4000,
|
||||||
|
});
|
||||||
|
if (is3DViewport) {
|
||||||
|
toolGroup.addTool(TrackballRotateTool.toolName);
|
||||||
|
} else {
|
||||||
|
toolGroup.addTool(StackScrollMouseWheelTool.toolName);
|
||||||
|
}
|
||||||
|
toolGroup.addTool(LengthTool.toolName);
|
||||||
|
toolGroup.addTool(StackScrollTool.toolName);
|
||||||
|
|
||||||
|
toolGroup.setToolActive(PanTool.toolName, {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Auxiliary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
numTouchPoints: 1,
|
||||||
|
modifierKey: KeyboardBindings.Ctrl,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
toolGroup.setToolActive(ZoomTool.toolName, {
|
||||||
|
bindings: zoomBindings,
|
||||||
|
});
|
||||||
|
// Need a binding to navigate without a wheel mouse
|
||||||
|
toolGroup.setToolActive(StackScrollTool.toolName, {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Primary,
|
||||||
|
modifierKey: KeyboardBindings.Alt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
numTouchPoints: 1,
|
||||||
|
modifierKey: KeyboardBindings.Alt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// Add a length tool binding to allow testing annotations on examples targetting
|
||||||
|
// other use cases. Use a primary button with shift+ctrl as that is relatively
|
||||||
|
// unlikely to be otherwise used.
|
||||||
|
toolGroup.setToolActive(LengthTool.toolName, {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Primary,
|
||||||
|
modifierKey: KeyboardBindings.ShiftCtrl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
numTouchPoints: 1,
|
||||||
|
modifierKey: KeyboardBindings.ShiftCtrl,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is3DViewport) {
|
||||||
|
toolGroup.setToolActive(TrackballRotateTool.toolName, {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Primary,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add extra tools from the toolMap
|
||||||
|
for (const [toolName, config] of toolMap) {
|
||||||
|
if (config.baseTool) {
|
||||||
|
if (!toolGroup.hasTool(config.baseTool)) {
|
||||||
|
toolGroup.addTool(
|
||||||
|
config.baseTool,
|
||||||
|
toolMap.get(config.baseTool)?.configuration
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toolGroup.addToolInstance(
|
||||||
|
toolName,
|
||||||
|
config.baseTool,
|
||||||
|
config.configuration
|
||||||
|
);
|
||||||
|
} else if (!toolGroup.hasTool(toolName)) {
|
||||||
|
toolGroup.addTool(toolName, config.configuration);
|
||||||
|
}
|
||||||
|
if (config.passive) {
|
||||||
|
// This can be applied during add/remove contours
|
||||||
|
toolGroup.setToolPassive(toolName);
|
||||||
|
}
|
||||||
|
if (config.bindings || config.selected) {
|
||||||
|
toolGroup.setToolActive(
|
||||||
|
toolName,
|
||||||
|
(config.bindings && config) || {
|
||||||
|
bindings: [{ mouseButton: MouseBindings.Primary }],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,11 @@
|
||||||
|
type VOIRange = {
|
||||||
|
/** upper value for display */
|
||||||
|
upper: number;
|
||||||
|
/** lower value for display */
|
||||||
|
lower: number;
|
||||||
|
};
|
||||||
|
|
||||||
let ctVoiRange;
|
let ctVoiRange: VOIRange
|
||||||
|
|
||||||
export interface CtTransferFunction {
|
export interface CtTransferFunction {
|
||||||
volumeActor: any
|
volumeActor: any
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
RenderingEngine,
|
RenderingEngine,
|
||||||
setVolumesForViewports,
|
setVolumesForViewports,
|
||||||
Enums as CoreEnums,
|
Enums as CoreEnums,
|
||||||
|
volumeLoader,
|
||||||
} from "@cornerstonejs/core";
|
} from "@cornerstonejs/core";
|
||||||
import {
|
import {
|
||||||
viewportColors,
|
viewportColors,
|
||||||
|
@ -18,6 +19,8 @@ import {
|
||||||
ViewportId,
|
ViewportId,
|
||||||
} from "./Crosshair.config";
|
} from "./Crosshair.config";
|
||||||
|
|
||||||
|
const viewportIds = [viewportId1, viewportId2, viewportId3];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ToolGroupManager,
|
ToolGroupManager,
|
||||||
CrosshairsTool,
|
CrosshairsTool,
|
||||||
|
@ -55,7 +58,7 @@ interface CrosshairMprProps {
|
||||||
windowCenter: number;
|
windowCenter: number;
|
||||||
windowWidth: number;
|
windowWidth: number;
|
||||||
};
|
};
|
||||||
volumeId: string;
|
imageIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CrosshairMpr = (props: CrosshairMprProps) => {
|
export const CrosshairMpr = (props: CrosshairMprProps) => {
|
||||||
|
@ -64,156 +67,129 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
||||||
const viewportRef_SAGITTAL = useRef<HTMLDivElement | null>(null);
|
const viewportRef_SAGITTAL = useRef<HTMLDivElement | null>(null);
|
||||||
const viewportRef_CORONAL = useRef<HTMLDivElement | null>(null);
|
const viewportRef_CORONAL = useRef<HTMLDivElement | null>(null);
|
||||||
const renderingEngine = useRef<RenderingEngine>();
|
const renderingEngine = useRef<RenderingEngine>();
|
||||||
const toolGroupRef = useRef<cornerstoneTools.Types.IToolGroup | undefined>(
|
const volumeId = "volumeId";
|
||||||
undefined
|
const toolGroupId = "mprToolGroup";
|
||||||
);
|
const renderingEngineId = "mprRenderingEngine";
|
||||||
const ts = "-" + Date.now();
|
|
||||||
const toolGroupId = "mprToolGroup" + ts;
|
|
||||||
const renderingEngineId = "mprRenderingEngine" + ts;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cornerstoneTools.addTool(StackScrollMouseWheelTool);
|
|
||||||
cornerstoneTools.addTool(CrosshairsTool);
|
|
||||||
cornerstoneTools.addTool(WindowLevelTool);
|
|
||||||
cornerstoneTools.addTool(ZoomTool);
|
|
||||||
|
|
||||||
toolGroupRef.current = ToolGroupManager.createToolGroup(toolGroupId);
|
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
if (
|
if (
|
||||||
!viewportRef_AXIAL.current ||
|
viewportRef_AXIAL.current &&
|
||||||
!viewportRef_SAGITTAL.current ||
|
viewportRef_SAGITTAL.current &&
|
||||||
!viewportRef_CORONAL.current
|
viewportRef_CORONAL.current
|
||||||
)
|
) {
|
||||||
return;
|
console.log("mpr rendering");
|
||||||
|
|
||||||
renderingEngine.current = new RenderingEngine(renderingEngineId);
|
renderingEngine.current = new RenderingEngine(renderingEngineId);
|
||||||
|
|
||||||
// Create the viewports
|
const viewportInputArray: PublicViewportInput[] = [
|
||||||
const viewportInputArray: PublicViewportInput[] = [
|
|
||||||
{
|
|
||||||
viewportId: viewportId1,
|
|
||||||
type: ViewportType.ORTHOGRAPHIC,
|
|
||||||
element: viewportRef_AXIAL.current,
|
|
||||||
defaultOptions: {
|
|
||||||
orientation: CoreEnums.OrientationAxis.AXIAL,
|
|
||||||
background: [0, 0, 0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
viewportId: viewportId2,
|
|
||||||
type: ViewportType.ORTHOGRAPHIC,
|
|
||||||
element: viewportRef_SAGITTAL.current,
|
|
||||||
defaultOptions: {
|
|
||||||
orientation: CoreEnums.OrientationAxis.SAGITTAL,
|
|
||||||
background: [0, 0, 0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
viewportId: viewportId3,
|
|
||||||
type: ViewportType.ORTHOGRAPHIC,
|
|
||||||
element: viewportRef_CORONAL.current,
|
|
||||||
defaultOptions: {
|
|
||||||
orientation: CoreEnums.OrientationAxis.CORONAL,
|
|
||||||
background: [0, 0, 0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
renderingEngine.current.setViewports(viewportInputArray);
|
|
||||||
|
|
||||||
// Set volumes on the viewports
|
|
||||||
await setVolumesForViewports(
|
|
||||||
renderingEngine.current,
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
volumeId: props.volumeId,
|
viewportId: viewportId1,
|
||||||
callback: ({ volumeActor }) =>
|
type: ViewportType.ORTHOGRAPHIC,
|
||||||
setCtTransferFunctionForVolumeActor({
|
element: viewportRef_AXIAL.current,
|
||||||
volumeActor,
|
defaultOptions: {
|
||||||
defaultWindowCenter: props.wwwl.windowCenter,
|
orientation: CoreEnums.OrientationAxis.AXIAL,
|
||||||
defaultWindowWidth: props.wwwl.windowWidth,
|
background: [0, 0, 0],
|
||||||
}),
|
},
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
[viewportId1, viewportId2, viewportId3]
|
viewportId: viewportId2,
|
||||||
);
|
type: ViewportType.ORTHOGRAPHIC,
|
||||||
|
element: viewportRef_SAGITTAL.current,
|
||||||
if (toolGroupRef.current) {
|
defaultOptions: {
|
||||||
// For the crosshairs to operate, the viewports must currently be
|
orientation: CoreEnums.OrientationAxis.SAGITTAL,
|
||||||
// added ahead of setting the tool active. This will be improved in the future.
|
background: [0, 0, 0],
|
||||||
toolGroupRef.current.addViewport(viewportId1, renderingEngineId);
|
},
|
||||||
toolGroupRef.current.addViewport(viewportId2, renderingEngineId);
|
},
|
||||||
toolGroupRef.current.addViewport(viewportId3, renderingEngineId);
|
{
|
||||||
|
viewportId: viewportId3,
|
||||||
|
type: ViewportType.ORTHOGRAPHIC,
|
||||||
|
element: viewportRef_CORONAL.current,
|
||||||
|
defaultOptions: {
|
||||||
|
orientation: CoreEnums.OrientationAxis.CORONAL,
|
||||||
|
background: [0, 0, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
renderingEngine.current.setViewports(viewportInputArray);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* zoom影像
|
* bug here: WebGL: INVALID_OPERATION: bindTexture: object does not belong to this context
|
||||||
*/
|
*/
|
||||||
toolGroupRef.current.addTool(ZoomTool.toolName);
|
await setVolumesForViewports(
|
||||||
toolGroupRef.current.setToolActive(ZoomTool.toolName, {
|
renderingEngine.current,
|
||||||
bindings: [
|
[
|
||||||
{
|
{
|
||||||
mouseButton: MouseBindings.Secondary, // 鼠标中键
|
volumeId: volumeId,
|
||||||
|
callback: ({ volumeActor }) =>
|
||||||
|
setCtTransferFunctionForVolumeActor({
|
||||||
|
volumeActor,
|
||||||
|
defaultWindowCenter: props.wwwl.windowCenter,
|
||||||
|
defaultWindowWidth: props.wwwl.windowWidth,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
viewportIds
|
||||||
|
);
|
||||||
|
|
||||||
// Manipulation Tools
|
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
|
||||||
toolGroupRef.current.addTool(StackScrollMouseWheelTool.toolName);
|
|
||||||
// Add Crosshairs tool and configure it to link the three viewports
|
|
||||||
// These viewports could use different tool groups. See the PET-CT example
|
|
||||||
// for a more complicated used case.
|
|
||||||
|
|
||||||
toolGroupRef.current.addTool(CrosshairsTool.toolName, {
|
if (toolGroup) {
|
||||||
getReferenceLineColor,
|
// 为使十字准线正常工作,目前必须在将工具设置为活动之前添加视口
|
||||||
getReferenceLineControllable,
|
toolGroup.addViewport(viewportId1, renderingEngineId);
|
||||||
getReferenceLineDraggableRotatable,
|
toolGroup.addViewport(viewportId2, renderingEngineId);
|
||||||
getReferenceLineSlabThicknessControlsOn,
|
toolGroup.addViewport(viewportId3, renderingEngineId);
|
||||||
});
|
|
||||||
|
|
||||||
toolGroupRef.current.setToolActive(CrosshairsTool.toolName, {
|
toolGroup.addTool(ZoomTool.toolName);
|
||||||
bindings: [{ mouseButton: 1 }],
|
toolGroup.addTool(StackScrollMouseWheelTool.toolName);
|
||||||
});
|
// 添加十字准线工具并将其配置为连接三个视口
|
||||||
// As the Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
|
toolGroup.addTool(CrosshairsTool.toolName, {
|
||||||
// hook instead of mouse buttons, it does not need to assign any mouse button.
|
getReferenceLineColor,
|
||||||
toolGroupRef.current.setToolActive(StackScrollMouseWheelTool.toolName);
|
getReferenceLineControllable,
|
||||||
|
getReferenceLineDraggableRotatable,
|
||||||
|
getReferenceLineSlabThicknessControlsOn,
|
||||||
|
});
|
||||||
|
toolGroup.addTool(WindowLevelTool.toolName);
|
||||||
|
|
||||||
toolGroupRef.current.addTool(WindowLevelTool.toolName);
|
toolGroup.setToolActive(CrosshairsTool.toolName, {
|
||||||
toolGroupRef.current.setToolActive(WindowLevelTool.toolName, {
|
bindings: [{ mouseButton: 1 }],
|
||||||
bindings: [
|
});
|
||||||
{
|
toolGroup.setToolActive(ZoomTool.toolName, {
|
||||||
mouseButton: MouseBindings.Auxiliary,
|
bindings: [
|
||||||
},
|
{
|
||||||
],
|
mouseButton: MouseBindings.Secondary, // 鼠标中键
|
||||||
});
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// using the `mouseWheelCallback`
|
||||||
|
toolGroup.setToolActive(StackScrollMouseWheelTool.toolName);
|
||||||
|
toolGroup.setToolActive(WindowLevelTool.toolName, {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Auxiliary,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderingEngine.current.renderViewports(viewportIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderingEngine.current.renderViewports([
|
|
||||||
viewportId1,
|
|
||||||
viewportId2,
|
|
||||||
viewportId3,
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// 禁用视口
|
|
||||||
renderingEngine.current?.disableElement(viewportId1);
|
renderingEngine.current?.disableElement(viewportId1);
|
||||||
renderingEngine.current?.disableElement(viewportId2);
|
renderingEngine.current?.disableElement(viewportId2);
|
||||||
renderingEngine.current?.disableElement(viewportId3);
|
renderingEngine.current?.disableElement(viewportId3);
|
||||||
|
|
||||||
// 销毁渲染引擎
|
|
||||||
renderingEngine.current?.destroy();
|
|
||||||
|
|
||||||
// 从 ToolGroupManager 中移除工具组
|
// 从 ToolGroupManager 中移除工具组
|
||||||
ToolGroupManager.destroyToolGroup(toolGroupId);
|
ToolGroupManager.destroyToolGroup(toolGroupId);
|
||||||
|
|
||||||
// 移出工具注册
|
// 销毁渲染引擎
|
||||||
cornerstoneTools.removeTool(StackScrollMouseWheelTool);
|
renderingEngine.current?.destroy();
|
||||||
cornerstoneTools.removeTool(CrosshairsTool);
|
|
||||||
cornerstoneTools.removeTool(WindowLevelTool);
|
|
||||||
cornerstoneTools.removeTool(ZoomTool);
|
|
||||||
};
|
};
|
||||||
}, [props, renderingEngineId, toolGroupId]);
|
}, [props, renderingEngineId, toolGroupId]);
|
||||||
|
|
||||||
|
@ -238,8 +214,14 @@ export const CrosshairMpr = (props: CrosshairMprProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="w-full h-full flex flex-col">
|
<div ref={containerRef} className="w-full h-full flex flex-col">
|
||||||
<div className="w-full h-1/3" ref={viewportRef_AXIAL} />
|
<div
|
||||||
<div className="w-full h-1/3" ref={viewportRef_SAGITTAL} />
|
className="w-full h-1/3 border-b border-secondary"
|
||||||
|
ref={viewportRef_AXIAL}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="w-full h-1/3 border-b border-secondary"
|
||||||
|
ref={viewportRef_SAGITTAL}
|
||||||
|
/>
|
||||||
<div className="w-full h-1/3" ref={viewportRef_CORONAL} />
|
<div className="w-full h-1/3" ref={viewportRef_CORONAL} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,22 +1,44 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import * as cornerstoneTools from "@cornerstonejs/tools";
|
||||||
import { PublicViewportInput } from "@cornerstonejs/core/dist/types/types/IViewport.js";
|
import { PublicViewportInput } from "@cornerstonejs/core/dist/types/types/IViewport.js";
|
||||||
import { Enums as CoreEnums, RenderingEngine } from "@cornerstonejs/core";
|
import { Enums as CoreEnums, RenderingEngine } from "@cornerstonejs/core";
|
||||||
import { IStackViewport } from "@cornerstonejs/core/dist/types/types";
|
import { IStackViewport } from "@cornerstonejs/core/dist/types/types";
|
||||||
import { Slider } from "@/components/ui/slider";
|
import { Slider } from "@/components/ui/slider";
|
||||||
|
import { ctVoiRange } from "../MprViewer/CornerstoneDicomLoader/setCtTransferFunctionForVolumeActor";
|
||||||
|
const {
|
||||||
|
ToolGroupManager,
|
||||||
|
WindowLevelTool,
|
||||||
|
ZoomTool,
|
||||||
|
Enums: csToolsEnums,
|
||||||
|
} = cornerstoneTools;
|
||||||
|
|
||||||
|
const { MouseBindings } = csToolsEnums;
|
||||||
|
|
||||||
export interface StackViewerProps {
|
export interface StackViewerProps {
|
||||||
imageIds: string[];
|
imageIds: string[];
|
||||||
|
wwwl: {
|
||||||
|
windowCenter: number;
|
||||||
|
windowWidth: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StackViewer = (props: StackViewerProps) => {
|
export const StackViewer = (props: StackViewerProps) => {
|
||||||
const viewportStackRef = useRef<HTMLDivElement | null>(null);
|
const viewportStackRef = useRef<HTMLDivElement | null>(null);
|
||||||
const renderingEngineRef = useRef<RenderingEngine>();
|
const renderingEngineRef = useRef<RenderingEngine>();
|
||||||
|
/**
|
||||||
|
* 当前的index
|
||||||
|
*/
|
||||||
|
const [index, setIndex] = useState(Math.floor(props.imageIds.length / 2));
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const viewportId = "stackViewport";
|
const viewportId = "stackViewport";
|
||||||
const renderingEngineId = "stackRenderingEngine";
|
const renderingEngineId = "stackRenderingEngine";
|
||||||
|
const toolGroupId = "stackToolGroup";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!viewportStackRef.current) return;
|
if (!viewportStackRef.current) return;
|
||||||
|
|
||||||
|
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId);
|
||||||
|
|
||||||
renderingEngineRef.current = new RenderingEngine(renderingEngineId);
|
renderingEngineRef.current = new RenderingEngine(renderingEngineId);
|
||||||
const viewportInput: PublicViewportInput = {
|
const viewportInput: PublicViewportInput = {
|
||||||
viewportId,
|
viewportId,
|
||||||
|
@ -32,13 +54,54 @@ export const StackViewer = (props: StackViewerProps) => {
|
||||||
) as IStackViewport;
|
) as IStackViewport;
|
||||||
|
|
||||||
viewport.setStack(props.imageIds);
|
viewport.setStack(props.imageIds);
|
||||||
|
// viewport.setProperties({
|
||||||
|
// voiRange: ctVoiRange,
|
||||||
|
// });
|
||||||
|
|
||||||
|
if (toolGroup) {
|
||||||
|
toolGroup.addViewport(viewportId, renderingEngineId);
|
||||||
|
toolGroup.addTool(ZoomTool.toolName);
|
||||||
|
toolGroup.setToolActive(ZoomTool.toolName, {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Secondary, // 鼠标中键
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
toolGroup.addTool(WindowLevelTool.toolName);
|
||||||
|
toolGroup.setToolActive(WindowLevelTool.toolName, {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Auxiliary,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
ToolGroupManager.destroyToolGroup(toolGroupId);
|
||||||
renderingEngineRef.current?.disableElement(viewportId);
|
renderingEngineRef.current?.disableElement(viewportId);
|
||||||
renderingEngineRef.current?.destroy();
|
renderingEngineRef.current?.destroy();
|
||||||
};
|
};
|
||||||
}, [props.imageIds, renderingEngineId, viewportId]);
|
}, [props.imageIds, renderingEngineId, viewportId]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚轮换图逻辑
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const handleWheel = (event: WheelEvent) => {
|
||||||
|
const delta = event.deltaY > 0 ? -1 : 1;
|
||||||
|
if (delta === -1 && index === 0) return;
|
||||||
|
if (delta === 1 && index === props.imageIds.length - 1) return;
|
||||||
|
setIndex((p) => p + delta);
|
||||||
|
};
|
||||||
|
const stackElement = viewportStackRef.current;
|
||||||
|
stackElement?.addEventListener("wheel", handleWheel);
|
||||||
|
return () => {
|
||||||
|
stackElement?.removeEventListener("wheel", handleWheel);
|
||||||
|
};
|
||||||
|
}, [index, props.imageIds.length]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
@ -58,22 +121,38 @@ export const StackViewer = (props: StackViewerProps) => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拖拽滚动条
|
||||||
|
*/
|
||||||
const onChangeIndex = (value: number[]) => {
|
const onChangeIndex = (value: number[]) => {
|
||||||
if (renderingEngineRef.current) {
|
setIndex(value[0]);
|
||||||
const viewport = renderingEngineRef.current.getViewport(viewportId);
|
|
||||||
(viewport as IStackViewport).setImageIdIndex(value[0]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (renderingEngineRef.current) {
|
||||||
|
const viewport = renderingEngineRef.current.getViewport(viewportId);
|
||||||
|
(viewport as IStackViewport).setImageIdIndex(index);
|
||||||
|
}
|
||||||
|
}, [index]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="w-full h-full flex flex-col pb-4">
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="relative w-full h-full flex flex-col pb-8"
|
||||||
|
>
|
||||||
<div className="w-full h-full" ref={viewportStackRef} />
|
<div className="w-full h-full" ref={viewportStackRef} />
|
||||||
<Slider
|
<div className="absolute w-1/2 left-1/2 transform -translate-x-1/2 bottom-4">
|
||||||
defaultValue={[0]}
|
<div>
|
||||||
max={props.imageIds.length - 1}
|
{index + 1}/{props.imageIds.length}
|
||||||
step={1}
|
</div>
|
||||||
onValueChange={onChangeIndex}
|
<Slider
|
||||||
/>
|
value={[index]}
|
||||||
|
min={0}
|
||||||
|
max={props.imageIds.length - 1}
|
||||||
|
step={1}
|
||||||
|
onValueChange={onChangeIndex}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,35 +9,42 @@ import {
|
||||||
} from "@/components/ui/resizable";
|
} from "@/components/ui/resizable";
|
||||||
import { StackViewer } from "./StackViewer";
|
import { StackViewer } from "./StackViewer";
|
||||||
import { createImageIdsAndCacheMetaData } from "./MprViewer/CornerstoneDicomLoader/createImageIdsAndCacheMetaData";
|
import { createImageIdsAndCacheMetaData } from "./MprViewer/CornerstoneDicomLoader/createImageIdsAndCacheMetaData";
|
||||||
|
|
||||||
|
import * as cornerstoneTools from "@cornerstonejs/tools";
|
||||||
import { volumeLoader } from "@cornerstonejs/core";
|
import { volumeLoader } from "@cornerstonejs/core";
|
||||||
|
|
||||||
const wadoRsRoot = "http://localhost:8042/dicom-web";
|
const wadoRsRoot = "http://localhost:8042/dicom-web";
|
||||||
|
|
||||||
|
const { StackScrollMouseWheelTool, WindowLevelTool, ZoomTool, CrosshairsTool } =
|
||||||
|
cornerstoneTools;
|
||||||
|
|
||||||
export const Viewer = () => {
|
export const Viewer = () => {
|
||||||
const [cornerstoneLoaded, setCornerstoneLoaded] = useState(false);
|
const [cornerstoneLoaded, setCornerstoneLoaded] = useState(false);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
const SeriesInstanceUID = queryParams.get("SeriesInstanceUID");
|
const SeriesInstanceUID = queryParams.get("SeriesInstanceUID");
|
||||||
const StudyInstanceUID = queryParams.get("StudyInstanceUID");
|
const StudyInstanceUID = queryParams.get("StudyInstanceUID");
|
||||||
const imageIdsRef = useRef<string[]>();
|
const imageIdsSorted = useRef<string[]>();
|
||||||
const volumeId = "volume";
|
|
||||||
const wwwl = { windowCenter: 50, windowWidth: 850 };
|
const wwwl = { windowCenter: 50, windowWidth: 850 };
|
||||||
|
const volumeId = "volumeId";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setImageOrderCache = async () => {
|
const setImageOrderCache = async () => {
|
||||||
if (!StudyInstanceUID || !SeriesInstanceUID) return;
|
if (!StudyInstanceUID || !SeriesInstanceUID) return;
|
||||||
// imageIds此时由于流式加载for mpr,是错乱的图片顺序
|
// imageIds此时由于流式加载for mpr,是错乱的图片顺序
|
||||||
const imageIds = await createImageIdsAndCacheMetaData({
|
const unSortedImageIds = await createImageIdsAndCacheMetaData({
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
SeriesInstanceUID,
|
SeriesInstanceUID,
|
||||||
wadoRsRoot,
|
wadoRsRoot,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 这一步会对imageIds进行排序,如果不排序imageIds会错误乱,stackViewport顺序会错误
|
// 这一步会对imageIds进行排序,如果不排序imageIds会错误乱,stackViewport顺序会错误
|
||||||
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
|
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
|
||||||
imageIds,
|
imageIds: unSortedImageIds,
|
||||||
});
|
});
|
||||||
volume.load();
|
volume.load();
|
||||||
imageIdsRef.current = volume.imageIds;
|
|
||||||
|
imageIdsSorted.current = volume.imageIds;
|
||||||
|
|
||||||
// 默认windowWidtth
|
// 默认windowWidtth
|
||||||
const { windowCenter, windowWidth } = volume.cornerstoneImageMetaData;
|
const { windowCenter, windowWidth } = volume.cornerstoneImageMetaData;
|
||||||
|
@ -48,8 +55,20 @@ export const Viewer = () => {
|
||||||
|
|
||||||
initCornerstone(() => {
|
initCornerstone(() => {
|
||||||
setImageOrderCache();
|
setImageOrderCache();
|
||||||
|
|
||||||
|
cornerstoneTools.addTool(StackScrollMouseWheelTool);
|
||||||
|
cornerstoneTools.addTool(WindowLevelTool);
|
||||||
|
cornerstoneTools.addTool(ZoomTool);
|
||||||
|
cornerstoneTools.addTool(CrosshairsTool);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// 移出工具注册
|
||||||
|
cornerstoneTools.removeTool(CrosshairsTool);
|
||||||
|
cornerstoneTools.removeTool(StackScrollMouseWheelTool);
|
||||||
|
cornerstoneTools.removeTool(WindowLevelTool);
|
||||||
|
cornerstoneTools.removeTool(ZoomTool);
|
||||||
|
};
|
||||||
}, [SeriesInstanceUID, StudyInstanceUID]);
|
}, [SeriesInstanceUID, StudyInstanceUID]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -67,8 +86,11 @@ export const Viewer = () => {
|
||||||
>
|
>
|
||||||
<ResizablePanel defaultSize={50}>
|
<ResizablePanel defaultSize={50}>
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
{cornerstoneLoaded && imageIdsRef.current && (
|
{cornerstoneLoaded && imageIdsSorted.current && (
|
||||||
<StackViewer imageIds={imageIdsRef.current} />
|
<StackViewer
|
||||||
|
imageIds={imageIdsSorted.current}
|
||||||
|
wwwl={wwwl}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
|
@ -81,8 +103,8 @@ export const Viewer = () => {
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle withHandle />
|
<ResizableHandle withHandle />
|
||||||
<ResizablePanel defaultSize={50}>
|
<ResizablePanel defaultSize={50}>
|
||||||
{cornerstoneLoaded && imageIdsRef.current && (
|
{cornerstoneLoaded && imageIdsSorted.current && (
|
||||||
<CrosshairMpr wwwl={wwwl} volumeId={volumeId} />
|
<CrosshairMpr wwwl={wwwl} imageIds={imageIdsSorted.current} />
|
||||||
)}
|
)}
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user