start implmeentting proper crosshairs
This commit is contained in:
+197
-2
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user