AI for Histology Image 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 Histology Image 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 для анализа гистологических изображений

Гистологические изображения — снимки тонких срезов биологической ткани под микроскопом. Задачи AI: классификация тканей (злокачественная vs доброкачественная), детекция и подсчёт клеток, сегментация структур (железы, опухоль, строма), определение степени злокачественности (Gleason score для рака простаты, градация по системе Elston-Ellis для молочной железы).

Специфика Whole Slide Images (WSI)

Гистологические слайды сканируются в формате WSI (Whole Slide Image) — гигапиксельные изображения: 100,000×200,000 пикселей, 5–20 ГБ на файл. Прямая загрузка в память невозможна. Работа через тайлинг:

import openslide
import numpy as np
from PIL import Image

class WSIProcessor:
    def __init__(self, wsi_path: str, level: int = 0):
        self.slide = openslide.OpenSlide(wsi_path)
        self.level = level
        self.dimensions = self.slide.level_dimensions[level]
        self.mpp = float(self.slide.properties.get(
            openslide.PROPERTY_NAME_MPP_X, 0.25
        ))  # microns per pixel

    def extract_tiles(self, tile_size: int = 224,
                       stride: int = 224,
                       tissue_threshold: float = 0.5) -> list[dict]:
        """Генерация тайлов с фильтрацией фона"""
        W, H = self.dimensions
        tiles = []

        for y in range(0, H - tile_size, stride):
            for x in range(0, W - tile_size, stride):
                tile = self.slide.read_region(
                    (x, y), self.level, (tile_size, tile_size)
                ).convert('RGB')

                # Фильтрация тайлов с фоном (белый фон)
                if self._has_tissue(np.array(tile), tissue_threshold):
                    tiles.append({
                        'image': tile,
                        'x': x, 'y': y,
                        'mpp': self.mpp
                    })

        return tiles

    def _has_tissue(self, tile_array: np.ndarray,
                     threshold: float) -> bool:
        """Определение наличия ткани по насыщенности HSV"""
        from skimage.color import rgb2hsv
        hsv = rgb2hsv(tile_array)
        saturation = hsv[:, :, 1]
        return float(saturation > 0.15).mean() > threshold

Multiple Instance Learning (MIL)

Для задач классификации WSI (рак есть/нет) без пиксельной разметки — Multiple Instance Learning: каждый тайл — это «instance», WSI-слайд — «bag». Если хотя бы один тайл содержит рак, слайд позитивен.

import torch
import torch.nn as nn

class AttentionMIL(nn.Module):
    """Attention-based MIL для классификации WSI"""
    def __init__(self, feature_dim: int = 512, num_classes: int = 2):
        super().__init__()

        # Attention mechanism
        self.attention = nn.Sequential(
            nn.Linear(feature_dim, 128),
            nn.Tanh(),
            nn.Linear(128, 1)
        )

        # Классификатор
        self.classifier = nn.Sequential(
            nn.Linear(feature_dim, 256),
            nn.GELU(),
            nn.Dropout(0.4),
            nn.Linear(256, num_classes)
        )

    def forward(self, tile_features: torch.Tensor) -> dict:
        """
        tile_features: [N_tiles, feature_dim]
        """
        # Attention weights
        A = self.attention(tile_features)  # [N, 1]
        A = torch.softmax(A, dim=0)

        # Weighted aggregation
        bag_representation = (A * tile_features).sum(dim=0, keepdim=True)

        # Classification
        logits = self.classifier(bag_representation)

        return {
            'logits': logits,
            'attention_weights': A.squeeze(),  # важность каждого тайла
            'bag_representation': bag_representation
        }

Грейдирование рака простаты (Gleason Score)

Систему Gleason заменяет ISUP Grade. AI достигает Kappa 0.76 против 0.68 у среднего патологоанатома:

ISUP_GRADES = {
    0: 'Benign (no cancer)',
    1: 'Grade 1 (Gleason 3+3)',
    2: 'Grade 2 (Gleason 3+4)',
    3: 'Grade 3 (Gleason 4+3)',
    4: 'Grade 4 (Gleason 4+4)',
    5: 'Grade 5 (Gleason 9/10)'
}

Cell Detection: обнаружение и подсчёт клеток

from ultralytics import YOLO

# HoVer-Net или YOLO для cell detection
cell_detector = YOLO('cell_detector.pt')

def count_cells_in_tile(tile: np.ndarray) -> dict:
    results = cell_detector(tile, conf=0.4)
    cell_counts = {}
    for box in results[0].boxes:
        cell_type = cell_detector.model.names[int(box.cls)]
        cell_counts[cell_type] = cell_counts.get(cell_type, 0) + 1
    return cell_counts

Ключевые датасеты

Датасет Задача Изображений
TCGA Multiple cancers 1M+ WSI
CAMELYON16/17 Метастазы рака молочной железы 400 WSI
PANDA Рак простаты (Gleason) 10,616 WSI
PanNuke Сегментация ядер 7,904 тайлов
Задача Срок
Классификация тайлов WSI 8–12 недель
MIL для WSI-level классификации 12–18 недель
Cell detection + грейдирование 16–24 недели