start grid setup

This commit is contained in:
Ross
2025-05-20 07:57:28 +01:00
parent 8a04f91c1c
commit eb5219b355
+248 -19
View File
@@ -6,13 +6,14 @@ import {
} 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 { 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 * as cornerstoneTools from '@cornerstonejs/tools';
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;
@@ -39,6 +40,9 @@ const TOOL_OPTIONS = [
{ label: "Arrow Annotate", value: ArrowAnnotateTool.toolName },
{ label: "Rectangle ROI", value: RectangleROITool.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
@@ -67,32 +71,90 @@ function App() {
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
const [mouseToolBindings, setMouseToolBindings] = useState({
Primary: WindowLevelTool.toolName,
Auxiliary: PanTool.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(() => {
console.log("applyMouseToolBindings", mouseToolBindings)
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
if (toolGroup) {
// Set all tools to passive first
// Set all tools to passive first, removing all bindings
TOOL_OPTIONS.forEach(opt => {
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]) => {
console.log("button", btn, "setToolActive", tool)
toolGroup.setToolActive(tool, {
bindings: [{ mouseButton: csToolsEnums.MouseBindings[btn] }],
})
if (!toolBindings[tool]) toolBindings[tool] = []
toolBindings[tool].push({ mouseButton: 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) => {
setMouseToolBindings(prev => ({ ...prev, [mouseButton]: toolName }))
@@ -152,6 +214,9 @@ function App() {
cornerstoneTools.addTool(ArrowAnnotateTool)
cornerstoneTools.addTool(RectangleROITool)
cornerstoneTools.addTool(EllipticalROITool)
cornerstoneTools.addTool(PlanarFreehandROITool)
cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool)
cornerstoneTools.addTool(SculptorTool)
// Create tool group ONCE
let toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
@@ -163,10 +228,26 @@ function App() {
toolGroup.addTool(StackScrollTool.toolName, { loop: false })
toolGroup.addTool(PlanarRotateTool.toolName)
toolGroup.addTool(LengthTool.toolName)
toolGroup.addTool(ProbeTool.toolName)
toolGroup.addTool(ArrowAnnotateTool.toolName)
toolGroup.addTool(RectangleROITool.toolName)
toolGroup.addTool(EllipticalROITool.toolName)
toolGroup.addTool(ArrowAnnotateTool.toolName, {
getTextCallback: () => '',
})
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`
@@ -206,6 +287,25 @@ function App() {
// Create a stack volume
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);
// Render the image
@@ -255,9 +355,11 @@ function App() {
viewport.render()
}
}, [])
// Re-apply bindings when tool sets or ctrl changes
useEffect(() => {
applyMouseToolBindings();
}, [mouseToolBindings, applyMouseToolBindings]);
applyMouseToolBindings()
}, [mouseToolBindings, ctrlMouseToolBindings, ctrlPressed, applyMouseToolBindings])
// Close the menu when clicking outside
useEffect(() => {
@@ -272,6 +374,28 @@ function App() {
return () => document.removeEventListener("mousedown", handleClick);
}, [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 (
<div style={{ position: "relative", width: "512px", height: "512px" }}>
<button
@@ -303,6 +427,89 @@ function App() {
backgroundColor: "#000",
}}
></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 */}
<div
style={{
@@ -465,7 +672,29 @@ function App() {
<label style={{ marginRight: 8 }}>{btn.label} Button:</label>
<select
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={{
background: "#333",
color: "#fff",