Source code for acia.tracking.processor.trackastra

"""Trackastra based tracking"""

import logging
import tempfile
from pathlib import Path

import numpy as np
import torch
from trackastra.model import Trackastra
from trackastra.tracking import graph_to_ctc

from acia.attribute import attribute_tracking
from acia.base import ImageSequenceSource, Overlay
from acia.segm.formats import read_ctc_segmentation_native
from acia.tracking import annotate_tracklet_times, ctc_track_graph
from acia.tracking.formats import read_ctc_tracklet_graph

from . import TrackingProcessor
from .utils import overlay_to_masks


[docs] class TrackastraTracker(TrackingProcessor): """Processor for Trackastra: https://doi.org/10.48550/arXiv.2405.15700"""
[docs] def __init__(self, mode="greedy"): device = "cuda" if torch.cuda.is_available() else "cpu" # Load a pretrained model self.model = Trackastra.from_pretrained("general_2d", device=device) self.mode = mode
def __call__(self, images: ImageSequenceSource, segmentation: Overlay): image = next(iter(images)).raw height, width = image.shape[:2] masks = overlay_to_masks(segmentation, height=height, width=width) imgs = np.stack([im.raw for im in images]) if len(imgs.shape) == 4: # strip off last because it should not be used imgs = imgs[..., 0] # the masks are indexed by absolute frame, so this compares what # actually has to line up: a mismatch here means the tracker would # associate cells against the wrong images if len(masks) != len(imgs): logging.warning( "Segmentation spans %d frames but the image source has %d; " "tracking will run on the overlapping frames only.", len(masks), len(imgs), ) # perform the actual tracking track_graph, tracked_masks = self.model.track(imgs, masks, mode=self.mode) # Write to cell tracking challenge format with tempfile.TemporaryDirectory() as td: _, _ = graph_to_ctc( track_graph, tracked_masks, outdir=td, ) input_path = Path(td) track_file = input_path / "man_track.txt" ov = read_ctc_segmentation_native(input_path) tracklet_graph = read_ctc_tracklet_graph(track_file) # Propagate the source's time calibration onto the tracked overlay so # real time (not just frame index) flows into the lineage graphs: the # tracked overlay is otherwise uncalibrated, which is why callers used # to have to re-supply timepoints downstream. Stamps cont.time on the # overlay and start_time/end_time on the tracklet graph; both a no-op # for an uncalibrated source. timepoints = images.timepoints if timepoints is not None: ov = ov.with_timepoints(timepoints) annotate_tracklet_times(tracklet_graph, timepoints) tracking_graph = ctc_track_graph(ov, tracklet_graph) attribute_tracking(ov, tracklet_graph, tracking_graph, self) return ov, tracklet_graph, tracking_graph