start grid setup
This commit is contained in:
+247
-18
@@ -6,13 +6,14 @@ import {
|
|||||||
} from "@cornerstonejs/core"
|
} from "@cornerstonejs/core"
|
||||||
import { init as csRenderInit } from "@cornerstonejs/core"
|
import { init as csRenderInit } from "@cornerstonejs/core"
|
||||||
import { init as csToolsInit, addTool, ToolGroupManager, Enums as csToolsEnums, PanTool, WindowLevelTool, StackScrollTool, ZoomTool, PlanarRotateTool } from "@cornerstonejs/tools"
|
import { init as csToolsInit, addTool, ToolGroupManager, Enums as csToolsEnums, PanTool, WindowLevelTool, StackScrollTool, ZoomTool, PlanarRotateTool } from "@cornerstonejs/tools"
|
||||||
import { LengthTool, ProbeTool, ArrowAnnotateTool, RectangleROITool, EllipticalROITool } from "@cornerstonejs/tools"
|
import { LengthTool, ProbeTool, ArrowAnnotateTool, RectangleROITool, EllipticalROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, SculptorTool } from "@cornerstonejs/tools"
|
||||||
import { init as dicomImageLoaderInit } from "@cornerstonejs/dicom-image-loader"
|
import { init as dicomImageLoaderInit } from "@cornerstonejs/dicom-image-loader"
|
||||||
import * as cornerstoneTools from '@cornerstonejs/tools';
|
import * as cornerstoneTools from '@cornerstonejs/tools';
|
||||||
import { StackScrollOutOfBoundsEvent } from 'core/src/types/EventTypes';
|
import { StackScrollOutOfBoundsEvent } from 'core/src/types/EventTypes';
|
||||||
|
import { segmentation, Enums as csEnums } from '@cornerstonejs/tools';
|
||||||
|
|
||||||
|
|
||||||
const { MouseBindings } = csToolsEnums;
|
const { MouseBindings, KeyboardBindings } = csToolsEnums;
|
||||||
|
|
||||||
const { IMAGE_RENDERED } = Enums.Events;
|
const { IMAGE_RENDERED } = Enums.Events;
|
||||||
|
|
||||||
@@ -39,6 +40,9 @@ const TOOL_OPTIONS = [
|
|||||||
{ label: "Arrow Annotate", value: ArrowAnnotateTool.toolName },
|
{ label: "Arrow Annotate", value: ArrowAnnotateTool.toolName },
|
||||||
{ label: "Rectangle ROI", value: RectangleROITool.toolName },
|
{ label: "Rectangle ROI", value: RectangleROITool.toolName },
|
||||||
{ label: "Ellipse ROI", value: EllipticalROITool.toolName },
|
{ label: "Ellipse ROI", value: EllipticalROITool.toolName },
|
||||||
|
{ label: "Freehand ROI", value: PlanarFreehandROITool.toolName },
|
||||||
|
{ label: "Freehand Contour Segmentation", value: PlanarFreehandContourSegmentationTool.toolName },
|
||||||
|
{ label: "Sculptor", value: SculptorTool.toolName },
|
||||||
]
|
]
|
||||||
|
|
||||||
// Mouse button labels
|
// Mouse button labels
|
||||||
@@ -67,32 +71,90 @@ function App() {
|
|||||||
windowWidth: "",
|
windowWidth: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const [viewportGrid, setViewportGrid] = useState({ rows: 1, cols: 1 })
|
||||||
|
const [viewportMenuOpen, setViewportMenuOpen] = useState(false)
|
||||||
|
|
||||||
|
// Store refs for each viewport
|
||||||
|
const viewportRefs = useRef<Array<HTMLDivElement | null>>([])
|
||||||
|
|
||||||
|
// Helper to generate grid options
|
||||||
|
const gridOptions = []
|
||||||
|
for (let rows = 1; rows <= 3; rows++) {
|
||||||
|
for (let cols = 1; cols <= 3; cols++) {
|
||||||
|
gridOptions.push({ rows, cols })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// State for selected tool per mouse button
|
// State for selected tool per mouse button
|
||||||
const [mouseToolBindings, setMouseToolBindings] = useState({
|
const [mouseToolBindings, setMouseToolBindings] = useState({
|
||||||
Primary: WindowLevelTool.toolName,
|
Primary: WindowLevelTool.toolName,
|
||||||
Auxiliary: PanTool.toolName,
|
Auxiliary: PanTool.toolName,
|
||||||
Secondary: ZoomTool.toolName,
|
Secondary: ZoomTool.toolName,
|
||||||
})
|
})
|
||||||
|
const [ctrlMouseToolBindings, setCtrlMouseToolBindings] = useState({
|
||||||
|
Primary: LengthTool.toolName,
|
||||||
|
Auxiliary: ProbeTool.toolName,
|
||||||
|
Secondary: ArrowAnnotateTool.toolName,
|
||||||
|
})
|
||||||
|
|
||||||
// Helper to apply all mouse tool bindings
|
// Track if Ctrl is pressed
|
||||||
|
const [ctrlPressed, setCtrlPressed] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.ctrlKey) setCtrlPressed(true)
|
||||||
|
}
|
||||||
|
const handleKeyUp = (e: KeyboardEvent) => {
|
||||||
|
if (!e.ctrlKey) setCtrlPressed(false)
|
||||||
|
}
|
||||||
|
window.addEventListener("keydown", handleKeyDown)
|
||||||
|
window.addEventListener("keyup", handleKeyUp)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown)
|
||||||
|
window.removeEventListener("keyup", handleKeyUp)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// Helper to apply all mouse tool bindings (normal or ctrl)
|
||||||
const applyMouseToolBindings = useCallback(() => {
|
const applyMouseToolBindings = useCallback(() => {
|
||||||
console.log("applyMouseToolBindings", mouseToolBindings)
|
|
||||||
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
|
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
|
||||||
if (toolGroup) {
|
if (toolGroup) {
|
||||||
// Set all tools to passive first
|
// Set all tools to passive first, removing all bindings
|
||||||
TOOL_OPTIONS.forEach(opt => {
|
TOOL_OPTIONS.forEach(opt => {
|
||||||
toolGroup.setToolPassive(opt.value, { removeAllBindings: true })
|
toolGroup.setToolPassive(opt.value, { removeAllBindings: true })
|
||||||
})
|
})
|
||||||
// Re-apply all bindings for all mouse buttons (Primary, Auxiliary, Secondary)
|
|
||||||
|
// Collect all bindings for each tool
|
||||||
|
const toolBindings: Record<string, Array<any>> = {}
|
||||||
|
|
||||||
|
// Normal bindings
|
||||||
Object.entries(mouseToolBindings).forEach(([btn, tool]) => {
|
Object.entries(mouseToolBindings).forEach(([btn, tool]) => {
|
||||||
console.log("button", btn, "setToolActive", tool)
|
if (!toolBindings[tool]) toolBindings[tool] = []
|
||||||
toolGroup.setToolActive(tool, {
|
toolBindings[tool].push({ mouseButton: MouseBindings[btn] })
|
||||||
bindings: [{ mouseButton: csToolsEnums.MouseBindings[btn] }],
|
|
||||||
})
|
})
|
||||||
|
// Ctrl bindings
|
||||||
|
Object.entries(ctrlMouseToolBindings).forEach(([btn, tool]) => {
|
||||||
|
if (!toolBindings[tool]) toolBindings[tool] = []
|
||||||
|
toolBindings[tool].push({ mouseButton: MouseBindings[btn], modifierKey: KeyboardBindings.Ctrl })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Apply all bindings for each tool in a single call
|
||||||
|
Object.entries(toolBindings).forEach(([tool, bindings]) => {
|
||||||
|
toolGroup.setToolActive(tool, { bindings })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Always re-apply the mouse wheel binding for StackScrollTool
|
||||||
|
toolGroup.setToolActive(StackScrollTool.toolName, {
|
||||||
|
bindings: [
|
||||||
|
{
|
||||||
|
mouseButton: MouseBindings.Wheel, // Wheel Mouse
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
// Do NOT set wheel bindings here!
|
|
||||||
}
|
}
|
||||||
}, [mouseToolBindings])
|
}, [mouseToolBindings, ctrlMouseToolBindings])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleToolChange = (mouseButton: string, toolName: string) => {
|
const handleToolChange = (mouseButton: string, toolName: string) => {
|
||||||
setMouseToolBindings(prev => ({ ...prev, [mouseButton]: toolName }))
|
setMouseToolBindings(prev => ({ ...prev, [mouseButton]: toolName }))
|
||||||
@@ -152,6 +214,9 @@ function App() {
|
|||||||
cornerstoneTools.addTool(ArrowAnnotateTool)
|
cornerstoneTools.addTool(ArrowAnnotateTool)
|
||||||
cornerstoneTools.addTool(RectangleROITool)
|
cornerstoneTools.addTool(RectangleROITool)
|
||||||
cornerstoneTools.addTool(EllipticalROITool)
|
cornerstoneTools.addTool(EllipticalROITool)
|
||||||
|
cornerstoneTools.addTool(PlanarFreehandROITool)
|
||||||
|
cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool)
|
||||||
|
cornerstoneTools.addTool(SculptorTool)
|
||||||
|
|
||||||
// Create tool group ONCE
|
// Create tool group ONCE
|
||||||
let toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
let toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
||||||
@@ -163,10 +228,26 @@ function App() {
|
|||||||
toolGroup.addTool(StackScrollTool.toolName, { loop: false })
|
toolGroup.addTool(StackScrollTool.toolName, { loop: false })
|
||||||
toolGroup.addTool(PlanarRotateTool.toolName)
|
toolGroup.addTool(PlanarRotateTool.toolName)
|
||||||
toolGroup.addTool(LengthTool.toolName)
|
toolGroup.addTool(LengthTool.toolName)
|
||||||
toolGroup.addTool(ProbeTool.toolName)
|
toolGroup.addTool(ArrowAnnotateTool.toolName, {
|
||||||
toolGroup.addTool(ArrowAnnotateTool.toolName)
|
getTextCallback: () => '',
|
||||||
toolGroup.addTool(RectangleROITool.toolName)
|
})
|
||||||
toolGroup.addTool(EllipticalROITool.toolName)
|
toolGroup.addTool(ProbeTool.toolName, {
|
||||||
|
statsCalculator: () => null,
|
||||||
|
})
|
||||||
|
toolGroup.addTool(RectangleROITool.toolName, {
|
||||||
|
calculateStats: false,
|
||||||
|
getTextCallback: () => '',
|
||||||
|
})
|
||||||
|
toolGroup.addTool(EllipticalROITool.toolName, {
|
||||||
|
getTextCallback: () => '',
|
||||||
|
})
|
||||||
|
toolGroup.addTool(PlanarFreehandROITool.toolName, {
|
||||||
|
calculateStats: false,
|
||||||
|
})
|
||||||
|
toolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName, {
|
||||||
|
})
|
||||||
|
toolGroup.addTool(SculptorTool.toolName, {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
|
// The Stack Scroll mouse wheel is a tool using the `mouseWheelCallback`
|
||||||
@@ -206,6 +287,25 @@ function App() {
|
|||||||
// Create a stack volume
|
// Create a stack volume
|
||||||
viewport.setStack(imageIds)
|
viewport.setStack(imageIds)
|
||||||
|
|
||||||
|
const segmentationId = 'my-segmentation-id';
|
||||||
|
|
||||||
|
// Create a segmentation representation for the stack
|
||||||
|
segmentation.addSegmentations([{
|
||||||
|
segmentationId,
|
||||||
|
representation: {
|
||||||
|
type: csEnums.SegmentationRepresentations.Contour
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
//segmentation.activeSegmentation.setActiveSegmentationRepresentations(segmentationId);
|
||||||
|
// Create a segmentation representation associated to the viewportId
|
||||||
|
await segmentation.addSegmentationRepresentations(viewportId, [
|
||||||
|
{
|
||||||
|
segmentationId,
|
||||||
|
type: csToolsEnums.SegmentationRepresentations.Contour,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
segmentation.segmentIndex.setActiveSegmentIndex(segmentationId, 1);
|
||||||
cornerstoneTools.utilities.stackPrefetch.enable(viewport.element);
|
cornerstoneTools.utilities.stackPrefetch.enable(viewport.element);
|
||||||
|
|
||||||
// Render the image
|
// Render the image
|
||||||
@@ -255,9 +355,11 @@ function App() {
|
|||||||
viewport.render()
|
viewport.render()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Re-apply bindings when tool sets or ctrl changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
applyMouseToolBindings();
|
applyMouseToolBindings()
|
||||||
}, [mouseToolBindings, applyMouseToolBindings]);
|
}, [mouseToolBindings, ctrlMouseToolBindings, ctrlPressed, applyMouseToolBindings])
|
||||||
|
|
||||||
// Close the menu when clicking outside
|
// Close the menu when clicking outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -272,6 +374,28 @@ function App() {
|
|||||||
return () => document.removeEventListener("mousedown", handleClick);
|
return () => document.removeEventListener("mousedown", handleClick);
|
||||||
}, [presetsOpen]);
|
}, [presetsOpen]);
|
||||||
|
|
||||||
|
|
||||||
|
// Render the grid of viewports
|
||||||
|
const viewports = []
|
||||||
|
for (let i = 0; i < viewportGrid.rows * viewportGrid.cols; i++) {
|
||||||
|
viewports.push(
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
ref={el => (viewportRefs.current[i] = el)}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
border: "1px solid #222",
|
||||||
|
background: "#000",
|
||||||
|
minWidth: 0,
|
||||||
|
minHeight: 0,
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* You can render overlays or controls per-viewport here */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative", width: "512px", height: "512px" }}>
|
<div style={{ position: "relative", width: "512px", height: "512px" }}>
|
||||||
<button
|
<button
|
||||||
@@ -303,6 +427,89 @@ function App() {
|
|||||||
backgroundColor: "#000",
|
backgroundColor: "#000",
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
{/* Viewport grid menu at top center */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 12,
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
zIndex: 100,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
padding: "6px 16px",
|
||||||
|
background: "#222",
|
||||||
|
color: "#fff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "15px",
|
||||||
|
marginRight: "8px",
|
||||||
|
}}
|
||||||
|
onClick={() => setViewportMenuOpen(open => !open)}
|
||||||
|
>
|
||||||
|
Grid: {viewportGrid.rows}x{viewportGrid.cols} ▼
|
||||||
|
</button>
|
||||||
|
{viewportMenuOpen && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "110%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
background: "#222",
|
||||||
|
color: "#fff",
|
||||||
|
borderRadius: "6px",
|
||||||
|
boxShadow: "0 2px 12px rgba(0,0,0,0.4)",
|
||||||
|
padding: "12px",
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(3, 40px)",
|
||||||
|
gap: "8px",
|
||||||
|
zIndex: 200,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{gridOptions.map(opt => (
|
||||||
|
<button
|
||||||
|
key={`${opt.rows}x${opt.cols}`}
|
||||||
|
style={{
|
||||||
|
width: "40px",
|
||||||
|
height: "40px",
|
||||||
|
background: (opt.rows === viewportGrid.rows && opt.cols === viewportGrid.cols) ? "#444" : "#333",
|
||||||
|
color: "#fff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "15px",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setViewportGrid({ rows: opt.rows, cols: opt.cols })
|
||||||
|
setViewportMenuOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{opt.rows}x{opt.cols}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Viewport grid */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateRows: `repeat(${viewportGrid.rows}, 1fr)`,
|
||||||
|
gridTemplateColumns: `repeat(${viewportGrid.cols}, 1fr)`,
|
||||||
|
width: "100vw",
|
||||||
|
height: "100vh",
|
||||||
|
gap: "2px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{viewports}
|
||||||
|
</div>
|
||||||
{/* Overlay at bottom left */}
|
{/* Overlay at bottom left */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -465,7 +672,29 @@ function App() {
|
|||||||
<label style={{ marginRight: 8 }}>{btn.label} Button:</label>
|
<label style={{ marginRight: 8 }}>{btn.label} Button:</label>
|
||||||
<select
|
<select
|
||||||
value={mouseToolBindings[btn.value]}
|
value={mouseToolBindings[btn.value]}
|
||||||
onChange={e => handleToolChange(btn.value, e.target.value)}
|
onChange={e => setMouseToolBindings(prev => ({ ...prev, [btn.value]: e.target.value }))}
|
||||||
|
style={{
|
||||||
|
background: "#333",
|
||||||
|
color: "#fff",
|
||||||
|
border: "1px solid #444",
|
||||||
|
borderRadius: "4px",
|
||||||
|
padding: "4px 8px",
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{TOOL_OPTIONS.map(opt => (
|
||||||
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div style={{ margin: "18px 0 8px 0", fontWeight: "bold" }}>Ctrl + Mouse Button Tool Bindings</div>
|
||||||
|
{MOUSE_BUTTONS.map((btn) => (
|
||||||
|
<div key={btn.value + "_ctrl"} style={{ marginBottom: 10 }}>
|
||||||
|
<label style={{ marginRight: 8 }}>{btn.label} + Ctrl:</label>
|
||||||
|
<select
|
||||||
|
value={ctrlMouseToolBindings[btn.value]}
|
||||||
|
onChange={e => setCtrlMouseToolBindings(prev => ({ ...prev, [btn.value]: e.target.value }))}
|
||||||
style={{
|
style={{
|
||||||
background: "#333",
|
background: "#333",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
|
|||||||
Reference in New Issue
Block a user