Технології AI Написано практикуючими розробниками

Spatial Omics + Deep Learning: коли клітини отримують координати

Оновлено: 11 хв читання 14 переглядів

Уявіть, що ви хочете зрозуміти місто. Традиційний підхід: зібрати всі будинки в блендер, зробити середнє — і отримати "середній будинок". Абсурд? Саме так працювала традиційна геноміка десятиліттями.


Уявіть, що ви хочете зрозуміти місто. Традиційний підхід: зібрати всі будинки в блендер, зробити середнє — і отримати "середній будинок". Абсурд? Саме так працювала традиційна геноміка десятиліттями.

Тканина — це не суміш клітин. Це архітектура. Імунна клітина поруч з пухлинною — це зовсім інша історія, ніж та сама клітина за мікрон. Нейрони формують circuits саме через свої з'єднання. Tumor microenvironment визначає відповідь на терапію більше, ніж мутації самої пухлини.

Spatial omics революціонізує біологію, додаючи просторову координату до молекулярних даних. А deep learning робить ці дані interpretable. Це frontier computational biology — і один з найперспективніших напрямків для дипломних та магістерських робіт.


Від Bulk до Spatial: еволюція омік

Традиційний підхід (Bulk Omics):

Тканина → Гомогенізація → Bulk measurement
Результат: Середнє по мільйонах клітин
Втрачено: Просторовий контекст, клітинна гетерогенність

Single-cell підхід:

Тканина → Дисоціація → Секвенування окремих клітин
Результат: Профіль кожної клітини
Втрачено: Просторове розташування

Spatial підхід:

Тканина → Imaging in situ → Вимірювання з координатами
Результат: Gene expression + XY координати
Збережено: Клітинні neighborhoods, tissue structure

Порівняння підходів:

| Feature | Bulk | Single-cell | Spatial |

|---------|------|-------------|---------|

| Resolution | Tissue average | Single cell | Single cell + location |

| Genes measured | Whole transcriptome | Whole transcriptome | 100-10,000+ |

| Spatial context | Lost | Lost | Preserved |

| Sample size | 1 value per sample | ~10K cells | ~100K-1M cells |

| Cost per sample | $100-500 | $5,000-20,000 | $10,000-50,000 |


Технології Spatial Omics

1. Spot-based технології (Visium, 10x Genomics)

import scanpy as sc
import squidpy as sq
import numpy as np

class VisiumAnalyzer:
    """Аналіз Visium Spatial Transcriptomics"""

    def __init__(self, adata_path: str):
        # Завантаження AnnData об'єкта
        self.adata = sc.read_h5ad(adata_path)

    def preprocess(self):
        """Стандартний preprocessing pipeline"""
        # QC фільтрація
        sc.pp.filter_cells(self.adata, min_genes=200)
        sc.pp.filter_genes(self.adata, min_cells=10)

        # Мітохондріальні гени (маркер якості)
        self.adata.var['mt'] = self.adata.var_names.str.startswith('MT-')
        sc.pp.calculate_qc_metrics(
            self.adata,
            qc_vars=['mt'],
            percent_top=None,
            inplace=True
        )

        # Нормалізація
        sc.pp.normalize_total(self.adata, target_sum=1e4)
        sc.pp.log1p(self.adata)

        # Highly variable genes
        sc.pp.highly_variable_genes(
            self.adata,
            n_top_genes=2000,
            flavor='seurat_v3'
        )

        return self.adata

    def analyze_neighborhoods(self, n_neighbors: int = 6):
        """Аналіз просторових neighborhoods"""
        # Побудова spatial graph
        sq.gr.spatial_neighbors(
            self.adata,
            n_neighs=n_neighbors,
            coord_type='generic'
        )

        # Neighborhood enrichment
        sq.gr.nhood_enrichment(
            self.adata,
            cluster_key='cluster'
        )

        # Interaction matrix
        sq.gr.interaction_matrix(
            self.adata,
            cluster_key='cluster'
        )

        return self.adata

    def find_spatial_patterns(self):
        """Пошук просторово-варіабельних генів"""
        # Moran's I для spatial autocorrelation
        sq.gr.spatial_autocorr(
            self.adata,
            mode='moran',
            genes=self.adata.var_names[:500]  # Top 500 genes
        )

        # Сортування за spatial variation
        spatial_genes = self.adata.var.sort_values(
            'moranI',
            ascending=False
        )

        return spatial_genes

2. Single-cell resolution (MERFISH, Xenium, CosMx)

import numpy as np
from scipy.spatial import Delaunay, cKDTree

class SingleCellSpatialAnalyzer:
    """Аналіз single-cell resolution spatial data"""

    def __init__(self, expression_matrix: np.ndarray,
                 coordinates: np.ndarray,
                 gene_names: list):
        self.expression = expression_matrix  # cells x genes
        self.coords = coordinates  # cells x 2 (x, y)
        self.genes = gene_names
        self.n_cells = expression_matrix.shape[0]

    def segment_cells(self, nuclei_image: np.ndarray,
                      membrane_image: np.ndarray = None):
        """Сегментація клітин з зображень"""
        from cellpose import models

        # Cellpose для сегментації
        model = models.Cellpose(model_type='cyto2')

        # Якщо є membrane image - використовуємо обидва канали
        if membrane_image is not None:
            images = np.stack([membrane_image, nuclei_image], axis=-1)
            channels = [1, 2]  # membrane, nuclei
        else:
            images = nuclei_image
            channels = [0, 0]  # grayscale

        masks, flows, styles, diams = model.eval(
            images,
            diameter=None,  # auto-detect
            channels=channels,
            flow_threshold=0.4,
            cellprob_threshold=0
        )

        return masks

    def build_spatial_graph(self, method: str = 'delaunay',
                            k: int = 10,
                            radius: float = None):
        """Побудова просторового графа"""
        if method == 'delaunay':
            # Delaunay triangulation
            tri = Delaunay(self.coords)
            edges = set()
            for simplex in tri.simplices:
                for i in range(3):
                    for j in range(i + 1, 3):
                        edges.add((simplex[i], simplex[j]))
                        edges.add((simplex[j], simplex[i]))
            return list(edges)

        elif method == 'knn':
            # k-nearest neighbors
            tree = cKDTree(self.coords)
            edges = []
            for i in range(self.n_cells):
                distances, indices = tree.query(self.coords[i], k=k+1)
                for j in indices[1:]:  # skip self
                    edges.append((i, j))
            return edges

        elif method == 'radius':
            # Radius-based
            tree = cKDTree(self.coords)
            edges = []
            for i in range(self.n_cells):
                indices = tree.query_ball_point(self.coords[i], radius)
                for j in indices:
                    if i != j:
                        edges.append((i, j))
            return edges

    def calculate_cell_interactions(self, cell_types: np.ndarray):
        """Розрахунок cell-cell interactions"""
        edges = self.build_spatial_graph(method='knn', k=6)

        # Count interactions between cell types
        unique_types = np.unique(cell_types)
        n_types = len(unique_types)
        interaction_matrix = np.zeros((n_types, n_types))

        type_to_idx = {t: i for i, t in enumerate(unique_types)}

        for i, j in edges:
            type_i = type_to_idx[cell_types[i]]
            type_j = type_to_idx[cell_types[j]]
            interaction_matrix[type_i, type_j] += 1

        # Normalize
        row_sums = interaction_matrix.sum(axis=1, keepdims=True)
        interaction_matrix = interaction_matrix / (row_sums + 1e-10)

        return interaction_matrix, unique_types

3. Imaging Mass Cytometry (IMC)

class IMCAnalyzer:
    """Аналіз Imaging Mass Cytometry data"""

    def __init__(self, intensity_matrix: np.ndarray,
                 coordinates: np.ndarray,
                 marker_names: list):
        self.intensities = intensity_matrix  # cells x markers
        self.coords = coordinates
        self.markers = marker_names

    def phenotype_cells(self, marker_thresholds: dict = None):
        """Фенотипування клітин за маркерами"""
        from sklearn.mixture import GaussianMixture

        phenotypes = np.zeros(self.intensities.shape[0], dtype=object)

        if marker_thresholds is None:
            # Автоматичне визначення порогів через GMM
            marker_thresholds = {}
            for i, marker in enumerate(self.markers):
                gmm = GaussianMixture(n_components=2, random_state=42)
                gmm.fit(self.intensities[:, i].reshape(-1, 1))
                threshold = np.mean(gmm.means_)
                marker_thresholds[marker] = threshold

        # Phenotype assignment (simplified)
        for idx in range(self.intensities.shape[0]):
            positive_markers = []
            for i, marker in enumerate(self.markers):
                if self.intensities[idx, i] > marker_thresholds[marker]:
                    positive_markers.append(marker)

            phenotypes[idx] = self._assign_cell_type(positive_markers)

        return phenotypes

    def _assign_cell_type(self, positive_markers: list) -> str:
        """Правила для визначення типу клітини"""
        # Simplified rules for immune cells
        if 'CD3' in positive_markers:
            if 'CD8' in positive_markers:
                return 'CD8+ T cell'
            elif 'CD4' in positive_markers:
                return 'CD4+ T cell'
            return 'T cell'
        elif 'CD20' in positive_markers:
            return 'B cell'
        elif 'CD68' in positive_markers:
            return 'Macrophage'
        elif 'CD56' in positive_markers:
            return 'NK cell'
        else:
            return 'Other'

Graph Neural Networks для Spatial Data

Клітини — це nodes. Spatial proximity — edges. Gene expression — node features. Cell-cell interactions — message passing. GNN — ідеальна архітектура.

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch_geometric as pyg
from torch_geometric.nn import GATConv, GCNConv, SAGEConv

class SpatialGNN(nn.Module):
    """Graph Neural Network для spatial omics"""

    def __init__(self, in_channels: int, hidden_channels: int,
                 out_channels: int, num_layers: int = 3,
                 conv_type: str = 'GAT', heads: int = 4,
                 dropout: float = 0.5):
        super().__init__()

        self.convs = nn.ModuleList()
        self.bns = nn.ModuleList()
        self.dropout = dropout

        # Input layer
        if conv_type == 'GAT':
            self.convs.append(GATConv(in_channels, hidden_channels, heads=heads))
            first_out = hidden_channels * heads
        elif conv_type == 'GCN':
            self.convs.append(GCNConv(in_channels, hidden_channels))
            first_out = hidden_channels
        elif conv_type == 'SAGE':
            self.convs.append(SAGEConv(in_channels, hidden_channels))
            first_out = hidden_channels

        self.bns.append(nn.BatchNorm1d(first_out))

        # Hidden layers
        for _ in range(num_layers - 2):
            if conv_type == 'GAT':
                self.convs.append(GATConv(first_out, hidden_channels, heads=heads))
            elif conv_type == 'GCN':
                self.convs.append(GCNConv(first_out, hidden_channels))
            elif conv_type == 'SAGE':
                self.convs.append(SAGEConv(first_out, hidden_channels))

            self.bns.append(nn.BatchNorm1d(first_out))

        # Output layer
        if conv_type == 'GAT':
            self.convs.append(GATConv(first_out, out_channels, heads=1, concat=False))
        else:
            self.convs.append(GCNConv(first_out, out_channels) if conv_type == 'GCN'
                              else SAGEConv(first_out, out_channels))

    def forward(self, x: torch.Tensor, edge_index: torch.Tensor) -> torch.Tensor:
        for i, conv in enumerate(self.convs[:-1]):
            x = conv(x, edge_index)
            x = self.bns[i](x)
            x = F.elu(x)
            x = F.dropout(x, p=self.dropout, training=self.training)

        x = self.convs[-1](x, edge_index)
        return x


class SpatialDomainIdentifier(nn.Module):
    """Ідентифікація spatial domains через GNN clustering"""

    def __init__(self, in_dim: int, hidden_dim: int, n_domains: int):
        super().__init__()

        self.encoder = SpatialGNN(
            in_channels=in_dim,
            hidden_channels=hidden_dim,
            out_channels=hidden_dim,
            num_layers=3,
            conv_type='GAT'
        )

        # Clustering head
        self.cluster_head = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, n_domains)
        )

        # Reconstruction head (for unsupervised learning)
        self.decoder = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, in_dim)
        )

    def forward(self, x: torch.Tensor, edge_index: torch.Tensor):
        # Encode
        z = self.encoder(x, edge_index)

        # Cluster assignment
        cluster_logits = self.cluster_head(z)

        # Reconstruction
        x_recon = self.decoder(z)

        return z, cluster_logits, x_recon

    def loss_function(self, x: torch.Tensor, x_recon: torch.Tensor,
                      cluster_logits: torch.Tensor, z: torch.Tensor,
                      edge_index: torch.Tensor):
        # Reconstruction loss
        recon_loss = F.mse_loss(x_recon, x)

        # Clustering loss (entropy regularization)
        cluster_probs = F.softmax(cluster_logits, dim=-1)
        entropy = -(cluster_probs * torch.log(cluster_probs + 1e-10)).sum(dim=-1).mean()

        # Spatial smoothness loss
        src, dst = edge_index
        neighbor_sim = F.cosine_similarity(z[src], z[dst])
        smoothness_loss = 1 - neighbor_sim.mean()

        total_loss = recon_loss + 0.1 * entropy + 0.5 * smoothness_loss

        return total_loss, {
            'recon': recon_loss.item(),
            'entropy': entropy.item(),
            'smoothness': smoothness_loss.item()
        }

Spatial Domain Detection: SpaGCN та альтернативи

class SpaGCNModel(nn.Module):
    """Імплементація SpaGCN для spatial domain detection"""

    def __init__(self, in_features: int, hidden_features: int,
                 out_features: int, alpha: float = 1.0):
        super().__init__()

        self.alpha = alpha  # Spatial weight

        self.gc1 = pyg.nn.GCNConv(in_features, hidden_features)
        self.gc2 = pyg.nn.GCNConv(hidden_features, out_features)

        self.cluster_layer = nn.Parameter(torch.Tensor(out_features, out_features))
        nn.init.xavier_normal_(self.cluster_layer)

    def forward(self, x: torch.Tensor, edge_index: torch.Tensor,
                edge_weight: torch.Tensor = None):
        # GCN layers
        x = F.relu(self.gc1(x, edge_index, edge_weight))
        x = F.dropout(x, p=0.5, training=self.training)
        z = self.gc2(x, edge_index, edge_weight)

        # Soft clustering
        q = self._compute_q(z)

        return z, q

    def _compute_q(self, z: torch.Tensor) -> torch.Tensor:
        """Student's t-distribution for soft assignment"""
        # q_ij = (1 + ||z_i - μ_j||^2)^(-1) / Σ_k (1 + ||z_i - μ_k||^2)^(-1)
        dist = torch.cdist(z, self.cluster_layer)
        q = 1.0 / (1.0 + dist ** 2)
        q = q / q.sum(dim=1, keepdim=True)
        return q

    @staticmethod
    def target_distribution(q: torch.Tensor) -> torch.Tensor:
        """Auxiliary target distribution"""
        weight = q ** 2 / q.sum(dim=0)
        return (weight.T / weight.sum(dim=1)).T


def build_spatial_graph_with_weights(coordinates: np.ndarray,
                                     expression: np.ndarray,
                                     n_neighbors: int = 10,
                                     spatial_weight: float = 1.0) -> pyg.data.Data:
    """Побудова графа з spatial та expression similarity"""
    from sklearn.neighbors import kneighbors_graph
    from scipy.sparse import csr_matrix

    n_cells = coordinates.shape[0]

    # Spatial neighbors
    spatial_adj = kneighbors_graph(
        coordinates,
        n_neighbors=n_neighbors,
        mode='connectivity'
    )

    # Expression similarity (для edge weights)
    from sklearn.metrics.pairwise import cosine_similarity
    expr_sim = cosine_similarity(expression)

    # Combine
    combined_adj = spatial_adj.multiply(expr_sim)

    # Convert to edge_index
    coo = combined_adj.tocoo()
    edge_index = torch.tensor(np.vstack([coo.row, coo.col]), dtype=torch.long)
    edge_weight = torch.tensor(coo.data, dtype=torch.float32)

    # Node features
    x = torch.tensor(expression, dtype=torch.float32)

    return pyg.data.Data(x=x, edge_index=edge_index, edge_attr=edge_weight)

Vision Transformers для Histology Integration

from transformers import ViTModel, ViTConfig
import torch.nn as nn

class HistologyEncoder(nn.Module):
    """ViT-based encoder для histology images"""

    def __init__(self, pretrained: str = "owkin/phikon"):
        super().__init__()

        # Foundation model для pathology
        self.vit = ViTModel.from_pretrained(pretrained)
        self.hidden_size = self.vit.config.hidden_size

        # Projection for spatial integration
        self.projection = nn.Linear(self.hidden_size, 256)

    def forward(self, pixel_values: torch.Tensor) -> torch.Tensor:
        # Extract features
        outputs = self.vit(pixel_values=pixel_values)
        cls_token = outputs.last_hidden_state[:, 0, :]

        # Project
        features = self.projection(cls_token)

        return features

    def extract_patch_features(self, wsi_image: np.ndarray,
                                patch_size: int = 256,
                                stride: int = 128) -> dict:
        """Екстракція features для всіх patches WSI"""
        import cv2
        from torchvision import transforms

        h, w = wsi_image.shape[:2]
        features_list = []
        coordinates = []

        transform = transforms.Compose([
            transforms.ToPILImage(),
            transforms.Resize((224, 224)),
            transforms.ToTensor(),
            transforms.Normalize(mean=[0.485, 0.456, 0.406],
                                 std=[0.229, 0.224, 0.225])
        ])

        for y in range(0, h - patch_size, stride):
            for x in range(0, w - patch_size, stride):
                patch = wsi_image[y:y+patch_size, x:x+patch_size]

                # Transform and extract features
                patch_tensor = transform(patch).unsqueeze(0)
                with torch.no_grad():
                    feat = self.forward(patch_tensor)
                    features_list.append(feat.squeeze().cpu().numpy())
                    coordinates.append((x + patch_size // 2, y + patch_size // 2))

        return {
            'features': np.array(features_list),
            'coordinates': np.array(coordinates)
        }


class MultiModalSpatialModel(nn.Module):
    """Multi-modal: H&E + Spatial transcriptomics"""

    def __init__(self, n_genes: int, n_classes: int,
                 histology_encoder: str = "owkin/phikon"):
        super().__init__()

        # Image encoder
        self.image_encoder = HistologyEncoder(histology_encoder)

        # Expression encoder
        self.expr_encoder = nn.Sequential(
            nn.Linear(n_genes, 512),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(512, 256),
            nn.ReLU(),
            nn.Linear(256, 128)
        )

        # Spatial GNN encoder
        self.gnn_encoder = SpatialGNN(
            in_channels=n_genes,
            hidden_channels=128,
            out_channels=128,
            num_layers=2
        )

        # Cross-attention fusion
        self.cross_attention = nn.MultiheadAttention(
            embed_dim=128,
            num_heads=4,
            batch_first=True
        )

        # Fusion MLP
        self.fusion = nn.Sequential(
            nn.Linear(128 * 3, 256),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(256, n_classes)
        )

    def forward(self, image_patches: torch.Tensor,
                expression: torch.Tensor,
                edge_index: torch.Tensor,
                batch_idx: torch.Tensor = None) -> torch.Tensor:
        # Image features
        img_feat = self.image_encoder(image_patches)  # [N, 256] -> [N, 128]
        img_feat = nn.Linear(256, 128)(img_feat)

        # Expression features
        expr_feat = self.expr_encoder(expression)  # [N, 128]

        # GNN features
        gnn_feat = self.gnn_encoder(expression, edge_index)  # [N, 128]

        # Cross-attention (expression attends to image)
        expr_feat_att, _ = self.cross_attention(
            expr_feat.unsqueeze(1),
            img_feat.unsqueeze(1),
            img_feat.unsqueeze(1)
        )
        expr_feat_att = expr_feat_att.squeeze(1)

        # Concatenate all modalities
        combined = torch.cat([img_feat, expr_feat_att, gnn_feat], dim=-1)

        # Classify
        output = self.fusion(combined)

        return output

Tumor Microenvironment Analysis

class TumorMicroenvironmentAnalyzer:
    """Аналіз tumor microenvironment"""

    def __init__(self, adata):
        self.adata = adata

    def identify_tumor_immune_interface(self, tumor_label: str,
                                         immune_labels: list,
                                         distance_threshold: float = 50):
        """Знаходження tumor-immune interface"""
        from scipy.spatial.distance import cdist

        # Get coordinates
        tumor_mask = self.adata.obs['cell_type'] == tumor_label
        immune_mask = self.adata.obs['cell_type'].isin(immune_labels)

        tumor_coords = self.adata.obsm['spatial'][tumor_mask]
        immune_coords = self.adata.obsm['spatial'][immune_mask]

        # Calculate distances
        distances = cdist(tumor_coords, immune_coords)

        # Interface cells
        tumor_interface = distances.min(axis=1) < distance_threshold
        immune_interface = distances.min(axis=0) < distance_threshold

        return {
            'tumor_interface_cells': np.where(tumor_mask)[0][tumor_interface],
            'immune_interface_cells': np.where(immune_mask)[0][immune_interface],
            'tumor_interface_fraction': tumor_interface.mean(),
            'immune_interface_fraction': immune_interface.mean()
        }

    def calculate_immune_infiltration_score(self, immune_labels: list) -> np.ndarray:
        """Розрахунок immune infiltration score для кожної точки"""
        from sklearn.neighbors import KernelDensity

        coords = self.adata.obsm['spatial']
        immune_mask = self.adata.obs['cell_type'].isin(immune_labels)
        immune_coords = coords[immune_mask]

        # Kernel density estimation
        kde = KernelDensity(bandwidth=100, kernel='gaussian')
        kde.fit(immune_coords)

        # Score for all points
        log_density = kde.score_samples(coords)
        infiltration_score = np.exp(log_density)

        return infiltration_score

    def predict_therapy_response(self, model: nn.Module,
                                  features: torch.Tensor,
                                  edge_index: torch.Tensor) -> dict:
        """Прогноз відповіді на терапію на основі TME"""
        model.eval()
        with torch.no_grad():
            predictions = model(features, edge_index)
            probs = F.softmax(predictions, dim=-1)

        # Aggregate to sample level
        response_prob = probs[:, 1].mean().item()  # Assuming binary: 0=non-responder, 1=responder

        # Identify predictive regions
        high_response_regions = probs[:, 1] > 0.7
        low_response_regions = probs[:, 1] < 0.3

        return {
            'predicted_response_probability': response_prob,
            'high_response_regions': high_response_regions.numpy(),
            'low_response_regions': low_response_regions.numpy(),
            'spatial_heterogeneity': probs[:, 1].std().item()
        }

Benchmarks та порівняння методів

| Method | Spatial Domain Detection | Cell Type Accuracy | Computational Cost |

|--------|--------------------------|--------------------|--------------------|

| SpaGCN | 0.72 ARI | N/A | Low |

| STAGATE | 0.78 ARI | N/A | Medium |

| GraphST | 0.81 ARI | N/A | Medium |

| Cell2location | N/A | 0.85 F1 | High |

| Tangram | N/A | 0.82 F1 | Medium |

| RCTD | N/A | 0.79 F1 | Low |


Ідеї для дослідження

Для бакалавра:

  • Cell segmentation pipeline з Cellpose
  • Spatial clustering порівняння методів
  • Visualization dashboard для spatial data

Для магістра:

  • GNN для spatial domain identification
  • Multi-modal integration (H&E + transcriptomics)
  • Transfer learning для нових тканин

Для PhD:

  • Novel architectures для spatial omics
  • Foundation models for spatial biology
  • Causal inference from spatial data
  • 3D spatial reconstruction

Інструменти

Data Analysis:

  • Scanpy/Squidpy (Python)
  • Seurat/Giotto (R)
  • Napari (visualization)

Deep Learning:

  • PyTorch Geometric
  • DGL (Deep Graph Library)
  • HistoEncoder

Platforms:

  • 10x Genomics Loupe Browser
  • Vizgen MERSCOPE
  • NanoString CosMx

Spatial omics — це frontier біології. Традиційна геноміка дала нам гени. Single-cell дала нам cell types. Spatial дає нам tissue architecture — розуміння того, як клітини організуються в функціональні одиниці.

Хвороби — не просто "неправильні гени". Це "неправильна організація". Пухлина — це не тільки мутації, а й те, як імунні клітини розташовані навколо неї. Нейродегенерація — це не тільки загибель нейронів, а й порушення їх зв'язків.

Якщо вас цікавить цей напрямок — від базового аналізу Visium до custom GNN для нових задач — звертайтесь до команди SKP-Degree на skp-degree.com.ua або пишіть у Telegram: @kursovi_diplomy. Допоможемо з вибором технології, архітектурою моделі та імплементацією.

Ключові слова: spatial omics, spatial transcriptomics, GNN, Graph Neural Networks, single-cell, bioinformatics, deep learning, cancer research, neuroscience, tumor microenvironment, дипломна робота, магістерська, AI, biology

Про автора

Команда SKP-Degree

Верифікований автор

Розробники та дослідники AI · Python, TensorFlow, PyTorch · Досвід у промисловій розробці

Команда SKP-Degree — професійні розробники з досвідом 7+ років у промисловій розробці. Виконали 1000+ проєктів для студентів з України, Польщі та країн Балтії.

Python Django Java ML/AI React C# / .NET JavaScript

Потрібна допомога з роботою?

Замовте курсову чи дипломну роботу з програмування. Оплата після демонстрації!

Без передоплати Відеодемонстрація Автономна робота 24/7
Написати в Telegram