Mitosis¶
In developmental simulations we often need to simulate cells which grow
and divide. In earlier versions of CompuCell3D we had to write quite
complicated plugin to do that which was quite cumbersome and
unintuitive. The only advantage of the plugin was that exactly after the
pixel copy which had triggered mitosis condition CompuCell3D called cell
division function immediately. This guaranteed that any cell which was
supposed divide at any instance in the simulation, actually did.
However, because state of the simulation is normally observed after
completion of full a Monte Carlo Step, and not in the middle of MCS it
makes actually more sense to implement Mitosis as a steppable. Let us
examine the simplest simulation which involves mitosis. We start with a
single cell and grow it. When cell reaches critical (doubling) volume it
undergoes Mitosis. We check if the cell has reached doubling volume at
the end of each MCS. The folder containing this simulation is
CompuCellPythonTutorial/steppableBasedMitosis
Let’s see how we implement mitosis steppable:
from cc3d.core.PySteppables import *
class MitosisSteppable(MitosisSteppableBase):
def __init__(self, frequency=1):
MitosisSteppableBase.__init__(self, frequency)
# 0 - parent child position will be randomized between mitosis event
# negative integer - parent appears on the 'left' of the child
# positive integer - parent appears on the 'right' of the child
self.set_parent_child_position_flag(-1)
def step(self, mcs):
cells_to_divide = []
for cell in self.cell_list:
if cell.volume > 50:
cells_to_divide.append(cell)
for cell in cells_to_divide:
# to change mitosis mode leave one of the below lines uncommented
self.divide_cell_random_orientation(cell)
# Other valid options
# self.divide_cell_orientation_vector_based(cell,1,1,0)
# self.divide_cell_along_major_axis(cell)
# self.divide_cell_along_minor_axis(cell)
def update_attributes(self):
# reducing parent target volume BEFORE cloning
self.parent_cell.targetVolume /= 2.0
self.clone_parent_2_child()
# implementing
if self.parent_cell.type == self.CONDENSING:
self.child_cell.type = self.NONCONDENSING
else:
self.child_cell.type = self.CONDENSING
The step
function is quite simple – we iterate over all cells in the
simulation and check if the volume of the cell is greater than 50. If it
is we append this cell to the list of cells that will undergo mitosis.
The actual mitosis happens in the second loop of the step function.
We have a choice there to divide cells along randomly oriented plane (line in 2D), along major, minor or user specified axis. When using user specified axis you specify vector which is perpendicular to the plane (axis in 2D) along which you want to divide the cell. This vector does not have to be normalized but it has to have length different than 0.The updateAttributes function is called automatically each time you call any of the functions which divide cells.
Note
The name of the function where we update attributes after mitosis has to be exactly update_attributes
. If it is called differently CC3D will not call it automatically. We can obviously call such function by hand, immediately we do the mitosis but this is not very elegant solution.
The update_attributes
of the function is actually the heart of the
mitosis module and you implement parameter adjustments for parent and
child cells inside this function. It is, in general, a good practice to
make sure that you update attributes of both parent and child
cells.Notice that we reset target volume of parent to 25:
self.parent_cell.targetVolume = 25.0
Had we forgotten to do that parent cell would keep high target volume
from before the mitosis and its actual volume would be, roughly 25
pixels. As a result, after the mitosis, the parent cell would “explode”
to get its volume close to the target target volume. As a matter of fact
if we keep increasing targetVolume
without resetting, the target volume
of parent cell would be higher for each consecutive mitosis event.
Therefore you should always make sure that attributes of parent and
child cells are adjusted properly in the updateAttribute function.
The next call in the update_attributes
function is
self.clone_parent_2_child()
. This function is a convenience function that
copies all parent cell’s attributes to child cell. That includes python
dictionary attached to a cell. It is completely up to you to call this
function or do manula copy of select attributes from parent to child
cell.
If you would like to use automatic copy of parent attributes but skip
certain dictionary elements (i.e. elements of the cell.dict
) you would
use the following call:
self.clone_attributes(source_cell=self.parent_cell,
target_cell=self.child_cell,
no_clone_key_dict_list=["ATTRIB_1", "ATTRIB_2"])
where the dictionary elements ATTRIB_1
and ATTRIB_2
no_clone_key_dict_list=["ATTRIB_1", "ATTRIB_2"]
are not copied. Remember that you can always ignore those convenience functions and assign parent and child cell attributes manually if this gives your code the behavior you want or makes code run faster.
For example the implementation of the update_attribute
function where we
manually set parent
and child
properties could look like that:
def updateAttributes(self):
self.child_cell.targetVolume = self.parent_ell.targetVolume
self.child_cell.lambdaVolume = self.parent_ell.lambdaVolume
if self.parent_cell.type == self.CONDENSING:
self.child_cell.type = self.NONCONDENSING
else:
self.child_cell.type = self.CONDENSING
Note
It is important to divide cells outside the loop where we iterate over entire cell inventory. If we keep dividing cells in this loop we are adding elements to the list over which we iterate over and this might have unwanted side effects. The solution is to use use list of cells to divide as we did in the example.
If you study the full example you will notice second steppable that we use to tom implement cell growth. Here is this steppable:
class VolumeParamSteppable(SteppablePy):
def __init__(self, frequency=1):
SteppablePy.__init__(self, frequency)
self.cellList = CellList(self.inventory)
def start(self):
for cell in self.cellList:
cell.targetVolume = 25
cell.lambdaVolume = 2.0
def step(self, mcs):
for cell in self.cell_list:
cell.targetVolume += 1
Again, this is quite simple module where in start function we assign
targetVolume
and lambdaVolume
to every cell. In the step function we
iterate over all cells in the simulation and increase target volume by 1
unit. As you may suspect to get it to work we have to make sure that we
use Volume without any parameters in the CC3DML plugin instead of Volume
plugin with parameters specified in the CC3DML.
At this point you have enough tools in your arsenal to start building complex simulations using CC3D. For example, combining steppable developed so far you can write a steppable where cell growth is dependent on the value of e.g. FGF concentration at the centroid of the cell. To get x coordinate of a centroid of a cell use the following syntax: .. code-block:: python
cell.xCOM
or in earlier versions of CC3D
cell.xCM/float(cell.volume)
Analogous code applies to remaining components of the centroid. Additionally , make sure you include CenterOfMass plugin in the XML or the above calls will return 0’s.
Python helper for mitosis is available from Twedit++ CC3D Python->Mitosis
.
Directionality of mitosis - a source of possible simulation bias¶
When mitosis module divides cells (and, for simplicity, let’s assume
that division happens along vertical line) then the parent cell will
always remain on the same side of the line i.e. if you run have a “stem”
cell that keeps dividing all of it’s offsprings will be created on the
same side of the dividing line. What you may observe then that if you
reassign cell type of a child cell after mitosis than in certain
simulations cell will appear to be biased to move in one direction of
the lattice. To avoid this bias you need to set call
self.set_parent_child_position_flag
function from Base
class of the Mitosis
steppable. When you call this function with argument 0 then relative
position of parent and child cell after mitosis will be randomized (this
is default behavior). When the argument is negative integer the child
cell will always appear on the right of the parent cell and when the
argument is positive integer the child cell will appear always on the
left hand side of the parent cell.