ImageDataFilters.contour_labels#
- ImageDataFilters.contour_labels(
- boundary_style: Literal['external', 'internal', 'all', 'strict_external'] = 'external',
- *,
- background_value: int = 0,
- select_inputs: int | VectorLikeInt | None = None,
- select_outputs: int | VectorLikeInt | None = None,
- pad_background: bool = True,
- output_mesh_type: Literal['quads', 'triangles'] | None = None,
- scalars: str | None = None,
- orient_faces: bool | None = None,
- 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 vtkSurfaceNets3D to extract polygonal surface contours from non-continuous label maps, which corresponds to discrete regions in an input 3D image (that is, volume). It is designed to generate surfaces from image point data, for example, 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.
Note
Where the foreground touches itself only along a voxel edge, the surface has a non-manifold junction which may be split into separate sheets with open edges. Smoothing pulls these sheets apart and leaves gaps, so the surface is not watertight. Filters which require a closed surface, such as
voxelize_binary_mask()andselect_enclosed_points(), may leak through the gaps. Checkn_open_edges, and either disablesmoothing(the staircase surface encloses the voxels exactly) or thicken the labels first, for example withdilate(), so that regions no longer touch only along an edge.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].For example,
[1, 0]for the boundary between region1and background0.Internal boundary values
Polygons between two connected foreground regions are sorted in ascending order.
For example,
[1, 2]for the boundary between regions1and2.By default, this filter returns
'external'contours only. Optionally, only the'internal'contours or'all'contours (that is, internal and external) may be returned.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.- background_value
int, default: 0 Background value of the input image. All other values are considered as foreground.
- select_inputs
int|VectorLikeInt, 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
internalorexternal. 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|VectorLikeInt, 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 (that is, 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
internalorexternal. 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 Padthe image withbackground_valueprior 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 toFalseis 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 hasTRIANGLEcells whensmoothingis enabled andQUADcells (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, optional
Deprecated since version 0.47: This keyword should not be used and will be removed in a future version. It was meant to address a vtk bug where the faces generated by vtkSurfaceNets3D have incorrect orientation. This bug is fixed in VTK 9.6, and therefore the faces now always have correct orientation by default.
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_backgroundisTrueandboundary_styleis'external'(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_valueand 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. For example, 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 (for example, 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_typeis'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
piconnected to a list of pointspjvia an edge,piis moved towards the average position ofpjmultiplied by thesmoothing_relaxationfactor, and limited by thesmoothing_distanceconstraint. This process is repeated either until convergence occurs, or the maximum number ofsmoothing_iterationsis 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.PolyDataSurface mesh of labeled regions.
- Raises:
ValueErrorIf the input is not 3-dimensional.
References#
S. Frisken, SurfaceNets for Multi-Label Segmentations with Preservation of Sharp Boundaries, J. Computer Graphics Techniques, 2022. Available online: https://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: https://arxiv.org/abs/2401.14906
Examples#
Download Python source code | Download Jupyter notebook
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)
... pl = pv.Plotter()
... pl.add_mesh(colored_mesh, show_edges=True)
... if zoom:
... pl.camera.zoom(zoom)
... return pl
>>>
>>> labels_plotter(image).show()
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()
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.
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 (that is, the background_value).
Note that all four foreground regions share a boundary with the background.
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.
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()
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()
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()
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 (for example, 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()
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 (for example, 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()
See Also#
voxelize_binary_mask()Filter that generates binary labeled
ImageDatafromPolyDatasurface contours. Can beloosely considered as an inverse of this filter.cells_to_points()Re-mesh
ImageDatato 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.
Used In#
API Examples
DataSetFilters.voxelize_binary_mask(1 use)ImageDataFilters.select_values(1 use)download_whole_body_ct_female(1 use)download_whole_body_ct_male(1 use)
Gallery Examples