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,
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 region1
and background0
.Internal boundary values
Polygons between two connected foreground regions are sorted in ascending order.
E.g.
[1, 2]
for the boundary between regions1
and2
.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. Theselect_inputs
andselect_outputs
options cannot be used with this style.- background_value
int
, default: 0 Background value of the input image. All other values are considered as foreground.
- select_inputs
int
|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
orexternal
. That is, an internal boundary becomes an external boundary when only one of the two foreground regions on the boundary is selected.- select_outputs
int
|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
orexternal
. 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 withbackground_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 toFalse
is useful if processing multiple volumes separately so that the generated surfaces fit together without creating surface overlap.- output_mesh_type
str
, default:None
Type of the output mesh. Can be either
'quads'
, or'triangles'
. By default, the output mesh hasTRIANGLE
cells whensmoothing
is enabled andQUAD
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.- scalars
str
,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
isTrue
andboundary_style
isTrue
(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. IfFalse
, 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 pointspj
via an edge,pi
is moved towards the average position ofpj
multiplied by thesmoothing_relaxation
factor, and limited by thesmoothing_distance
constraint. This process is repeated either until convergence occurs, or the maximum number ofsmoothing_iterations
is reached.- smoothing_iterations
int
, default: 16 Maximum number of smoothing iterations to use.
- smoothing_relaxation
float
, default: 0.5 Relaxation factor used at each smoothing iteration.
- smoothing_distance
float
, 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_scale
float
, 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
fromPolyData
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()
Generate surface contours of the foreground regions and plot it. Note that the
background_value
is0
by default.>>> contours = image.contour_labels() >>> labels_plotter(contours, zoom=1.5).show()
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
toFalse
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 always0
(i.e. thebackground_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
and3
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 thenegative_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()
Generate contours for all boundaries, and use
select_outputs
to filter the output to only include polygons which share a boundary with region3
.>>> region_3 = image.contour_labels( ... 'all', select_outputs=3, simplify_output=True ... ) >>> labels_plotter(region_3, zoom=3).show()
Note how using
select_outputs
preserves the sharp features and boundary labels for non-selected regions. If desired, useselect_inputs
instead to completely “ignore” non-selected regions.>>> region_3 = image.contour_labels(select_inputs=3) >>> labels_plotter(region_3, zoom=3).show()
The sharp features are now smoothed and the internal boundaries are now labeled as external boundaries. Note that using
'all'
here is optional since usingselect_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), settingpad_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()
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()
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()
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()