2025-08-12 13:33:25 +03:00

930 lines
36 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}DICOM Image Viewer - Radiology{% endblock %}
{% block content %}
<div class="content">
<div class="container-fluid">
<!-- Page Header -->
<div class="row">
<div class="col-12">
<div class="page-header">
<div class="page-title">
<h4>DICOM Image Viewer</h4>
<h6>Advanced medical image viewing and analysis</h6>
</div>
<div class="page-btn">
<div class="btn-group">
<button type="button" class="btn btn-primary" onclick="openStudy()">
<i class="fa fa-folder-open"></i> Open Study
</button>
<button type="button" class="btn btn-info" onclick="compareStudies()">
<i class="fa fa-columns"></i> Compare
</button>
<button type="button" class="btn btn-success" onclick="exportImages()">
<i class="fa fa-download"></i> Export
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Study Information -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">Study Information</h5>
<div class="card-tools">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="toggleStudyInfo()">
<i class="fa fa-info-circle"></i> Study Info
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="toggleSeriesList()">
<i class="fa fa-list"></i> Series
</button>
<button type="button" class="btn btn-sm btn-outline-warning" onclick="toggleMeasurements()">
<i class="fa fa-ruler"></i> Measurements
</button>
</div>
</div>
</div>
<div class="card-body" id="studyInfoPanel">
<div class="row">
<div class="col-md-3">
<strong>Patient:</strong> {{ study.patient_name|default:"John Smith" }}<br>
<strong>MRN:</strong> {{ study.patient_mrn|default:"123456" }}<br>
<strong>DOB:</strong> {{ study.patient_dob|default:"1980-05-15"|date:"M d, Y" }}<br>
<strong>Sex:</strong> {{ study.patient_sex|default:"M" }}
</div>
<div class="col-md-3">
<strong>Study Date:</strong> {{ study.study_date|default:"Jan 15, 2024"|date:"M d, Y" }}<br>
<strong>Study Time:</strong> {{ study.study_time|default:"14:30" }}<br>
<strong>Accession #:</strong> {{ study.accession_number|default:"ACC123456" }}<br>
<strong>Study ID:</strong> {{ study.study_id|default:"STU001" }}
</div>
<div class="col-md-3">
<strong>Modality:</strong> {{ study.modality|default:"CT" }}<br>
<strong>Body Part:</strong> {{ study.body_part|default:"Chest" }}<br>
<strong>Protocol:</strong> {{ study.protocol|default:"Chest CT with Contrast" }}<br>
<strong>Institution:</strong> {{ study.institution|default:"General Hospital" }}
</div>
<div class="col-md-3">
<strong>Referring Physician:</strong> {{ study.referring_physician|default:"Dr. Johnson" }}<br>
<strong>Reading Radiologist:</strong> {{ study.radiologist|default:"Dr. Smith" }}<br>
<strong>Study Status:</strong> <span class="badge bg-success">{{ study.status|default:"Completed" }}</span><br>
<strong>Series Count:</strong> {{ study.series_count|default:3 }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Main Viewer Layout -->
<div class="row">
<!-- Series Navigator -->
<div class="col-md-2">
<div class="card">
<div class="card-header">
<h6 class="card-title">Series Navigator</h6>
</div>
<div class="card-body p-2">
<div class="series-list" id="seriesList">
<div class="series-item active" onclick="loadSeries(1)">
<div class="series-thumbnail">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiBmaWxsPSIjZjBmMGYwIi8+CjxwYXRoIGQ9Ik0yMCAyMEg0NFY0NEgyMFYyMFoiIGZpbGw9IiM2NjY2NjYiLz4KPC9zdmc+" alt="Series 1">
</div>
<div class="series-info">
<small><strong>Series 1</strong></small><br>
<small class="text-muted">Axial</small><br>
<small class="text-muted">120 images</small>
</div>
</div>
<div class="series-item" onclick="loadSeries(2)">
<div class="series-thumbnail">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiBmaWxsPSIjZjBmMGYwIi8+CjxwYXRoIGQ9Ik0yMCAyMEg0NFY0NEgyMFYyMFoiIGZpbGw9IiM4ODg4ODgiLz4KPC9zdmc+" alt="Series 2">
</div>
<div class="series-info">
<small><strong>Series 2</strong></small><br>
<small class="text-muted">Coronal</small><br>
<small class="text-muted">80 images</small>
</div>
</div>
<div class="series-item" onclick="loadSeries(3)">
<div class="series-thumbnail">
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiBmaWxsPSIjZjBmMGYwIi8+CjxwYXRoIGQ9Ik0yMCAyMEg0NFY0NEgyMFYyMFoiIGZpbGw9IiNhYWFhYWEiLz4KPC9zdmc+" alt="Series 3">
</div>
<div class="series-info">
<small><strong>Series 3</strong></small><br>
<small class="text-muted">Sagittal</small><br>
<small class="text-muted">60 images</small>
</div>
</div>
</div>
</div>
</div>
<!-- Image Navigator -->
<div class="card mt-3">
<div class="card-header">
<h6 class="card-title">Image Navigator</h6>
</div>
<div class="card-body p-2">
<div class="image-navigator">
<div class="navigator-controls mb-2">
<button class="btn btn-sm btn-outline-primary" onclick="firstImage()">
<i class="fa fa-step-backward"></i>
</button>
<button class="btn btn-sm btn-outline-primary" onclick="previousImage()">
<i class="fa fa-backward"></i>
</button>
<button class="btn btn-sm btn-outline-primary" onclick="playImages()" id="playBtn">
<i class="fa fa-play"></i>
</button>
<button class="btn btn-sm btn-outline-primary" onclick="nextImage()">
<i class="fa fa-forward"></i>
</button>
<button class="btn btn-sm btn-outline-primary" onclick="lastImage()">
<i class="fa fa-step-forward"></i>
</button>
</div>
<div class="image-slider mb-2">
<input type="range" class="form-range" id="imageSlider" min="1" max="120" value="60" onchange="goToImage(this.value)">
</div>
<div class="image-info text-center">
<small>Image <span id="currentImage">60</span> of <span id="totalImages">120</span></small>
</div>
</div>
</div>
</div>
</div>
<!-- Main Viewport -->
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h6 class="card-title">Image Viewport</h6>
<div class="card-tools">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="resetView()">
<i class="fa fa-home"></i> Reset
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="fitToWindow()">
<i class="fa fa-expand-arrows-alt"></i> Fit
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="actualSize()">
<i class="fa fa-search"></i> 1:1
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="toggleFullscreen()">
<i class="fa fa-expand"></i> Fullscreen
</button>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="image-viewport" id="imageViewport">
<!-- Main DICOM Image Display -->
<div class="dicom-image-container">
<canvas id="dicomCanvas" width="800" height="600"></canvas>
<!-- Image Overlay Information -->
<div class="image-overlay top-left">
<div class="overlay-text">
<div>{{ study.patient_name|default:"Smith, John" }}</div>
<div>{{ study.patient_mrn|default:"MRN: 123456" }}</div>
<div>{{ study.study_date|default:"2024-01-15" }}</div>
</div>
</div>
<div class="image-overlay top-right">
<div class="overlay-text">
<div>{{ study.institution|default:"General Hospital" }}</div>
<div>{{ study.modality|default:"CT" }} - {{ study.body_part|default:"Chest" }}</div>
<div>Series: <span id="currentSeries">1</span></div>
</div>
</div>
<div class="image-overlay bottom-left">
<div class="overlay-text">
<div>W: <span id="windowWidth">400</span></div>
<div>L: <span id="windowLevel">40</span></div>
<div>Zoom: <span id="zoomLevel">100</span>%</div>
</div>
</div>
<div class="image-overlay bottom-right">
<div class="overlay-text">
<div>Slice: <span id="slicePosition">60</span>/<span id="totalSlices">120</span></div>
<div>Thickness: <span id="sliceThickness">5.0</span>mm</div>
<div>Pixel: <span id="pixelValue">-</span> HU</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tools Panel -->
<div class="col-md-2">
<div class="card">
<div class="card-header">
<h6 class="card-title">Tools</h6>
</div>
<div class="card-body p-2">
<div class="tool-group mb-3">
<h6 class="tool-group-title">Navigation</h6>
<div class="btn-group-vertical w-100">
<button class="btn btn-sm btn-outline-primary active" onclick="selectTool('pan')" data-tool="pan">
<i class="fa fa-hand-paper"></i> Pan
</button>
<button class="btn btn-sm btn-outline-primary" onclick="selectTool('zoom')" data-tool="zoom">
<i class="fa fa-search-plus"></i> Zoom
</button>
<button class="btn btn-sm btn-outline-primary" onclick="selectTool('window')" data-tool="window">
<i class="fa fa-adjust"></i> Window/Level
</button>
</div>
</div>
<div class="tool-group mb-3">
<h6 class="tool-group-title">Measurements</h6>
<div class="btn-group-vertical w-100">
<button class="btn btn-sm btn-outline-success" onclick="selectTool('length')" data-tool="length">
<i class="fa fa-ruler"></i> Length
</button>
<button class="btn btn-sm btn-outline-success" onclick="selectTool('angle')" data-tool="angle">
<i class="fa fa-drafting-compass"></i> Angle
</button>
<button class="btn btn-sm btn-outline-success" onclick="selectTool('area')" data-tool="area">
<i class="fa fa-vector-square"></i> Area
</button>
<button class="btn btn-sm btn-outline-success" onclick="selectTool('roi')" data-tool="roi">
<i class="fa fa-circle"></i> ROI
</button>
</div>
</div>
<div class="tool-group mb-3">
<h6 class="tool-group-title">Annotations</h6>
<div class="btn-group-vertical w-100">
<button class="btn btn-sm btn-outline-warning" onclick="selectTool('arrow')" data-tool="arrow">
<i class="fa fa-arrow-right"></i> Arrow
</button>
<button class="btn btn-sm btn-outline-warning" onclick="selectTool('text')" data-tool="text">
<i class="fa fa-font"></i> Text
</button>
<button class="btn btn-sm btn-outline-warning" onclick="selectTool('freehand')" data-tool="freehand">
<i class="fa fa-pencil-alt"></i> Freehand
</button>
</div>
</div>
<div class="tool-group">
<h6 class="tool-group-title">Actions</h6>
<div class="btn-group-vertical w-100">
<button class="btn btn-sm btn-outline-danger" onclick="clearAnnotations()">
<i class="fa fa-eraser"></i> Clear All
</button>
<button class="btn btn-sm btn-outline-info" onclick="saveAnnotations()">
<i class="fa fa-save"></i> Save
</button>
</div>
</div>
</div>
</div>
<!-- Window/Level Presets -->
<div class="card mt-3">
<div class="card-header">
<h6 class="card-title">W/L Presets</h6>
</div>
<div class="card-body p-2">
<div class="preset-buttons">
<button class="btn btn-sm btn-outline-secondary w-100 mb-1" onclick="applyPreset('soft_tissue')">
Soft Tissue
</button>
<button class="btn btn-sm btn-outline-secondary w-100 mb-1" onclick="applyPreset('lung')">
Lung
</button>
<button class="btn btn-sm btn-outline-secondary w-100 mb-1" onclick="applyPreset('bone')">
Bone
</button>
<button class="btn btn-sm btn-outline-secondary w-100 mb-1" onclick="applyPreset('brain')">
Brain
</button>
<button class="btn btn-sm btn-outline-secondary w-100 mb-1" onclick="applyPreset('liver')">
Liver
</button>
<button class="btn btn-sm btn-outline-secondary w-100" onclick="applyPreset('mediastinum')">
Mediastinum
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Measurements Panel -->
<div class="row" id="measurementsPanel" style="display: none;">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">Measurements & Annotations</h5>
<div class="card-tools">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="exportMeasurements()">
<i class="fa fa-download"></i> Export
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm" id="measurementsTable">
<thead>
<tr>
<th>Type</th>
<th>Series</th>
<th>Image</th>
<th>Value</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="badge bg-success">Length</span></td>
<td>1</td>
<td>45</td>
<td>23.4 mm</td>
<td>Nodule diameter</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="goToMeasurement(1)">
<i class="fa fa-eye"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteMeasurement(1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
<tr>
<td><span class="badge bg-success">Area</span></td>
<td>1</td>
<td>52</td>
<td>145.2 mm²</td>
<td>Lesion area</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="goToMeasurement(2)">
<i class="fa fa-eye"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteMeasurement(2)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let currentTool = 'pan';
let currentSeries = 1;
let currentImage = 60;
let totalImages = 120;
let isPlaying = false;
let playInterval;
let zoomLevel = 100;
let windowWidth = 400;
let windowLevel = 40;
document.addEventListener('DOMContentLoaded', function() {
initializeViewer();
setupCanvasEvents();
});
function initializeViewer() {
const canvas = document.getElementById('dicomCanvas');
const ctx = canvas.getContext('2d');
// Draw placeholder DICOM image
drawPlaceholderImage(ctx);
// Update UI elements
updateImageInfo();
}
function drawPlaceholderImage(ctx) {
const canvas = ctx.canvas;
// Clear canvas
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw simulated medical image
ctx.fillStyle = '#333333';
ctx.fillRect(100, 100, 600, 400);
// Add some simulated anatomical structures
ctx.fillStyle = '#666666';
ctx.beginPath();
ctx.arc(400, 300, 80, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = '#999999';
ctx.beginPath();
ctx.arc(350, 250, 30, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.arc(450, 350, 25, 0, 2 * Math.PI);
ctx.fill();
// Add grid lines for reference
ctx.strokeStyle = '#222222';
ctx.lineWidth = 1;
for (let i = 0; i < canvas.width; i += 50) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, canvas.height);
ctx.stroke();
}
for (let i = 0; i < canvas.height; i += 50) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(canvas.width, i);
ctx.stroke();
}
}
function setupCanvasEvents() {
const canvas = document.getElementById('dicomCanvas');
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseup', handleMouseUp);
canvas.addEventListener('wheel', handleWheel);
canvas.addEventListener('contextmenu', e => e.preventDefault());
}
function handleMouseDown(e) {
console.log(`Mouse down with tool: ${currentTool}`);
// Tool-specific mouse down handling would go here
}
function handleMouseMove(e) {
const rect = e.target.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Update pixel value display (simulated)
const pixelValue = Math.floor(Math.random() * 200 - 100);
document.getElementById('pixelValue').textContent = pixelValue;
}
function handleMouseUp(e) {
console.log(`Mouse up with tool: ${currentTool}`);
// Tool-specific mouse up handling would go here
}
function handleWheel(e) {
e.preventDefault();
if (e.deltaY > 0) {
nextImage();
} else {
previousImage();
}
}
function loadSeries(seriesNumber) {
currentSeries = seriesNumber;
// Update active series in navigator
document.querySelectorAll('.series-item').forEach(item => {
item.classList.remove('active');
});
document.querySelectorAll('.series-item')[seriesNumber - 1].classList.add('active');
// Update series info
document.getElementById('currentSeries').textContent = seriesNumber;
// Update image count based on series
const imageCounts = [120, 80, 60];
totalImages = imageCounts[seriesNumber - 1];
currentImage = Math.floor(totalImages / 2);
document.getElementById('totalImages').textContent = totalImages;
document.getElementById('totalSlices').textContent = totalImages;
document.getElementById('imageSlider').max = totalImages;
document.getElementById('imageSlider').value = currentImage;
updateImageInfo();
console.log(`Loaded series ${seriesNumber}`);
}
function selectTool(toolName) {
currentTool = toolName;
// Update active tool button
document.querySelectorAll('[data-tool]').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`[data-tool="${toolName}"]`).classList.add('active');
// Change cursor based on tool
const canvas = document.getElementById('dicomCanvas');
const cursors = {
'pan': 'grab',
'zoom': 'zoom-in',
'window': 'crosshair',
'length': 'crosshair',
'angle': 'crosshair',
'area': 'crosshair',
'roi': 'crosshair',
'arrow': 'crosshair',
'text': 'text',
'freehand': 'crosshair'
};
canvas.style.cursor = cursors[toolName] || 'default';
console.log(`Selected tool: ${toolName}`);
}
function firstImage() {
currentImage = 1;
updateImageInfo();
console.log('Moved to first image');
}
function previousImage() {
if (currentImage > 1) {
currentImage--;
updateImageInfo();
}
}
function nextImage() {
if (currentImage < totalImages) {
currentImage++;
updateImageInfo();
}
}
function lastImage() {
currentImage = totalImages;
updateImageInfo();
console.log('Moved to last image');
}
function goToImage(imageNumber) {
currentImage = parseInt(imageNumber);
updateImageInfo();
}
function playImages() {
const playBtn = document.getElementById('playBtn');
if (isPlaying) {
clearInterval(playInterval);
playBtn.innerHTML = '<i class="fa fa-play"></i>';
isPlaying = false;
} else {
playInterval = setInterval(() => {
if (currentImage < totalImages) {
nextImage();
} else {
currentImage = 1;
updateImageInfo();
}
}, 200);
playBtn.innerHTML = '<i class="fa fa-pause"></i>';
isPlaying = true;
}
}
function updateImageInfo() {
document.getElementById('currentImage').textContent = currentImage;
document.getElementById('slicePosition').textContent = currentImage;
document.getElementById('imageSlider').value = currentImage;
// Simulate image loading
const ctx = document.getElementById('dicomCanvas').getContext('2d');
drawPlaceholderImage(ctx);
}
function resetView() {
zoomLevel = 100;
document.getElementById('zoomLevel').textContent = zoomLevel;
console.log('View reset to default');
}
function fitToWindow() {
zoomLevel = 85;
document.getElementById('zoomLevel').textContent = zoomLevel;
console.log('Image fitted to window');
}
function actualSize() {
zoomLevel = 100;
document.getElementById('zoomLevel').textContent = zoomLevel;
console.log('Image set to actual size');
}
function toggleFullscreen() {
const viewport = document.getElementById('imageViewport');
if (!document.fullscreenElement) {
viewport.requestFullscreen().then(() => {
console.log('Entered fullscreen mode');
});
} else {
document.exitFullscreen().then(() => {
console.log('Exited fullscreen mode');
});
}
}
function applyPreset(presetName) {
const presets = {
'soft_tissue': { width: 400, level: 40 },
'lung': { width: 1500, level: -600 },
'bone': { width: 1800, level: 400 },
'brain': { width: 80, level: 40 },
'liver': { width: 150, level: 30 },
'mediastinum': { width: 350, level: 50 }
};
const preset = presets[presetName];
if (preset) {
windowWidth = preset.width;
windowLevel = preset.level;
document.getElementById('windowWidth').textContent = windowWidth;
document.getElementById('windowLevel').textContent = windowLevel;
console.log(`Applied ${presetName} preset: W=${windowWidth}, L=${windowLevel}`);
}
}
function toggleStudyInfo() {
const panel = document.getElementById('studyInfoPanel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
function toggleSeriesList() {
const panel = document.getElementById('seriesList');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
function toggleMeasurements() {
const panel = document.getElementById('measurementsPanel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
function clearAnnotations() {
if (confirm('Clear all annotations and measurements?')) {
console.log('All annotations cleared');
alert('All annotations and measurements have been cleared.');
}
}
function saveAnnotations() {
console.log('Saving annotations...');
alert('Annotations and measurements saved successfully.');
}
function goToMeasurement(measurementId) {
console.log(`Navigating to measurement ${measurementId}`);
alert(`Navigating to measurement ${measurementId}`);
}
function deleteMeasurement(measurementId) {
if (confirm('Delete this measurement?')) {
console.log(`Deleting measurement ${measurementId}`);
alert(`Measurement ${measurementId} deleted.`);
}
}
function exportMeasurements() {
console.log('Exporting measurements...');
alert('Measurements exported to CSV file.');
}
function openStudy() {
console.log('Opening study browser...');
alert('Study browser would open here to select a different study.');
}
function compareStudies() {
console.log('Opening study comparison...');
alert('Study comparison interface would open here.');
}
function exportImages() {
const format = prompt('Export format:\n1. DICOM\n2. JPEG\n3. PNG\n4. TIFF\n\nEnter number:');
if (format && ['1', '2', '3', '4'].includes(format)) {
const formats = {
'1': 'DICOM',
'2': 'JPEG',
'3': 'PNG',
'4': 'TIFF'
};
console.log(`Exporting images as: ${formats[format]}`);
alert(`Images exported as ${formats[format]} format.`);
}
}
</script>
<style>
.image-viewport {
background-color: #000;
min-height: 600px;
position: relative;
overflow: hidden;
}
.dicom-image-container {
position: relative;
width: 100%;
height: 600px;
}
#dicomCanvas {
width: 100%;
height: 100%;
object-fit: contain;
}
.image-overlay {
position: absolute;
color: #00ff00;
font-family: monospace;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.7);
padding: 5px;
border-radius: 3px;
}
.image-overlay.top-left {
top: 10px;
left: 10px;
}
.image-overlay.top-right {
top: 10px;
right: 10px;
}
.image-overlay.bottom-left {
bottom: 10px;
left: 10px;
}
.image-overlay.bottom-right {
bottom: 10px;
right: 10px;
}
.overlay-text div {
margin-bottom: 2px;
}
.series-list {
max-height: 400px;
overflow-y: auto;
}
.series-item {
display: flex;
align-items: center;
padding: 8px;
margin-bottom: 8px;
border: 1px solid #dee2e6;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.series-item:hover {
background-color: #f8f9fa;
border-color: #007bff;
}
.series-item.active {
background-color: #e3f2fd;
border-color: #007bff;
}
.series-thumbnail {
width: 40px;
height: 40px;
margin-right: 8px;
border: 1px solid #ccc;
border-radius: 2px;
overflow: hidden;
}
.series-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.series-info {
flex: 1;
min-width: 0;
}
.image-navigator {
text-align: center;
}
.navigator-controls {
display: flex;
justify-content: space-between;
}
.navigator-controls .btn {
flex: 1;
margin: 0 1px;
}
.tool-group {
border-bottom: 1px solid #dee2e6;
padding-bottom: 1rem;
}
.tool-group:last-child {
border-bottom: none;
padding-bottom: 0;
}
.tool-group-title {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: #6c757d;
}
.btn-group-vertical .btn {
margin-bottom: 2px;
}
.btn-group-vertical .btn:last-child {
margin-bottom: 0;
}
.preset-buttons .btn {
font-size: 0.75rem;
}
@media (max-width: 768px) {
.image-overlay {
font-size: 10px;
padding: 3px;
}
.series-item {
flex-direction: column;
text-align: center;
}
.series-thumbnail {
margin-right: 0;
margin-bottom: 4px;
}
.navigator-controls .btn {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
}
/* Fullscreen styles */
.image-viewport:fullscreen {
background-color: #000;
display: flex;
align-items: center;
justify-content: center;
}
.image-viewport:fullscreen #dicomCanvas {
max-width: 100vw;
max-height: 100vh;
}
</style>
{% endblock %}