653 lines
20 KiB
Markdown
653 lines
20 KiB
Markdown
# Аналитический документ: DXA Quality Assessment System
|
||
|
||
## Содержание
|
||
|
||
1. [Обзор системы](#обзор-системы)
|
||
2. [Архитектура](#архитектура)
|
||
3. [Компоненты системы](#компоненты-системы)
|
||
4. [Пайплайн обработки](#пайплайн-обработки)
|
||
5. [API Endpoints](#api-endpoints)
|
||
6. [Модели и алгоритмы](#модели-и-алгоритмы)
|
||
7. [PlantUML Диаграммы](#plantuml-диаграммы)
|
||
|
||
---
|
||
|
||
## Обзор системы
|
||
|
||
Система DXA Quality Assessment — это медицинский AI-сервис для автоматизированной оценки качества исследований DXA (денситометрия костей). Система анализирует DICOM файлы и определяет:
|
||
|
||
- **Анатомическую область**: позвоночник (spine) или бедро (hip_left/hip_right)
|
||
- **Качество изображения**: OK (класс 0) или нарушение (класс 1)
|
||
- **Тип нарушения**: движение, артефакты, позиционирование, ROI, ротация
|
||
|
||
### Целевое назначение
|
||
|
||
| Параметр | Значение |
|
||
|----------|----------|
|
||
| Тип | Медицинский AI сервис |
|
||
| Входные данные | DICOM файлы (DXA исследования) |
|
||
| Выходные данные | JSON / XLSX отчеты |
|
||
| Тип классификации | Бинарный (OK / Violation) |
|
||
| Целевые регионы | Позвоночник (L1-L4), Бедро (левое/правое) |
|
||
|
||
---
|
||
|
||
## Архитектура
|
||
|
||
### Высокоуровневая архитектура
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ DXA Quality Assessment │
|
||
├─────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
|
||
│ │ Клиент │───▶│ FastAPI │───▶│ Orchestrator │ │
|
||
│ │ (Web UI) │ │ Server │ │ (Pipeline Control) │ │
|
||
│ └──────────────┘ └──────────────┘ └───────────┬──────────────┘ │
|
||
│ │ │
|
||
│ ┌───────────────────┼───────────────┐ │
|
||
│ ▼ ▼ ▼ │
|
||
│ ┌───────────────┐ ┌─────────────┐ ┌──────────┐ │
|
||
│ │ DXA Classifier │ │ Segmentor │ │ Quality │ │
|
||
│ │ (ResNet18) │ │ (Threshold) │ │ Scorer │ │
|
||
│ └───────────────┘ └─────────────┘ └──────────┘ │
|
||
│ │
|
||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ Detailed Assessment Module │ │
|
||
│ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌───────┐ ┌────────┐ │ │
|
||
│ │ │ Motion │ │ Artifact │ │ Position│ │ ROI │ │Rotation│ │ │
|
||
│ │ │Detector │ │ Detector │ │Validator│ │ Check │ │ Checker│ │ │
|
||
│ │ └─────────┘ └──────────┘ └─────────┘ └───────┘ └────────┘ │ │
|
||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Технологический стек
|
||
|
||
| Компонент | Технология |
|
||
|-----------|------------|
|
||
| Backend | Python 3.10, FastAPI, Uvicorn |
|
||
| ML/DL | PyTorch, torchvision (ResNet18) |
|
||
| Image Processing | PIL, OpenCV, pydicom, scipy |
|
||
| Data Handling | pandas, openpyxl |
|
||
| Containerization | Docker |
|
||
|
||
---
|
||
|
||
## Компоненты системы
|
||
|
||
### 1. API Layer (`src/main.py`)
|
||
|
||
Точка входа — FastAPI приложение с REST эндпоинтами.
|
||
|
||
**Основные функции:**
|
||
- Прием DICOM файлов через multipart/form-data
|
||
- Предобработка изображений (нормализация, ресайз до 224x224)
|
||
- Инференс модели
|
||
- Формирование ответов
|
||
|
||
```python
|
||
# Основной API эндпоинт
|
||
@app.post("/api/v1/analyze/detailed")
|
||
async def analyze_dicom_detailed(file: UploadFile = File(...)):
|
||
# 1. Загрузка модели
|
||
# 2. Предобработка DICOM
|
||
# 3. Инференс ResNet18
|
||
# 4. Генерация сегментации
|
||
# 5. Детальная оценка качества
|
||
# 6. Формирование ответа
|
||
```
|
||
|
||
### 2. DXA Classifier (`src/dxa/model.py`)
|
||
|
||
Модель для бинарной классификации качества изображения.
|
||
|
||
**Архитектура:**
|
||
```
|
||
Input (224x224x3)
|
||
│
|
||
▼
|
||
ResNet18 (pretrained on ImageNet)
|
||
│
|
||
├── Remove final FC layer
|
||
│
|
||
▼
|
||
Classifier Head:
|
||
├── Dropout(0.3)
|
||
├── Linear(512 → 256)
|
||
├── ReLU
|
||
├── Dropout(0.3)
|
||
└── Linear(256 → 2)
|
||
│
|
||
▼
|
||
Output: [prob_OK, prob_Violation]
|
||
```
|
||
|
||
**Параметры модели:**
|
||
- Backbone: ResNet18 (ImageNet pretrained)
|
||
- Input: 224x224 RGB
|
||
- Output: 2 класса (OK / Violation)
|
||
- Dropout: 0.3
|
||
|
||
### 3. Segmentator (Простая реализация)
|
||
|
||
Для сегментации используется простой пороговый метод:
|
||
|
||
```python
|
||
# Threshold-based segmentation
|
||
threshold = np.percentile(image, 90)
|
||
segmentation = (image > threshold).astype(np.uint8)
|
||
```
|
||
|
||
**Планируется:** Замена на UNet или TotalSegmentator для более точной сегментации.
|
||
|
||
### 4. Detailed Assessment (`src/quality/detailed_assessment.py`)
|
||
|
||
Модуль детальной оценки качества включает:
|
||
|
||
#### 4.1 Motion Detection
|
||
- **Laplacian variance** — дисперсия лапласиана (ниже = размытие)
|
||
- **FFT blur** — анализ высокочастотной энергии (ниже = размытие)
|
||
- **Edge duplication** — проверка "призрачных" краев
|
||
|
||
#### 4.2 Artifact Detection
|
||
- **Metal detection** — яркие пятна (>99.5 перцентиль)
|
||
- **Implant detection** — линейные структуры
|
||
- **Cement detection** — локальные яркие области
|
||
- **Calcification** — малые яркие пятна
|
||
|
||
#### 4.3 Region-Specific Checks
|
||
|
||
**Для позвоночника (spine):**
|
||
- Проверка количества позвонков (3-4 для поясничного отдела)
|
||
- Проверка полноты (не обрезаны ли позвонки)
|
||
- Проверка выравнивания позвонков
|
||
- Проверка контуров позвонков
|
||
|
||
**Для бедра (hip):**
|
||
- Проверка полноты видимости (шейка бедра, головка)
|
||
- Проверка ротации (угол главной оси)
|
||
- Проверка соотношения сторон
|
||
|
||
#### 4.4 ROI Validation
|
||
- Проверка отступов от краев
|
||
- Проверка размера ROI
|
||
|
||
### 5. Region Detector (`src/dxa/inference.py`)
|
||
|
||
Определение анатомической области по содержимому изображения:
|
||
|
||
**Алгоритм:**
|
||
1. Пороговая бинаризация (95 перцентиль)
|
||
2. Вычисление bounding box яркой области
|
||
3. Соотношение сторон bbox:
|
||
- < 1.5 → **spine** (более квадратная область)
|
||
- ≥ 1.5 → **hip** (вытянутая область)
|
||
4. Для hip: определение левого/правого по асимметрии яркости
|
||
|
||
---
|
||
|
||
## Пайплайн обработки
|
||
|
||
### Основной пайплайн
|
||
|
||
```plantuml
|
||
@startuml
|
||
title DXA Quality Assessment Pipeline
|
||
|
||
start
|
||
:Upload DICOM file;
|
||
:Parse DICOM metadata;
|
||
:Preprocess image;
|
||
|
||
note right
|
||
- Normalize to 0-1
|
||
- Convert to 3-channel
|
||
- Resize to 224x224
|
||
end note
|
||
|
||
:Model Inference (ResNet18);
|
||
note right
|
||
- Get prediction (OK/Violation)
|
||
- Get confidence scores
|
||
end note
|
||
|
||
:Determine Anatomical Region;
|
||
note right
|
||
- bbox_aspect ratio analysis
|
||
- left/right brightness ratio
|
||
end note
|
||
|
||
:Generate Segmentation;
|
||
note right
|
||
- Threshold-based (90th percentile)
|
||
end note
|
||
|
||
:Detailed Quality Assessment;
|
||
|
||
note right
|
||
- Motion detection
|
||
- Artifact detection
|
||
- Region-specific checks
|
||
- ROI validation
|
||
end note
|
||
|
||
:Determine Violation Type;
|
||
|
||
note right
|
||
- correct
|
||
- position_error
|
||
- artifact_motion
|
||
- artifact_other
|
||
- labeling_error
|
||
- incomplete_view
|
||
- roi_error
|
||
- rotation
|
||
end note
|
||
|
||
:Generate Response;
|
||
stop
|
||
|
||
@enduml
|
||
```
|
||
|
||
### Детальная диаграмма последовательности
|
||
|
||
```plantuml
|
||
@startuml
|
||
title Sequence: Detailed Analysis
|
||
|
||
actor User
|
||
participant "FastAPI" as API
|
||
participant "DXA Model" as Model
|
||
participant "Region Detector" as Region
|
||
participant "Segmentator" as Seg
|
||
participant "Quality Assessor" as QA
|
||
|
||
User -> API: POST /api/v1/analyze/detailed
|
||
API -> API: preprocess_dicom()
|
||
API -> Model: predict(image)
|
||
Model --> API: [prediction, confidence]
|
||
|
||
API -> Region: determine_region(image)
|
||
Region --> API: "spine" | "hip_left" | "hip_right"
|
||
|
||
API -> Seg: segment(image)
|
||
Seg --> API: segmentation_mask
|
||
|
||
API -> QA: generate_quality_report()
|
||
note over QA
|
||
- detect_motion_blur()
|
||
- detect_artifacts()
|
||
- check_spine_completeness() / check_hip_completeness()
|
||
- check_hip_rotation()
|
||
- check_roi_boundaries()
|
||
- determine_violation_type()
|
||
end note
|
||
|
||
QA --> API: quality_report
|
||
|
||
API -> API: convert_to_serializable()
|
||
API --> User: JSON response
|
||
|
||
@enduml
|
||
```
|
||
|
||
---
|
||
|
||
## API Endpoints
|
||
|
||
| Method | Endpoint | Description |
|
||
|--------|----------|-------------|
|
||
| GET | `/` | Web interface |
|
||
| GET | `/api/v1/health` | Health check |
|
||
| POST | `/api/v1/analyze` | Basic analysis |
|
||
| POST | `/api/v1/analyze/detailed` | Detailed analysis with metrics |
|
||
| POST | `/api/v1/analyze/sr` | DICOM SR report |
|
||
| POST | `/api/v1/batch` | Batch processing |
|
||
| POST | `/api/v1/export` | Export to XLSX |
|
||
|
||
### Response Format (Detailed)
|
||
|
||
```json
|
||
{
|
||
"anatomical_region": "spine",
|
||
"quality_class": 1,
|
||
"quality_label": "Violation detected",
|
||
"violation_type": "artifact_motion",
|
||
"reason": "Обнаружен артефакт движения (размытие)",
|
||
"confidence": 0.85,
|
||
"confidence_per_class": {
|
||
"correct": 0.15,
|
||
"violation": 0.85
|
||
},
|
||
"view_quality": "full",
|
||
"metrics": {
|
||
"motion": {
|
||
"motion_detected": true,
|
||
"blur_laplacian": 0.001,
|
||
"severity": "HIGH"
|
||
},
|
||
"artifacts": {
|
||
"any_detected": false,
|
||
"metal_detected": false
|
||
},
|
||
"roi_check": {
|
||
"valid": true
|
||
}
|
||
},
|
||
"overall_quality": "POOR",
|
||
"severity": "HIGH"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Модели и алгоритмы
|
||
|
||
### Типы нарушений
|
||
|
||
**Для позвоночника:**
|
||
- `correct` — качество соответствует норме
|
||
- `position_error` — ошибка позиционирования
|
||
- `artifact_motion` — артефакт движения (размытие)
|
||
- `artifact_other` — другие артефакты
|
||
- `labeling_error` — ошибка маркировки
|
||
- `incomplete_view` — неполный вид
|
||
- `roi_error` — ошибка ROI
|
||
|
||
**Для бедра:**
|
||
- `correct` — качество соответствует норме
|
||
- `position_error` — ошибка позиционирования
|
||
- `rotation` — нарушение ротации
|
||
- `artifact_motion` — артефакт движения
|
||
- `artifact_other` — другие артефакты
|
||
- `roi_error` — ошибка ROI
|
||
- `incomplete_view` — неполный вид
|
||
|
||
### Метрики детекции движения
|
||
|
||
| Метрика | Порог | Описание |
|
||
|---------|-------|----------|
|
||
| Laplacian variance | < 0.002 | Низкая дисперсия = размытие |
|
||
| FFT high-freq ratio | < 0.3 | Низкая высокочастотная энергия |
|
||
| Edge duplication | bool | Дублирование краев |
|
||
|
||
### Алгоритм определения региона
|
||
|
||
```plantuml
|
||
@startuml
|
||
title Region Detection Algorithm
|
||
|
||
start
|
||
:Load DICOM image;
|
||
:Normalize to 0-1;
|
||
:Threshold at 95th percentile;
|
||
|
||
if (binary.sum > 0?) then (yes)
|
||
:Calculate bounding box;
|
||
:Compute bbox_aspect = height / width;
|
||
|
||
if (bbox_aspect < 1.5) then (yes)
|
||
:return "spine";
|
||
else (no)
|
||
if (bbox_aspect < 1.8) then (yes)
|
||
:Calculate symmetry;
|
||
if (symmetry > 0.35) then (yes)
|
||
:return "spine";
|
||
else (no)
|
||
:return "hip";
|
||
end
|
||
else (no)
|
||
:Calculate left/right brightness ratio;
|
||
|
||
if (ratio > 1.3) then (yes)
|
||
:return "hip_right";
|
||
else if (ratio < 0.7) then (yes)
|
||
:return "hip_left";
|
||
else (no)
|
||
:return "hip";
|
||
end
|
||
end
|
||
end
|
||
else (no)
|
||
:Fallback: height < 270 → hip;
|
||
:return "spine";
|
||
end
|
||
|
||
stop
|
||
|
||
@enduml
|
||
```
|
||
|
||
---
|
||
|
||
## PlantUML Диаграммы
|
||
|
||
### Диаграмма компонентов
|
||
|
||
```plantuml
|
||
@startuml
|
||
!theme plain
|
||
skinparam componentStyle uml2
|
||
|
||
component "Web Client" as Client {
|
||
[Web UI]
|
||
}
|
||
|
||
component "FastAPI Server" as API {
|
||
[Upload Handler]
|
||
[Preprocessor]
|
||
[Response Builder]
|
||
}
|
||
|
||
component "DXA Models" as Models {
|
||
[ResNet18 Classifier]
|
||
[Region Detector]
|
||
[Segmentator]
|
||
}
|
||
|
||
component "Quality Assessment" as QA {
|
||
[Motion Detector]
|
||
[Artifact Detector]
|
||
[Position Validator]
|
||
[ROI Checker]
|
||
[Rotation Checker]
|
||
}
|
||
|
||
database "File System" as FS {
|
||
[DICOM Files]
|
||
[Model Weights]
|
||
}
|
||
|
||
Client -down-> API : HTTP
|
||
API -down-> FS : Read/Write
|
||
API -right-> Models : Inference
|
||
Models -down-> QA : Quality Metrics
|
||
|
||
@enduml
|
||
```
|
||
|
||
### Диаграмма классов (основные сущности)
|
||
|
||
```plantuml
|
||
@startuml
|
||
!theme plain
|
||
|
||
class DXAQualityClassifier {
|
||
+backbone: str
|
||
+num_classes: int
|
||
+forward(x: Tensor) -> Tensor
|
||
+extract_features(x: Tensor) -> Tensor
|
||
}
|
||
|
||
class DXAQualityModel {
|
||
+model: DXAQualityClassifier
|
||
+device: str
|
||
+predict(images: Tensor) -> (preds, probs)
|
||
+train_epoch(loader)
|
||
+validate(loader)
|
||
}
|
||
|
||
class DetailedAssessment {
|
||
+detect_motion_blur(image) -> Dict
|
||
+detect_artifacts(image, segmentation) -> Dict
|
||
+check_spine_completeness(segmentation) -> Dict
|
||
+check_hip_completeness(segmentation) -> Dict
|
||
+check_hip_rotation(segmentation, image) -> Dict
|
||
+check_roi_boundaries(segmentation, shape) -> Dict
|
||
+generate_quality_report(...) -> Dict
|
||
}
|
||
|
||
class RegionDetector {
|
||
+determine_region_from_image(image) -> str
|
||
+determine_anatomical_region(dcm_path) -> str
|
||
}
|
||
|
||
DXAQualityModel --> DXAQualityClassifier
|
||
DXAQualityModel ..> DetailedAssessment : uses
|
||
RegionDetector ..> DetailedAssessment : provides region
|
||
|
||
@enduml
|
||
```
|
||
|
||
### Диаграмма развертывания
|
||
|
||
```plantuml
|
||
@startuml
|
||
!theme plain
|
||
skinparam rectangle {
|
||
BackgroundColor #White
|
||
BorderColor #Black
|
||
}
|
||
|
||
rectangle "Client Layer" {
|
||
rectangle "Browser" as Browser
|
||
rectangle "Web UI (HTML/JS)" as WebUI
|
||
}
|
||
|
||
rectangle "Application Layer" {
|
||
rectangle "FastAPI" as API
|
||
rectangle "Uvicorn" as Uvicorn
|
||
}
|
||
|
||
rectangle "ML Pipeline" {
|
||
rectangle "DXA Classifier" as Classifier
|
||
rectangle "Segmentator" as Segmentator
|
||
rectangle "Quality Assessor" as Assessor
|
||
}
|
||
|
||
rectangle "Infrastructure" {
|
||
rectangle "CPU/GPU" as Compute
|
||
rectangle "File System" as Storage
|
||
rectangle "Docker" as Docker
|
||
}
|
||
|
||
Browser -right-> WebUI
|
||
WebUI -right-> API
|
||
API -right-> Uvicorn
|
||
Uvicorn -right-> Classifier
|
||
Uvicorn -right-> Segmentator
|
||
Classifier -right-> Assessor
|
||
Classifier -up-> Compute : PyTorch
|
||
Segmentator -up-> Compute : NumPy/SciPy
|
||
API -down-> Storage : Models/Data
|
||
Compute -up-> Docker
|
||
|
||
@enduml
|
||
```
|
||
|
||
---
|
||
|
||
## Потоки данных
|
||
|
||
```plantuml
|
||
@startuml
|
||
!theme plain
|
||
skinparam ranksep 50
|
||
skinparam nodesep 50
|
||
|
||
node "Input" {
|
||
[DICOM File]
|
||
}
|
||
|
||
node "Preprocessing" {
|
||
[Normalization]
|
||
[3-Channel Conv]
|
||
[Resize 224x224]
|
||
}
|
||
|
||
node "ML Models" {
|
||
[ResNet18]
|
||
[Region Detector]
|
||
[Threshold Seg]
|
||
}
|
||
|
||
node "Quality Analysis" {
|
||
[Motion Detection]
|
||
[Artifact Detection]
|
||
[Completeness Check]
|
||
[ROI Validation]
|
||
}
|
||
|
||
node "Output" {
|
||
[JSON Response]
|
||
[XLSX Export]
|
||
}
|
||
|
||
[DICOM File] --> [Normalization]
|
||
[Normalization] --> [3-Channel Conv]
|
||
[3-Channel Conv] --> [Resize 224x224]
|
||
|
||
[Resize 224x224] --> [ResNet18]
|
||
[Resize 224x224] --> [Region Detector]
|
||
[Resize 224x224] --> [Threshold Seg]
|
||
|
||
[ResNet18] --> [Quality Analysis]
|
||
[Region Detector] --> [Quality Analysis]
|
||
[Threshold Seg] --> [Quality Analysis]
|
||
|
||
[Quality Analysis] --> [JSON Response]
|
||
[Quality Analysis] --> [XLSX Export]
|
||
|
||
@enduml
|
||
```
|
||
|
||
---
|
||
|
||
## Ограничения и планы развития
|
||
|
||
### Текущие ограничения
|
||
|
||
1. **Сегментация** — простая пороговая обработка, требует замены на Deep Learning (UNet/TotalSegmentator)
|
||
2. **Модель** — бинарная классификация, нужно расширение до многоклассовой для типов нарушений
|
||
3. **Dataset** — ~100 исследований, требуется расширение до 500+
|
||
4. **F1 Score** — текущий ~0.27, требует улучшения
|
||
|
||
### Планируемые улучшения
|
||
|
||
| Компонент | План |
|
||
|-----------|------|
|
||
| Region Detector | Отдельная модель для детекции региона |
|
||
| Segmentator | UNet или TotalSegmentator |
|
||
| Quality Classifier | Расширение на 8 классов нарушений |
|
||
| Violation Classifier | Отдельная модель для классификации типа нарушения |
|
||
| Artifact Detector | CNN для детекции металла/имплантатов |
|
||
| Visualization | Grad-CAM для внимания модели |
|
||
|
||
---
|
||
|
||
## Заключение
|
||
|
||
Система DXA Quality Assessment представляет собой полнофункциональный AI-сервис для автоматизированной оценки качества DXA исследований. Архитектура построена на принципах модульности и расширяемости, что позволяет поэтапно улучшать отдельные компоненты.
|
||
|
||
Основные характеристики:
|
||
- ✅ RESTful API с FastAPI
|
||
- ✅ Глубокое обучение (ResNet18)
|
||
- ✅ Детальная оценка качества с метриками
|
||
- ✅ Поддержка нескольких типов нарушений
|
||
- ✅ Экспорт в XLSX/CSV
|
||
- ✅ Docker контейнеризация
|