Guidance by Followers

Persistent Identifier

Use this permanent link to cite or share this Morpheus model:

The migration of zebrafish polster cells is oriented by the migration of follower cells, allowing robust long-range coordination of different cell populations.


Collective cell migration is an important process during biological development and tissue repair. During early zebrafish development, random cell motility of so-called polster cells needs to be aligned and coordinated to form a compact cell cluster that moreover maintains contact with the following axial cells. Model selection against quantitative cell tracks from experimental microscopy data and perturbation experiments has revealed a local mechanism of cell-cell interaction that self-organizes the required collective migration of polster cells (Boutillon et al., 2022).

This local mechanism, termed ‘guidance by followers’, involves the transmission of motion direction (e.g. driven by front-rear cell polarity) from a moving cell onto another cell when it is hit by the moving cell. At the level of the collective, information is thereby propagating in the direction of motion faster than the individual cell velocity and cells at the front of the cluster can therefore respond with slowing down to perturbations at the rear of the cluster, as observed in experiments (Boutillon et al., 2022). Interestingly, the slowing down at the front, as required to maintain compactness, is achieved by reduced alignment and increased randomness of cell trajectories.

Video of the model simulation M0006_guidance-by-followers_model.xml: Zebrafish polster cells (green) are being guided by axial mesoderm cells (yellow). The first $20\ \mathrm{min}$ serve as initialization time (see description) for all cells (white). The mechanism allows directional information to be effectively propagated throughout the whole tissue and spontaneously provides robustness to the system.


In these simulations, a two-dimensional hexagonal lattice with periodic boundary conditions is used with a size of $500 \times 1500$ grid nodes. There are two static obstacles on either side, leaving a channel of $200$ nodes in the middle for the cells to migrate through. These obstacles are included to mimic lateral confinement by paraxial mesoderm. Each grid interval in the simulation represents $1\ \mathrm{\mu m}$ of space, and each time step represents $1\ \mathrm{min}$. The Monte Carlo step duration (MCSDuration) is set at $0.1\ \mathrm{min}$, allowing for thousands of potential updates per lattice node over the simulated time period. The shape of the cells in the simulations is controlled using a target area of $326\ \mathrm{\mu m}^2$, which is an average of 360 cells measured in the experiment (Boutillon et al., 2022). The target circumference is taken from the isoareal circle, favoring circular individual cells if in isolation. Both volume (VolumeConstraint) and surface constraints (SurfaceConstraint) are included in the Hamiltonian with Lagrange multipliers of $1$. In addition, the axial mesoderm cells (which are depicted as yellow in the simulations) are given directed motion towards the animal pole. The speed of this motion can be adjusted by varying the strength of the DirectedMotion plugin.

To characterize the cell-autonomous behavior of polster cells (without guidance by followers), wild-type polster cells were transplanted to the animal pole of wild-type embryos and migration of isolated cells was tracked. The tracks showed alternating phases of relatively straight migration and phases of slowed and poorly directed movement, indicating that mesendodermal cells exhibit run-and-tumble motion (Boutillon et al., 2022). To model this behavior, we implemented a run-and-tumble motility with a uniform probability of reorienting the target direction (angle), a scaling factor (run_duration_adjustment) for the probabilistic waiting times (tumble.run_duration) for reorientation events, which are distributed according to a Gamma distribution, and a adjustable Lagrange multiplier (RandT_or_Mech_motion_strength_global) that scales the motion (DirectedMotion) speed. The values of the scaling factor and the Lagrange multiplier were obtained via parameter estimation using experimental data of single cells trajectories (Boutillon et al., 2022).

Besides the run-and-tumble motion, mechanical orientation of polster cells was simulated using the PyMapper plugin (see also the installation instructions). For each cell, at fixed time-steps of $1\ \mathrm{min}$, the neighbors are detected on $50$ membrane points using NeighborhoodReporters. The angle between the velocity vector of each neighbor and the direction towards the current cell is calculated. If this angle is below a threshold called max_angle (indicating that the neighbor is moving towards the current cell), the velocity vector of the neighbor is used to update the direction of the considered cell in the DirectedMotion plugin. If there are multiple neighbors migrating towards the considered cell, the direction vector is calculated as the average of their velocity vectors, with the size of the cell-cell contact serving as the weighting factor.

A Population of 400 cells is initializied and given $20\ \mathrm{min}$ (init_time) to settle and adjust their shapes and packing. Once this initial phase is over, two Events are triggered and the cells are assigned an identity based on their position along the anteroposterior axis, and the appropriate motility characteristics are applied to them.

The main parameters are:

  • $A_0 = 326\ \mathrm{\mu m}^2$: target cell area based on experimental measurements (,
  • $C_0 = \sqrt{4 \pi A_0}$: target cell circumference (,
  • $\lambda_1 = 0.5$: motion strength of polster cells fitted to single cell data (RandT_or_Mech_motion_strength_global),
  • $T_1 = 0.76 \cdot 2.5\ \mathrm{min}$: mean run time of polster cells fitted to single cell data (see tumble.run_duration),
  • $\alpha_\text{max} = \frac{\pi}{6}$: maximum angle fitted to collective behaviour (max_angle).


Directed Migration

Polster cells, which are modeled to exhibit a run-and-tumble behavior consistent with observations of isolated polster cells, typically disperse on their own (see the left simulation in the video below). But when they are followed by axial mesoderm cells, they move towards the animal pole and mix with the axial mesoderm cells (simulation shown in the middle of the video below). Giving the polster cells a sensitivity to the migration of neighboring cells towards them allows these cells to move collectively in a coordinated manner (right simulation in the video below).

Video of the model simulation M0006_guidance-by-followers_model.xml: Mechanical orientation (‘guidance by followers’) can account for directed migration (see also Fig. 7A in the referenced paper).

Robustness and Coordination

Systems in which cells have a directed motion towards the animal pole are very sensitive to changes in speed (represented by the length of the arrows in the video below) between polster and more posterior cells cause disruptions in the axis (shown in the first two simulations on the left in the video below). Guidance by followers spontaneously leads to robustness in the system (right). If posterior cells slow down, they are less effective at orienting of cells in front of them, which results in less directed movement. As a consequence, their animalward movement is reduced and speed is spontaneously adapted to the speed of the follower cells resulting in the formation of a stable axis.

Video of the model simulation M0006_guidance-by-followers_model.xml: Mechanical orientation (‘guidance by followers’) provides robustness and coordination (see also Fig. 7B in the referenced paper).

Guidance by followers, where the directional information is passed from cell to cell to guide cell migration, is thus a simple yet effective way to ensure coordinating the movements of cells over long distances.


This model is the original used in the publication, up to technical updates:

A. Boutillon, S. Escot, A. Elouin, D. Jahn, S. González-Tirado, J. Starruß, L. Brusch, N. B. David: Guidance by followers ensures long-range coordination of cell migration through α-catenin mechanoperception. Dev. Cell 57 (12): 1529-1544.e5, 2022.


Get this model via:

  • Morpheus-Link or
  •  Download: M0006_guidance-by-followers_model.xml
  • XML Preview

    <?xml version='1.0' encoding='UTF-8'?>
    <MorpheusModel version="4">
            <Details>Full title:		Guidance by followers ensures long-range coordination of cell migration through α-catenin mechanoperception
    Date:		20.06.2022
    Authors:		A. Boutillon, S. Escot, A. Elouin, D. Jahn, S. González-Tirado, J. Starruß, L. Brusch, N. B. David
    Contributors:	D. Jahn
    Software:       	Morpheus (open-source). Download from
    Time unit:		min
    Model ID:
    Comment:		The migration of zebrafish polster cells is oriented by the migration of follower cells, allowing robust long-range coordination of different cell populations.
                        		This model is the original used in the publication, up to technical updates.
    Reference:		A. Boutillon, S. Escot, A. Elouin, D. Jahn, S. González-Tirado, J. Starruß, L. Brusch, N. B. David: Guidance by followers ensures long-range coordination of cell migration through α-catenin mechanoperception. Dev. Cell 57 (12): 1529-1544.e5, 2022.
            <Title>Guidance by Followers</Title>
            <Lattice class="hexagonal">
                <Size symbol="size" value="500, 1500, 0"/>
                    <Condition type="periodic" boundary="x"/>
                    <Condition type="periodic" boundary="y"/>
            <SpaceSymbol symbol="space"/>
                <Resolution symbol="membrane_size" value="50"/>
                <SpaceSymbol symbol="membrane_pos"/>
            <StartTime value="0"/>
            <StopTime value="100"/>
            <TimeSymbol symbol="time"/>
            <Constant symbol="init_time" name="Time for cell initialization" value="20"/>
            <Constant symbol="directed_motion_strength_global" name="Speed of axial cells" value="0.5">
                <Annotation>Strength of the DirectedMotion plugin</Annotation>
            <Constant symbol="run_duration_adjustment" name="Mean duration of run phases" tags="RunandTumble" value="0.76"/>
            <Constant symbol="RandT_or_Mech_motion_strength_global" name="Speed of polster cells" value="0.5">
                <Annotation>Strength of the DirectedMotion plugin</Annotation>
            <Constant symbol="max_angle" name="Max. angle of influence" tags="guidance_by_followers" value="pi/6">
                <Annotation>Angle between cell movement and direction to the neighbor. Below max_angle, the cell is considered as moving towards the neighbouring cell and will influence its direction</Annotation>
            <Constant symbol="min_push_velocity" name="Min. velocity of influence" tags="guidance_by_followers" value="0.1">
                <Annotation>Minimal velocity for a cell to influence its neighbors</Annotation>
            <VariableVector symbol="velocity" value="0.0, 0.0, 0.0">
                <Annotation>Needed for NeighborhoodReporters neighbor_velocity_x and neighbor_velocity_y</Annotation>
            <CellType class="biological" name="cell">
                <Property symbol="color" name="Set cell colors" value="1">
                    <Annotation>Cell colors:
    - white: uninitialized cells (time >= init_time)
    - yellow: axial cells (RandT_or_Mech_motion_strength == 0)
    - green: polster cells (RandT_or_Mech_motion_strength > 0)</Annotation>
                <SurfaceConstraint target="1" strength="1" mode="aspherity"/>
                <Property symbol="target_volume" value="326.0"/>
                <VolumeConstraint target="target_volume" strength="1"/>
                <Property symbol="directed_motion_strength" name="Strength of the directed motion" value="0.0"/>
                <Event name="Axial cell initialization">
                    <Annotation>After init_time, set the properties of axial cells</Annotation>
                    <Condition>time == init_time and &lt;= 360</Condition>
                    <Rule symbol-ref="directed_motion_strength">
                    <Rule symbol-ref="RandT_or_Mech_motion_strength">
                    <Rule symbol-ref="color">
                <PropertyVector symbol="directed_motion_dir" name="Direction of the directed motion (axial cells)." value="0.0, 1.0, 0.0"/>
                <DirectedMotion direction="directed_motion_dir" name="Directed movement of axial cells" strength="directed_motion_strength">
                    <Annotation>Set to strength = 0 for polster cells</Annotation>
                <Property symbol="RandT_or_Mech_motion_strength" name="Strength of Run and Tumble or mechanically induced motion." tags="guidance_by_followers" value="0"/>
                <Event name="Polster cell initialization">
                    <Annotation>After init_time, set the properties of polster cells</Annotation>
                    <Condition>time == init_time and > 360</Condition>
                    <Rule symbol-ref="RandT_or_Mech_motion_strength">
                    <Rule symbol-ref="directed_motion_strength">
                    <Rule symbol-ref="color">
                <Property symbol="tumble.run_duration" name="run duration" tags="RunandTumble" value="0.0"/>
                <Property symbol="tumble.last" name="last tumble event" tags="RunandTumble" value="0"/>
                <PropertyVector symbol="RaT_dir" tags="RunandTumble" value="0.0, 0.0, 0.0"/>
                <Event trigger="when-true" time-step="5" name="Run and Tumble" tags="RunandTumble">
                    <Condition>(time >= tumble.last + tumble.run_duration)</Condition>
                    <Rule symbol-ref="tumble.last">
                    <Rule symbol-ref="tumble.run_duration" name="New update time">
                        <Expression>run_duration_adjustment * rand_gamma(0.5, 5)</Expression>
                    <Intermediate symbol="angle" value="rand_uni(0, 2 * pi)"/>
                    <VectorRule symbol-ref="RaT_dir" notation="φ,θ,r">
                        <Expression>angle, 0 , 1</Expression>
                <MembraneProperty symbol="cell_center_x" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                <MembraneProperty symbol="cell_center_y" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                <Mapper time-step="1.0" name="cell_center_x" tags="py">
                    <Input value=""/>
                    <Output symbol-ref="cell_center_x"/>
                <Mapper time-step="1.0" name="cell_center_y" tags="py">
                    <Input value=""/>
                    <Output symbol-ref="cell_center_y"/>
                <MembraneProperty symbol="neighbor_center_x" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                <MembraneProperty symbol="neighbor_center_y" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                <NeighborhoodReporter time-step="1.0" name="neighbor_center_x" tags="py">
                    <Input scaling="length" value=""/>
                    <Output symbol-ref="neighbor_center_x" mapping="discrete"/>
                <NeighborhoodReporter time-step="1.0" name="neighbor_center_y" tags="py">
                    <Input scaling="length" value=""/>
                    <Output symbol-ref="neighbor_center_y" mapping="discrete"/>
                <PropertyVector symbol="velocity" value="0.0, 0.0, 0.0"/>
                <MotilityReporter time-step="1" name="Get velocity for computing mechanics">
                    <Annotation>Needs time-step = 1</Annotation>
                    <Velocity symbol-ref="velocity"/>
                <MembraneProperty symbol="neighbor_velocity_x" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                <MembraneProperty symbol="neighbor_velocity_y" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                <NeighborhoodReporter time-step="1.0" name="neighbor_velocity_x" tags="py">
                    <Input scaling="length" value="velocity.x"/>
                    <Output symbol-ref="neighbor_velocity_x" mapping="discrete"/>
                <NeighborhoodReporter time-step="1.0" name="neighbor_velocity_y" tags="py">
                    <Input scaling="length" value="velocity.y"/>
                    <Output symbol-ref="neighbor_velocity_y" mapping="discrete"/>
                <Property symbol="py_angle" tags="py" value="0.0"/>
                <Property symbol="py_induced_dir_x" tags="py" value="0.0"/>
                <Property symbol="py_induced_dir_y" tags="py" value="0.0"/>
                <PyMapper time-step="1" name="Code for multi-neighbor collisions" tags="py">
                    <Annotation>Needs time-step = 1</Annotation>
                    <Input symbol-ref="cell_center_x"/>
                    <Input symbol-ref="cell_center_y"/>
                    <Input symbol-ref="neighbor_center_x"/>
                    <Input symbol-ref="neighbor_center_y"/>
                    <Input symbol-ref="neighbor_velocity_x"/>
                    <Input symbol-ref="neighbor_velocity_y"/>
                    <Input symbol-ref="max_angle"/>
                    <Input symbol-ref="min_push_velocity"/>
                    <Script>import numpy as np
    # merge all inputs into one big data frame df
    # with two multiindex-columns "MemX"=membrane-position and "Cell"
    # and many rows for all membrane-positions and one cell after the other
    df = pandas.concat([cell_center_x, cell_center_y, neighbor_center_x, neighbor_center_y, neighbor_velocity_x, neighbor_velocity_y], axis=1, keys=['cell_center_x', 'cell_center_y', 'neighbor_center_x', 'neighbor_center_y', 'neighbor_velocity_x', 'neighbor_velocity_y'])
    # relative_position_to_neighbor = if(neighbor_center.abs>0, - neighbor_center, 0)
    df['neighbor_center_abs'] = (df['neighbor_center_x']**2+df['neighbor_center_y']**2)**0.5
    df['relative_position_to_neighbor_x'] = df['cell_center_x']-df['neighbor_center_x']
    df['relative_position_to_neighbor_y'] = df['cell_center_y']-df['neighbor_center_y']
    df.loc[df['neighbor_center_abs'] == 0, 'relative_position_to_neighbor_x'] = np.NaN
    df.loc[df['neighbor_center_abs'] == 0, 'relative_position_to_neighbor_y'] = np.NaN
    # angle = if(neighbor_center.abs>0, neighbor_velocity.phi - relative_position_to_neighbor.phi, pi)
    df['angle'] = np.arctan2(df['neighbor_velocity_y'],df['neighbor_velocity_x'])-np.arctan2(df['relative_position_to_neighbor_y'],df['relative_position_to_neighbor_x'])
    df.loc[df['neighbor_center_abs'] == 0, 'angle'] = np.pi
    df['angle_abs'] = abs((df['angle'] + np.pi) % (2 * np.pi) - np.pi)
    # induced_dir = if(cos(angle)>cos(max_angle) and neighbor_velocity.abs>min_push_velocity, neighbor_velocity, 0)
    df['induced_dir_x'] = df['neighbor_velocity_x']
    df['induced_dir_y'] = df['neighbor_velocity_y']
    df['induced_dir_abs'] = (df['neighbor_velocity_x']**2+df['neighbor_velocity_y']**2)**0.5
    df.loc[(np.cos(df['angle'])&lt;np.cos(max_angle)) | (df['induced_dir_abs']&lt;min_push_velocity), 'induced_dir_x'] = np.NaN
    df.loc[(np.cos(df['angle'])&lt;np.cos(max_angle)) | (df['induced_dir_abs']&lt;min_push_velocity), 'induced_dir_y'] = np.NaN
    # to monitor, also return per-cell aggregated intermediate quantities
    py_angle = df.groupby('Cell')['angle'].mean()
    # aggregate information per cell and apply summary statistics
    # contact-length weighted mean of direction vectors
    py_induced_dir_x = df.groupby('Cell')['induced_dir_x'].mean()
    py_induced_dir_y = df.groupby('Cell')['induced_dir_y'].mean()
                    <Output symbol-ref="py_angle"/>
                    <Output symbol-ref="py_induced_dir_x"/>
                    <Output symbol-ref="py_induced_dir_y"/>
                <PropertyVector symbol="mech_induced_dir" name="Mechanically induced direction (computed in the PyMapper)." notation="x,y,z" tags="py" value="0.0, 0.0, 0.0"/>
                <VectorEquation symbol-ref="mech_induced_dir" name="Set induced direction of polster cells" notation="x,y,z" tags="py">
                    <Expression>py_induced_dir_x, py_induced_dir_y, 0.0</Expression>
                <PropertyVector symbol="dir" value="0.0, 0.0, 0.0"/>
                <VectorEquation symbol-ref="dir" name="Set direction of polster cells">
                    <Expression>if(mech_induced_dir.abs > 0, mech_induced_dir, RaT_dir)</Expression>
                <DirectedMotion direction="dir" name="Directed movement polster cells" strength="RandT_or_Mech_motion_strength" tags="guidance_by_followers">
                    <Annotation>RandT or mechanically induced movement. Set to strength = 0 for axial cells</Annotation>
            <CellType class="biological" name="confinement">
                <Property symbol="color" value="0"/>
                <Contact type1="cell" type2="confinement" value="20.0"/>
            <ShapeSurface scaling="norm">
            <MonteCarloSampler stepper="edgelist">
                <MCSDuration value="0.1"/>
                <MetropolisKinetics temperature="1"/>
            <Population type="cell" name="Axial and polster cells" size="1">
                <InitRectangle random-offset="5" number-of-cells="400" mode="regular">
                    <Dimensions origin="160,15.0, 0.0" size="180.0, 650.0, 1.0"/>
            <Population type="confinement" name="Lateral confinement" size="1">
                <InitCellObjects mode="order">
                    <Arrangement displacements="1, 1, 1" repetitions="1, 1, 1">
                        <Box origin="0.0, 0.0, 0.0" size="150.0, 1500.0*0.866, 0.0"/>
                <InitCellObjects mode="distance">
                    <Arrangement displacements="1, 1, 1" repetitions="1, 1, 1">
                        <Box origin="size.x-150, 0.0, 0.0" size="150.0, 1500.0*0.866, 0.0"/>
            <Gnuplotter time-step="1">
                <Plot title=" ">
                    <Cells min="0" max="3" value="color">
                            <Color value="0" color="gray90"/>
                            <Color value="1" color="white"/>
                            <Color value="2" color="yellow"/>
                            <Color value="3" color="green"/>
                    <CellArrows orientation="dir * 0"/>
                <Terminal name="png"/>
            <ModelGraph format="png" reduced="false" include-tags="#untagged,RunandTumble,guidance_by_followers,py"/>

    This model requires a special Morpheus version that features the PyMapper capability. For this, please see the installation instructions below.
    Model Graph
    Model Graph

    Installation Instructions

    The model can be run with a prebuilt Morpheus PyMapper version, which requires Docker installed on a Linux system.

    Install the Docker Engine with the following commands:

    sudo apt update
    sudo apt install

    Now enter the directory containing the model M0006_guidance-by-followers_model.xml and run it by typing:

    sudo docker run --rm -it -v${PWD}:/morpheus M0006_guidance-by-followers_model.xml

    When you call the model for the first time, a Docker image with the Morpheus PyMapper version is automatically downloaded before launching the simulation. You should now be able to see the Morpheus output in the console and observe how the simulation results are written to the same folder where the XML file is located.


    Files associated with this model: