forked from pyvista/pyvista
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add conversion of 3D label maps to labeled meshes with SurfaceNets. (p…
…yvista#5176) * Add conversion of 3D label maps to labeled meshes. ImageData.contour_labeled is added and SurfaceNets3D algorithm from a pre-release version of VTK is used. This is particularly useful for visualization of medical images with their labels and the outputs of multi-label segmentation maps. Implements pyvista#5170 Note, version of vtk 9.3+ must be used which is currently in the pre-release stage. Otherwise, the example is not generated and the test is not run. See also: Sarah F. Frisken, SurfaceNets for Multi-Label Segmentations with Preservation of Sharp Boundaries, Journal of Computer Graphics Techniques (JCGT), vol. 11, no. 1, 34-54, 2022. Available online http://jcgt.org/published/0011/01/03/ Note, silencing Mypy errors (as in pyvista#3837): pyvista/core/filters/image_data.py:853: error: Argument 1 to "set_default_active_scalars" has incompatible type "ImageDataFilters"; expected "DataSet" [arg-type] pyvista/core/filters/image_data.py:854: error: "ImageDataFilters" has no attribute "active_scalars_info" [attr-defined] pyvista/core/filters/image_data.py:858: error: "ImageDataFilters" has no attribute "get_array_association" [attr-defined] * Refer to the contouring example * Add tests for SurfaceNets contour extraction * Update comments * Update pyvista/core/filters/image_data.py Co-authored-by: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> * Update pyvista/core/filters/image_data.py Co-authored-by: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> * Update examples/01-filter/contouring.py Co-authored-by: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> * Update pyvista/core/filters/image_data.py Co-authored-by: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> * Use FieldAssiciation enums * Use FieldAssociation enums correctly * Add tests for improved coverage * Add tests for improved coverage * Fix quotes in tests --------- Co-authored-by: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> Co-authored-by: Bane Sullivan <banesullivan@gmail.com>
- Loading branch information
Showing
4 changed files
with
272 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import numpy as np | ||
import pytest | ||
|
||
import pyvista as pv | ||
from pyvista import examples | ||
|
||
VTK93 = pv.vtk_version_info >= (9, 3) | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
label_map = examples.download_frog_tissue() | ||
|
||
# Extract surface for each label | ||
mesh = label_map.contour_labeled() | ||
|
||
assert label_map.point_data.active_scalars.max() == 29 | ||
assert 'BoundaryLabels' in mesh.cell_data | ||
assert np.max(mesh['BoundaryLabels'][:, 0]) == 29 | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled_with_smoothing(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
label_map = examples.download_frog_tissue() | ||
|
||
# Extract smooth surface for each label | ||
mesh = label_map.contour_labeled(smoothing=True) | ||
# this somehow mutates the object... also the n_labels is likely not correct | ||
|
||
assert 'BoundaryLabels' in mesh.cell_data | ||
assert np.max(mesh['BoundaryLabels'][:, 0]) == 29 | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled_with_reduced_labels_count(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
label_map = examples.download_frog_tissue() | ||
|
||
# Extract surface for each label | ||
mesh = label_map.contour_labeled(n_labels=2) | ||
# this somehow mutates the object... also the n_labels is likely not correct | ||
|
||
assert 'BoundaryLabels' in mesh.cell_data | ||
assert np.max(mesh['BoundaryLabels'][:, 0]) == 2 | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled_with_triangle_output_mesh(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
label_map = examples.download_frog_tissue() | ||
|
||
# Extract surface for each label | ||
mesh = label_map.contour_labeled(scalars='MetaImage', output_mesh_type='triangles') | ||
|
||
assert 'BoundaryLabels' in mesh.cell_data | ||
assert np.max(mesh['BoundaryLabels'][:, 0]) == 29 | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled_with_boundary_output_style(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
label_map = examples.download_frog_tissue() | ||
|
||
# Extract surface for each label | ||
mesh = label_map.contour_labeled(output_style='boundary') | ||
|
||
assert 'BoundaryLabels' in mesh.cell_data | ||
assert np.max(mesh['BoundaryLabels'][:, 0]) == 29 | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled_with_invalid_output_mesh_type(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
label_map = examples.download_frog_tissue() | ||
|
||
# Extract surface for each label | ||
with pytest.raises(ValueError): | ||
label_map.contour_labeled(output_mesh_type='invalid') | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled_with_invalid_output_style(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
label_map = examples.download_frog_tissue() | ||
|
||
# Extract surface for each label | ||
with pytest.raises(NotImplementedError): | ||
label_map.contour_labeled(output_style='selected') | ||
|
||
with pytest.raises(ValueError): | ||
label_map.contour_labeled(output_style='invalid') | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled_with_scalars(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
# and create a new array with reduced number of labels | ||
label_map = examples.download_frog_tissue() | ||
label_map['labels'] = label_map['MetaImage'] // 2 | ||
|
||
# Extract surface for each label | ||
mesh = label_map.contour_labeled(scalars='labels') | ||
|
||
assert 'BoundaryLabels' in mesh.cell_data | ||
assert np.max(mesh['BoundaryLabels'][:, 0]) == 14 | ||
|
||
|
||
@pytest.mark.skipif(not VTK93, reason='At least VTK 9.3 is required') | ||
def test_contour_labeled_with_invalid_scalars(): | ||
# Load a 3D label map (segmentation of a frog's tissue) | ||
label_map = examples.download_frog_tissue() | ||
|
||
# Nonexistent scalar key | ||
with pytest.raises(KeyError): | ||
label_map.contour_labeled(scalars='nonexistent_key') | ||
|
||
# Using cell data | ||
label_map.cell_data['cell_data'] = np.zeros(label_map.n_cells) | ||
with pytest.raises(ValueError, match='Can only process point data'): | ||
label_map.contour_labeled(scalars='cell_data') | ||
|
||
# When no scalas are given and active scalars are not point data | ||
label_map.set_active_scalars('cell_data', preference='cell') | ||
with pytest.raises(ValueError, match='active scalars must be point array'): | ||
label_map.contour_labeled() |