TrackMate: Parse exact relationships of splitting events within each track

Hello!

I am using TrackMate to manually track cells in time-lapse images over many generations. I am mostly interested in the divisions, so I usually place a spot on the mitotic cell, place spots on the daughter cells whenever I can first distinguish them and then place the next spots when they go into mitosis and so on.

In my post-analysis I now would want to extract the exact family relationships of the splitting events.

My ideal output would be a dataframe showing the Track_ID, XYT-coordinates of all the splitting events in that track and how they relate, for example by assigning numbers such as:
1
1.1, 1.2
1.1.1, 1.1.2 / 1.2.1, 1.2.2
etc.

I am already a bit familiar with parsing data from the TrackMate xml files in Python, but I have no clue how to proceed here, so any input would be highly appreciated! :slight_smile:

Thanks!
Tobias

1 Like

Hello Tobias.
Just out of curiosity, could you post a screenshot of TrackScheme with the tracks?

Trying to find a representative section:

Beautiful! (I wish the stripes were not the default L&F though.)

I cannot think of an immediate way to do that. I would go for two routes, depending on how much you like to program.

1.
Import the data in Python or MATLAB as a directed graph, and use graph iterators.

2.
Do convoluted massages with the CSV tables that you can export with the Analysis button in TrackMate.

  • The Track statistics table has a column called NUMBER_SPLITS that count how many divisions do you have for each track. You can get the track ID form the TRACK_ID column.

  • The Links statistics table also has a column TRACK_ID that will allow you to isolate all the links in a track.

  • From that you must now find the ID of a spot that appears twice in the SOURCE_SPOT_ID column. If it is part of 2 links, it means that it is the spot for which there is a division.

  • Get its ID, and look for X, Y, Z and frame info in Spots table.

2 Likes

Thanks a lot, I will give it a try and post an update on this!

1 Like

I wrote a Python script that does what I wanted! :slight_smile:
Output will be a Pandas DataFrame containing a column β€œLineage” that accumulates the Source_IDs of the parental cells, e.g.:
β€œ75” Mother
β€œ75.120” Daughter A
β€œ75.124” Daughter B
β€œ75.120.456” Grand-daughter A.1
etc. etc.

And with this information I can do plenty of downstream analysis.
I hope someone else might find it useful:

import pandas as pd
from collections import Counter

# Path to directory (needs to be specified by user)

datadir = "INSERT_PATH_TO_YOUR_ANALYSIS_FOLDER"

tracks = pd.read_csv(datadir+"NAME_OF_TRACK_STATISTICS.csv",\
                     usecols = ['NUMBER_SPOTS', 'NUMBER_GAPS', 'LONGEST_GAP', 'NUMBER_SPLITS', 'TRACK_ID'])
links = pd.read_csv(datadir+"NAME_OF_LINKS_STATISTICS.csv", \
                    usecols = ['Label', 'TRACK_ID', 'SPOT_SOURCE_ID', 'SPOT_TARGET_ID', 'EDGE_X_LOCATION', \
                               'EDGE_Y_LOCATION'])
spots = pd.read_csv(datadir+"NAME_OF_SPOTS_STATISTICS.csv",\
                    usecols = ['Label', 'ID', 'TRACK_ID', 'POSITION_X', 'POSITION_Y', 'FRAME'])


# Generate a list of spot_ids that correspond to a splitting event
# (SOURCE_IDs of splitting event appear twice)

source_ids = list(links["SPOT_SOURCE_ID"])
source_id_counts = Counter(source_ids)
splitting_event_ids = [id for id in source_id_counts if source_id_counts[id] > 1]

# Add Boolean to Spots and Links Dataframes

spots["Splitting_event"] = spots["ID"].apply(lambda x:\
                                             False if x not in splitting_event_ids\
                                             else True)

links["Splitting_event"] = links["SPOT_SOURCE_ID"].apply(lambda x:\
                                             False if x not in splitting_event_ids\
                                             else True)
# Rename link dataframe columns

links.columns = ['LABEL',\
                 'TRACK_ID',\
                 'SOURCE_ID',\
                 'TARGET_ID',\
                 'EDGE_X_LOCATION',\
                 'EDGE_Y_LOCATION',\
                 'SPLITTING_EVENT']


# Get lineages

def get_lineage(x, links_df):
    num = x.SOURCE_ID
    if x.SPLITTING_EVENT:
        lineage = [str(num)]
    else:
        lineage=[]
    while True:
        y = links_df.loc[links_df['TARGET_ID'] == num,:]
        if y.empty:
            break
        if y.SPLITTING_EVENT.values[0]:
            lineage.append(str(y.SOURCE_ID.values[0]))
        num = y.SOURCE_ID.values[0]
    if lineage:
        return ".".join(reversed(lineage))
    else:
        return None

   
links['LINEAGE'] = links.apply(get_lineage, links_df=links, axis=1)

# Exclude intermediate spots and links

links = links[links["SPLITTING_EVENT"]==True]

spots = spots[spots["Splitting_event"]==True]
spots.rename(columns={"ID": "SOURCE_ID"}, inplace=True)

# merging

df_merged = pd.merge(links, spots, how="outer", on=["SOURCE_ID", "TRACK_ID"])
df_merged = df_merged.drop(['Splitting_event', 'Label', 'LABEL'], axis=1)
df_merged["Generation"] = df_merged["LINEAGE"].apply(lambda x: x.count(".") + 1)

# Add column containing the SOURCE_ID of the mother cell

def get_mother(x):
    lineage_list = x.split(".")
    if len(lineage_list)==1:
        pass
    else:
        return lineage_list[-2] 
    
df_merged["MOTHER_ID"] = df_merged["LINEAGE"].apply(lambda x: get_mother(x))
2 Likes