pyvista.ImageDataFilters.contour_labels

pyvista.ImageDataFilters.contour_labels#

ImageDataFilters.contour_labels(
boundary_style: Literal['external', 'internal', 'all', 'strict_external'] = 'external',
*,
background_value: int = 0,
select_inputs: int | VectorLike[int] | None = None,
select_outputs: int | VectorLike[int] | None = None,
pad_background: bool = True,
output_mesh_type: Literal['quads', 'triangles'] | None = None,
scalars: str | None = None,
orient_faces: bool = True,
simplify_output: bool | None = None,
smoothing: bool = True,
smoothing_iterations: int = 16,
smoothing_relaxation: float = 0.5,
smoothing_distance: float | None = None,
smoothing_scale: float = 1.0,
progress_bar: bool = False,
) PolyData[source]#

Generate surface contours from 3D image label maps.

This filter uses vtkSurfaceNets to extract polygonal surface contours from non-continuous label maps, which corresponds to discrete regions in an input 3D image (i.e., volume). It is designed to generate surfaces from image point data, e.g. voxel point samples from 3D medical images, though images with cell data are also supported.

The generated surface is smoothed using a constrained smoothing filter, which may be fine-tuned to control the smoothing process. Optionally, smoothing may be disabled to generate a staircase-like surface.

The output surface includes a two-component cell data array 'boundary_labels'. The array indicates the labels/regions on either side of the polygons composing the output. The array’s values are structured as follows:

External boundary values

Polygons between a foreground region and the background have the form [foreground, background].

E.g. [1, 0] for the boundary between region 1 and background 0.

Internal boundary values

Polygons between two connected foreground regions are sorted in ascending order.

E.g. [1, 2] for the boundary between regions 1 and 2.

By default, this filter returns 'external' contours only. Optionally, only the 'internal' contours or 'all' contours (i.e. internal and external) may be returned.

Note

This filter requires VTK version 9.3.0 or greater.

Added in version 0.45.

Parameters:
boundary_style‘external’ | ‘internal’ | ‘all’ | ‘strict_external’, default: ‘external’

Style of boundary polygons to generate. 'internal' polygons are generated between two connected foreground regions. 'external' polygons are generated between foreground and background regions. 'all' includes both internal and external boundary polygons.

These styles are generated such that internal + external = all. Internally, the filter computes all boundary polygons by default and then removes any undesired polygons in post-processing. This improves the quality of the output, but can negatively affect the filter’s performance since all boundaries are always initially computed.

The 'strict_external' style can be used as a fast alternative to 'external'. This style strictly generates external polygons and does not compute or consider internal boundaries. This computation is fast, but also results in jagged, non-smooth boundaries between regions. The select_inputs and select_outputs options cannot be used with this style.

background_valueint, default: 0

Background value of the input image. All other values are considered as foreground.

select_inputsint | VectorLike[int], default: None

Specify label ids to include as inputs to the filter. Labels that are not selected are removed from the input before generating the surface. By default, all label ids are used.

Since the smoothing operation occurs across selected input regions, using this option to filter the input can result in smoother and more visually pleasing surfaces since non-selected inputs are not considered during smoothing. However, this also means that the generated surface will change shape depending on which inputs are selected.

Note

Selecting inputs can affect whether a boundary polygon is considered to be internal or external. That is, an internal boundary becomes an external boundary when only one of the two foreground regions on the boundary is selected.

select_outputsint | VectorLike[int], default: None

Specify label ids to include in the output of the filter. Labels that are not selected are removed from the output after generating the surface. By default, all label ids are used.

Since the smoothing operation occurs across all input regions, using this option to filter the output means that the selected output regions will have the same shape (i.e. smoothed in the same manner), regardless of the outputs that are selected. This is useful for generating a surface for specific labels while also preserving sharp boundaries with non-selected outputs.

Note

Selecting outputs does not affect whether a boundary polygon is considered to be internal or external. That is, an internal boundary remains internal even if only one of the two foreground regions on the boundary is selected.

pad_backgroundbool, default: True

Pad the image with background_value prior to contouring. This will generate polygons to “close” the surface at the boundaries of the image. This option is only relevant when there are foreground regions on the border of the image. Setting this value to False is useful if processing multiple volumes separately so that the generated surfaces fit together without creating surface overlap.

output_mesh_typestr, default: None

Type of the output mesh. Can be either 'quads', or 'triangles'. By default, the output mesh has TRIANGLE cells when smoothing is enabled and QUAD cells (quads) otherwise. The mesh type can be forced to be triangles or quads; however, if smoothing is enabled and the type is 'quads', the generated quads may not be planar.

scalarsstr, optional

Name of scalars to process. Defaults to currently active scalars. If cell scalars are specified, the input image is first re-meshed with cells_to_points() to transform the cell data into point data.

orient_facesbool, default: True

Orient the faces of the generated contours so that they have consistent ordering and face outward. If False, the generated polygons may have inconsistent ordering and orientation, which can negatively impact downstream calculations and the shading used for rendering.

Note

Orienting the faces can be computationally expensive for large meshes. Consider disabling this option to improve this filter’s performance.

Warning

Enabling this option is likely to generate surfaces with normals pointing outward when pad_background is True and boundary_style is True (the default). However, this is not guaranteed if the generated surface is not closed or if internal boundaries are generated. Do not assume the normals will point outward in all cases.

simplify_outputbool, optional

Simplify the 'boundary_labels' array as a single-component 1D array. If False, the returned 'boundary_labels' array is a two-component 2D array. This simplification is useful when only external boundaries are generated and/or when visualizing internal boundaries. The simplification is as follows:

  • External boundaries are simplified by keeping the first component and removing the second. Since external polygons may only share a boundary with the background, the second component is always background_value and therefore can be dropped without loss of information. The values of external boundaries always match the foreground values of the input.

  • Internal boundaries are simplified by assigning them unique negative values sequentially. E.g. the boundary label [1, 2] is replaced with -1, [1, 3] is replaced with -2, etc. The mapping to negative values is not fixed, and can change depending on the input.

    This simplification is particularly useful for unsigned integer labels (e.g. scalars with 'uint8' dtype) since external boundaries will be positive and internal boundaries will be negative in this case.

By default, the output is simplified when boundary_type is 'external' or 'strict_external', and is not simplified otherwise.

smoothingbool, default: True

Smooth the generated surface using a constrained smoothing filter. Each point in the surface is smoothed as follows:

For a point pi connected to a list of points pj via an edge, pi is moved towards the average position of pj multiplied by the smoothing_relaxation factor, and limited by the smoothing_distance constraint. This process is repeated either until convergence occurs, or the maximum number of smoothing_iterations is reached.

smoothing_iterationsint, default: 16

Maximum number of smoothing iterations to use.

smoothing_relaxationfloat, default: 0.5

Relaxation factor used at each smoothing iteration.

smoothing_distancefloat, default: None

Maximum distance each point is allowed to move (in any direction) during smoothing. This distance may be scaled with smoothing_scale. By default, the distance is computed dynamically from the image spacing as:

distance = norm(image_spacing) * smoothing_scale.

smoothing_scalefloat, default: 1.0

Relative scaling factor applied to smoothing_distance. See that parameter for details.

progress_barbool, default: False

Display a progress bar to indicate progress.

Returns:
pyvista.PolyData

Surface mesh of labeled regions.

See also

voxelize_binary_mask()

Filter that generates binary labeled ImageData from PolyData surface contours. Can beloosely considered as an inverse of this filter.

cells_to_points()

Re-mesh ImageData to a points-based representation.

extract_values()

Threshold-like filter which can used to process the multi-component scalars generated by this filter.

contour()

Generalized contouring method which uses MarchingCubes or FlyingEdges.

pack_labels()

Function used internally by SurfaceNets to generate contiguous label data.

color_labels()

Color labeled data, e.g. labeled volumes or contours.

Contouring, Visualize Anatomical Groups

Additional examples using this filter.

References

S. Frisken, “SurfaceNets for Multi-Label Segmentations with Preservation of Sharp Boundaries”, J. Computer Graphics Techniques, 2022. Available online: http://jcgt.org/published/0011/01/03/

W. Schroeder, S. Tsalikis, M. Halle, S. Frisken. A High-Performance SurfaceNets Discrete Isocontouring Algorithm. arXiv:2401.14906. 2024. Available online: http://arxiv.org/abs/2401.14906

Examples

Load labeled image data with a background region 0 and four foreground regions.

>>> import pyvista as pv
>>> import numpy as np
>>> from pyvista import examples
>>> image = examples.load_channels()
>>> label_ids = np.unique(image.active_scalars)
>>> label_ids
pyvista_ndarray([0, 1, 2, 3, 4])
>>> image.dimensions
(251, 251, 101)

Crop the image to simplify the data.

>>> image = image.extract_subset(voi=(75, 109, 75, 109, 85, 100))
>>> image.dimensions
(35, 35, 16)

Plot the cropped image for context. Use color_labels() to generate consistent coloring of the regions for all plots. Negative indexing is used for plotting internal boundaries.

>>> def labels_plotter(mesh, zoom=None):
...     colored_mesh = mesh.color_labels(negative_indexing=True)
...     plotter = pv.Plotter()
...     plotter.add_mesh(colored_mesh, show_edges=True)
...     if zoom:
...         plotter.camera.zoom(zoom)
...     return plotter
>>>
>>> labels_plotter(image).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_00_00.png

Generate surface contours of the foreground regions and plot it. Note that the background_value is 0 by default.

>>> contours = image.contour_labels()
>>> labels_plotter(contours, zoom=1.5).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_01_00.png

By default, only external boundary polygons are generated and the returned 'boundary_labels' array is a single-component array. The output values match the input label values.

>>> contours['boundary_labels'].ndim
1
>>> np.unique(contours['boundary_labels'])
pyvista_ndarray([1, 2, 3, 4])

Set simplify_output to False to generate a two-component array instead showing the two boundary regions associated with each polygon.

>>> contours = image.contour_labels(simplify_output=False)
>>> contours['boundary_labels'].ndim
2

Show the unique values. Since only 'external' boundaries are generated by default, the second component is always 0 (i.e. the background_value). Note that all four foreground regions share a boundary with the background.

>>> np.unique(contours['boundary_labels'], axis=0)
array([[1, 0],
       [2, 0],
       [3, 0],
       [4, 0]])

Repeat the example but this time generate internal contours only. The generated array is 2D by default.

>>> contours = image.contour_labels('internal')
>>> contours['boundary_labels'].ndim
2

Show the unique two-component boundary labels again. From these values we can determine that all foreground regions share an internal boundary with each other except for regions 1 and 3 since the boundary value [1, 3] is missing.

>>> np.unique(contours['boundary_labels'], axis=0)
array([[1, 2],
       [1, 4],
       [2, 3],
       [2, 4],
       [3, 4]])

Simplify the output so that each internal multi-component boundary value is assigned a unique negative integer value instead. This makes it easier to visualize the result with color_labels() using the negative_indexing option.

>>> contours = image.contour_labels('internal', simplify_output=True)
>>> contours['boundary_labels'].ndim
1
>>> np.unique(contours['boundary_labels'])
pyvista_ndarray([-5, -4, -3, -2, -1])
>>> labels_plotter(contours, zoom=1.5).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_02_00.png

Generate contours for all boundaries, and use select_outputs to filter the output to only include polygons which share a boundary with region 3.

>>> region_3 = image.contour_labels(
...     'all', select_outputs=3, simplify_output=True
... )
>>> labels_plotter(region_3, zoom=3).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_03_00.png

Note how using select_outputs preserves the sharp features and boundary labels for non-selected regions. If desired, use select_inputs instead to completely “ignore” non-selected regions.

>>> region_3 = image.contour_labels(select_inputs=3)
>>> labels_plotter(region_3, zoom=3).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_04_00.png

The sharp features are now smoothed and the internal boundaries are now labeled as external boundaries. Note that using 'all' here is optional since using select_inputs converts previously-internal boundaries into external ones.

Do not pad the image with background values before contouring. Since the input image has foreground regions visible at the edges of the image (e.g. the +Z bound), setting pad_background=False in this example causes the top and sides of the mesh to be “open”.

>>> surf = image.contour_labels(pad_background=False)
>>> labels_plotter(surf, zoom=1.5).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_05_00.png

Disable smoothing to generate staircase-like surface. Without smoothing, the surface has quadrilateral cells by default.

>>> surf = image.contour_labels(smoothing=False)
>>> labels_plotter(surf, zoom=1.5).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_06_00.png

Keep smoothing enabled but reduce the smoothing scale. A smoothing scale less than one may help preserve sharp features (e.g. corners).

>>> surf = image.contour_labels(smoothing_scale=0.5)
>>> labels_plotter(surf, zoom=1.5).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_07_00.png

Use the 'strict_external' style to compute external contours quickly. Note that this produces jagged and non-smooth boundaries between regions, which may not be desirable. Also note how the top of the surface is perfectly flat compared to the default 'external' style (see first example above) since the strict style ignores the smoothing effects of all internal boundaries.

>>> surf = image.contour_labels('strict_external')
>>> labels_plotter(surf, zoom=1.5).show()
../../../_images/pyvista-ImageDataFilters-contour_labels-1_08_00.png