add a few new features
This commit is contained in:
@@ -12,8 +12,8 @@
|
||||
data-images="["http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm", "http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm", "http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm", "http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm", "http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm", "http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm", "http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm", "http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm", "http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm", "http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm", "http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm", "http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm", "http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm", "http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm", "http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm", "http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm", "http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm", "http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm", "http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm", "http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm", "http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm", "http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm"]"
|
||||
|
||||
data-auto-cache-stack="true"
|
||||
data-viewerstate='{"grid":{"rows":1,"cols":1},"modes":["stack"],"stacks":[{"i":["wadouri:http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm","wadouri:http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm","wadouri:http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm","wadouri:http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm","wadouri:http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm","wadouri:http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm","wadouri:http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm","wadouri:http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm","wadouri:http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm","wadouri:http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm","wadouri:http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm"],"u":"2.1163931708166249655895918785713305273005"}],"stackIdx":[0],"stackPos":[11],"props":[{"voiRange":{"lower":-707.7203723576732,"upper":1313.7029625973},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR"}],"cam":[{"viewUp":[0.012033252051397651,-0.42083716478070027,-0.9070563687942972],"viewPlaneNormal":[0.9998766662057034,-0.004091402933210223,0.015162875697125407],"position":[215.72314005628635,-63.00598963917986,24.208010679564232],"focalPoint":[-0.06152398404300907,-62.12301873163119,20.935691053779152],"parallelProjection":true,"parallelScale":117.33333333333333,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":148.19404114160696}]}
|
||||
'
|
||||
data-viewerstate='{"grid":{"rows":1,"cols":3},"modes":["stack","stack","volume"],"stacks":[{"i":["wadouri:http://localhost:8000/media/atlas/dicom/b879d6488a79d38ba374366f5543aab49fa8cb86.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9628d1d2fb457bc6f03efa8c980ef3165092230f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/9b02fae29bf6e775c5ba94b0b7158e10d4697ff6.dcm","wadouri:http://localhost:8000/media/atlas/dicom/2cb43b50760d9335df9a5f2d6bd645daf2a0c917.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0e1490ed17cdf2ceb1bcf069400f8f47a4437561.dcm","wadouri:http://localhost:8000/media/atlas/dicom/d4f6521a9e9a26975e1218ba3b7f48a78ac46d8f.dcm","wadouri:http://localhost:8000/media/atlas/dicom/09adc694456f1e51a5ab9c1cf7eaf9d748c938db.dcm","wadouri:http://localhost:8000/media/atlas/dicom/39963ce191beafe943a9cf980f7320adad74c1d5.dcm","wadouri:http://localhost:8000/media/atlas/dicom/6d9b09614f74b21a0b2778a5129d22b3cbafd3da.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24fa49c0e482bb885c92b71f74451f558940cb4b.dcm","wadouri:http://localhost:8000/media/atlas/dicom/bb586d17474988a07b1c90afce456abd7f69a753.dcm","wadouri:http://localhost:8000/media/atlas/dicom/24d1e5397fa97dd0c904568a20e4649fea13d1cf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/8720dc9b0b3b2c596f1d87acc951818307b7ad6a.dcm","wadouri:http://localhost:8000/media/atlas/dicom/db8ddb477387a41b023efae98e7e40be890886ef.dcm","wadouri:http://localhost:8000/media/atlas/dicom/48801a3aa1fea5d943cc934b74e9bc7273c1e944.dcm","wadouri:http://localhost:8000/media/atlas/dicom/613166d33e9e35898238f0bd334969c500822bbf.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0cec8d98bd80818c38c657b9c5934b30316920a2.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c5f854a8c2ac36da60c8a5a2bc215639d5302d20.dcm","wadouri:http://localhost:8000/media/atlas/dicom/5f0a6b97da797b7639361732599bfcf607c17dfc.dcm","wadouri:http://localhost:8000/media/atlas/dicom/512bfc7f9d07d54ae8f92af1ab98be390174d618.dcm","wadouri:http://localhost:8000/media/atlas/dicom/c98950f460ed153c14bd1d0d32e712397ace4564.dcm","wadouri:http://localhost:8000/media/atlas/dicom/0fb1283bab62d691bdaf7c7e47b8c674b600cddb.dcm"],"u":"2.1163931708166249655895918785713305273005"}],"stackIdx":[0,0,0],"stackPos":[0,0,195],"props":[{"voiRange":{"lower":137.5,"upper":1967.5},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR"},{"voiRange":{"lower":137.5,"upper":1967.5},"invert":false,"interpolationType":1,"VOILUTFunction":"LINEAR"},{"voiRange":{"lower":140,"upper":1999},"invert":false,"colormap":{"name":"Grayscale","opacity":1,"threshold":null}}],"cam":[{"viewUp":[-0.015545368194580078,-0.12045262008905411,0.9925973415374756],"viewPlaneNormal":[0.9998766662057034,-0.00409140293321022,0.015162875697125405],"position":[150.10646385504754,-62.737490075094016,23.212950690129],"focalPoint":[-66.0533860650337,-61.85298394159119,19.934941465766144],"parallelProjection":true,"parallelScale":170.33890849893743,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0},{"viewUp":[-0.015545368194580078,-0.12045262008905411,0.9925973415374756],"viewPlaneNormal":[0.9998766662057034,-0.00409140293321022,0.015162875697125405],"position":[150.10646385504754,-62.737490075094016,23.212950690129],"focalPoint":[-66.0533860650337,-61.85298394159119,19.934941465766144],"parallelProjection":true,"parallelScale":193.61549696093098,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0},{"viewUp":[0,0,1],"viewPlaneNormal":[0,-1,0],"position":[-6.060784295600456,-289.53396115406485,20.844714115543866],"focalPoint":[-6.060784295600456,-62.09846953239139,20.844714115543866],"parallelProjection":true,"parallelScale":150.85189284207627,"viewAngle":90,"flipHorizontal":false,"flipVertical":false,"rotation":0}]}'
|
||||
|
||||
></div>
|
||||
<!-- <div id="root2" class="dicom-viewer-root" style="max-height:500px; height:500px; overflow:auto;"
|
||||
data-images="["http://localhost:8000/media/atlas/dicom/4596ea7654b7a5074d2c3f40039c556b11fb01ae.dcm", "http://localhost:8000/media/atlas/dicom/b2fa301bc731d15be3e000402c9a79c114d17aa5.dcm", "http://localhost:8000/media/atlas/dicom/66310ef801fbecb37c5ced160c450dfd5ecc6d2d.dcm", "http://localhost:8000/media/atlas/dicom/63d2d117bf256a76a61190066f91eda6c4d67c8f.dcm", "http://localhost:8000/media/atlas/dicom/181a3575ea7fb8bf8f6449ff61e6e369a3247454.dcm", "http://localhost:8000/media/atlas/dicom/7c5c0538339266ac8897fa21c481b2f61172068e.dcm", "http://localhost:8000/media/atlas/dicom/1681410681308af7111af6644fed598fde304b96.dcm", "http://localhost:8000/media/atlas/dicom/4adcd3d7a64a9c59b530f730ce0d991bf7f27813.dcm", "http://localhost:8000/media/atlas/dicom/671e6004c7fb402a627a2282bbf304d7b4dcc12b.dcm", "http://localhost:8000/media/atlas/dicom/8d18ac56914b9de72b8aecced09654ec4b2c9bb7.dcm", "http://localhost:8000/media/atlas/dicom/74ce708a90dff0636fc4bc914fe47e8b6d9320bd.dcm", "http://localhost:8000/media/atlas/dicom/a96b6c9900570e076edf849a0ae3b52734ed2650.dcm", "http://localhost:8000/media/atlas/dicom/0183718d9336458478ec42130395d78e01492bd6.dcm", "http://localhost:8000/media/atlas/dicom/52eee968bbce5590274346eed43f1837f9daba11.dcm", "http://localhost:8000/media/atlas/dicom/961449f88197566c4643fcfd20f48ea4e810490b.dcm", "http://localhost:8000/media/atlas/dicom/1a16ad5b25e11739eadbcb8ea461072b100113a0.dcm", "http://localhost:8000/media/atlas/dicom/b89ddcc3f499059d2dedb0028a2bb1ce4abf5574.dcm", "http://localhost:8000/media/atlas/dicom/c1379c03a4e449c2fa88784d601cb766af907739.dcm", "http://localhost:8000/media/atlas/dicom/4fb532d5003c59a73f7e4248f365b1a8660ea710.dcm", "http://localhost:8000/media/atlas/dicom/e861b0de042a1f213cbccc995679aacd29c7ef53.dcm", "http://localhost:8000/media/atlas/dicom/1a854282afe264c3afff6a6e287cd646b2193a0c.dcm", "http://localhost:8000/media/atlas/dicom/f59d6d9bc33160abad3af8e2b246179b3574d884.dcm", "http://localhost:8000/media/atlas/dicom/04fadab0ed8b3f1925e601c4f9cfb792c95626c2.dcm", "http://localhost:8000/media/atlas/dicom/d45d0b12fb650e65daeeee4e5fa3b21c988597f6.dcm", "http://localhost:8000/media/atlas/dicom/e19c47b7787ac45dbc225319dfa143dc2c1fdf1f.dcm", "http://localhost:8000/media/atlas/dicom/dccabf3103a5be254a45457ce2d8d9ce9cb19a6a.dcm"]"
|
||||
|
||||
+295
-76
@@ -169,9 +169,26 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
|
||||
const [availableStacks, setAvailableStacks] = useState<StackEntry[]>([]);
|
||||
const [activeViewport, setActiveViewport] = useState<number | null>(0);
|
||||
const [viewportImageIds, setViewportImageIds] = useState<string[][]>(
|
||||
// After your useState for viewportImageIds:
|
||||
const [viewportImageIds, _setViewportImageIds] = useState<string[][]>(
|
||||
() => Array(MAX_VIEWPORTS).fill([])
|
||||
);
|
||||
|
||||
// Replace all setViewportImageIds calls with this function:
|
||||
const setViewportImageIds = (value: React.SetStateAction<string[][]>) => {
|
||||
// Log every call
|
||||
if (typeof value === "function") {
|
||||
// If called with a function, log the result
|
||||
_setViewportImageIds(prev => {
|
||||
const next = (value as (prev: string[][]) => string[][])(prev);
|
||||
console.log("setViewportImageIds (fn):", next);
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
console.log("setViewportImageIds:", value);
|
||||
_setViewportImageIds(value);
|
||||
}
|
||||
};
|
||||
const [selectedStackIdx, setSelectedStackIdx] = useState<number[]>(
|
||||
() => Array(MAX_VIEWPORTS).fill(0)
|
||||
);
|
||||
@@ -333,7 +350,6 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
if (autoCacheStack) {
|
||||
for (const imageId of imageIds) {
|
||||
if (imageId.startsWith("wadouri:")) {
|
||||
console.log("Caching metadata for imageId:", imageId);
|
||||
try {
|
||||
const url = imageId.slice(8);
|
||||
if (cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager) {
|
||||
@@ -442,6 +458,8 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
const [metaModalOpen, setMetaModalOpen] = useState(false);
|
||||
const [metaModalContent, setMetaModalContent] = useState<any>(null);
|
||||
|
||||
@@ -866,7 +884,8 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
}
|
||||
|
||||
if (viewportModes[i] === 'stack') {
|
||||
if (imageIds && !restoringStateRef.current) {
|
||||
//if (imageIds && !restoringStateRef.current) {
|
||||
if (imageIds && imageIds.length > 0) {
|
||||
viewport.setStack(imageIds);
|
||||
}
|
||||
// CrosshairsTool is not applicable for stack viewports
|
||||
@@ -1078,24 +1097,24 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
|
||||
return () => document.removeEventListener("mousedown", handleClick);
|
||||
}, [presetsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const preventExtraButtonDefault = (e: MouseEvent) => {
|
||||
// 4th button: button === 3, 5th button: button === 4
|
||||
if (e.button === 3 || e.button === 4) {
|
||||
e.preventDefault();
|
||||
//e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
window.addEventListener("mousedown", preventExtraButtonDefault, true);
|
||||
window.addEventListener("mouseup", preventExtraButtonDefault, true);
|
||||
window.addEventListener("auxclick", preventExtraButtonDefault, true);
|
||||
return () => {
|
||||
window.removeEventListener("mousedown", preventExtraButtonDefault, true);
|
||||
window.removeEventListener("mouseup", preventExtraButtonDefault, true);
|
||||
window.removeEventListener("auxclick", preventExtraButtonDefault, true);
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const preventExtraButtonDefault = (e: MouseEvent) => {
|
||||
// 4th button: button === 3, 5th button: button === 4
|
||||
if (e.button === 3 || e.button === 4) {
|
||||
e.preventDefault();
|
||||
//e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
window.addEventListener("mousedown", preventExtraButtonDefault, true);
|
||||
window.addEventListener("mouseup", preventExtraButtonDefault, true);
|
||||
window.addEventListener("auxclick", preventExtraButtonDefault, true);
|
||||
return () => {
|
||||
window.removeEventListener("mousedown", preventExtraButtonDefault, true);
|
||||
window.removeEventListener("mouseup", preventExtraButtonDefault, true);
|
||||
window.removeEventListener("auxclick", preventExtraButtonDefault, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
@@ -1164,47 +1183,47 @@ useEffect(() => {
|
||||
};
|
||||
}, [setAvailableStacks, setViewportImageIds, setSelectedStackIdx, setLoadedImageIdsAndCacheMeta]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orderBySliceLocation) return;
|
||||
useEffect(() => {
|
||||
if (!orderBySliceLocation) return;
|
||||
|
||||
// Only sort if all metadata is loaded for all viewports
|
||||
const allLoaded = viewportImageIds.every(imageIds =>
|
||||
!imageIds || imageIds.length === 0 ||
|
||||
imageIds.every(id => !!metaData.get("imagePlaneModule", id))
|
||||
);
|
||||
if (!allLoaded) return;
|
||||
// Only sort if all metadata is loaded for all viewports
|
||||
const allLoaded = viewportImageIds.every(imageIds =>
|
||||
!imageIds || imageIds.length === 0 ||
|
||||
imageIds.every(id => !!metaData.get("imagePlaneModule", id))
|
||||
);
|
||||
if (!allLoaded) return;
|
||||
|
||||
setViewportImageIds(prev => {
|
||||
let modified = false;
|
||||
const next = prev.map(imageIds => {
|
||||
if (!imageIds || imageIds.length === 0) return imageIds;
|
||||
setViewportImageIds(prev => {
|
||||
let modified = false;
|
||||
const next = prev.map(imageIds => {
|
||||
if (!imageIds || imageIds.length === 0) return imageIds;
|
||||
|
||||
const withSliceLoc = imageIds.map(id => ({
|
||||
imageId: id,
|
||||
sliceLoc: (() => {
|
||||
const meta = metaData.get('imagePlaneModule', id);
|
||||
return meta && meta.sliceLocation !== undefined ? Number(meta.sliceLocation) : null;
|
||||
})(),
|
||||
}));
|
||||
const withSliceLoc = imageIds.map(id => ({
|
||||
imageId: id,
|
||||
sliceLoc: (() => {
|
||||
const meta = metaData.get('imagePlaneModule', id);
|
||||
return meta && meta.sliceLocation !== undefined ? Number(meta.sliceLocation) : null;
|
||||
})(),
|
||||
}));
|
||||
|
||||
const sorted = [...withSliceLoc].sort((a, b) => {
|
||||
if (a.sliceLoc !== null && b.sliceLoc !== null) {
|
||||
return a.sliceLoc - b.sliceLoc;
|
||||
const sorted = [...withSliceLoc].sort((a, b) => {
|
||||
if (a.sliceLoc !== null && b.sliceLoc !== null) {
|
||||
return a.sliceLoc - b.sliceLoc;
|
||||
}
|
||||
return a.imageId.localeCompare(b.imageId);
|
||||
});
|
||||
|
||||
const sortedIds = sorted.map(obj => obj.imageId);
|
||||
if (JSON.stringify(sortedIds) !== JSON.stringify(imageIds)) {
|
||||
modified = true;
|
||||
return sortedIds;
|
||||
}
|
||||
return a.imageId.localeCompare(b.imageId);
|
||||
return imageIds;
|
||||
});
|
||||
|
||||
const sortedIds = sorted.map(obj => obj.imageId);
|
||||
if (JSON.stringify(sortedIds) !== JSON.stringify(imageIds)) {
|
||||
modified = true;
|
||||
return sortedIds;
|
||||
}
|
||||
return imageIds;
|
||||
if (modified) setStackOrderModified(true);
|
||||
return next;
|
||||
});
|
||||
if (modified) setStackOrderModified(true);
|
||||
return next;
|
||||
});
|
||||
}, [orderBySliceLocation, availableStacks]);
|
||||
}, [orderBySliceLocation, availableStacks]);
|
||||
|
||||
function updateActiveReferenceLineViewport(mainToolGroup: cornerstoneTools.Types.IToolGroup) {
|
||||
// Ensure the main tool group is defined (it won't be during setup)
|
||||
@@ -1248,7 +1267,7 @@ useEffect(() => {
|
||||
viewportGrid.cols !== 1 ||
|
||||
availableStacks.length !== 1 ||
|
||||
!viewportImageIds[0] ||
|
||||
viewportImageIds[0].length === 0 ||
|
||||
viewportImageIds[0].length === 0 ||
|
||||
stackOrderModified
|
||||
) {
|
||||
alert("Truncation is only allowed when a single stack is loaded in a single viewport (and the order has not been modified in the viewer).");
|
||||
@@ -1320,6 +1339,33 @@ useEffect(() => {
|
||||
window[`truncateStack_${apiKey}`] = truncateStack;
|
||||
window[`getCurrentStackPosition_${apiKey}`] = getCurrentStackPosition;
|
||||
|
||||
window[`loadAdditionalStack_${apiKey}`] = async (imageIds: string[], studyInstanceUID?: string) => {
|
||||
if (!Array.isArray(imageIds) || imageIds.length === 0) return;
|
||||
// Optionally extract StudyInstanceUID if not provided
|
||||
let uid = studyInstanceUID;
|
||||
if (!uid && imageIds[0]?.startsWith("wadouri:")) {
|
||||
try {
|
||||
const url = imageIds[0].slice(8);
|
||||
const response = await fetch(url);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
|
||||
uid = dataSet.string('x0020000d');
|
||||
} catch (e) {
|
||||
// Ignore errors, fallback to undefined
|
||||
}
|
||||
}
|
||||
setAvailableStacks(prev => [...prev, { imageIds, studyInstanceUID: uid }]);
|
||||
// Optionally, auto-populate a viewport with the new stack if there is an empty viewport
|
||||
setViewportImageIds(prev => {
|
||||
const next = [...prev];
|
||||
const emptyIdx = next.findIndex(ids => !ids || ids.length === 0);
|
||||
if (emptyIdx !== -1) {
|
||||
next[emptyIdx] = imageIds;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
setLoadedImageIdsAndCacheMeta(imageIds);
|
||||
};
|
||||
|
||||
// Export the current viewer state as JSON
|
||||
window[`exportViewerState_${apiKey}`] = () => {
|
||||
@@ -1372,7 +1418,6 @@ useEffect(() => {
|
||||
return JSON.stringify(state);
|
||||
};
|
||||
|
||||
// Import and restore a viewer state from JSON (using stack indices)
|
||||
window[`importViewerState_${apiKey}`] = (json: string) => {
|
||||
try {
|
||||
restoringStateRef.current = true;
|
||||
@@ -1391,38 +1436,101 @@ useEffect(() => {
|
||||
setSelectedStackIdx(state.stackIdx);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
// --- Delay the start of tryRestore until after the next paint ---
|
||||
const maxAttempts = 30;
|
||||
let attempts = 0;
|
||||
|
||||
async function tryRestore() {
|
||||
const renderingEngine = renderingEngineRef.current;
|
||||
if (!renderingEngine) {
|
||||
restoringStateRef.current = false;
|
||||
return;
|
||||
}
|
||||
const numActive = state.grid?.rows * state.grid?.cols || 1;
|
||||
for (let i = 0; i < numActive; i++) {
|
||||
const viewportId = `CT_${i}`;
|
||||
const viewport = renderingEngine.getViewport(viewportId);
|
||||
if (!viewport) continue;
|
||||
let allReady = true;
|
||||
for (let i = 0; i < numActive; i++) {
|
||||
const viewport = renderingEngine.getViewport(`CT_${i}`);
|
||||
if (!viewport) { allReady = false; break; }
|
||||
const stackIdx = state.stackIdx?.[i];
|
||||
const stackObj = state.stacks?.[stackIdx];
|
||||
const importedImageIds = stackObj?.i || [];
|
||||
if (!importedImageIds.length) continue;
|
||||
const viewportImageIds = viewport.getImageIds?.() || [];
|
||||
if (
|
||||
viewportImageIds.length !== importedImageIds.length ||
|
||||
!viewportImageIds.every((id, idx) => id === importedImageIds[idx])
|
||||
) {
|
||||
console.warn(
|
||||
`Viewport ${i}: importedImageIds do not match viewportImageIds`,
|
||||
{ importedImageIds, viewportImageIds }
|
||||
);
|
||||
allReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allReady && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
setTimeout(() => { tryRestore(); }, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore stack position
|
||||
if (
|
||||
state.stackPos &&
|
||||
typeof state.stackPos[i] === "number" &&
|
||||
typeof viewport.setImageIdIndex === "function"
|
||||
) {
|
||||
if (csUtilities && csUtilities.jumpToSlice) {
|
||||
csUtilities.jumpToSlice(viewport.element, { imageIndex: state.stackPos[i] });
|
||||
} else {
|
||||
viewport.setImageIdIndex(state.stackPos[i]);
|
||||
console.log("HELLO");
|
||||
// Now safe to restore state for ALL viewports
|
||||
for (let i = 0; i < numActive; i++) {
|
||||
console.log("Restoring state for viewport", i);
|
||||
const viewport = renderingEngine.getViewport(`CT_${i}`);
|
||||
if (!viewport) continue;
|
||||
const stackIdx = state.stackIdx?.[i];
|
||||
const stackObj = state.stacks?.[stackIdx];
|
||||
const importedImageIds = stackObj?.i || [];
|
||||
if (!importedImageIds.length) continue;
|
||||
|
||||
console.log("mode", state.modes?.[i]);
|
||||
|
||||
if (state.modes?.[i] === "volume") {
|
||||
// --- Restore volume ---
|
||||
console.log("Restoring volume for viewport", i, importedImageIds);
|
||||
const volumeId = `myVolumeId_${i}`;
|
||||
const volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: importedImageIds });
|
||||
volume.load();
|
||||
viewport.setVolumes([{ volumeId }]);
|
||||
viewport.render();
|
||||
} else {
|
||||
// --- Restore stack ---
|
||||
if (viewport.setStack) {
|
||||
viewport.setStack(importedImageIds);
|
||||
viewport.render();
|
||||
}
|
||||
// Restore stack position
|
||||
if (
|
||||
state.stackPos &&
|
||||
typeof state.stackPos[i] === "number" &&
|
||||
typeof viewport.setImageIdIndex === "function"
|
||||
) {
|
||||
if (csUtilities && csUtilities.jumpToSlice) {
|
||||
csUtilities.jumpToSlice(viewport.element, { imageIndex: state.stackPos[i] });
|
||||
} else {
|
||||
viewport.setImageIdIndex(state.stackPos[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore properties
|
||||
// Restore properties (defensive colormap check)
|
||||
if (
|
||||
state.props &&
|
||||
state.props[i] &&
|
||||
typeof viewport.setProperties === "function"
|
||||
) {
|
||||
viewport.setProperties(state.props[i]);
|
||||
const propsToRestore = { ...state.props[i] };
|
||||
if (
|
||||
propsToRestore.colormap !== undefined &&
|
||||
propsToRestore.colormap !== null &&
|
||||
typeof propsToRestore.colormap !== "string" &&
|
||||
(typeof propsToRestore.colormap !== "object" || !("name" in propsToRestore.colormap))
|
||||
) {
|
||||
delete propsToRestore.colormap;
|
||||
}
|
||||
viewport.setProperties(propsToRestore);
|
||||
viewport.render();
|
||||
}
|
||||
|
||||
@@ -1437,13 +1545,16 @@ useEffect(() => {
|
||||
}
|
||||
}
|
||||
restoringStateRef.current = false;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Delay start until after next paint
|
||||
setTimeout(() => { tryRestore(); }, 0);
|
||||
|
||||
} catch (e) {
|
||||
restoringStateRef.current = false;
|
||||
alert("Failed to import viewer state: " + e);
|
||||
}
|
||||
};
|
||||
|
||||
// Export all annotations using cornerstoneTools.annotation.state.getAllAnnotations()
|
||||
window[`exportAnnotations_${apiKey}`] = () => {
|
||||
try {
|
||||
@@ -1669,6 +1780,7 @@ useEffect(() => {
|
||||
window[`resetViewer_${apiKey}`] = undefined;
|
||||
window[`truncateStack_${apiKey}`] = undefined;
|
||||
window[`getCurrentStackPosition_${apiKey}`] = undefined;
|
||||
window[`loadAdditionalStack_${apiKey}`] = undefined;
|
||||
};
|
||||
}, [
|
||||
viewportGrid,
|
||||
@@ -1681,9 +1793,77 @@ useEffect(() => {
|
||||
setSelectedStackIdx,
|
||||
setAvailableStacks,
|
||||
setViewportImageIds,
|
||||
setAvailableStacks,
|
||||
setViewportImageIds,
|
||||
setLoadedImageIdsAndCacheMeta,
|
||||
]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
function handleArrowKeyNavigation(e: KeyboardEvent) {
|
||||
if (activeViewport === null) return;
|
||||
const renderingEngine = renderingEngineRef.current;
|
||||
if (!renderingEngine) return;
|
||||
const viewport = renderingEngine.getViewport(`CT_${activeViewport}`);
|
||||
if (!viewport) return;
|
||||
|
||||
// --- Stack mode navigation ---
|
||||
if (viewportModes[activeViewport] === "stack") {
|
||||
if (
|
||||
typeof viewport.getCurrentImageIdIndex !== "function" ||
|
||||
typeof viewport.setImageIdIndex !== "function"
|
||||
) return;
|
||||
const imageIds = viewportImageIds[activeViewport];
|
||||
if (!imageIds || imageIds.length === 0) return;
|
||||
let idx = viewport.getCurrentImageIdIndex();
|
||||
|
||||
if (e.key === "ArrowUp" || e.key === "ArrowRight") {
|
||||
idx = idx < imageIds.length - 1 ? idx + 1 : 0;
|
||||
csUtilities.jumpToSlice(viewport.element, { imageIndex: idx });
|
||||
updateOverlay(activeViewport, viewport);
|
||||
e.preventDefault();
|
||||
} else if (e.key === "ArrowDown" || e.key === "ArrowLeft") {
|
||||
idx = idx > 0 ? idx - 1 : imageIds.length - 1;
|
||||
csUtilities.jumpToSlice(viewport.element, { imageIndex: idx });
|
||||
updateOverlay(activeViewport, viewport);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Volume mode navigation ---
|
||||
if (viewportModes[activeViewport] === "volume") {
|
||||
// Try to scroll through slices in the current orientation
|
||||
if (
|
||||
typeof viewport.getSliceIndex === "function" &&
|
||||
typeof viewport.setSliceIndex === "function" &&
|
||||
typeof viewport.getNumberOfSlices === "function"
|
||||
) {
|
||||
const numSlices = viewport.getNumberOfSlices();
|
||||
let idx = viewport.getSliceIndex();
|
||||
if (numSlices > 1) {
|
||||
if (e.key === "ArrowUp" || e.key === "ArrowRight") {
|
||||
idx = idx < numSlices - 1 ? idx + 1 : 0;
|
||||
viewport.setSliceIndex(idx);
|
||||
viewport.render();
|
||||
updateOverlay(activeViewport, viewport);
|
||||
e.preventDefault();
|
||||
} else if (e.key === "ArrowDown" || e.key === "ArrowLeft") {
|
||||
idx = idx > 0 ? idx - 1 : numSlices - 1;
|
||||
viewport.setSliceIndex(idx);
|
||||
viewport.render();
|
||||
updateOverlay(activeViewport, viewport);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", handleArrowKeyNavigation);
|
||||
return () => window.removeEventListener("keydown", handleArrowKeyNavigation);
|
||||
}, [activeViewport, viewportModes, viewportImageIds, updateOverlay]);
|
||||
|
||||
|
||||
|
||||
// Reference stack and UID for study comparison (use active viewport)
|
||||
const referenceStackIdx = activeViewport !== null ? selectedStackIdx[activeViewport] : 0;
|
||||
@@ -2719,6 +2899,45 @@ useEffect(() => {
|
||||
onFocus={() => setGridBtnActive(true)}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
{/* --- Fullscreen button next to grid button --- */}
|
||||
<button
|
||||
style={{
|
||||
position: "relative",
|
||||
top: gridBtnActive ? "0" : "-22px",
|
||||
opacity: gridBtnActive ? 1 : 0.5,
|
||||
transition: "top 0.25s cubic-bezier(.4,2,.6,1), opacity 0.2s",
|
||||
padding: "6px 16px",
|
||||
background: "#222",
|
||||
color: "#fff",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
fontWeight: "bold",
|
||||
cursor: "pointer",
|
||||
fontSize: "15px",
|
||||
marginLeft: "4px",
|
||||
marginRight: "8px",
|
||||
boxShadow: "0 2px 8px rgba(0,0,0,0.2)",
|
||||
pointerEvents: gridBtnActive ? "auto" : "none",
|
||||
zIndex: 2,
|
||||
display: gridBtnActive ? "block" : "none",
|
||||
}}
|
||||
title="Toggle Fullscreen"
|
||||
onClick={() => {
|
||||
const root = document.querySelector(".dicom-viewer-root") as HTMLElement;
|
||||
if (!root) return;
|
||||
if (document.fullscreenElement === root) {
|
||||
document.exitFullscreen();
|
||||
} else if (root.requestFullscreen) {
|
||||
root.requestFullscreen();
|
||||
} else if ((root as any).webkitRequestFullscreen) {
|
||||
(root as any).webkitRequestFullscreen();
|
||||
} else if ((root as any).msRequestFullscreen) {
|
||||
(root as any).msRequestFullscreen();
|
||||
}
|
||||
}}
|
||||
>
|
||||
⛶ Fullscreen
|
||||
</button>
|
||||
<button
|
||||
className="grid-menu-btn"
|
||||
style={{
|
||||
|
||||
Reference in New Issue
Block a user