fix annotation selection
This commit is contained in:
@@ -397,6 +397,49 @@ function App({ container_id, imageStacks, autoCacheStack, ...props }: AppProps)
|
|||||||
const [metaModalOpen, setMetaModalOpen] = useState(false);
|
const [metaModalOpen, setMetaModalOpen] = useState(false);
|
||||||
const [metaModalContent, setMetaModalContent] = useState<any>(null);
|
const [metaModalContent, setMetaModalContent] = useState<any>(null);
|
||||||
|
|
||||||
|
const handleSelectAnnotation = (viewportIdx: number, uid: string | null) => {
|
||||||
|
// Use the official selection API for Cornerstone3D Tools
|
||||||
|
if (window.cornerstone3dTools?.annotation?.selection?.setAnnotationSelected) {
|
||||||
|
window.cornerstone3dTools.annotation.selection.setAnnotationSelected(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally, update your own UI state
|
||||||
|
setSelectedAnnUIDs(prev => {
|
||||||
|
const next = [...prev];
|
||||||
|
next[viewportIdx] = uid;
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteAnnotation = (viewportIdx: number, uid: string, currentAnnotations: any[]) => {
|
||||||
|
const allAnnotations = cornerstoneTools.annotation.state.getAllAnnotations() || [];
|
||||||
|
allAnnotations.forEach(ann => {
|
||||||
|
if (ann.annotationUID === uid) ann.isSelected = false;
|
||||||
|
});
|
||||||
|
cornerstoneTools.annotation.state.removeAnnotation(uid);
|
||||||
|
if (cornerstoneTools.annotation.state.triggerAnnotationRender) {
|
||||||
|
cornerstoneTools.annotation.state.triggerAnnotationRender();
|
||||||
|
} else if (renderingEngineRef.current) {
|
||||||
|
renderingEngineRef.current.render();
|
||||||
|
}
|
||||||
|
// Auto-select another annotation if available
|
||||||
|
const remaining = currentAnnotations.filter(ann => ann.annotationUID !== uid);
|
||||||
|
setSelectedAnnUIDs(prev => {
|
||||||
|
const next = [...prev];
|
||||||
|
const newSelected = remaining.length > 0 ? remaining[0].annotationUID : null;
|
||||||
|
next[viewportIdx] = newSelected;
|
||||||
|
allAnnotations.forEach(ann => {
|
||||||
|
ann.isSelected = ann.annotationUID === newSelected;
|
||||||
|
});
|
||||||
|
if (cornerstoneTools.annotation.state.triggerAnnotationRender) {
|
||||||
|
cornerstoneTools.annotation.state.triggerAnnotationRender();
|
||||||
|
} else if (renderingEngineRef.current) {
|
||||||
|
renderingEngineRef.current.render();
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Add this function to gather and format metadata:
|
// Add this function to gather and format metadata:
|
||||||
const handleShowMetadata = () => {
|
const handleShowMetadata = () => {
|
||||||
// Find the first visible viewport with images
|
// Find the first visible viewport with images
|
||||||
@@ -472,6 +515,28 @@ function App({ container_id, imageStacks, autoCacheStack, ...props }: AppProps)
|
|||||||
|
|
||||||
const [altPressed, setAltPressed] = useState(false);
|
const [altPressed, setAltPressed] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mainToolGroup = ToolGroupManager.getToolGroup(MAIN_TOOL_GROUP_ID);
|
||||||
|
if (!mainToolGroup) return;
|
||||||
|
|
||||||
|
// List all annotation tools you use
|
||||||
|
[
|
||||||
|
'ArrowAnnotate',
|
||||||
|
'NoLabelArrowAnnotate',
|
||||||
|
'Length',
|
||||||
|
'RectangleROI',
|
||||||
|
'EllipticalROI',
|
||||||
|
'PlanarFreehandROI',
|
||||||
|
'PlanarFreehandContourSegmentation',
|
||||||
|
// Add more if needed
|
||||||
|
].forEach(toolName => {
|
||||||
|
mainToolGroup.setToolConfiguration(toolName, {
|
||||||
|
color: '#00e676', // normal color
|
||||||
|
selectedColor: '#ffeb3b', // yellow for selected
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [MAIN_TOOL_GROUP_ID]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.altKey) setAltPressed(true);
|
if (e.altKey) setAltPressed(true);
|
||||||
@@ -1452,6 +1517,10 @@ function App({ container_id, imageStacks, autoCacheStack, ...props }: AppProps)
|
|||||||
const referenceStackObj = availableStacks[referenceStackIdx];
|
const referenceStackObj = availableStacks[referenceStackIdx];
|
||||||
const referenceUID = referenceStackObj?.studyInstanceUID;
|
const referenceUID = referenceStackObj?.studyInstanceUID;
|
||||||
|
|
||||||
|
const [selectedAnnUIDs, setSelectedAnnUIDs] = useState<(string | null)[]>(() =>
|
||||||
|
Array(MAX_VIEWPORTS).fill(null)
|
||||||
|
);
|
||||||
|
|
||||||
const numVisible = viewportGrid.rows * viewportGrid.cols;
|
const numVisible = viewportGrid.rows * viewportGrid.cols;
|
||||||
const viewports = [];
|
const viewports = [];
|
||||||
//for (let i = 0; i < MAX_VIEWPORTS; i++) {
|
//for (let i = 0; i < MAX_VIEWPORTS; i++) {
|
||||||
@@ -1653,6 +1722,22 @@ function App({ container_id, imageStacks, autoCacheStack, ...props }: AppProps)
|
|||||||
imageIds.includes(ann.metadata.referencedImageId)
|
imageIds.includes(ann.metadata.referencedImageId)
|
||||||
);
|
);
|
||||||
if (!stackAnnotations.length) return null;
|
if (!stackAnnotations.length) return null;
|
||||||
|
// Find the annotation(s) on the current image
|
||||||
|
const renderingEngine = renderingEngineRef.current;
|
||||||
|
const viewport = renderingEngine?.getViewport(`CT_${i}`);
|
||||||
|
const currentIdx = viewport?.getCurrentImageIdIndex?.() ?? 0;
|
||||||
|
const currentImageId = imageIds[currentIdx];
|
||||||
|
const currentAnnotations = stackAnnotations.filter(
|
||||||
|
ann => ann.metadata.referencedImageId === currentImageId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use per-viewport selection state
|
||||||
|
const selectedAnnUID = selectedAnnUIDs[i];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -1664,8 +1749,10 @@ function App({ container_id, imageStacks, autoCacheStack, ...props }: AppProps)
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
gap: 4,
|
gap: 4,
|
||||||
pointerEvents: "auto",
|
pointerEvents: "auto",
|
||||||
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Prev/Next buttons ... */}
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
background: "rgba(30,30,30,0.7)",
|
background: "rgba(30,30,30,0.7)",
|
||||||
@@ -1695,6 +1782,12 @@ function App({ container_id, imageStacks, autoCacheStack, ...props }: AppProps)
|
|||||||
if (prevIdx !== null && typeof viewport.setImageIdIndex === "function") {
|
if (prevIdx !== null && typeof viewport.setImageIdIndex === "function") {
|
||||||
csUtilities.jumpToSlice(viewport.element, { imageIndex: prevIdx });
|
csUtilities.jumpToSlice(viewport.element, { imageIndex: prevIdx });
|
||||||
updateOverlay(i, viewport);
|
updateOverlay(i, viewport);
|
||||||
|
// Select the first annotation on the new image
|
||||||
|
const newImageId = imageIds[prevIdx];
|
||||||
|
const annsOnImage = stackAnnotations.filter(
|
||||||
|
ann => ann.metadata.referencedImageId === newImageId
|
||||||
|
);
|
||||||
|
handleSelectAnnotation(i, annsOnImage.length > 0 ? annsOnImage[0].annotationUID : null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1729,11 +1822,71 @@ function App({ container_id, imageStacks, autoCacheStack, ...props }: AppProps)
|
|||||||
if (nextIdx !== null && typeof viewport.setImageIdIndex === "function") {
|
if (nextIdx !== null && typeof viewport.setImageIdIndex === "function") {
|
||||||
csUtilities.jumpToSlice(viewport.element, { imageIndex: nextIdx });
|
csUtilities.jumpToSlice(viewport.element, { imageIndex: nextIdx });
|
||||||
updateOverlay(i, viewport);
|
updateOverlay(i, viewport);
|
||||||
|
// Select the first annotation on the new image
|
||||||
|
const newImageId = imageIds[nextIdx];
|
||||||
|
const annsOnImage = stackAnnotations.filter(
|
||||||
|
ann => ann.metadata.referencedImageId === newImageId
|
||||||
|
);
|
||||||
|
handleSelectAnnotation(i, annsOnImage.length > 0 ? annsOnImage[0].annotationUID : null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">▶</span>
|
<span aria-hidden="true">▶</span>
|
||||||
</button>
|
</button>
|
||||||
|
{/* List and select/delete individual annotations */}
|
||||||
|
{currentAnnotations.length > 0 && (
|
||||||
|
<div style={{ display: "flex", gap: 2, alignItems: "center" }}>
|
||||||
|
{currentAnnotations.map(ann => (
|
||||||
|
<div
|
||||||
|
key={ann.annotationUID}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
background: selectedAnnUID === ann.annotationUID ? "rgb(4, 225, 0)" : "#444",
|
||||||
|
color: selectedAnnUID === ann.annotationUID ? "#222" : "#fff",
|
||||||
|
borderRadius: "4px",
|
||||||
|
padding: "2px 6px",
|
||||||
|
marginRight: 2,
|
||||||
|
cursor: "pointer",
|
||||||
|
border: selectedAnnUID === ann.annotationUID ? "2px solid #1976d2" : "none",
|
||||||
|
fontWeight: selectedAnnUID === ann.annotationUID ? "bold" : "normal",
|
||||||
|
}}
|
||||||
|
onClick={() => handleSelectAnnotation(i, ann.annotationUID)}
|
||||||
|
>
|
||||||
|
<span style={{ marginRight: 4 }}>
|
||||||
|
{selectedAnnUID === ann.annotationUID ? "★ " : ""}
|
||||||
|
{ann.metadata.toolName || "Annotation"}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
background: "#b71c1c",
|
||||||
|
color: "#fff",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "50%",
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
fontSize: "13px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
cursor: "pointer",
|
||||||
|
marginLeft: 2,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
opacity: 0.85,
|
||||||
|
}}
|
||||||
|
title="Delete this annotation"
|
||||||
|
aria-label="Delete annotation"
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleDeleteAnnotation(i, ann.annotationUID, currentAnnotations);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|||||||
Reference in New Issue
Block a user