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 [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 [referenceLinesEnabled, setReferenceLinesEnabled] = useState(true);
|
||||||
|
|
||||||
const [showAdvancedMouseBindings, setShowAdvancedMouseBindings] = useState(false);
|
const [showAdvancedMouseBindings, setShowAdvancedMouseBindings] = useState(false);
|
||||||
@@ -3884,8 +3888,16 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
mainToolGroup.addTool(CrosshairsTool.toolName);
|
mainToolGroup.addTool(CrosshairsTool.toolName);
|
||||||
}
|
}
|
||||||
mainToolGroup.setToolEnabled(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, {
|
mainToolGroup.setToolConfiguration(CrosshairsTool.toolName, {
|
||||||
// Example: color: '#ffeb3b', lineWidth: 2
|
referenceLinesCenterGapRadius: showCenterPoint ? 0 : 20,
|
||||||
|
slabThicknessBlendMode: initialBlendMode,
|
||||||
});
|
});
|
||||||
mainToolGroup.setToolActive(CrosshairsTool.toolName, {
|
mainToolGroup.setToolActive(CrosshairsTool.toolName, {
|
||||||
bindings: [{ mouseButton: MouseBindings.Primary }],
|
bindings: [{ mouseButton: MouseBindings.Primary }],
|
||||||
@@ -3918,7 +3930,114 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
});
|
});
|
||||||
cancelAnimationFrame(raf);
|
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
|
// Handler to apply a preset for a specific viewport
|
||||||
const handleApplyPreset = useCallback((viewportIdx: number, center: number, width: number) => {
|
const handleApplyPreset = useCallback((viewportIdx: number, center: number, width: number) => {
|
||||||
const viewportId = `CT_${viewportIdx}`;
|
const viewportId = `CT_${viewportIdx}`;
|
||||||
@@ -7174,6 +7293,82 @@ export default function App({ container_id, imageStacks, autoCacheStack, annotat
|
|||||||
>
|
>
|
||||||
{crossHairsEnabled ? "Disable" : "Enable"} Cross Reference Lines
|
{crossHairsEnabled ? "Disable" : "Enable"} Cross Reference Lines
|
||||||
</button>
|
</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
|
<button
|
||||||
style={{
|
style={{
|
||||||
padding: "10px 18px",
|
padding: "10px 18px",
|
||||||
|
|||||||
Reference in New Issue
Block a user