Fill type

FillType is an enum that is passed as the fill_type keyword argument to contour_generator() and is used to specify the format of data returned from calls to filled(). If not explicitly specified then the default is used for the requested algorithm name.

Supported enum members and the default vary by algorithm:

FillType

mpl2005

mpl2014

serial

threaded

OuterCode

✔️ default

✔️ default

✔️

✔️

OuterOffset

✔️ default

✔️ default

ChunkCombinedCode

✔️

✔️

ChunkCombinedOffset

✔️

✔️

ChunkCombinedCodeOffset

✔️

✔️

ChunkCombinedOffsetOffset

✔️

✔️

A string name can be used instead of the enum member so the following are equivalent:

>>> contour_generator(fill_type="OuterOffset", ...)
>>> contour_generator(fill_type=FillType.OuterOffset, ...)

Note

Filled contours are more complicated than contour lines because lines are independent of each other whereas filled contours consist of polygons that always have an outer (exterior) boundary but may also contain any number of holes (interior boundaries). The relationship between outer boundaries and their holes is calculated and returned by filled() for all FillType members except for ChunkCombinedCode and ChunkCombinedOffset.

Enum members are a combination of the following words:

  • Outer: each outer boundary is stored with its holes in a separate array to other outer boundaries.

  • Combined: multiple boundaries are concatenated in the same array regardless of whether they are outer boundaries or holes.

  • Chunk: each chunk is separate, and is None if the chunk has no polygons.

  • Code: includes Matplotlib kind codes for the previous array.

  • Offset: the previous array is divided up using start and end offsets.

Where Offset occurs twice the first refers to the offsets of individual boundaries (outers and holes) within a larger collection and the second to which of those boundaries are grouped together into polygons.

The format of data returned by filled() for each of the possible FillType members is best illustrated through an example. This is the same example data used for Line type but calling filled(1, 2) instead of lines(2).

../_images/fill_type_0.svg

This example returns two polygons:

  • A polygon that has an outer (exterior boundary) and one hole (interior boundary). The outer has 8 points (first and last are identical) that are on either the lower contour level or the domain boundary, the hole has 5 points (first and last are identical) all on the upper contour level.

  • A simple polygon without any holes consisting of 5 points (first and last are identical) that follows the lower contour level, then the domain boundary, then the upper contour level and the domain boundary again.

Note

Outer boundaries are oriented in an anticlockwise manner, holes are oriented clockwise. This assumes a right-hand coordinate system.

Set up the imports and data:

>>> from contourpy import contour_generator, FillType
>>> import numpy as np
>>> np.set_printoptions(precision=2)
>>> z = [[1.4, 1.2, 0.9, 0], [0.6, 3, 0.4, 0.7], [0.2, 0.2, 0.5, 3]]

OuterCode

>>> cont_gen = contour_generator(z=z, fill_type=FillType.OuterCode)
>>> filled = cont_gen.filled(1, 2)
>>> filled
([array([[0., 0.], [1., 0.], [1.67, 0.], [1.77, 1.], [1., 1.71], [0.17, 1.], [0., 0.5],
         [0., 0.], [1., 0.44], [0.58, 1.], [1., 1.36], [1.38, 1.], [1., 0.44]]),
  array([[2.2 , 2.], [3., 1.13], [3., 1.57], [2.6, 2.], [2.2, 2.]])],
 [array([1, 2, 2, 2, 2, 2, 2, 79, 1, 2, 2, 2, 79], dtype=uint8),
  array([1, 2, 2, 2, 79], dtype=uint8)])

This returns a tuple of two lists, each list has a length equal to the number of polygons. Each polygon comprises an outer boundary and its holes. The first list contains the polygons points and the second list their corresponding Matplotlib kind codes. For polygon i the points are filled[0][i] and the matplotlib kind codes are filled[1][i].

Here the first polygon has 13 points, 8 for the outer and 5 for the hole. The hole starts at index 8 which corresponds to a kind code of 1.

OuterOffset

>>> cont_gen = contour_generator(z=z, fill_type=FillType.OuterCode)
>>> filled = cont_gen.filled(1, 2)
>>> filled
([array([[0., 0.], [1., 0.], [1.67, 0.], [1.77, 1.], [1., 1.71], [0.17, 1.], [0., 0.5],
         [0., 0.], [1., 0.44], [0.58, 1.], [1., 1.36], [1.38, 1.], [1., 0.44]]),
  array([[2.2 , 2.], [3., 1.13], [3., 1.57], [2.6, 2.], [2.2, 2.]])],
 [array([0, 8, 13], dtype=uint32),
  array([0, 5], dtype=uint32)])

This returns a tuple of two lists, each list has a length equal to the number of polygons. Each polygon comprises an outer boundary and its holes. The first list contains the polygons points and the second list the offsets into the points arrays for the start and end indices of the outers and holes. For polygon i the points are filled[0][i] and offsets are filled[1][i].

Here the first polygon has 13 points, the outer is indices 0:8 and the hole is indices 8:13. The second polygon does not have any holes so its indices 0:5 cover the whole of its points array.

ChunkCombinedCode

>>> cont_gen = contour_generator(z=z, fill_type=FillType.ChunkCombinedCode)
>>> filled = cont_gen.filled(1, 2)
>>> filled
([array([[0., 0.], [1., 0.], [1.67, 0.], [1.77, 1.], [1., 1.71], [0.17, 1.], [0., 0.5],
         [0., 0.], [1., 0.44], [0.58, 1.], [1., 1.36], [1.38, 1.], [1., 0.44], [2.2 , 2.],
         [3., 1.13], [3., 1.57], [2.6, 2.], [2.2, 2.]])],
 [array([1, 2, 2, 2, 2, 2, 2, 79, 1, 2, 2, 2, 79, 1, 2, 2, 2, 79], dtype=uint8)])

This returns a tuple of two lists, each list has a length equal to the number of chunks used which is one here. All of the boundary points are combined into a single array per chunk, there is no information on the relationship between the outer boundaries and their holes, and each outer is not necessarily stored contiguously with its corresponding holes. The first list contains the boundary points and the second list their corresponding Matplotlib kind codes.

For chunk j the combined points are filled[0][j] and the combined codes are filled[1][j]. An empty chunk has None for each. The start of each polygon boundary is identified by a kind code of 1, so here there are three boundaries.

ChunkCombinedOffset

>>> cont_gen = contour_generator(z=z, fill_type=FillType.ChunkCombinedCode)
>>> filled = cont_gen.filled(1, 2)
>>> filled
([array([[0., 0.], [1., 0.], [1.67, 0.], [1.77, 1.], [1., 1.71], [0.17, 1.], [0., 0.5],
         [0., 0.], [1., 0.44], [0.58, 1.], [1., 1.36], [1.38, 1.], [1., 0.44], [2.2 , 2.],
         [3., 1.13], [3., 1.57], [2.6, 2.], [2.2, 2.]])],
 [array([0, 8, 13, 18], dtype=uint32)])

This returns a tuple of two lists, each list has a length equal to the number of chunks used which is one here. All of the boundary points are combined into a single array per chunk, there is no information on the relationship between the outer boundaries and their holes, and each outer is not necessarily stored contiguously with its corresponding holes. The first list contains the boundary points and the second list the offsets in the points array of the boundary starts and ends.

For chunk j the combined points are filled[0][j] and the combined offsets` are filled[1][j]. An empty chunk has None for each. Here there are three boundaries with point indices 0:8, 8:13 and 13:18 respectively.

ChunkCombinedCodeOffset

>>> cont_gen = contour_generator(z=z, fill_type=FillType.ChunkCombinedCodeOffset)
>>> filled = cont_gen.filled(1, 2)
>>> filled
([array([[0., 0.], [1., 0.], [1.67, 0.], [1.77, 1.], [1., 1.71], [0.17, 1.], [0., 0.5],
         [0., 0.], [1., 0.44], [0.58, 1.], [1., 1.36], [1.38, 1.], [1., 0.44], [2.2 , 2.],
         [3., 1.13], [3., 1.57], [2.6, 2.], [2.2, 2.]])],
 [array([1, 2, 2, 2, 2, 2, 2, 79, 1, 2, 2, 2, 79, 1, 2, 2, 2, 79], dtype=uint8)],
 [array([ 0, 13, 18], dtype=uint32)])

This returns a tuple of three lists, each list has a length equal to the number of chunks used which is one here. The first two lists are the same as for ChunkCombinedCode except that each outer and its holes are stored contiguously. The third list is an array of offsets into the points and codes arrays to identify the start and end indices of each polygon (outer with its holes) within those arrays.

For chunk j the combined points are filled[0][j], the combined codes are filled[1][j] and the combined polygon offsets are filled[2][j]. An empty chunk has None for all three.

Here there are 18 points in three boundaries, the latter starting at indices 0, 8 and 13 which are determined from the kind codes of 1. The polygon offsets arrays indicates that there are two polygons, the first is indices 0:13 (so outer plus one hole) and the second is indices 13:18 (outer only).

ChunkCombinedOffsetOffset

>>> cont_gen = contour_generator(z=z, fill_type=FillType.ChunkCombinedOffsetOffset)
>>> filled = cont_gen.filled(1, 2)
>>> filled
([array([[0., 0.], [1., 0.], [1.67, 0.], [1.77, 1.], [1., 1.71], [0.17, 1.], [0., 0.5],
         [0., 0.], [1., 0.44], [0.58, 1.], [1., 1.36], [1.38, 1.], [1., 0.44], [2.2 , 2.],
         [3., 1.13], [3., 1.57], [2.6, 2.], [2.2, 2.]])],
 [array([ 0,  8, 13, 18], dtype=uint32)],
 [array([0, 2, 3], dtype=uint32)])

This returns a tuple of three lists, each list has a length equal to the number of chunks used which is one here. The first two lists are the same as for ChunkCombinedOffset except that each outer and its holes are stored contiguously. The third list is an array of polygon offsets into the boundary offsets array to identify the start and end indices of each polygon.

For chunk j the combined points are filled[0][j], the combined boundary offsets are filled[1][j] and the combined polygon offsets are filled[2][j]. An empty chunk has None for all three.

Here there are three boundaries with point indices 0:8, 8:13 and 13:18 respectively, and two polygons with boundary indices 0:2 and 2:3 respectively. So the first polygon consists of two boundaries (outer plus one hole) and the second polygon is a single boundary (outer only).

How to choose which fill type to use

  1. Do you need Matplotlib kind codes?

  2. Do you want each boundary’s points in a separate array or combined together?

  3. Do you want each outer boundary and its corresponding holes to be grouped together?

As with contour lines, the second question is one of convenience and performance. It is often more convenient to deal with a single array of points per polygon, but it is slower to do this as more arrays have to be created. The difference may only be significant for scenarios that generate many polygons. See Benchmarks.

The decision also depends on how the polygon data is to be used. The performance advantage of combined arrays is usually wasted if the polygons have to separated out into their own arrays for subsequent analysis.

Note

The order of boundaries returned by a particular filled() call is deterministic except for the combination of name="threaded" and either fill_type=FillType.OuterCode or fill_type=FillType.OuterOffset. This is because the order that the chunks are processed in is not deterministic and boundaries are appended to the returned arrays as soon as their chunks are completed.