Examples

The following sections contain some usage examples of CellSium.

Creating random training data

The core mode of operation is the creation of ground truth data as training data for machine learning/deep learning applications. To this end, CellSium contains two output modes specifically tailored to produce outputs for common deep learning based object detectors/instance segmentation toolkits: The COCO and YOLO format. CellSium can as well just output binary masks along the images for use with other learning tools.

For example, the following command will random cell images, and output three datasets:

> python -m cellsium training \
    -t TrainingDataCount=64 \
    -t TrainingCellCount=512 \
    -t TrainingImageWidth=512 \
    -t TrainingImageHeight=512 \
    -t Calibration=0.0905158 \
    -t ChipmunkPlacementRadius=0.01 \
    -o training \
    --Output COCOOutput \
    --Output YOLOOutput \
    --Output GenericMaskOutput \
    -p

Note how the main mode of configuration of CellSium are tunables, these tunable parameters are set using the -t argument, followed by Name=value. The tunables are explained in the documentation, and can be listed via --tunables-show as well.

In this example, the output of 64 images of 512x512 size are requested, setting the pixel calibration to 0.0905158 µm per pixel. ChipmunkPlacementRadius configures the physical placement and yields denser colonies. The name of the output files/directories is specified using -o.

The outputs of CellSium are modular. In this example, the COCOOutput, YOLOOutput, and GenericMaskOutput are enabled. As to prevent name clashes and allow easy coexistence, the -p:code: switch enables prefixing of the output names with the name of the respective output module.

Once the exmaple has run, the directories COCOOutput-training, GenericMaskOutput-training, and YOLOOutput-training have been created, with examples of the GenericMaskOutput shown.

_images/000000000000.png

GenericMaskOutput-training/images/000000000000.png

_images/0000000000001.png

GenericMaskOutput-training/masks/000000000000.png

Creating a timelapse simulation

CellSium was originally developed to create time lapse simulations to create realistic microcolonies, which can serve as input data e.g., for simulations based on the geometry or the training and validation of tracking algorithms. To run a simulation, ste up the desired outputs and tunables as explained, and use the simulate subcommand.

> python -m cellsium simulate \
    -o simulate \
    --Output GenericMaskOutput \
    --Output TiffOutput \
    -p

In this case, a microcolony will be simulated, a time lapse TIFF stack as well as mask output generated. Shown are three example images:

_images/0000000000002.png

GenericMaskOutput-training/images/000000000000.png

_images/000000000016.png

GenericMaskOutput-training/images/000000000016.png

_images/000000000032.png

GenericMaskOutput-training/images/000000000032.png

Adding a custom cell model

In the previous examples, the standard (sizer) cell model was used. However, the modular nature of CellSium makes it easy to integrate a custom cell model. In this example, the sizer model will be defined externally, so it can be more easily changed, and to showcase its difference, the easter egg square geometry will be applied:

square.py
from cellsium.model import assemble_cell, SimulatedCell, h_to_s, Square


class SquareCellModel(SimulatedCell):
    @staticmethod
    def random_sequences(sequence):
        return dict(elongation_rate=sequence.normal(1.5, 0.25))  # µm·h⁻¹

    def birth(self, parent=None, ts=None) -> None:
        self.elongation_rate = next(self.random.elongation_rate)
        self.division_time = h_to_s(1.0)

    def grow(self, ts) -> None:
        self.length += self.elongation_rate * ts.hours

        if ts.time > (self.birth_time + self.division_time):
            offspring_a, offspring_b = self.divide(ts)
            offspring_a.length = offspring_b.length = self.length / 2


Cell = assemble_cell(SquareCellModel, Square)

The custom model can be specified using the -c switch, specifying either an importable Python module, or the path of a Python file. If no class name is specified after the : colon, CellSium will attempt to import a class named Cell from the file/module.

> python -m cellsium simulate \
    -o square \
    --Output GenericMaskOutput \
    -c square.py:Cell \
    -p

CellSium cell objects are Python objects. They are built lending from OOP principles, using mixins in a very flexible way to join various properties. To gain deeper insights how to implement and alter cellular behavior or rendering, it is best to study the source code of CellSium.

_images/000000000033.png

GenericMaskOutput-square/images/000000000033.png

Jupyter Notebook Embedding Example

In this example, CellSium is embedded in a Jupyter notebook to interactively run small simulations.

First, the necessary modules are imported:

# plotting
from matplotlib_inline.backend_inline import set_matplotlib_formats
set_matplotlib_formats('svg')
from matplotlib import pyplot

# general
from functools import partial

# RRF is the central random helper, used for seeding
from cellsium.random import RRF
# the model parts the new model is built upon
from cellsium.model import PlacedCell, SimulatedCell, assemble_cell
# the functions to actually perform the simulation
from cellsium.cli.simulate import perform_simulation, initialize_cells, h_to_s, s_to_h
# the PlotRenderer as it embeds nicely in Jupyter
from cellsium.output.plot import PlotRenderer

For the example, a model is defined directly within a Jupyter cell:

class SizerCell(SimulatedCell):
    @staticmethod
    def random_sequences(sequence):
        return dict(elongation_rate=sequence.normal(1.5, 0.25))  # µm·h⁻¹

    def birth(
        self, parent=None, ts=None
    ) -> None:
        self.elongation_rate = next(self.random.elongation_rate)
        self.division_time = h_to_s(0.5)  # fast division rate

    def grow(self, ts):
        self.length += self.elongation_rate * ts.hours

        if ts.time > (self.birth_time + self.division_time):
            offspring_a, offspring_b = self.divide(ts)
            offspring_a.length = offspring_b.length = self.length / 2
# seed the random number generator
RRF.seed(1)

# perform_simulation returns an iterator which will indefinitely yield timesteps

simulation_iterator = perform_simulation(
    setup=partial(
        initialize_cells,
        count=1,
        cell_type=assemble_cell(SizerCell, placed_cell=PlacedCell)),
    time_step=30.0 * 60.0
)

# we step thru the first 5 of them ...
for _, ts in zip(range(5), simulation_iterator):
    # ... and plot them
    PlotRenderer().output(world=ts.world)
    pyplot.title("Simulation output at time=%.2fh" % (s_to_h(ts.time)))
    pyplot.show()
    # we have access to the Cell objects as well
    print(repr(ts.world.cells))
_images/Embedding_4_0.svg
[Cell(angle=2.3294692309443428, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=0.0, division_time=1800.0, elongation_rate=1.5981931788501658, id_=1, length=2.872894940610261, lineage_history=[0], parent_id=0, position=[17.854225629902448, 28.67645476278087], width=0.8960666826747447)]
_images/Embedding_4_2.svg
[Cell(angle=2.3294692309443428, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=3600.0, division_time=1800.0, elongation_rate=1.4017119040732795, id_=2, length=1.835995765017672, lineage_history=[0, 1], parent_id=1, position=[18.485770454254308, 28.010218132802386], width=0.8960666826747447), Cell(angle=2.3294692309443428, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=3600.0, division_time=1800.0, elongation_rate=1.7743185975635118, id_=3, length=1.835995765017672, lineage_history=[0, 1], parent_id=1, position=[17.22268080555059, 29.342691392759356], width=0.8960666826747447)]
_images/Embedding_4_4.svg
[Cell(angle=2.3360626329715437, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=3600.0, division_time=1800.0, elongation_rate=1.4017119040732795, id_=2, length=2.5368517170543115, lineage_history=[0, 1], parent_id=1, position=[18.756872759489948, 27.72423023484169], width=0.8960666826747447), Cell(angle=2.3228640880321927, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=3600.0, division_time=1800.0, elongation_rate=1.7743185975635118, id_=3, length=2.7231550637994277, lineage_history=[0, 1], parent_id=1, position=[16.951578500314948, 29.62867929072005], width=0.8960666826747447)]
_images/Embedding_4_6.svg
[Cell(angle=2.352798559960036, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=7200.0, division_time=1800.0, elongation_rate=0.8317980611709823, id_=4, length=1.6188538345454755, lineage_history=[0, 1, 2], parent_id=2, position=[19.593155424028517, 26.85358455987381], width=0.8960666826747447), Cell(angle=2.3416190585127383, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=7200.0, division_time=1800.0, elongation_rate=1.2231841085165691, id_=5, length=1.6188538345454755, lineage_history=[0, 1, 2], parent_id=2, position=[18.471310193545275, 28.02153383690875], width=0.8960666826747447), Cell(angle=2.314495314084782, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=7200.0, division_time=1800.0, elongation_rate=1.4247110511570817, id_=6, length=1.8051571812905918, lineage_history=[0, 1, 3], parent_id=3, position=[17.289158390942983, 29.25243321304336], width=0.8960666826747447), Cell(angle=2.304654468034007, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=7200.0, division_time=1800.0, elongation_rate=1.8079038604401219, id_=7, length=1.8051571812905918, lineage_history=[0, 1, 3], parent_id=3, position=[16.06327851109302, 30.578267441297573], width=0.8960666826747447)]
_images/Embedding_4_8.svg
[Cell(angle=2.3820603181599442, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=7200.0, division_time=1800.0, elongation_rate=0.8317980611709823, id_=4, length=2.0347528651309665, lineage_history=[0, 1, 2], parent_id=2, position=[20.228643748180197, 26.199313752340714], width=0.8960666826747447), Cell(angle=2.3526475982043165, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=7200.0, division_time=1800.0, elongation_rate=1.2231841085165691, id_=5, length=2.23044588880376, lineage_history=[0, 1, 2], parent_id=2, position=[18.75284683312141, 27.731672298371393], width=0.8960666826747447), Cell(angle=2.2968665429831496, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=7200.0, division_time=1800.0, elongation_rate=1.4247110511570817, id_=6, length=2.5175127068691325, lineage_history=[0, 1, 3], parent_id=3, position=[17.088954083136315, 29.417294148769457], width=0.8960666826747447), Cell(angle=2.2660140670444004, bend_lower=-0.016261360844027475, bend_overall=-0.04126808565523994, bend_upper=-0.09614075306364522, birth_time=7200.0, division_time=1800.0, elongation_rate=1.8079038604401219, id_=7, length=2.709109111510653, lineage_history=[0, 1, 3], parent_id=3, position=[15.346457855171874, 31.35753885164192], width=0.8960666826747447)]