Shapes#

The shape of the objects in a Dead Leaves image are specified via the leaf_shapeargument of the LeafGeometryGenerator. Currently support shapes are:

  • circular

  • ellipsoid

  • rectangular

  • polygon

Shape-specific parameters are passed through the shape_param_distributionsdictionary. The required parameters depend on the chosen shape.

Note

For comparability all shape sizes are parameterized via an area parameter, which describes the distribution of the leaves size in terms of number of pixels (i.e. squared pixels).

Circles#

Circular leaves (leaf_shape = "circular") are the simplest, requiring only the area or radius distribution:

{
    "area": <distribution>
}

or

{
    "radius": <distribution>
}

The leaf mask for a circular leaf with position \((\bar{x},\bar{y})\) and area \(A\) or radius \(r\) is then generated via

\[\begin{split} L(x,y) = \begin{cases} 1, & \text{if } \sqrt{(x-\bar{x})^2 + (y-\bar{y})^2} \leq \sqrt{\frac{A}{\pi}} = r \\ 0, & \text{else.} \end{cases} \end{split}\]

Tip

To generate images with powerlaw distributed leaf radius (for a \(1/f\) power spectrum) simply use a powerlaw distributed area with exponent k. For parameterization via the area the value of k should be half of what you would use for the radius to achieve the same distribution.

Example

Hide code cell source

from deadleaves import LeafGeometryGenerator, LeafAppearanceSampler, ImageRenderer

model = LeafGeometryGenerator(
    "circular", 
    {"area": {"powerlaw": {"low": 100.0, "high": 10000.0, "k": 1.5}}},
    (256, 256)
)
leaf_table, segmentation_map = model.generate_segmentation()

colormodel = LeafAppearanceSampler(leaf_table)
colormodel.sample_color({"gray": {"uniform": {"low": 0.0, "high": 1.0}}})

renderer = ImageRenderer(colormodel.leaf_table, segmentation_map)
renderer.render_image()
renderer.show(figsize = (3,3))
../_images/9ce8400764badf2f6da5946df6f889cc2f4760187425fb8b7fecc99130700253.png

Ellipsoids#

Ellipsoidal leaves (leaf_shape = "ellipsoid") require distributions for

  • area: size of the ellipse

  • aspect_ratio: ratio of minor to major axis

  • orientation: rotation angle.

{
    "area": <distribution>, 
    "orientation": <distribution>, 
    "aspect_ratio": <distribution>
}

The leaf mask for a ellipsoidal leaf with position \((\bar{x},\bar{y})\), area \(A\), aspect ratio \(\rho\), and orientation \(\phi\) is then generated via

\[\begin{split} a = \sqrt{A \cdot \frac{\rho}{\pi}} \qquad b = \sqrt{\frac{A}{\pi \cdot \rho}} \\ u = (x-\bar{x}) \cdot \cos(\phi) - (y-\bar{y}) \cdot \sin(\phi) \\ v = (x-\bar{x}) \cdot \sin(\phi) + (y-\bar{y}) \cdot \cos(\phi)\\ L(x,y) = \begin{cases} 1, & \text{if } \sqrt{\left(\frac{u}{a}\right)^2 + \left(\frac{v}{b}\right)^2} \leq 1 \\ 0, & \text{else.} \end{cases} \end{split}\]

Example

Hide code cell source

from deadleaves import LeafGeometryGenerator, LeafAppearanceSampler, ImageRenderer
import torch


model = LeafGeometryGenerator(
    "ellipsoid", 
    {
        "area": {"powerlaw": {"low": 100.0, "high": 10000.0, "k": 1.5}},
        "orientation": {"uniform": {"low": 0.0, "high": 2 * torch.pi}},
        "aspect_ratio": {"uniform": {"low": 0.5, "high": 2}}
        },
    (256, 256)
)
leaf_table, segmentation_map = model.generate_segmentation()

colormodel = LeafAppearanceSampler(leaf_table)
colormodel.sample_color({"gray": {"uniform": {"low": 0.0, "high": 1.0}}})

renderer = ImageRenderer(colormodel.leaf_table, segmentation_map)
renderer.render_image()
renderer.show(figsize = (3,3))
../_images/008bf653c7c9404b978da485522065b65e85adf28971292578d80a30b9b89894.png

Rectangles#

Rectangular leaves (leaf_shape = "rectangular") use the same parameters as ellipsoids:

{
    "area": <distribution>, 
    "orientation": <distribution>, 
    "aspect_ratio": <distribution>
}

The leaf mask for a rectangular leaf with position \((\bar{x},\bar{y})\), area \(A\), aspect ratio \(R\), and orientation \(\phi\) is then generated via

\[\begin{split} h = \sqrt{\frac{A}{\rho}} \qquad w = h \cdot \rho \\ u = (x-\bar{x})\cdot \cos(\phi) - (y-\bar{y})\cdot \sin(\phi) \\ v = (x-\bar{x})\cdot \sin(\phi) - (y-\bar{y})\cdot \cos(\phi) \\ L(x,y) = \begin{cases} 1, & \text{if } \vert u \vert \leq \frac{w}{2} \text{ and } \vert v \vert \leq \frac{h}{2} \\ 0, & \text{else.} \end{cases} \end{split}\]

Example

Hide code cell source

from deadleaves import LeafGeometryGenerator, LeafAppearanceSampler, ImageRenderer
import torch

model = LeafGeometryGenerator(
    "rectangular", 
    {
        "area": {"powerlaw": {"low": 100.0, "high": 10000.0, "k": 1.5}},
        "orientation": {"uniform": {"low": 0.0, "high": 2 * torch.pi}},
        "aspect_ratio": {"uniform": {"low": 0.5, "high": 2}}
        },
    (256, 256)
)
leaf_table, segmentation_map = model.generate_segmentation()

colormodel = LeafAppearanceSampler(leaf_table)
colormodel.sample_color({"gray": {"uniform": {"low": 0.0, "high": 1.0}}})

renderer = ImageRenderer(colormodel.leaf_table, segmentation_map)
renderer.render_image()
renderer.show(figsize = (3,3))
../_images/6165b56d69136037e79e11332312dd3f1f6e543a37fa9c68946eda67af9f66fb.png

Regular polygons#

Currently only regular polygons with fixed orientation are supported (leaf_shape = "polygon"). The parameters are area and number of vertices n_vertices:

{
    "area": <distribution>, 
    "n_vertices": <distribution>
}

The leaf mask for a regular polygon leaf with position \((\bar{x},\bar{y})\), area \(A\), and number of vertices \(n\) is then generated by computing the positions of the vertices via

\[\begin{split} r = \sqrt{2\cdot \frac{A}{n\cdot \sin(2\cdot\frac{\pi}{n})}} \\ \psi_k = \frac{2\pi k}{n} \qquad v_k = \begin{pmatrix}\bar{x} + r\cdot \cos(\psi_k) \\ \bar{y} + r\cdot \sin(\psi_k) \end{pmatrix}. \end{split}\]

We then use a simple Ray-casting algorithm to check if a pixel \((x,y)\) is with in the convex hull of the vertices, i.e. the polygon.

Example

Hide code cell source

from deadleaves import LeafGeometryGenerator, LeafAppearanceSampler, ImageRenderer

model = LeafGeometryGenerator(
    "polygon", 
    {
        "area": {"powerlaw": {"low": 100.0, "high": 10000.0, "k": 1.5}},
        "n_vertices": {"poisson": {"rate": 5}},
        },
    (256, 256)
)
leaf_table, segmentation_map = model.generate_segmentation()

colormodel = LeafAppearanceSampler(leaf_table)
colormodel.sample_color({"gray": {"uniform": {"low": 0.0, "high": 1.0}}})

renderer = ImageRenderer(colormodel.leaf_table, segmentation_map)
renderer.render_image()
renderer.show(figsize = (3,3))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[4], line 11
      7         "n_vertices": {"poisson": {"rate": 5}},
      8         },
      9     (256, 256)
     10 )
---> 11 leaf_table, segmentation_map = model.generate_segmentation()
     12 
     13 colormodel = LeafAppearanceSampler(leaf_table)
     14 colormodel.sample_color({"gray": {"uniform": {"low": 0.0, "high": 1.0}}})

File ~/checkouts/readthedocs.org/user_builds/deadleaves/checkouts/stable/deadleaves/deadleaves.py:335, in LeafGeometryGenerator.generate_segmentation(self)
    332     continue
    334 # Compute AABB, clip to canvas, query live tiles
--> 335 y_min, x_min, y_max, x_max = leaf_mask_kw[self.leaf_shape].bbox(
    336     params
    337 )  # pyright: ignore[reportCallIssue]
    338 y_min = max(y_min, 0)
    339 x_min = max(x_min, 0)

File ~/checkouts/readthedocs.org/user_builds/deadleaves/checkouts/stable/deadleaves/leaf_masks.py:304, in leaf_aabb(params, leaf_shape)
    302     area = float(params["area"])
    303     n_v = int(params["n_vertices"])
--> 304     r = math.sqrt(2 * area / (n_v * math.sin(2 * math.pi / n_v)))
    305     return (
    306         math.floor(cy - r),
    307         math.floor(cx - r),
    308         math.ceil(cy + r),
    309         math.ceil(cx + r),
    310     )
    312 # Fallback — unknown shape, return infinite box (no acceleration)

ValueError: math domain error