Quickstart#

This package generates synthetic images using the Dead Leaves Model, a process in which random shapes (leaves) are repeatedly layered until the canvas is fully covered.

The workflow consists of three steps:

  1. Geometry: A LeafGeometryGenerator defines the geometry of the leaves, e.g. shape, size, orientation.

  2. Appearance: The LeafAppearanceSampler defines the surfaces of the leaves, i.e. color and texture specifications.

  3. Rendering: The ImageRenderer is used to generate the final image.

This Quickstart walks you through the basic usage.

Import#

Begin by importing the two core classes:

from deadleaves import LeafGeometryGenerator, LeafAppearanceSampler, ImageRenderer

Setting up a Model#

The LeafGeometryGenerator controls only the geometry of the leaves and the canvas size. You choose a leaf shape (circles, ellipses, rectangles, or regular polygons) and assign sampling distributions to that shape’s parameters. Leaf positions are sampled uniformly on the canvas.

For example, circles require only one parameter: area. Here we use a power-law distribution for the area:

model = LeafGeometryGenerator(
    leaf_shape = "circular", 
    shape_param_distributions = {"area": {"powerlaw": {"low": 100.0, "high": 10000.0, "k": 1.5}}},
    image_shape = (512,512)
)

To generate a sample, use .generate_segmentation(). This will trigger a sampling process where in each iteration a sample for each leaf parameter is drawn (in our example the parameters are x_pos, y_pos, and area). The parameters samples are then used to generate a leaf mask which is placed on top of the canvas and each pixel which does not jet belong to a leaf will be assigned to the current leaf through a leaf_idx, i.e. we add leaves to the canvas from front to back with occlusion. This process is repeated until all pixels belong to an object. After the sampling process we receive two outputs:

  • a pandas DataFrame describing all generated leaves (location + shape parameters), and

  • a segmentation map assigning each pixel to the leaf on top at that location.

leaf_table, segmentation_map = model.generate_segmentation()
leaf_table.head()
x_pos y_pos area leaf_idx leaf_shape
0 tensor(313.2332) tensor(338.3260) [tensor(115.2461)] 1 circular
1 tensor(205.1495) tensor(248.2015) [tensor(839.0661)] 2 circular
2 tensor(167.7799) tensor(497.3185) [tensor(8480.0625)] 3 circular
3 tensor(330.0020) tensor(144.9766) [tensor(358.8875)] 4 circular
4 tensor(227.6606) tensor(363.8465) [tensor(110.7182)] 5 circular
../_images/193f8217bb12fe0fcbbf5f610fc28c36f3dbbec741d9ecabfee741523ca90b1d.png

Each shape type has required parameters, and each of those parameters must be assigned a sampling distribution.

Tip

Since the sampling process is random you will generate a new segmentation map each time the command .generate_segmentation() is called. For reproducibility you may set a seed with torch.manual_seed().

Using distributions#

You can specify parameter distributions using dictionaries. The package includes common PyTorch distributions and several custom ones:

Name

Required keys

Notes

"uniform"

low, high

"normal"

loc, scale

"beta"

concentration0, concentration1

"poisson"

rate

"powerlaw"

low, high, k

"cosine"

amplitude, frequency

"expcosine"

frequency, exponential_constant

"image"

dir

"constant"

value

deterministic

Each parameter is defined by a dictionary of the form:

{"distribution_name": { ... parameters ...}}

Define leaf appearance#

The LeafAppearanceSampler is used to sample a colors and optionally textures for each leaf in the leaf_table. To trigger sampling you pass the color mode and the color distributions to the .sample_color() method. This will draw a sample from your color distribution for each leaf index and save the value to the leaf_table.

colormodel = LeafAppearanceSampler(leaf_table)
leaf_table_w_o_texture = colormodel.sample_color(color_param_distributions = {"gray": {"normal": {"loc": 0.5, "scale": 0.2}}})
leaf_table_w_o_texture.head()
x_pos y_pos area leaf_idx leaf_shape color_R color_G color_B color_gray
0 tensor(313.2332) tensor(338.3260) [tensor(115.2461)] 1 circular 0.492396 0.492396 0.492396 0.492396
1 tensor(205.1495) tensor(248.2015) [tensor(839.0661)] 2 circular 0.762991 0.762991 0.762991 0.762991
2 tensor(167.7799) tensor(497.3185) [tensor(8480.0625)] 3 circular 0.324124 0.324124 0.324124 0.324124
3 tensor(330.0020) tensor(144.9766) [tensor(358.8875)] 4 circular 0.196061 0.196061 0.196061 0.196061
4 tensor(227.6606) tensor(363.8465) [tensor(110.7182)] 5 circular 0.350680 0.350680 0.350680 0.350680

Adding texture (optional)#

You can optionally add texture on top of the leaf colors. The simplest example uses pixel-wise Gaussian noise:

leaf_table_w_texture = colormodel.sample_texture(texture_param_distributions = {"gray": {"normal": {"loc": 0, "scale": {"uniform": {"low": 0.01, "high": 0.05}}}}})
leaf_table_w_texture.head()
x_pos y_pos area leaf_idx leaf_shape color_R color_G color_B color_gray texture_gray_dist texture_gray_loc texture_gray_scale
0 tensor(313.2332) tensor(338.3260) [tensor(115.2461)] 1 circular 0.492396 0.492396 0.492396 0.492396 normal 0 0.020756
1 tensor(205.1495) tensor(248.2015) [tensor(839.0661)] 2 circular 0.762991 0.762991 0.762991 0.762991 normal 0 0.025510
2 tensor(167.7799) tensor(497.3185) [tensor(8480.0625)] 3 circular 0.324124 0.324124 0.324124 0.324124 normal 0 0.028376
3 tensor(330.0020) tensor(144.9766) [tensor(358.8875)] 4 circular 0.196061 0.196061 0.196061 0.196061 normal 0 0.031817
4 tensor(227.6606) tensor(363.8465) [tensor(110.7182)] 5 circular 0.350680 0.350680 0.350680 0.350680 normal 0 0.019906

Rendering the image#

To render our sampled image we pass the leaf_table and segmentation_map to the ImageRenderer. If no segmentation_map is provided the segmentation will be regenerated based on the leaf table. In this case an image_shape has to be given.

The rendering is performed by assigning each pixel in the canvas the color value of the leaf it belongs to. We can render the dead leaves image without texture by using our leaf table without texture.

renderer = ImageRenderer(leaf_table_w_o_texture, segmentation_map)
renderer.render_image()
renderer.show()
../_images/a7616332c78d0731578a5cf72a8f7132c03942a2548afc0821a7755290b5e035.png

To render the image with texture we use the leaf table which also contains texture information. Since the texture adds pixelwise information we only resolve the texture at rendering time, such that the specific noise pattern will be resampled each time we render.

renderer = ImageRenderer(leaf_table_w_texture, segmentation_map)
renderer.render_image()
renderer.show()
../_images/5099ba3bf5afbcc0b1aa5c5716063851ef6217e70dd5509833234f7c63deccaf.png

Note

Since we added the texture to our existing model the two above images only differ in the leaf texture.

Full example#

Putting everything together, a complete workflow looks like this:

# Define geometric model
model = LeafGeometryGenerator(
    leaf_shape = "circular", 
    shape_param_distributions = {"area": {"powerlaw": {"low": 100.0, "high": 10000.0, "k": 1.5}}},
    image_shape = (512,512)
)

# Sample segmentation of canvas
leaf_table, segmentation_map = model.generate_segmentation()

# Define appearance sample
colormodel = LeafAppearanceSampler(leaf_table)

# Sample colors and textures
colormodel.sample_color(color_param_distributions = {"gray": {"normal": {"loc": 0.5, "scale": 0.2}}})
colormodel.sample_texture(texture_param_distributions = {"gray": {"normal": {"loc": 0, "scale": 0.05}}})

# Display the result
renderer = ImageRenderer(colormodel.leaf_table, segmentation_map)
renderer.render_image()
renderer.show()
../_images/e92918c07e200360ae7f8490f6b17279820feceed7f9a73dc4a060f5047063f8.png

Note

Since the rendering process simply adds the color (and texture) based on the partition to the canvas the resulting dead leaves image has a pixel-perfect rendering and a know segmentation map.

Next steps#

See the documentation on shape parameters.
Learn more about color models.
Learn more about texture models.
Browse examples in the Gallery for example scripts.
Consult the full list of available distributions.