add basic thumbnail support
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user