start grid setup
This commit is contained in:
+248
-19
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user