NeoScan_Radiologist/app/assets/dicom/dicom-viewer.html
2025-08-13 18:21:57 +05:30

503 lines
16 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DICOM Viewer</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
background: rgb(8, 0, 0);
font-family: Arial, sans-serif;
}
#dicomImage {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: white;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #2196F3;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
color: #F44336;
text-align: center;
padding: 20px;
}
.success {
color: #4CAF50;
text-align: center;
padding: 20px;
}
.debug {
position: fixed;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
max-width: 300px;
z-index: 1000;
}
</style>
</head>
<body>
<div id="dicomImage">
<div class="loading">
<div class="spinner"></div>
<div>Loading DICOM Viewer...</div>
</div>
</div>
<div id="debug" class="debug" style="display: none;">
<strong>Debug Info:</strong><br>
<div id="debugContent"></div>
</div>
<script>
// Global variables
let cornerstone = null;
let cornerstoneWADOImageLoader = null;
let dicomParser = null;
let isLoaded = false;
let currentDicomUrl = null;
let debugMode = true;
// Debug logging function
function debugLog(message, type = 'info') {
if (debugMode) {
console.log(`[DICOM Viewer] ${message}`);
const debugContent = document.getElementById('debugContent');
if (debugContent) {
const timestamp = new Date().toLocaleTimeString();
debugContent.innerHTML += `<div>[${timestamp}] ${message}</div>`;
}
}
}
// Show debug panel
function showDebug() {
if (debugMode) {
document.getElementById('debug').style.display = 'block';
}
}
// Function to load external libraries with retry
async function loadLibraries() {
try {
debugLog('Starting library loading process...');
showDebug();
// Load DICOM Parser first
await loadDicomParser();
// Load Cornerstone Core
await loadCornerstoneCore();
// Load Cornerstone WADO Image Loader with fallback
await loadCornerstoneWADO();
// Initialize viewer
initializeViewer();
// If we already have a DICOM URL, load it
if (currentDicomUrl) {
loadDicomImage(currentDicomUrl);
}
} catch (error) {
debugLog(`Error loading libraries: ${error.message}`, 'error');
showError(`Failed to load DICOM viewer libraries: ${error.message}`);
}
}
// Load DICOM Parser
function loadDicomParser() {
return new Promise((resolve, reject) => {
debugLog('Loading DICOM Parser...');
const dicomParserScript = document.createElement('script');
dicomParserScript.src = 'https://unpkg.com/dicom-parser@1.8.6/dist/dicomParser.min.js';
dicomParserScript.onload = () => {
dicomParser = window.dicomParser;
debugLog('DICOM Parser loaded successfully');
resolve();
};
dicomParserScript.onerror = (error) => {
debugLog(`Failed to load DICOM Parser: ${error}`, 'error');
reject(new Error('Failed to load DICOM Parser'));
};
// Set timeout for loading
const timeout = setTimeout(() => {
reject(new Error('DICOM Parser loading timeout'));
}, 10000);
dicomParserScript.onload = () => {
clearTimeout(timeout);
dicomParser = window.dicomParser;
debugLog('DICOM Parser loaded successfully');
resolve();
};
document.head.appendChild(dicomParserScript);
});
}
// Load Cornerstone Core
function loadCornerstoneCore() {
return new Promise((resolve, reject) => {
debugLog('Loading Cornerstone Core...');
const cornerstoneScript = document.createElement('script');
cornerstoneScript.src = 'https://unpkg.com/cornerstone-core@2.3.0/dist/cornerstone.js';
cornerstoneScript.onload = () => {
cornerstone = window.cornerstone;
debugLog('Cornerstone Core loaded successfully');
resolve();
};
cornerstoneScript.onerror = (error) => {
debugLog(`Failed to load Cornerstone Core: ${error}`, 'error');
reject(new Error('Failed to load Cornerstone Core'));
};
// Set timeout for loading
const timeout = setTimeout(() => {
reject(new Error('Cornerstone Core loading timeout'));
}, 10000);
cornerstoneScript.onload = () => {
clearTimeout(timeout);
cornerstone = window.cornerstone;
debugLog('Cornerstone Core loaded successfully');
resolve();
};
document.head.appendChild(cornerstoneScript);
});
}
// Load Cornerstone WADO Image Loader with fallback
function loadCornerstoneWADO() {
return new Promise((resolve, reject) => {
debugLog('Loading Cornerstone WADO Image Loader...');
// Try multiple sources for WADO loader
const wadoSources = [
'https://unpkg.com/cornerstone-wado-image-loader@4.17.1/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.16.0/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.15.0/dist/cornerstoneWADOImageLoader.js',
'https://cdn.jsdelivr.net/npm/cornerstone-wado-image-loader@4.0.1/dist/cornerstoneWADOImageLoader.js'
];
let currentSourceIndex = 0;
function tryNextSource() {
if (currentSourceIndex >= wadoSources.length) {
reject(new Error('All WADO Image Loader sources failed'));
return;
}
const currentSource = wadoSources[currentSourceIndex];
debugLog(`Trying WADO source ${currentSourceIndex + 1}: ${currentSource}`);
const wadoScript = document.createElement('script');
wadoScript.src = currentSource;
wadoScript.onload = () => {
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
debugLog(`Cornerstone WADO Image Loader loaded successfully from: ${currentSource}`);
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
debugLog(`WADO loader initialization failed: ${error.message}`, 'error');
currentSourceIndex++;
tryNextSource();
}
};
wadoScript.onerror = (error) => {
debugLog(`Failed to load WADO from ${currentSource}: ${JSON.stringify(error)}`, 'error');
currentSourceIndex++;
tryNextSource();
};
// Set timeout for loading
const timeout = setTimeout(() => {
debugLog(`WADO loader timeout from ${currentSource}`, 'error');
currentSourceIndex++;
tryNextSource();
}, 8000);
wadoScript.onload = () => {
clearTimeout(timeout);
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
debugLog(`Cornerstone WADO Image Loader loaded successfully from: ${currentSource}`);
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
debugLog(`WADO loader initialization failed: ${error.message}`, 'error');
currentSourceIndex++;
tryNextSource();
}
};
document.head.appendChild(wadoScript);
}
tryNextSource();
});
}
// Initialize the viewer
function initializeViewer() {
try {
const element = document.getElementById('dicomImage');
cornerstone.enable(element);
isLoaded = true;
debugLog('Cornerstone viewer initialized successfully');
} catch (error) {
debugLog(`Error initializing viewer: ${error.message}`, 'error');
showError(`Failed to initialize DICOM viewer: ${error.message}`);
}
}
// Load DICOM image with better error handling
async function loadDicomImage(dicomUrl) {
if (!isLoaded) {
debugLog('Libraries not loaded yet, storing URL');
currentDicomUrl = dicomUrl;
return;
}
try {
showLoading();
debugLog(`Loading DICOM image from: ${dicomUrl}`);
// Test if URL is accessible first
await testUrlAccessibility(dicomUrl);
// Try to load with DICOM Parser first for validation
try {
await validateDicomWithParser(dicomUrl);
debugLog('DICOM file validated with parser');
} catch (parserError) {
debugLog(`DICOM parser validation failed: ${parserError.message}`, 'warning');
// Continue anyway, as Cornerstone might still be able to handle it
}
const element = document.getElementById('dicomImage');
const image = await cornerstone.loadImage(`wadouri:${dicomUrl}`);
cornerstone.displayImage(element, image);
showSuccess('DICOM image loaded successfully');
debugLog('DICOM image loaded successfully');
// Send success message to React Native
sendMessageToReactNative({
type: 'success',
message: 'DICOM image loaded successfully'
});
} catch (error) {
debugLog(`Error loading DICOM image: ${error.message}`, 'error');
showError(`Failed to load DICOM image: ${error.message}`);
// Send error message to React Native
sendMessageToReactNative({
type: 'error',
message: `Failed to load DICOM image: ${error.message}`
});
}
}
// Validate DICOM file with parser
async function validateDicomWithParser(url) {
try {
debugLog('Validating DICOM file with parser...');
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
if (!dicomParser) {
throw new Error('DICOM Parser not available');
}
const dataSet = dicomParser.parseDicom(arrayBuffer);
debugLog(`DICOM file validated. Patient: ${dataSet.string('x00100010') || 'Unknown'}`);
debugLog(`Modality: ${dataSet.string('x00080060') || 'Unknown'}`);
debugLog(`Study Date: ${dataSet.string('x00080020') || 'Unknown'}`);
return dataSet;
} catch (error) {
debugLog(`DICOM parser validation failed: ${error.message}`, 'warning');
throw error;
}
}
// Test URL accessibility
async function testUrlAccessibility(url) {
try {
debugLog(`Testing URL accessibility: ${url}`);
const response = await fetch(url, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
debugLog(`Content-Type: ${contentType}`);
if (contentType && !contentType.includes('application/dicom') && !contentType.includes('image/')) {
debugLog(`Warning: Unexpected content type: ${contentType}`);
}
} catch (error) {
debugLog(`URL accessibility test failed: ${error.message}`, 'error');
throw new Error(`URL not accessible: ${error.message}`);
}
}
// Send message to React Native
function sendMessageToReactNative(data) {
if (window.ReactNativeWebView) {
try {
const message = JSON.stringify(data);
window.ReactNativeWebView.postMessage(message);
debugLog(`Message sent to React Native: ${message}`);
} catch (error) {
debugLog(`Failed to send message to React Native: ${error.message}`, 'error');
}
} else {
debugLog('ReactNativeWebView not available');
}
}
// Show loading state
function showLoading() {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="loading">
<div class="spinner"></div>
<div>Loading DICOM Image...</div>
</div>
`;
}
// Show error state
function showError(message) {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="error">
<h3>Error</h3>
<p>${message}</p>
<p>Please check the DICOM URL and try again.</p>
<button onclick="retryLoading()" style="background: #2196F3; color: white; border: none; padding: 10px 20px; border-radius: 5px; margin-top: 10px;">Retry</button>
</div>
`;
}
// Show success state
function showSuccess(message) {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="success">
<h3>Success</h3>
<p>${message}</p>
</div>
`;
// Clear success message after 2 seconds
setTimeout(() => {
if (element.querySelector('.success')) {
element.innerHTML = '';
}
}, 2000);
}
// Retry loading
function retryLoading() {
if (currentDicomUrl) {
debugLog('Retrying DICOM image loading...');
loadDicomImage(currentDicomUrl);
}
}
// Listen for messages from React Native
window.addEventListener('message', function(event) {
try {
const dicomUrl = event.data;
debugLog(`Received DICOM URL from React Native: ${dicomUrl}`);
if (dicomUrl && typeof dicomUrl === 'string') {
currentDicomUrl = dicomUrl;
if (isLoaded) {
loadDicomImage(dicomUrl);
}
}
} catch (error) {
debugLog(`Error processing message from React Native: ${error.message}`, 'error');
showError('Invalid message received from app');
}
});
// Start loading libraries when page loads
window.addEventListener('load', () => {
debugLog('Page loaded, starting library loading...');
loadLibraries();
});
// Send ready message to React Native
sendMessageToReactNative({
type: 'ready',
message: 'DICOM viewer HTML loaded with DICOM Parser support'
});
// Global error handler
window.addEventListener('error', function(event) {
debugLog(`Global error: ${event.error?.message || event.message}`, 'error');
});
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', function(event) {
debugLog(`Unhandled promise rejection: ${event.reason}`, 'error');
});
</script>
</body>
</html>