add basic thumbnail support

This commit is contained in:
Ross
2025-12-15 09:04:54 +00:00
parent 6ee9489a5f
commit 8a0a08000e
+99 -8
View File
@@ -252,6 +252,18 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
return null;
}
// Helper to detect external (non-DICOM) image ids
function isExternalImageId(imageId: string | null | undefined) {
if (!imageId) return false;
// Treat wadouri: as DICOM (our existing loader)
if (imageId.startsWith('wadouri:')) return false;
// blob: or data:image/ or common extensions
if (imageId.startsWith('blob:') || imageId.startsWith('data:image/')) return true;
const lower = imageId.toLowerCase();
if (lower.endsWith('.png') || lower.endsWith('.jpg') || lower.endsWith('.jpeg') || lower.endsWith('.gif')) return true;
return false;
}
const [stackOrderModified, setStackOrderModified] = useState(false);
const [loadingState, setLoadingState] = useState<null | "annotations" | "viewerState">(null);
// Load annotations and viewer state from props if provided
@@ -446,6 +458,43 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
return;
}
// If the chosen image is an external non-DICOM image, draw it to a canvas directly
if (isExternalImageId(chosenImageId)) {
try {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = chosenImageId as string;
await new Promise((resolve, reject) => {
img.onload = () => resolve(true);
img.onerror = (e) => reject(e);
});
const canvas = document.createElement('canvas');
const size = 160;
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
if (ctx) {
// draw with aspect-fit
const ratio = Math.min(size / img.width, size / img.height);
const w = img.width * ratio;
const h = img.height * ratio;
const x = (size - w) / 2;
const y = (size - h) / 2;
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, size, size);
ctx.drawImage(img, x, y, w, h);
const dataUrl = canvas.toDataURL('image/png');
setStackThumbnails(prev => ({ ...prev, [stackIdx]: dataUrl }));
} else {
setStackThumbnails(prev => ({ ...prev, [stackIdx]: 'BROKEN' }));
}
} catch (err) {
console.warn('generateThumbnail: failed to generate thumbnail for external image', err);
setStackThumbnails(prev => ({ ...prev, [stackIdx]: 'BROKEN' }));
}
return;
}
const renderingEngine = renderingEngineRef.current;
if (!renderingEngine) {
console.warn('generateThumbnail: no renderingEngine yet for', stackIdx);
@@ -971,14 +1020,30 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
const fileArray = Array.from(files).sort((a, b) => a.name.localeCompare(b.name));
let studyInstanceUID: string | undefined = undefined;
const arrayBuffer = await fileArray[0].arrayBuffer();
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
studyInstanceUID = dataSet.string('x0020000d');
console.log("StudyInstanceUID:", studyInstanceUID);
const imageIds = fileArray.map(
file => `wadouri:${URL.createObjectURL(file)}`
);
// If the first file is likely a DICOM (no mime or .dcm extension), try to parse.
const firstFile = fileArray[0];
const isImageFile = firstFile.type && firstFile.type.startsWith('image/');
if (!isImageFile) {
try {
const arrayBuffer = await firstFile.arrayBuffer();
const dataSet = dicomParser.parseDicom(new Uint8Array(arrayBuffer));
studyInstanceUID = dataSet.string('x0020000d');
console.log("StudyInstanceUID:", studyInstanceUID);
} catch (e) {
// not DICOM or failed to parse; leave UID undefined
console.debug('handleFilesSelected: first file is not a DICOM or failed to parse', e);
}
}
// Create imageIds: use wadouri: for DICOM files, direct blob URLs for image files
const imageIds = fileArray.map((file) => {
const isImage = file.type && file.type.startsWith('image/');
if (isImage) {
return URL.createObjectURL(file);
}
return `wadouri:${URL.createObjectURL(file)}`;
});
setAvailableStacks(prev => {
const next = [...prev, { imageIds, studyInstanceUID }];
@@ -1205,7 +1270,33 @@ function App({ container_id, imageStacks, autoCacheStack, annotationJson, viewer
if (viewportModes[i] === 'stack') {
if (imageIds && imageIds.length > 0) {
viewport.setStack(imageIds);
// If this is an external (non-DICOM) image stack, render an <img> element as a fallback
const firstId = imageIds[0];
if (isExternalImageId(firstId)) {
try {
// Clear existing content and insert an image element
element.innerHTML = '';
const img = document.createElement('img');
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'contain';
img.style.display = 'block';
img.src = firstId as string;
// allow drag to open in new tab etc.
img.setAttribute('draggable', 'false');
element.appendChild(img);
// Do not attach DICOM-specific tools to external images
mainToolGroup.removeViewports(renderingEngineId, viewportId);
} catch (err) {
console.warn('Failed to render external image in viewport', err);
}
} else {
try {
viewport.setStack(imageIds);
} catch (err) {
console.warn('viewport.setStack failed', err);
}
}
}
// CrosshairsTool is not applicable for stack viewports
if (!crossHairsEnabled) {