This tutorial shows, step by step, how to employ Python and MorphoNet to visualize and analyze complex 3D+time dynamics. It guides users from segmented data to final results.
Segmented images are in standard .tiff format. To import them in Python we use the library ImageHandling. Each image corresponds to the 3D reconstruction of the whole embryo at a given time. Two consecutive images are separated by a timegap of two minutes. Segmented images are divided into elementary objects, each identified by its corresponding numeric ID. Each cell is an object with an ID>1. The background, important to identify external the embryonic surface, corresponds to the ID=1.
We want now to analyze the shape of a few selected cells, those which will constitute the endoderm. We connect to MorphoNet, pick all cells we are interested in, apply a color selection to them. We then save this color selection with the name "endoderm", as shown in the video below.
We have seen how to manually create a selection from the MorphoNet online interface. Here we will download such a selection in order to start the analysis of endodermal cellular shapes.
dataset_name="Phallusia Mammillata PM1"
segmented_files="Astec-pm1/POST/POST_RELEASE/Astec-pm1_post_t{:03d}.inr.gz" #Segmented images filename
fused_files="Astec-pm1/FUSE/FUSE_RELEASE/Astec-Pm1_fuse_t{:03d}.inr.gz" #Fused images filename
properties_file="Astec-pm1/properties/Astec-pm1_properties.pkl" #Properties file
begin=1 #first timepoint of dataset
end=180 #last timepoint of dataset
import morphonet
mn=morphonet.Net("your login","your password") #Connection to MorphoNet database with your MorphoNet Login credentials
mn.selectDataSetByName(dataset_name) # Select the dataset of interest
endoderm=mn.getObjectsFromInfos('endoderm')#Get the selection done with in MorphoNet
endodermal_cells={} #We create here a dictionary giving, for each time point, the list of IDs of endodermal cells at that time.
for e in endoderm:
t=int(e.split(',')[0])
end_at_t=endodermal_cells.get(t,[])
end_at_t.append(int(e.split(',')[1]))
endodermal_cells.update({int(t):end_at_t})
Now that we know the ID of all endodermal cells at all timepoints, we can start analysizing their shapes. The following functions extract shape information from the 3D volumetric segmentation of each cell. Here in particular we calculate, for all endodermal cells, the area of their external surface and their main elongation through a principal components analysis of their volume distribution.
import numpy as np
from scipy import ndimage as nd
#Since these segmented images are usually quite big, when analyzing single cells one can cut the image domain to a box containing the cell of interest
def bound_boxes_cells(im):
from scipy import ndimage as nd
bboxes=nd.find_objects(im)
cel=np.unique(im)
cells=cel[1:]
return (bboxes,cells)
#The next two functions are used to extract cells surfaces
def __slices_dilation(slices, maximum=[np.inf, np.inf, np.inf]):
return tuple([slice(max(0, s.start-1), min(s.stop+1, maximum[i])) for i, s in enumerate(slices)])
def slices_dilation(slices, maximum=[np.inf, np.inf, np.inf], iterations=1):
for i in range(iterations):
slices=__slices_dilation(slices, maximum)
return slices
#Principal Component Analysis of datasets
def PCA(data):
data -= np.mean(data,axis=0)
R = np.cov(data, rowvar=False)
evals, evecs = np.linalg.eigh(R)
evecs=np.transpose(evecs)
evecs=[evecs[2],evecs[1],evecs[0]]
evals=[evals[2],evals[1],evals[0]]
return(evecs,evals)
#This function returns shape information of a given cell c: cell elongation and area of external surface.
#We characterize cell elongations by the ratio (l1-l_2)/l_1, l_1 (l_2) being the first (second) principal value of the cell's PCA.
def find_cell_shape(c, bboxes, image):
bb=slices_dilation(bboxes[c-1], iterations=2)
orig=[int(bb[0].start),int(bb[1].start),int(bb[2].start)]
tmp=image[bb]
w=np.where(tmp==c)
points=[(w[0][k],w[1][k],w[2][k]) for k in range(0,len(w[0]))] #cell points
pvecs,pvals=PCA(points) #PCA of cell points
r=(nd.binary_dilation(tmp==c) & (tmp!=c))
w=np.where(r==True) #cell contour
ext_surfs=[]
for k in range(0,len(w[0])):
lab=tmp[w[0][k],w[1][k],w[2][k]]
if lab in [0,1]:
ext_surfs.append([w[0][k],w[1][k],w[2][k]]) #external surface
return pvecs,pvals,len(ext_surfs)
#We can now perform the actual shape analysis.
external_areas={}
ratio_main_elongations={}
for t in range(begin,end+1):
print "Analyzing time",t
endodermal_cells_at_t=endodermal_cells[t]
img=morphonet.tools.imread(segmented_files.format(t))
bboxes,cells=bound_boxes_cells(img)
for cell in endodermal_cells_at_t:
pvecs,pvals,ext_area=find_cell_shape(cell, bboxes, img)
ratio_main_el=1.0*(pvals[0]-pvals[1])/pvals[0]
external_areas.update({tuple([t,cell]):ext_area}) #cells here are encoded as a tuple (t,cell_id), which is practical for MorphoNet upload
ratio_main_elongations.update({tuple([t,cell]):ratio_main_el})
#We can also calculate the time variation of these properties. To do so, we need the lineage tree.
import pickle as pkl
with open(properties_file, 'rb') as file:
properties=pkl.load(file,encoding='latin1')
lineage_tree=properties['cell_lineage']
#Variation of a property on cell c at time t is: property[t,c]-property[t-1,c]
external_areas_var={}
ratio_main_elongations_var={}
for x in external_areas.keys():
if x[0]<end:
cell=x[0]*10**4+x[1] #remember that cells in lineage tree are encoded as t*10**4+cell_id
cell_at_next_timepoint=tuple([lineage_tree[cell][0]/10**4,lineage_tree[cell][0]%10**4])
external_areas_var.update({cell_at_next_timepoint:external_areas[cell_at_next_timepoint]-external_areas[x]})
ratio_main_elongations_var.update({cell_at_next_timepoint:ratio_main_elongations[cell_at_next_timepoint]-ratio_main_elongations[x]})
We now have two dictionaries containing the desired shape information. All is left to do is to upload these tracks to MorphoNet
external_area_inf="type:float"+"\n" #External Areas information. This is a quantitative information
external_area_var_inf="type:float"+"\n" #External Area variation information. This is a quantitative information
ratio_main_elongations_inf="type:float"+"\n" #Elongation ratio information. This is a quantitative information
ratio_main_elongations_var_inf="type:float"+"\n" #Variation of elongation ratio information. This is a quantitative information
for x in external_areas.keys():
external_area_inf+=str(x[0])+","+str(x[1])+":"+str(external_areas[x])+"\n"
ratio_main_elongations_inf+=str(x[0])+","+str(x[1])+":"+str(ratio_main_elongations[x])+"\n"
for x in external_areas_var.keys():
external_area_var_inf+=str(x[0])+","+str(x[1])+":"+str(external_areas_var[x])+"\n"
ratio_main_elongations_var_inf+=str(x[0])+","+str(x[1])+":"+str(ratio_main_elongations_var[x])+"\n"
#We can now upload each information with a specific name to the MorphoNet database
mn.uploadInfos("External Area",external_area_inf)
mn.uploadInfos("Variation External Area",external_area_var_inf)
mn.uploadInfos("Elongation Ratio",ratio_main_elongations_inf)
mn.uploadInfos("Variation Elongation Ratio",ratio_main_elongations_var_inf)
We can now visualize in MorphoNet the uploaded information as heat maps projected onto endodermal cells