The vtk VoxelContoursToSurfaceFilter output is not closed surface

I used closed polygons as input polydata, but the result is not closed。
Anyone can give me some advice?

How to make the output closed?

Mesh reconstruction(const Ring3dList &ring_list, double slice_spacing)
{

// ring3d: {plane, ring2d}; 
// pre-condition: slices in contour list are parallel and equal spacing
Point3dDouble normal_vector = ring_list.front().first.getNormalVector();
Point3dDouble plane_origin = ring_list[0].first.getOrigin();
arma::mat matrix = transformMatrix(plane_origin, normal_vector); // use first plane as the z=0 plane.

vtkSmartPointer<vtkAppendPolyData> appendFilter = vtkSmartPointer<vtkAppendPolyData>::New();
for (size_t i = 0; i < ring_list.size(); i++) {
    Points3dDoubleList pt_list;
    for (auto &point : ring_list[i].second.points) {
        Point3dDouble pt = ring_list[i].first.transfer2DTo3D(point);
        pt_list.push_back(transform(matrix, pt));
    }
    vtkSmartPointer<vtkPolyData> data = vtkSmartPointer<vtkPolyData>::New();
    vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
    vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New();
    points->SetNumberOfPoints(static_cast<long long>(pt_list.size()));
    cells->Allocate(1, points->GetNumberOfPoints());
    cells->InsertNextCell(static_cast<int>(points->GetNumberOfPoints()));
    for (size_t j = 0; j < pt_list.size(); j++) {
        Point3dDouble pt = pt_list[j];
        points->SetPoint(static_cast<int>(j), pt.x(), pt.y(), pt.z());
        cells->InsertCellPoint(static_cast<int>(j));
    }
    data->Initialize();
    data->SetPolys(cells);
    data->SetPoints(points);
    appendFilter->AddInputData(data);
}
appendFilter->Update();
double bounds[6];
vtkPolyData* contours = appendFilter->GetOutput();
contours->GetBounds( bounds );
double origin[3] = {bounds[0], bounds[2], bounds[4] };
double spacing[3] = {(bounds[1] - bounds[0]) / K_RING_RESOLUTION,
                     (bounds[3] - bounds[2]) / K_RING_RESOLUTION,
                     slice_spacing};

vtkSmartPointer<vtkPolyData> poly = vtkSmartPointer<vtkPolyData>::New();
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
vtkPoints* contourPoints = contours->GetPoints();
int numPoints = static_cast<int>(contourPoints->GetNumberOfPoints());
points->SetNumberOfPoints( numPoints );
for( int i = 0; i < numPoints; ++i )
{
    double pt[3];
    contourPoints->GetPoint( i, pt );
    pt[0] = static_cast<int>( (pt[0] - origin[0]) / spacing[0] + 0.5 );
    pt[1] = static_cast<int>( (pt[1] - origin[1]) / spacing[1] + 0.5 );
    pt[2] = static_cast<int>( (pt[2] - origin[2]) / spacing[2] + 0.5 );
    points->SetPoint( i, pt );
}
poly->SetPolys( contours->GetPolys() );
poly->SetPoints( points );
vtkSmartPointer<vtkVoxelContoursToSurfaceFilter> contoursToSurface =
        vtkSmartPointer<vtkVoxelContoursToSurfaceFilter>::New();
contoursToSurface->SetInputData( poly );
contoursToSurface->SetSpacing( spacing[0], spacing[1], spacing[2] );
contoursToSurface->Update();

double scaleCenter[3];
contoursToSurface->GetOutput()->GetCenter( scaleCenter );
double center[3];
contours->GetCenter(center);

vtkSmartPointer<vtkTransform> translate = vtkSmartPointer<vtkTransform>::New();
translate->Translate(-scaleCenter[0], -scaleCenter[1], -scaleCenter[2]);
translate->Translate(center[0], center[1], center[2]);
translate->Update();

vtkSmartPointer<vtkTransformPolyDataFilter> transformFilter1 = vtkSmartPointer<vtkTransformPolyDataFilter>::New();
transformFilter1->SetInputConnection(contoursToSurface->GetOutputPort());
transformFilter1->SetTransform(translate);
transformFilter1->Update();

vtkSmartPointer<vtkTransform> reverse = vtkSmartPointer<vtkTransform>::New();
arma::mat reverse_matrix = matrix.i();
double m[16];
for (size_t i = 0; i < 16; i++) {
    m[i] = reverse_matrix[(i % 4 ) * 4 + i / 4];
}
reverse->SetMatrix(m);
reverse->Update();

vtkSmartPointer<vtkTransformPolyDataFilter> transformFilter2 = vtkSmartPointer<vtkTransformPolyDataFilter>::New();
transformFilter2->SetInputConnection(transformFilter1->GetOutputPort());
transformFilter2->SetTransform(reverse);
transformFilter2->Update();

if (!isMeshDataValid(transformFilter2)) {
    DLOG("Reconstruction of ring list failed!");
    return Mesh();
}
MeshData mesh_data = cleanMeshData(transformFilter2);
MeshProp mesh_prop = getMeshDataProp(mesh_data);
DLOG("Reconstruction of ring list success!");
return Mesh{mesh_data, mesh_prop};

}

The result is

Thanks !!!

vtkVoxelContoursToSurfaceFilter has so many restrictions on the input contours that it is hard to find applications where it is usable in practice.

Would you like to use it to rasterize DICOM RT structure sets?

In my work, for example, I draw the outline of a tumor in different dicom images,then make them to be a 3d surface so that I can get the contour of tumor in any plane

If the outline has a sharp corner, the result will be not closed in high probability

Reconstructing 3D shape from 2D intersections is a very common but challenging task. There is no general solution but there are lots of solutions for specific cases.

Do you segment the tumor on parallel slices only?
Do you segment in every slice, regularly (in every n-th slice), or with random spacing between slices?
Can you have branching between slices?
Can your 3D shapes be concave (i.e., segmented slice contain holes)?

Segment on parallel slices only, with same spacing, in every slice, no holes, but the shape may be concave.

Is any way to remove the boundary of surface as i mention in the pic ? Or I can force the output to be closed?

In this case, you can try vtkRuledSurfaceFilter implementation in VTK or 3D Slicer’s planar contour to closed surface converter .

Sorry.
Some situation have branching between slices. Some contain holes.
Can 3D Slicer’splanar contour to closed surface converter can slove it?
vtkRuledSurfaceFilter implementation seems not to handle branching.

Yes, 3D Slicer’s converter handles both branching and holes.

Note that if you draw the contour based on images and you have segmentation for all the slices then it may be simpler to represent your segmentation as a 3D image, too. Then, generating a closed surface is trivial (using flying edges or marching cubes filter).