AI for CT Scan Analysis

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
AI for CT Scan Analysis
Complex
~2-4 weeks
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_logo-aider_0.jpg
    AIDER company logo development
    762
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    848

Разработка AI для анализа КТ-снимков

КТ (компьютерная томография) — трёхмерная визуализация. Данные — стек из 200–600 двумерных срезов толщиной 0.5–5 мм. Ключевое отличие от рентгена: 3D объём, единицы Хаунсфилда (HU) как количественная мера плотности тканей. AI-задачи на КТ: сегментация органов, детекция узлов (лёгкие, печень, почки), стадирование опухолей, измерение объёмов.

3D обработка КТ-данных

import numpy as np
import torch
import nibabel as nib
from monai.transforms import (
    Compose, LoadImaged, AddChanneld, Orientationd,
    Spacingd, ScaleIntensityRanged, CropForegroundd,
    ResizeWithPadOrCropd, ToTensord
)

class CTAnalysisSystem:
    def __init__(self, model_path: str, task: str = 'lung_nodule'):
        self.preprocessing = self._build_preprocessing(task)
        self.model = self._load_model(model_path)
        self.task = task

    def _build_preprocessing(self, task: str) -> Compose:
        if task == 'lung_nodule':
            # Лёгочные узлы: lung window (-1200 to 600 HU)
            hu_min, hu_max = -1200, 600
        elif task == 'liver_tumor':
            # Печень: soft tissue window (-150 to 250 HU)
            hu_min, hu_max = -150, 250
        else:
            hu_min, hu_max = -1000, 1000

        return Compose([
            LoadImaged(keys=['image']),
            AddChanneld(keys=['image']),
            Orientationd(keys=['image'], axcodes='RAS'),
            Spacingd(keys=['image'],
                      pixdim=(1.5, 1.5, 2.0),   # ресемплинг до равного voxel spacing
                      mode='bilinear'),
            ScaleIntensityRanged(
                keys=['image'],
                a_min=hu_min, a_max=hu_max,
                b_min=0.0, b_max=1.0, clip=True
            ),
            ToTensord(keys=['image'])
        ])

    def analyze(self, nifti_path: str) -> dict:
        data = {'image': nifti_path}
        data = self.preprocessing(data)
        volume = data['image'].unsqueeze(0)  # [1, 1, D, H, W]

        with torch.no_grad():
            prediction = self.model(volume)

        if self.task == 'lung_nodule':
            return self._process_nodule_detection(prediction, data)
        elif self.task == 'organ_segmentation':
            return self._process_segmentation(prediction)

Сегментация органов: MONAI и nnU-Net

MONAI (Medical Open Network for AI) — PyTorch-фреймворк специально для медицинского CV:

from monai.networks.nets import UNet
from monai.losses import DiceCELoss
from monai.metrics import DiceMetric

# Стандартная 3D U-Net для КТ сегментации
model = UNet(
    spatial_dims=3,         # 3D
    in_channels=1,
    out_channels=14,        # TotalSegmentator: 104 структуры, базовый вариант 14
    channels=(16, 32, 64, 128, 256),
    strides=(2, 2, 2, 2),
    num_res_units=2,
    dropout=0.1
)

criterion = DiceCELoss(
    include_background=False,
    to_onehot_y=True,
    softmax=True
)

TotalSegmentator — готовая модель для сегментации 104 анатомических структур на КТ:

from totalsegmentator.python_api import totalsegmentator

# Автоматическая сегментация всех органов
output = totalsegmentator(
    input='ct_scan.nii.gz',
    output='segmentations/',
    task='total',
    fast=False  # full quality
)

Детекция лёгочных узлов

Лёгочные узлы — первый признак рака лёгкого. Задача: найти все потенциальные узлы диаметром > 3 мм в 3D объёме.

class NoduleDetector:
    def __init__(self, model_path: str,
                 min_nodule_mm: float = 3.0,
                 confidence_threshold: float = 0.5):
        self.model = load_nodule_model(model_path)
        self.min_size = min_nodule_mm
        self.threshold = confidence_threshold

    def detect(self, ct_volume: np.ndarray,
               voxel_spacing: tuple) -> list[dict]:
        """
        ct_volume: [D, H, W] в HU
        voxel_spacing: (mm/voxel в каждом направлении)
        """
        # Сегментация лёгкого для ограничения поиска
        lung_mask = self._segment_lung(ct_volume)

        # Детекция узлов только в области лёгкого
        nodule_mask = self.model.predict(ct_volume * lung_mask)

        # Извлечение отдельных узлов
        nodules = self._extract_nodules(nodule_mask, voxel_spacing)

        # Фильтрация по минимальному размеру
        return [n for n in nodules
                if n['diameter_mm'] >= self.min_size and
                n['confidence'] >= self.threshold]

Количественный анализ

Уникальная возможность КТ — точное измерение объёмов. После сегментации:

def measure_volume_ml(mask: np.ndarray,
                       voxel_spacing: tuple) -> float:
    """Объём сегментированной структуры в мл (cm³)"""
    voxel_volume_mm3 = np.prod(voxel_spacing)
    volume_mm3 = mask.sum() * voxel_volume_mm3
    return volume_mm3 / 1000  # мм³ → мл

def measure_nodule_diameter(nodule_mask: np.ndarray,
                             voxel_spacing: tuple) -> dict:
    """Диаметр узла по методу RECIST"""
    coords = np.where(nodule_mask)
    # Longest axis в 3D
    from scipy.spatial import ConvexHull
    points = np.column_stack(coords) * np.array(voxel_spacing)
    if len(points) < 4:
        return {'diameter_mm': 0}

    hull = ConvexHull(points)
    max_dist = 0
    hull_pts = points[hull.vertices]
    for i in range(len(hull_pts)):
        for j in range(i+1, len(hull_pts)):
            d = np.linalg.norm(hull_pts[i] - hull_pts[j])
            max_dist = max(max_dist, d)

    return {'diameter_mm': round(max_dist, 2)}
Задача Датасет Dice SOTA
Сегментация лёгких LUNA16 0.98
Детекция узлов LUNA16 FROC 0.89
Сегментация печени LiTS 0.96
Сегментация опухоли печени LiTS 0.75
Multi-organ BTCV 0.88
Задача Срок
Сегментация органов (TotalSegmentator fine-tune) 8–14 недель
Детекция лёгочных узлов 14–20 недель
Полный CAD КТ-комплекс 24–36 недель