Volver a la lista

Cellpose 3 + SAM: una guía práctica para la segmentación de células en imágenes de microscopía de fluorescencia.

Cellpose 3 (2024, versión optimizada para la restauración de imágenes) + SAM 2: un híbrido para la detección automática de los límites celulares en imágenes de microscopía de fluorescencia. Incluye la restauración del ruido de fondo, la segmentación de instancias celulares, la cuantificación de métricas estadísticas de forma (área, circularidad e intensidad) y la visualización completa con napari.

Intermedio
|
30min
|
Verificado (2026-07)
Progreso0/15 (0%)

Cellpose 3 + SAM: una solución para la segmentación de células en imágenes de microscopía de fluorescencia

Si alguna vez ha tenido que delinear manualmente los límites de las células en imágenes de microscopía de fluorescencia, entenderá por qué este artículo es importante. En una sola lámina con 500 células y 100 imágenes por día, etiquetar manualmente 50.000 células puede llevar varios días. Sin embargo, la cuantificación de la forma, el área y la intensidad de las células es una entrada fundamental para los análisis posteriores (respuesta a fármacos, cribado fenotípico, patología de tejidos). Si no se resuelve este cuello de botella, todo el ciclo experimental se ralentizará. Este artículo presenta una solución práctica que combina la arquitectura Cellpose 3, especializada en la restauración de imágenes, con Segment Anything 2 de Meta, para reducir este problema a unos 30 minutos.

📚 Recomendación de contenido previo (muy recomendable)

Este artículo es un contenido avanzado sobre la aplicación de la IA en la biología. Se recomienda encarecidamente que consulte y revise primero los siguientes artículos de DryBench antes de continuar.

Si no consulta el contenido previo, le resultará difícil seguir este artículo, ya que comenzará con el código práctico sin explicar de nuevo los principios de la arquitectura codificador-decodificador de la familia U-Net, los tensores de PyTorch y los fundamentos de la transferencia a la GPU.


Ya hemos aprendido esto en DryBench

En DryBench ai-native #2, aprendimos que las redes neuronales aprenden representaciones mediante la repetición de multiplicaciones ponderadas y activaciones no lineales sobre las entradas, y que la arquitectura codificador-decodificador de la familia U-Net es especialmente eficaz para la segmentación de imágenes a nivel de píxeles. En #12, repasamos los fundamentos de la manipulación de tensores de PyTorch, la transferencia a la GPU y el paso hacia adelante del modelo.

Sin embargo, las imágenes de laboratorio reales presentan algunos desafíos adicionales: iluminación desigual (viñeteado), desenfoque, superposición de límites debido a la alta densidad celular, desplazamiento de color entre canales y condiciones de fluorescencia con una baja relación señal/ruido. En este artículo, analizaremos la robustez de las CNN preentrenadas en estas condiciones y cómo los modelos fundacionales más recientes (SAM 2) pueden mejorar aún más esta robustez.

Definición del problema

Requisitos prácticos para la segmentación de células

De una sola imagen de fluorescencia (2048×2048), debemos extraer automáticamente los siguientes cuatro elementos:

  • Segmentación de instancias: ID único + máscara de píxeles para cada célula. Las células superpuestas deben distinguirse.
  • Estadísticas de forma: área (px²), circularidad y excentricidad para cada célula.
  • Estadísticos de intensidad: Media, valor máximo y desviación estándar del canal de fluorescencia por célula.
  • Relaciones de posición: Distancia entre células vecinas, presencia de agrupaciones.

Métricas objetivo:

  • Exhaustividad de la detección de células ≥ 0,92 (en comparación con el etiquetado manual).
  • Precisión de la detección de células ≥ 0,90.
  • Velocidad de procesamiento: GPU de 3 a 5 segundos por imagen de 2048×2048, CPU de 30 a 60 segundos.

Espectro de los enfoques existentes

  • Enfoque clásico basado en reglas (umbral de Otsu + cuencas hidrográficas): Exhaustividad de 0,7 a 0,8 en imágenes de baja densidad y alta relación señal/ruido, falla con frecuencia en las células superpuestas.
  • StarDist (polígono convexo en forma de estrella): Potente para la segmentación de núcleos de forma aproximadamente circular, débil para las células complejas.
  • Cellpose 1/2 (U-Net + campo de flujo): Cubre diversas formas de células, potente en el modo "zero-shot" gracias a los pesos preentrenados. En 2024, Cellpose 3 añade una red neuronal para la restauración de imágenes.
  • SAM (Segment Anything Model): Entrenado con imágenes naturales, pero funciona sorprendentemente bien en modo "zero-shot" con imágenes biológicas, aunque requiere indicaciones.
  • SAM 2: Integra vídeo e imágenes 2D, potente en la propagación de máscaras. El equipo de Cellpose anuncia la integración de Cellpose-SAM (2025).

La combinación de Cellpose 3 y SAM 2 es actualmente el enfoque "zero-shot" más sólido.

Pila de herramientas y requisitos de infraestructura

HerramientaFunciónLicencia
Cellpose 3 (cellpose>=3.0)Restauración de imágenes + segmentación de célulasBSD-3-Clause
Segment Anything 2 (segment-anything-2)Refinamiento de máscaras, segmentación de precisión basada en indicacionesApache 2.0
napariVisualización interactiva y verificación de etiquetasBSD-3-Clause
scikit-imageCálculo de estadísticas de forma e intensidadBSD-3-Clause
tifffileLectura de imágenes de microscopía multicanal en formato TIFFBSD-3-Clause
numpy, pandasProcesamiento de datos numéricos y tabularesBSD

Requisitos de infraestructura:

  • Inferencia de Cellpose 3: GPU de consumo de gama baja (se recomienda RTX 3060 con 6 GB o más). Se puede utilizar la CPU como alternativa, pero es 10 a 20 veces más lenta.
  • SAM 2: GPU de consumo de gama baja a media (se recomienda RTX 4060 con 8 GB o más). La inferencia en la CPU es muy lenta.
  • RAM de 16 GB o más (para el procesamiento por lotes de imágenes multicanal de 2048×2048).
  • Disco: pesos de Cellpose de aproximadamente 30 MB, pesos base de SAM 2 de aproximadamente 160 MB, pesos grandes de aproximadamente 900 MB.

Coste estimado para la reproducción del modelo: Coste de la API: 0 (totalmente local). Tiempo de GPU: 5 a 10 minutos por cada 100 imágenes (estimado para RTX 4060). El conjunto de datos es de código abierto (imágenes de ejemplo oficiales de Cellpose, gratuitas).

Implementación práctica de la canalización

Flujo general:

mermaid

Paso 1. Carga y preprocesamiento de imágenes TIFF

Las imágenes de microscopio suelen ser archivos TIFF multicanal (por ejemplo, DAPI + FITC + TRITC). Dado que el rango dinámico de cada canal puede variar mucho, la normalización es importante.

python
from pathlib import Path
from typing import NamedTuple
import numpy as np
import tifffile
class MicroscopyImage(NamedTuple):
"""Contenedor de una imagen microscópica."""
data: np.ndarray # shape: (channels, height, width) or (height, width)
channel_names: list[str]
pixel_size_um: float # Tamaño físico de un píxel en micrómetros
filename: str
def load_microscopy_image(
path: Path,
channel_names: list[str] | None = None,
pixel_size_um: float = 0.325,
) -> MicroscopyImage:
"""Carga un TIFF; puede extraer automáticamente el tamaño de píxel de metadatos OME-TIFF."""
data = tifffile.imread(path)
if data.ndim == 2:
data = data[np.newaxis, ...] # (H, W) → (1, H, W)
if channel_names is None:
channel_names = [f"ch{i}" for i in range(data.shape[0])]
return MicroscopyImage(
data=data,
channel_names=channel_names,
pixel_size_um=pixel_size_um,
filename=path.name,
)
def normalize_channel(
channel_data: np.ndarray,
lower_percentile: float = 1.0,
upper_percentile: float = 99.5,
) -> np.ndarray:
"""Normalización por percentiles, más robusta frente a outliers que min-max."""
p_low = np.percentile(channel_data, lower_percentile)
p_high = np.percentile(channel_data, upper_percentile)
if p_high - p_low < 1e-6:
return np.zeros_like(channel_data, dtype=np.float32)
normalized = np.clip((channel_data - p_low) / (p_high - p_low), 0, 1)
return normalized.astype(np.float32)

Paso 2. Restauración de imágenes y segmentación con Cellpose 3

Cellpose 3 integra un modelo de restauración de imágenes (eliminación de ruido, desenfoque y aumento de resolución) en la etapa previa a la segmentación, lo que mejora significativamente los resultados en imágenes de baja calidad [1].

python
from cellpose import models, io as cp_io
from cellpose.denoise import DenoiseModel
class CellposeSegmenter:
"""Integra restauración y segmentación con Cellpose 3."""
def __init__(
self,
model_type: str = "cyto3", # cyto3: modelo reciente de cuerpos celulares; nuclei: solo núcleos
restore_type: str = "denoise", # denoise / deblur / upsample / None
device: str = "cuda",
):
self.model = models.CellposeModel(
gpu=(device == "cuda"),
model_type=model_type,
)
self.restore_type = restore_type
if restore_type:
self.denoiser = DenoiseModel(
model_type=f"{restore_type}_{model_type}",
gpu=(device == "cuda"),
)
else:
self.denoiser = None
def segment(
self,
image: np.ndarray,
diameter: float | None = None,
channels: list[int] = [0, 0],
cellprob_threshold: float = 0.0,
flow_threshold: float = 0.4,
) -> dict:
"""
image: (H, W) grayscale or (C, H, W) multi-channel.
diameter: diámetro celular esperado en px; None activa la estimación automática.
channels: índices [canal del cuerpo celular, canal nuclear]; [0,0] = escala de grises.
Returns: {masks, flows, styles, diams}
"""
if self.denoiser is not None:
image = self.denoiser.eval(image, channels=channels)[0]
masks, flows, styles = self.model.eval(
image,
diameter=diameter,
channels=channels,
cellprob_threshold=cellprob_threshold,
flow_threshold=flow_threshold,
)
return {
"masks": masks, # (H, W) int, 0=fondo, 1..N=ID de célula
"flows": flows, # gradient flow visualization
"diameter_used": diameter or self.model.diam_labels,
}

Paso 3. Refinamiento con SAM 2 (opcional)

Las regiones de contorno imprecisas o las células superpuestas detectadas por Cellpose se refinan con SAM 2. SAM 2 recibe un indicador de punto/recuadro/máscara y devuelve una máscara precisa [2].

python
from segment_anything_2 import SAM2ImagePredictor
class SAM2Refiner:
"""Refina máscaras de Cellpose con SAM 2."""
def __init__(self, model_id: str = "facebook/sam2-hiera-base", device: str = "cuda"):
self.predictor = SAM2ImagePredictor.from_pretrained(model_id)
self.predictor.model.to(device)
def refine_mask(
self,
image: np.ndarray,
cellpose_mask: np.ndarray,
cell_id: int,
) -> np.ndarray:
"""Refina con SAM 2 la máscara Cellpose de un ID celular concreto."""
self.predictor.set_image(image)
# Usa el bounding box y el centroide de la máscara Cellpose como prompt de SAM
cell_mask = (cellpose_mask == cell_id)
if not cell_mask.any():
return cell_mask
ys, xs = np.where(cell_mask)
bbox = np.array([xs.min(), ys.min(), xs.max(), ys.max()])
centroid = np.array([[xs.mean(), ys.mean()]])
refined_masks, scores, _ = self.predictor.predict(
point_coords=centroid,
point_labels=np.array([1]), # foreground
box=bbox,
multimask_output=True,
)
# Selecciona la máscara con mayor puntuación
best_idx = scores.argmax()
return refined_masks[best_idx]
def refine_uncertain_cells(
self,
image: np.ndarray,
cellpose_result: dict,
uncertainty_threshold: float = 0.5,
) -> np.ndarray:
"""Refina con SAM solo células con baja confianza en el flow de Cellpose."""
masks = cellpose_result["masks"].copy()
# flows[2] es el mapa de probabilidad celular
cell_probs = cellpose_result["flows"][2] if len(cellpose_result["flows"]) > 2 else None
if cell_probs is None:
return masks
for cell_id in np.unique(masks):
if cell_id == 0:
continue
cell_region = (masks == cell_id)
mean_prob = cell_probs[cell_region].mean()
if mean_prob < uncertainty_threshold:
refined = self.refine_mask(image, masks, cell_id)
masks[cell_region] = 0
masks[refined] = cell_id
return masks

Paso 4. Cuantificación de parámetros estadísticos de forma e intensidad

Extraiga los parámetros estándar para cada célula utilizando scikit-image y regionprops.

python
from dataclasses import dataclass, asdict
from typing import Iterable
from skimage.measure import regionprops, regionprops_table
import pandas as pd
@dataclass
class CellFeatures:
"""Características de forma e intensidad de una célula."""
cell_id: int
area_px: int
area_um2: float
perimeter_px: float
circularity: float # 4π·area / perimeter² (círculo=1)
eccentricity: float # Excentricidad (círculo=0, línea=1)
solidity: float # area / convex_hull_area
centroid_y: float
centroid_x: float
mean_intensity_per_channel: dict[str, float]
max_intensity_per_channel: dict[str, float]
def extract_features(
mask: np.ndarray,
intensity_channels: dict[str, np.ndarray],
pixel_size_um: float,
) -> list[CellFeatures]:
"""
mask: (H, W) int, 0=fondo, 1..N=ID de célula.
intensity_channels: {channel_name: (H, W) array}.
"""
features = []
for prop in regionprops(mask):
perimeter = prop.perimeter if prop.perimeter > 0 else 1e-6
circularity = (4 * np.pi * prop.area) / (perimeter ** 2)
mean_intensity = {
name: float(ch[prop.coords[:, 0], prop.coords[:, 1]].mean())
for name, ch in intensity_channels.items()
}
max_intensity = {
name: float(ch[prop.coords[:, 0], prop.coords[:, 1]].max())
for name, ch in intensity_channels.items()
}
features.append(CellFeatures(
cell_id=int(prop.label),
area_px=int(prop.area),
area_um2=float(prop.area * (pixel_size_um ** 2)),
perimeter_px=float(prop.perimeter),
circularity=float(circularity),
eccentricity=float(prop.eccentricity),
solidity=float(prop.solidity),
centroid_y=float(prop.centroid[0]),
centroid_x=float(prop.centroid[1]),
mean_intensity_per_channel=mean_intensity,
max_intensity_per_channel=max_intensity,
))
return features
def features_to_dataframe(features: Iterable[CellFeatures]) -> pd.DataFrame:
"""Formato tabular para análisis downstream."""
rows = []
for f in features:
row = asdict(f)
for ch_name, val in row.pop("mean_intensity_per_channel").items():
row[f"mean_{ch_name}"] = val
for ch_name, val in row.pop("max_intensity_per_channel").items():
row[f"max_{ch_name}"] = val
rows.append(row)
return pd.DataFrame(rows)

Paso 5. Visualización con napari + revisión manual

napari es un visor de imágenes 4D en tiempo real que permite a los usuarios revisar y corregir rápidamente los resultados automáticos [3].

python
def visualize_with_napari(
image: np.ndarray,
masks: np.ndarray,
channel_names: list[str],
) -> None:
"""Visualiza en napari la imagen con una superposición de máscaras.
Al ejecutarlo abre una ventana GUI. En un servidor remoto usa reenvío X11
o el visor web napari-remote.
"""
import napari
viewer = napari.Viewer()
for i, ch_name in enumerate(channel_names):
viewer.add_image(
image[i] if image.ndim == 3 else image,
name=ch_name,
colormap=["green", "red", "blue"][i % 3],
blending="additive",
)
viewer.add_labels(masks, name="cell masks", opacity=0.5)
napari.run()

Flujo de trabajo integrado

python
def full_pipeline(
image_path: Path,
channel_names: list[str],
pixel_size_um: float,
output_csv: Path,
use_sam_refinement: bool = False,
device: str = "cuda",
) -> pd.DataFrame:
"""Una imagen → DataFrame de características celulares."""
img = load_microscopy_image(image_path, channel_names, pixel_size_um)
# Normaliza el canal del cuerpo celular, por ejemplo FITC
cell_channel_idx = channel_names.index("FITC") if "FITC" in channel_names else 0
normalized = normalize_channel(img.data[cell_channel_idx])
segmenter = CellposeSegmenter(model_type="cyto3", restore_type="denoise", device=device)
result = segmenter.segment(normalized)
masks = result["masks"]
if use_sam_refinement:
refiner = SAM2Refiner(device=device)
masks = refiner.refine_uncertain_cells(normalized, result)
intensity_channels = {
name: normalize_channel(img.data[i])
for i, name in enumerate(channel_names)
}
features = extract_features(masks, intensity_channels, pixel_size_um)
df = features_to_dataframe(features)
df["source_image"] = img.filename
df.to_csv(output_csv, index=False)
return df

Rendimiento, costo y casos de fallo conocidos

Referencia de rendimiento (basado en pruebas públicas)

EnfoqueConjunto de datosF1 (detección de células)Tiempo de procesamientoFuente
Otsu + WatershedConjunto de pruebas de Cellpose0.62CPU < 1 sLínea de base heredada
StarDistLIVECell0.71GPU 1-2 sSchmidt et al., MICCAI 2018
Cellpose 2Conjunto de pruebas de Cellpose0.86GPU 2-4 sPachitariu y Stringer, Nat Methods 2022
Cellpose 3 (con reducción de ruido)Conjunto de pruebas de Cellpose + bajo SNR0.91GPU 3-5 sStringer y Pachitariu, Nat Methods 2025 [1]
Cellpose-SAMLIVECell + Conjunto de pruebas de Cellpose0.93-0.95GPU 5-8 sPresentación del equipo de Cellpose 2025 (estimado)
SAM 2 independiente (aplicado a biología)LIVECell0.84 (sin indicaciones)GPU 3-5 sMeta AI 2024 [2]

Costo estimado para la reproducción por parte del alumno

  • Costo de la API: 0 (totalmente local).
  • Con una GPU de consumo pequeño (RTX 4060 8 GB), el procesamiento de aproximadamente 100 imágenes tarda entre 5 y 10 minutos.
  • En caso de retroceso a la CPU, el procesamiento de 100 imágenes tarda entre 1 y 2 horas.
  • Descarga de datos: las imágenes de ejemplo de Cellpose son gratuitas, y el conjunto de datos LIVECell es de acceso público y gratuito.

Tres casos de fallo conocidos (recopilados de la comunidad y de artículos)

  1. Fusión de células adyacentes en células densas (subsegmentación) Síntoma: si las células están muy juntas, se fusionan en una sola máscara grande. Causa: el campo de flujo de Cellpose no detecta la señal de gradiente en el límite de la célula. Esto es especialmente grave en entornos de bajo SNR. Solución: (a) reducir flow_threshold (por ejemplo, a 0.2) para reconocer más flujo como perteneciente a la célula, (b) ajustar cellprob_threshold, (c) refinar solo las áreas inciertas con SAM 2. Fuente: Cellpose GitHub Issues: varios hilos sobre "sobresegmentación/subsegmentación" [4].

  2. Fallo en células con formas atípicas (por ejemplo, axones y dendritas de neuronas) Síntoma: el modelo de cito/núcleo está entrenado para que se parezca a una forma circular, por lo que no detecta las células con axones largos. Causa: sesgo en los datos de entrenamiento. Evitar: (a) Usar el modelo livecell o neurips, (b) realizar un ajuste fino con datos propios (Cellpose 3 lo hace muy fácil desde la GUI). Fuente: Documento oficial de la colección de modelos de Cellpose y informes de la comunidad [5].

  3. Error al especificar los canales en imágenes multicanal Síntoma: se establece channels=[0,0] (escala de grises), pero en realidad se necesita un canal de núcleos separado para obtener resultados precisos. Causa: Cellpose recibe la especificación de canales en el formato [canal_cito, canal_núcleo], y si se proporciona incorrectamente, el rendimiento se reduce drásticamente. Evitar: verificar el orden de los canales de la imagen y especificarlo correctamente. Si hay DAPI, especifíquelo como el canal de núcleos. Fuente: sección "channels" de la documentación oficial de Cellpose [6].

Ideas de ampliación

  • Imágenes de series temporales en vivo: utilizar la propagación de máscaras de vídeo de SAM 2 para el seguimiento de células y la detección automática de eventos de división.
  • Pila confocal 3D: modo 3D de Cellpose 3 + refinamiento de SAM 2 en cada capa de la pila z.
  • Análisis fenotípico: introducir métricas estadísticas de forma en un clasificador posterior (por ejemplo, XGBoost) para la clasificación automática de la respuesta a fármacos.
  • Integración con QuPath: para imágenes grandes de portaobjetos completos, dividir en mosaicos en QuPath → aplicar esta canalización a cada mosaico → combinar los resultados.
  • Empaquetado como un complemento de napari: distribuir esta canalización como un complemento de napari para que los usuarios de la GUI del laboratorio puedan utilizarla.

Próximos temas

  • Episodio 05 pathology-image-embedding: generar embeddings de los recortes de células obtenidos en este episodio con un modelo fundacional de patología (Virchow) para buscar casos similares.
  • Episodio 07 drug-target-gnn: utilizar las métricas estadísticas de forma y intensidad de las células como entrada para una GNN que prediga la respuesta a fármacos.
  • Episodio 12 single-cell-perturbation: aplicar predicciones de perturbación genética in silico a las células segmentadas automáticamente.
  • Episodio 14 bio-mcp-agent: exponer esta canalización como una herramienta MCP para permitir consultas autónomas como "cuente el número de células en esta imagen".

Referencias

  1. Stringer C, Pachitariu M. "Cellpose3: one-click image restoration for improved cellular segmentation." Nature Methods 2025. https://www.nature.com/articles/s41592-025-02595-5
  2. Ravi N, Gabeur V, Hu Y-T, et al. "SAM 2: Segment Anything in Images and Videos." Meta AI 2024. https://ai.meta.com/research/publications/sam-2-segment-anything-in-images-and-videos/
  3. napari documentation: https://napari.org/stable/
  4. Cellpose GitHub Issues (over/under segmentation): https://github.com/MouseLand/cellpose/issues
  5. Cellpose Model Zoo: https://cellpose.readthedocs.io/en/latest/models.html
  6. Cellpose channels documentation: https://cellpose.readthedocs.io/en/latest/inputs.html
  7. Pachitariu M, Stringer C. "Cellpose 2.0: how to train your own model." Nature Methods 2022. https://www.nature.com/articles/s41592-022-01663-4
  8. Schmidt U, Weigert M, Broaddus C, Myers G. "Cell Detection with Star-Convex Polygons (StarDist)." MICCAI 2018.
  9. LIVECell dataset: https://sartorius-research.github.io/LIVECell/
  10. Cellpose GitHub: https://github.com/MouseLand/cellpose
  11. Segment Anything 2 GitHub: https://github.com/facebookresearch/segment-anything-2
  12. scikit-image regionprops: https://scikit-image.org/docs/stable/api/skimage.measure.html
  13. QuPath (whole slide image analysis): https://qupath.github.io/
  14. Human Protein Atlas (imagen de ejemplo): https://www.proteinatlas.org/
  15. Broad Bioimage Benchmark Collection: https://bbbc.broadinstitute.org/

💬 Preguntas y comentarios

0 comentarios

Puedes publicar sin iniciar sesión. Los comentarios de invitados no pueden editarse ni eliminarse después.

0/2000

Cargando...