start implmeentting proper crosshairs

This commit is contained in:
Ross
2026-06-08 12:51:08 +01:00
parent 1ddc9a810a
commit b961a806d9
+197 -2
View File
@@ -1153,6 +1153,10 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
};
const [crossHairsEnabled, setCrossHairsEnabled] = useState(false);
const [crosshairsProjectionType, setCrosshairsProjectionType] = useState<string>("None");
const [syncSlabThickness, setSyncSlabThickness] = useState<boolean>(true);
const [showCenterPoint, setShowCenterPoint] = useState<boolean>(true);
const slabThicknessSynchronizerRef = useRef<any>(null);
const [referenceLinesEnabled, setReferenceLinesEnabled] = useState(true);
const [showAdvancedMouseBindings, setShowAdvancedMouseBindings] = useState(false);
@@ -3884,8 +3888,16 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
mainToolGroup.addTool(CrosshairsTool.toolName);
}
mainToolGroup.setToolEnabled(CrosshairsTool.toolName);
const blendModeMap: Record<string, any> = {
"None": Enums.BlendModes.COMPOSITE,
"MIP": Enums.BlendModes.MAXIMUM_INTENSITY_BLEND,
"MinIP": Enums.BlendModes.MINIMUM_INTENSITY_BLEND,
"Average": Enums.BlendModes.AVERAGE_INTENSITY_BLEND,
};
const initialBlendMode = blendModeMap[crosshairsProjectionType] ?? Enums.BlendModes.COMPOSITE;
mainToolGroup.setToolConfiguration(CrosshairsTool.toolName, {
// Example: color: '#ffeb3b', lineWidth: 2
referenceLinesCenterGapRadius: showCenterPoint ? 0 : 20,
slabThicknessBlendMode: initialBlendMode,
});
mainToolGroup.setToolActive(CrosshairsTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }],
@@ -3918,7 +3930,114 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
});
cancelAnimationFrame(raf);
};
}, [elementRef, updateOverlay, viewportGrid, viewportModes, viewportImageIds]);
}, [elementRef, updateOverlay, viewportGrid, viewportModes, viewportImageIds, crossHairsEnabled]);
// Synchronize Crosshairs Projection Type
useEffect(() => {
if (!crossHairsEnabled) return;
const renderingEngine = renderingEngineRef.current;
if (!renderingEngine) return;
const blendModeMap: Record<string, any> = {
"None": Enums.BlendModes.COMPOSITE,
"MIP": Enums.BlendModes.MAXIMUM_INTENSITY_BLEND,
"MinIP": Enums.BlendModes.MINIMUM_INTENSITY_BLEND,
"Average": Enums.BlendModes.AVERAGE_INTENSITY_BLEND,
};
const targetBlendMode = blendModeMap[crosshairsProjectionType] ?? Enums.BlendModes.COMPOSITE;
// Update all active volume viewports
const visibleCount = viewportGrid.rows * viewportGrid.cols;
for (let i = 0; i < visibleCount; i++) {
if (viewportModes[i] === 'volume') {
const viewportId = `CT_${i}`;
const viewport = renderingEngine.getViewport(viewportId) as any;
if (viewport && typeof viewport.setBlendMode === "function") {
try {
viewport.setBlendMode(targetBlendMode);
viewport.render();
} catch (e) {
appLogger.warn("Failed to set blend mode for viewport", viewportId, e);
}
}
}
}
// Also update CrosshairsTool configuration
const mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID);
if (mainToolGroup && mainToolGroup.hasTool(CrosshairsTool.toolName)) {
mainToolGroup.setToolConfiguration(CrosshairsTool.toolName, {
slabThicknessBlendMode: targetBlendMode,
});
renderingEngine.render();
}
}, [crosshairsProjectionType, crossHairsEnabled, viewportGrid, viewportModes]);
// Synchronize Crosshairs Center Point (Gap Radius)
useEffect(() => {
if (!crossHairsEnabled) return;
const mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID);
if (mainToolGroup && mainToolGroup.hasTool(CrosshairsTool.toolName)) {
mainToolGroup.setToolConfiguration(CrosshairsTool.toolName, {
referenceLinesCenterGapRadius: showCenterPoint ? 0 : 20,
});
const renderingEngine = renderingEngineRef.current;
if (renderingEngine) {
renderingEngine.render();
}
}
}, [showCenterPoint, crossHairsEnabled]);
// Manage Slab Thickness Synchronizer
useEffect(() => {
// Clean up previous synchronizer
if (slabThicknessSynchronizerRef.current) {
try {
slabThicknessSynchronizerRef.current.destroy();
} catch (e) {
appLogger.warn("Failed to destroy slab thickness synchronizer", e);
}
slabThicknessSynchronizerRef.current = null;
}
if (!crossHairsEnabled || !syncSlabThickness) return;
const renderingEngine = renderingEngineRef.current;
if (!renderingEngine) return;
const activeVolumeViewportIds = [];
const visibleCount = viewportGrid.rows * viewportGrid.cols;
for (let i = 0; i < visibleCount; i++) {
if (viewportModes[i] === 'volume') {
activeVolumeViewportIds.push(`CT_${i}`);
}
}
if (activeVolumeViewportIds.length >= 2) {
try {
const { createSlabThicknessSynchronizer } = cornerstoneTools.synchronizers;
const slabSync = createSlabThicknessSynchronizer("slabThicknessSync");
slabThicknessSynchronizerRef.current = slabSync;
activeVolumeViewportIds.forEach((viewportId) => {
slabSync.add({
renderingEngineId,
viewportId,
});
});
} catch (e) {
appLogger.error("Failed to create and add to slab thickness synchronizer", e);
}
}
return () => {
if (slabThicknessSynchronizerRef.current) {
try {
slabThicknessSynchronizerRef.current.destroy();
} catch (e) { /* ignore */ }
slabThicknessSynchronizerRef.current = null;
}
};
}, [syncSlabThickness, crossHairsEnabled, viewportGrid, viewportModes]);
// Handler to apply a preset for a specific viewport
const handleApplyPreset = useCallback((viewportIdx: number, center: number, width: number) => {
const viewportId = `CT_${viewportIdx}`;
@@ -7174,6 +7293,82 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
>
{crossHairsEnabled ? "Disable" : "Enable"} Cross Reference Lines
</button>
{crossHairsEnabled && (
<div
style={{
background: "rgba(255, 255, 255, 0.05)",
border: "1px solid rgba(255, 255, 255, 0.1)",
borderRadius: "6px",
padding: "14px",
marginBottom: "18px",
color: "#e0e0e0",
fontSize: "14px",
display: "flex",
flexDirection: "column",
gap: "12px",
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
}}
>
<div style={{ fontWeight: "bold", borderBottom: "1px solid rgba(255, 255, 255, 0.1)", paddingBottom: "6px", color: "#fff" }}>
Crosshairs Options
</div>
{/* Projection Type Selector */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<label htmlFor="projection-type-select" style={{ fontSize: "12px", color: "#aaa", fontWeight: "bold" }}>
PROJECTION TYPE
</label>
<select
id="projection-type-select"
value={crosshairsProjectionType}
onChange={(e) => setCrosshairsProjectionType(e.target.value)}
style={{
padding: "8px 12px",
background: "#1e1e1e",
border: "1px solid #444",
borderRadius: "4px",
color: "#fff",
cursor: "pointer",
outline: "none",
fontSize: "13px",
}}
>
<option value="None">Standard (None)</option>
<option value="MIP">Maximum Intensity (MIP)</option>
<option value="MinIP">Minimum Intensity (MinIP)</option>
<option value="Average">Average (AvgIP)</option>
</select>
</div>
{/* Sync Slab Thickness */}
<div style={{ display: "flex", alignItems: "center", gap: "8px", cursor: "pointer", marginTop: "4px" }}>
<input
id="sync-slab-thickness-checkbox"
type="checkbox"
checked={syncSlabThickness}
onChange={(e) => setSyncSlabThickness(e.target.checked)}
style={{ cursor: "pointer", width: "16px", height: "16px" }}
/>
<label htmlFor="sync-slab-thickness-checkbox" style={{ cursor: "pointer", userSelect: "none", fontSize: "13px" }}>
Sync Slab Thickness
</label>
</div>
{/* Show Center Point */}
<div style={{ display: "flex", alignItems: "center", gap: "8px", cursor: "pointer" }}>
<input
id="show-center-point-checkbox"
type="checkbox"
checked={showCenterPoint}
onChange={(e) => setShowCenterPoint(e.target.checked)}
style={{ cursor: "pointer", width: "16px", height: "16px" }}
/>
<label htmlFor="show-center-point-checkbox" style={{ cursor: "pointer", userSelect: "none", fontSize: "13px" }}>
Show Center Point
</label>
</div>
</div>
)}
<button
style={{
padding: "10px 18px",