Tutorial 3: Interactive 3D segmentation with MorphoNet

This tutorial shows, step by step, how to employ Python and MorphoNet to segment ands track 3D cells. It guides users from raw data to final results.

Requirements

For this tutorial, you need python 2.7 and several libaries : scipy , numpy, scikit-image, vtk, httplib,bz2,hashlib, json, requests, morphsnakes

Part 1 : Data Preparation

Three dimentional data of live embryonic development at single-cell resolution were taken through light-sheet microscopy as explained in Guignard, Fiuza et al., bioRxiv 238741 (2017).

In this tutorial we take as example the data corresponding to the embryo referred to as A1 in the paper.

You can directly download the ascidian data associated to this tutorial here http://www.morphonet.org/Tutorials/MorphoNet_DATA.zip

1.1 Dataset specification

We begin with intensity images to be segmented, what we call here raw data. These images, and the ones resulting from the segmentation we will perform, are in the standard .tiff format. To import them in Python we use the library ImageHandling. Each raw image corresponds to the intensity of fluorescence captured by the microscope, after a process of fusion of data collected at different angles. Two consecutive images are separated by a timegap of two minutes.

In [1]:
path_to_images="DATA/ASCIDIAN/RAWDATA/" #this is the directory where rawdata images are stored
image_name="fuse_t"
path_to_segmentation="DATA/ASCIDIAN/NEWSEGMENTATION/"  #this is the directory where segmented images will be stored
seg_name="segmentation_t"
path_to_background="DATA/ASCIDIAN/BACKGROUND/"  #this is the directory where the background segmentations are stored
back_name="background_t"
begin=1 #first timepoint of dataset
end=10 #last timepoint of dataset
reduce_factor=2 #Reduce the quality of the mesh but also the computation time

1.2 MorphoNet Connection and dataset Creation

We will now connect to the MorphoNet database via the dedicated API and create a new dataset which will host the segmentation of these images (after being converted into surface mesh in the specific MorphoNet format).

In [2]:
import os,sys
sys.path.append('API')
from MorphoNet import MorphoNet #MorphoNet API

#MorphoNet Login credentials
mn_login="your.login"
mn_password="your.password"

#Connection to MorphoNet database
mn=MorphoNet(mn_login,mn_password)
mn.connect()

# Creation of a new dataset
dataset_name="Interactive Segmentation"
mn.selectDataSetByName(dataset_name)
#A specific dataset is selected here. All future API interactions will concern this database, unless a new one is selected later on
if mn.id_dataset==-1: #If the dataset does not yet exist
    mn.createDataSet(dataset_name,begin,end)  #Creation of a dataset containing end-begin timepoints, 
else: #If the dataset already exists
    mn.clearDataSet() #Clears all meshes and infos associated with this dataset
emmanuel.faure is connected to MorphoNet
 --> found dataset Interactive Segmentation with id 726 from 1 to 10 owned by 1 with type=1
 --> dataset cleared

1.3 Data segmentation, first mesh creation and upload to MorphoNet

Now that a new dataset is created, we begin the segmentation process of raw images. Each image, after being segmented is transformed into an .obj containing the surface mesh and the objects identities, following the standard MorphoNet format. One segmented image and one associated .obj file is created for each timepoint and uploaded to the MorphoNet database to the new dataset we just created.

First of all, we read 3D raw images in Python thanks to the function read_3D_image, and then apply a simple segmentation algorithm with 3D watersheds. The output of this algorithm is saved to the pre-selected folder through the function save_3D_image and converted into .obj format thanks to the function convertVolumetoOBJ. This obj is then uploaded to MorphoNet via the API function uploadMesh.

The Python functions read_3D_image, save_3D_image and convertVolumetoOBJ are provided together with the Python API as advanced Python-MorphoNet tools.

In [3]:
from tools import convertVolumetoOBJ,save_3D_image,read_3D_image
from skimage.morphology import watershed
from skimage.filters import gaussian 
from skimage.feature import peak_local_max
from scipy import ndimage as ndi
import numpy as np

for t in range(begin,end+1):
    im=read_3D_image(t,path_to_images,im_name=image_name,reduce_factor=reduce_factor) #Read the rawdata images
    img=gaussian(im,sigma=8/reduce_factor) #Process a large Gaussian filter
    seeds =ndi.label( peak_local_max(img.max()-img, indices=False) )[0] #Measure the local maximim for the seed 
    back=read_3D_image(t,path_to_background,im_name=back_name,reduce_factor=reduce_factor)#Read Background
    labels = watershed(im, seeds) #Process Watersheed
    labels[back==1]=0 #Remove Background
    save_3D_image(np.uint8(labels),t,path_to_segmentation,im_name=seg_name) #Save the segmented images
    obj=convertVolumetoOBJ(labels,factor_scale=reduce_factor,background_threshold=0,min_pixel=100,prefix=str(t)+",",verbose=1)#Compute the meshes from the 3D segmented volume
    mn.uploadMesh(t,obj) #Upload Meshes for timepoint t to selected dataset 
 -> read DATA/RAWDATA/fuse_t001.tiff
 -> read DATA/BACKGROUND/background_t001.tiff
/Library/Python/2.7/site-packages/skimage/morphology/watershed.py:151: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
  new_im[[slice(p, -p, None) for p in pads]] = im
/Library/Python/2.7/site-packages/skimage/morphology/watershed.py:228: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result.
  image.ndim]
 -> save DATA/NEWSEGMENTATION/segmentation_t001.tiff
-> Found 92 objects
 --> meshes at time point 1 uploaded ( with id 115110 )
 -> read DATA/RAWDATA/fuse_t002.tiff
 -> read DATA/BACKGROUND/background_t002.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t002.tiff
-> Found 98 objects
 --> meshes at time point 2 uploaded ( with id 115111 )
 -> read DATA/RAWDATA/fuse_t003.tiff
 -> read DATA/BACKGROUND/background_t003.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t003.tiff
-> Found 78 objects
 --> meshes at time point 3 uploaded ( with id 115112 )
 -> read DATA/RAWDATA/fuse_t004.tiff
 -> read DATA/BACKGROUND/background_t004.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t004.tiff
-> Found 83 objects
 --> meshes at time point 4 uploaded ( with id 115113 )
 -> read DATA/RAWDATA/fuse_t005.tiff
 -> read DATA/BACKGROUND/background_t005.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t005.tiff
-> Found 83 objects
 --> meshes at time point 5 uploaded ( with id 115114 )
 -> read DATA/RAWDATA/fuse_t006.tiff
 -> read DATA/BACKGROUND/background_t006.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t006.tiff
-> Found 82 objects
 --> meshes at time point 6 uploaded ( with id 115115 )
 -> read DATA/RAWDATA/fuse_t007.tiff
 -> read DATA/BACKGROUND/background_t007.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t007.tiff
-> Found 83 objects
 --> meshes at time point 7 uploaded ( with id 115116 )
 -> read DATA/RAWDATA/fuse_t008.tiff
 -> read DATA/BACKGROUND/background_t008.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t008.tiff
-> Found 92 objects
 --> meshes at time point 8 uploaded ( with id 115117 )
 -> read DATA/RAWDATA/fuse_t009.tiff
 -> read DATA/BACKGROUND/background_t009.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t009.tiff
-> Found 81 objects
 --> meshes at time point 9 uploaded ( with id 115118 )
 -> read DATA/RAWDATA/fuse_t010.tiff
 -> read DATA/BACKGROUND/background_t010.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t010.tiff
-> Found 96 objects
 --> meshes at time point 10 uploaded ( with id 115119 )

1.4 Log in to MorphoNet and check you data

You can now find your dataset online at http://www.morphonet.org. Log with your credidentials and open the dataset (in the Generic section), after waiting for the necessary conversion time.

Part 2 : Segmentation corrections

2.1 Split some cells

One common problem during segmentations is the so-called undersegmentation, consisting in two or more cells detected as one. MorphoNet can easily help identifying and solving this class of problems: pick all undersegmented cells, apply a color selection to them and save it with the name "split", as shown in the video below.

One can now import this selection directly in Python via the MorphoNet API through the function getObjectsFromInfos. One has thus the complete list of undersegmented cells. With this information, one can perform local enhanced segmentations to recover the correct cellular shapes. Then, a new mesh can be created and uploaded to MorphoNet.

In [4]:
cells_to_split=mn.getObjectsFromInfos('split')
time_to_upload=[]
for c in cells_to_split:
    t=int(c.split(',')[0])
    pixel_v=int(c.split(',')[1])
    print('Split At ' +str(t)+' -> ' + str(pixel_v))
    im=read_3D_image(t,path_to_images,im_name=image_name,reduce_factor=reduce_factor) #Read the rawdata image
    seg=read_3D_image(t,path_to_segmentation,im_name=seg_name)#Read the corresponding segmentation
    coords=np.where(seg==pixel_v) # Take the coordinate of this cell
    box=[coords[0].min(),coords[0].max()+1,coords[1].min(),coords[1].max()+1,coords[2].min(),coords[2].max()+1]
    coordsb=tuple([coords[0]-coords[0].min(),coords[1]-coords[1].min(),coords[2]-coords[2].min()])
    imb=im[box[0]:box[1],box[2]:box[3],box[4]:box[5]] #Take only the image focus on the cell 

    #Now we look for 2 peaks for this cell
    sigma=7 #Starting Sigma For Gaussian
    nbPeaks=1
    while nbPeaks<=1 and sigma>0:
        img=gaussian(im,sigma=float(sigma)/reduce_factor) #Process a less Gaussian filter
        peak = peak_local_max(img.max()-img, indices=False) #Measure the local maximim
        nbPeaks=len(np.where(peak[coords])[0])
        #print('For sigma '+str(sigma)+ ' found  '+str(nbPeaks)+ ' peaks')
        if nbPeaks<=1:
            sigma=sigma-1
    if nbPeaks<=1:
        print(' -> could not split this cell '+str(pixel_v))
    else:
        markers = ndi.label(peak[box[0]:box[1],box[2]:box[3],box[4]:box[5]])[0] #Create seed
        mask=np.zeros_like(imb);mask[coordsb]=1 #Create a mask on the other cells
        labels = np.uint8(watershed(imb, markers,mask=mask)) #Process Watersheed
        last_label=seg.max() #We take the maximum labeled cell
        new_labels=np.unique(labels[labels!=0])
        nlabels=np.zeros_like(labels)
        #We relabeld the new cells
        nl=0
        for l in new_labels:
            if nl==0: #The first we keep it as it was
                nlabels[labels==l]=pixel_v
            else:
                nlabels[labels==l]=last_label+nl
            nl+=1
        print(' -> create cells '+str(np.unique(nlabels[nlabels!=0])))
        seg[coords]=nlabels[coordsb]
        save_3D_image(np.uint8(seg),t,path_to_segmentation,im_name=seg_name) #Save the New image
        time_to_upload.append(t) #Add this time step to re-upload

#We have to re-upload all new segmentation
for t in np.unique(time_to_upload): 
    seg=read_3D_image(t,path_to_segmentation,im_name=seg_name)#Read the last segmentation
    obj=convertVolumetoOBJ(seg,factor_scale=reduce_factor,background_threshold=0,min_pixel=100,prefix=str(t)+",",verbose=0)
    mn.clearMeshAt(t) #Remove previous mesh
    mn.uploadMesh(t,obj)  #Upload the new mesh
Split At 5 -> 12
 -> read DATA/RAWDATA/fuse_t005.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t005.tiff
 -> create cells [ 12 122 123]
 -> save DATA/NEWSEGMENTATION/segmentation_t005.tiff
Split At 5 -> 28
 -> read DATA/RAWDATA/fuse_t005.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t005.tiff
 -> create cells [ 28 124]
 -> save DATA/NEWSEGMENTATION/segmentation_t005.tiff
Split At 6 -> 7
 -> read DATA/RAWDATA/fuse_t006.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t006.tiff
 -> create cells [  7 111 112]
 -> save DATA/NEWSEGMENTATION/segmentation_t006.tiff
Split At 5 -> 10
 -> read DATA/RAWDATA/fuse_t005.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t005.tiff
 -> create cells [ 10 125]
 -> save DATA/NEWSEGMENTATION/segmentation_t005.tiff
Split At 10 -> 95
 -> read DATA/RAWDATA/fuse_t010.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t010.tiff
 -> create cells [ 95 123]
 -> save DATA/NEWSEGMENTATION/segmentation_t010.tiff
Split At 9 -> 32
 -> read DATA/RAWDATA/fuse_t009.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t009.tiff
 -> create cells [ 32 100 101]
 -> save DATA/NEWSEGMENTATION/segmentation_t009.tiff
Split At 4 -> 51
 -> read DATA/RAWDATA/fuse_t004.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t004.tiff
 -> create cells [ 51 138 139]
 -> save DATA/NEWSEGMENTATION/segmentation_t004.tiff
Split At 7 -> 11
 -> read DATA/RAWDATA/fuse_t007.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t007.tiff
 -> create cells [ 11 105]
 -> save DATA/NEWSEGMENTATION/segmentation_t007.tiff
Split At 8 -> 29
 -> read DATA/RAWDATA/fuse_t008.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t008.tiff
 -> create cells [ 29 122]
 -> save DATA/NEWSEGMENTATION/segmentation_t008.tiff
Split At 3 -> 2
 -> read DATA/RAWDATA/fuse_t003.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t003.tiff
 -> create cells [  2 119]
 -> save DATA/NEWSEGMENTATION/segmentation_t003.tiff
Split At 4 -> 5
 -> read DATA/RAWDATA/fuse_t004.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t004.tiff
 -> create cells [  5 140 141]
 -> save DATA/NEWSEGMENTATION/segmentation_t004.tiff
Split At 3 -> 38
 -> read DATA/RAWDATA/fuse_t003.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t003.tiff
 -> create cells [ 38 120]
 -> save DATA/NEWSEGMENTATION/segmentation_t003.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t003.tiff
 --> mesh cleared at 3
 --> meshes at time point 3 uploaded ( with id 115120 )
 -> read DATA/NEWSEGMENTATION/segmentation_t004.tiff
 --> mesh cleared at 4
 --> meshes at time point 4 uploaded ( with id 115121 )
 -> read DATA/NEWSEGMENTATION/segmentation_t005.tiff
 --> mesh cleared at 5
 --> meshes at time point 5 uploaded ( with id 115122 )
 -> read DATA/NEWSEGMENTATION/segmentation_t006.tiff
 --> mesh cleared at 6
 --> meshes at time point 6 uploaded ( with id 115123 )
 -> read DATA/NEWSEGMENTATION/segmentation_t007.tiff
 --> mesh cleared at 7
 --> meshes at time point 7 uploaded ( with id 115124 )
 -> read DATA/NEWSEGMENTATION/segmentation_t008.tiff
 --> mesh cleared at 8
 --> meshes at time point 8 uploaded ( with id 115125 )
 -> read DATA/NEWSEGMENTATION/segmentation_t009.tiff
 --> mesh cleared at 9
 --> meshes at time point 9 uploaded ( with id 115126 )
 -> read DATA/NEWSEGMENTATION/segmentation_t010.tiff
 --> mesh cleared at 10
 --> meshes at time point 10 uploaded ( with id 115127 )

2.2 Merge some cells

Another common problem during segmentations is the so-called oversegmentation, consisting one cell detected as many. MorphoNet can easily help identifying and solving also this class of problems: as done for the undersegmentation case, pick all cells to merge together and apply a specific color code to them. Use a different color code for each group of cells to merge together. Finally save this multicolor selection with the name "merge".

Once again we can now import this selection directly in Python via the MorphoNet API through the function getObjectsFromInfos. One has thus the complete list of oversegmented cells. In addition, the different color selections we have applied in MorphoNet automatically indicate what must be merged with what. After cells have been merged together, a new mesh can be created and uploaded to MorphoNet.

In [5]:
cells_to_merge=mn.getObjectsFromInfos('merge')
selections=np.unique(cells_to_merge.values()) #List all selection number
time_to_upload=[]
for s in selections: #For each merged
    cells=[]
    for c in cells_to_merge:
        if cells_to_merge[c]==s: 
            t=int(c.split(',')[0])
            pixel_v=int(c.split(',')[1])
            cells.append(pixel_v)
    print('-> At ' +str(t)+' merge cells ' + str(cells) + ' in '+str(cells[0]))
    seg=read_3D_image(t,path_to_segmentation,im_name=seg_name)#Read the corresponding segmentation
    for c in cells:
        if c!=cells[0]:
            seg[seg==c]=cells[0] #Merge all the ids in the first id cell
    save_3D_image(np.uint8(seg),t,path_to_segmentation,im_name=seg_name) #Save the New image
    time_to_upload.append(t) #Add this time step to re-upload

#We have to re-upload all new segmentation
for t in np.unique(time_to_upload): 
    seg=read_3D_image(t,path_to_segmentation,im_name=seg_name)#Read the last segmentation
    obj=convertVolumetoOBJ(seg,factor_scale=reduce_factor,background_threshold=0,min_pixel=10,prefix=str(t)+",",verbose=0)
    mn.clearMeshAt(t) #Remove previous mesh
    mn.uploadMesh(t,obj)  #Upload the new mesh
-> At 1 merge cells [7, 57] in 7
 -> read DATA/NEWSEGMENTATION/segmentation_t001.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t001.tiff
-> At 4 merge cells [25, 28] in 25
 -> read DATA/NEWSEGMENTATION/segmentation_t004.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t004.tiff
-> At 5 merge cells [94, 97] in 94
 -> read DATA/NEWSEGMENTATION/segmentation_t005.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t005.tiff
-> At 7 merge cells [53, 56] in 53
 -> read DATA/NEWSEGMENTATION/segmentation_t007.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t007.tiff
-> At 10 merge cells [66, 63] in 66
 -> read DATA/NEWSEGMENTATION/segmentation_t010.tiff
 -> save DATA/NEWSEGMENTATION/segmentation_t010.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t001.tiff
 --> mesh cleared at 1
 --> meshes at time point 1 uploaded ( with id 115128 )
 -> read DATA/NEWSEGMENTATION/segmentation_t004.tiff
 --> mesh cleared at 4
 --> meshes at time point 4 uploaded ( with id 115129 )
 -> read DATA/NEWSEGMENTATION/segmentation_t005.tiff
 --> mesh cleared at 5
 --> meshes at time point 5 uploaded ( with id 115130 )
 -> read DATA/NEWSEGMENTATION/segmentation_t007.tiff
 --> mesh cleared at 7
 --> meshes at time point 7 uploaded ( with id 115131 )
 -> read DATA/NEWSEGMENTATION/segmentation_t010.tiff
 --> mesh cleared at 10
 --> meshes at time point 10 uploaded ( with id 115132 )

2.3 Perform cell trackings

A third possible problem arising during segmentation of timestacks is the one of to missed time links between mother cells and daughter cells after cell division. To begin with, we build a first time lineage by tracking each individual cell using a nearest neirghobrs algorithms.

In [ ]:
from tools import calcul_barycenters,findRecursiveCorrespondance
#We calcul the barycenters of each cells
barys={}
for t in range(begin,end+1):
    seg=read_3D_image(t,path_to_segmentation,im_name=seg_name)#Read the corresponding segmentation
    barys[t]=calcul_barycenters(seg,min_pixel=100)
In [20]:
#Create the lineage information find the closest point in the barycenters
lineage_inf="#Dataset "+dataset_name+"\n" 
lineage_inf+="#Upload by "+mn_login+"\n" 
lineage_inf+="type:time"+"\n" 
for t in range(begin,end):
    correspondonce=findRecursiveCorrespondance(t,barys[t],barys[t+1])
    for c in correspondonce:
         lineage_inf+=str(t)+","+str(c)+":"+str(t+1)+","+str(correspondonce[c])+"\n"
mn.uploadInfos("Lineage",lineage_inf) #We can now upload the cell lineage to the MorphoNet database
Calcul cell overlap at 1
 -> read DATA/NEWSEGMENTATION/segmentation_t001.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t002.tiff
Calcul cell overlap at 2
 -> read DATA/NEWSEGMENTATION/segmentation_t002.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t003.tiff
Calcul cell overlap at 3
 -> read DATA/NEWSEGMENTATION/segmentation_t003.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t004.tiff
Calcul cell overlap at 4
 -> read DATA/NEWSEGMENTATION/segmentation_t004.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t005.tiff
Calcul cell overlap at 5
 -> read DATA/NEWSEGMENTATION/segmentation_t005.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t006.tiff
Calcul cell overlap at 6
 -> read DATA/NEWSEGMENTATION/segmentation_t006.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t007.tiff
Calcul cell overlap at 7
 -> read DATA/NEWSEGMENTATION/segmentation_t007.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t008.tiff
Calcul cell overlap at 8
 -> read DATA/NEWSEGMENTATION/segmentation_t008.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t009.tiff
Calcul cell overlap at 9
 -> read DATA/NEWSEGMENTATION/segmentation_t009.tiff
 -> read DATA/NEWSEGMENTATION/segmentation_t010.tiff
 --> Lineage uploaded (with id 3189)
Out[20]:
3189

Some links may not be well detected. One example is shown in the video below, in which one daughter cell is not assigned to the lineage tree of the mother. In MorphoNet, pick the two cells between which you wish to create a time link, apply a color selection to them and save it with the name "link"

We import this selection in Python, and include the daughter in the lineage tree of the mother. We can then easily re-upload the lineage tree to MorphoNet.

In [ ]:
#Retreive the previous lineage
lineage=mn.getInfosByName("Lineage") 
cells_to_link=mn.getObjectsFromInfos('link')
selections=np.unique(cells_to_link.values()) #List all selection number
time_to_upload=[]
for s in selections: #For each merged
    cells=[]
    for c in cells_to_link:
        if cells_to_link[c]==s: 
            t=int(c.split(',')[0])
            pixel_v=int(c.split(',')[1])
            cells.append((t,pixel_v))
    print('-> link cells ' + str(cells))
    for c1 in cells:
        for c2 in cells:
            if c1[0]==c2[0]-1:
                #print('-> link  ' + str(c1) + ' with '+str(c2))
                line=str(c1[0])+","+str(c1[1])+":"+str(c2[0])+","+str(c2[1])
                if lineage.find(line)<0:
                    lineage+=line+"\n"
                    print('add link '+line)
                else:
                    print('link already exist'+line)
mn.deleteInfosByName('Lineage')
mn.uploadInfos("Lineage",lineage_inf) #We can now upload the new cell lineage to the MorphoNet database