1 /*
2  * Medical Image Registration ToolKit (MIRTK)
3  *
4  * Copyright 2016 Imperial College London
5  * Copyright 2016 Andreas Schuh
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 
20 #include "mirtk/Common.h"
21 #include "mirtk/Options.h"
22 
23 #include "mirtk/PointSetIO.h"
24 #include "mirtk/PointSetUtils.h"
25 #include "mirtk/GenericImage.h"
26 #include "mirtk/Stripper.h"
27 #include "mirtk/SurfaceBoundary.h"
28 #include "mirtk/ConnectedComponents.h"
29 #include "mirtk/SurfacePatches.h"
30 #include "mirtk/SurfaceRemeshing.h"
31 #include "mirtk/MeshSmoothing.h"
32 #include "mirtk/Matrix3x3.h"
33 #include "mirtk/Vector3.h"
34 #include "mirtk/Triangle.h"
35 
36 #include "mirtk/Vtk.h"
37 #include "vtkSmartPointer.h"
38 #include "vtkPolyData.h"
39 #include "vtkPointData.h"
40 #include "vtkCellData.h"
41 #include "vtkCleanPolyData.h"
42 #include "vtkAppendPolyData.h"
43 #include "vtkImageData.h"
44 #include "vtkImageStencilData.h"
45 #include "vtkIdList.h"
46 #include "vtkCellArray.h"
47 #include "vtkPolygon.h"
48 #include "vtkPolyDataNormals.h"
49 #include "vtkCellDataToPointData.h"
50 #include "vtkPointLocator.h"
51 #include "vtkCellLocator.h"
52 #include "vtkPlaneSource.h"
53 #include "vtkIntersectionPolyDataFilter.h"
54 #include "vtkGenericCell.h"
55 #include "vtkMergePoints.h"
56 #include "vtkPolyDataConnectivityFilter.h"
57 #include "vtkLine.h"
58 #include "vtkDelaunay2D.h"
59 #include "vtkMatrix4x4.h"
60 #include "vtkMatrixToLinearTransform.h"
61 
62 using namespace mirtk;
63 
64 
65 // =============================================================================
66 // Help
67 // =============================================================================
68 
69 // -----------------------------------------------------------------------------
PrintHelp(const char * name)70 void PrintHelp(const char *name)
71 {
72   cout << "\n";
73   cout << "Usage: " << name << " [options]\n";
74   cout << "       " << name << " [<input>...] <output> [options]\n";
75   cout << "\n";
76   cout << "Description:\n";
77   cout << "  Merge surface meshes at segmentation label boundaries. The input\n";
78   cout << "  surfaces must follow closely the boundary of a segment in the given\n";
79   cout << "  image segmentation. Two surfaces are then merged at the longest\n";
80   cout << "  common intersection boundary. When the two surfaces share no such\n";
81   cout << "  segmentation boundary, the surfaces are not connected.\n";
82   cout << "\n";
83   cout << "Arguments:\n";
84   cout << "  input    File name of input surface mesh.\n";
85   cout << "  output   File name of output surface mesh.\n";
86   cout << "\n";
87   cout << "Options:\n";
88   cout << "  -i, -input <file>...\n";
89   cout << "      Input surfaces. These are added to the list of input surfaces\n";
90   cout << "      to be merged after the <input> arguments.\n";
91   cout << "  -o, -output <file>\n";
92   cout << "      Output surface file. When this option is given, the <output>\n";
93   cout << "      argument is appended to the <input> surfaces before any surface\n";
94   cout << "      specified using :option:`-i`. Either this option or an <output>\n";
95   cout << "      argument is required.\n";
96   cout << "  -labels <file>\n";
97   cout << "      Segmentation labels image. Currently required.\n";
98   cout << "  -labels-percentage <value>\n";
99   cout << "      Percentage of voxels within an input surface that must have the same label\n";
100   cout << "      to be considered for determining boundaries between segments.\n";
101   cout << "  -source-array <name>\n";
102   cout << "      Add point/cell data array with the specified name with one-based\n";
103   cout << "      labels corresponding to the input point set from which an output\n";
104   cout << "      point/cell originates from. When the first input point set has\n";
105   cout << "      a scalar array with the specified name, the labels of this first\n";
106   cout << "      surface are preserved, while successive labels are offset by the\n";
107   cout << "      maximum integer value of the input data array. This is useful when\n";
108   cout << "      successively merging surface meshes instead of with a single execution\n";
109   cout << "      of this command. (default: none)\n";
110   cout << "  -point-source-array <name>\n";
111   cout << "      Name of output point data :option:`-source-array`.\n";
112   cout << "  -cell-source-array <name>\n";
113   cout << "      Name of output cell data :option:`-source-array`.\n";
114   cout << "  -join [on|off], -nojoin\n";
115   cout << "      Whether to join surfaces at intersection boundaries.\n";
116   cout << "      When off, the output data set has unconnected components.\n";
117   cout << "      (default: on)\n";
118   cout << "  -largest [on|off]\n";
119   cout << "      Retain only largest surface component after :option:`-join` of boundaries. (default: off)\n";
120   cout << "  -tolerance, -tol <float>\n";
121   cout << "      Maximum distance of an input cell from the segmentation\n";
122   cout << "      boundary to be removed. The resulting intersection boundary\n";
123   cout << "      edges are joined again after the removal of these cells.\n";
124   cout << "      (default: max voxel size)\n";
125   cout << "  -smooth-boundaries, -boundary-smoothing [<n>]\n";
126   cout << "      Number of intersection boundary edge smoothing iterations.\n";
127   cout << "      (default: 3)\n";
128   cout << "  -nosmooth-boundaries, -noboundary-smoothing\n";
129   cout << "      Disable smoothing of boundaries, same as :option:`-smooth-boundaries` 0.\n";
130   cout << "  -remeshing, -remesh [<n>]\n";
131   cout << "      Enable remeshing of newly inserted triangles at intersection\n";
132   cout << "      boundaries. The default number of remeshing iterations <n> is 3.\n";
133   cout << "      A different value can be specified using this option or\n";
134   cout << "      :option:`-remeshing-iterations`. (default: 3)\n";
135   cout << "  -remeshing-iterations, -remesh-iterations\n";
136   cout << "      Number of intersection triangle remeshing iterations. (default: 3)\n";
137   cout << "  -noremeshing, -noremesh\n";
138   cout << "      Disable remeshing of newly inserted intersection triangles,\n";
139   cout << "      same as :option:`-remeshing` or :option:`-remeshing-iterations` 0.\n";
140   cout << "  -edge-length <min> [<max>]\n";
141   cout << "      Edge length range for remeshing newly inserted triangles at\n";
142   cout << "      intersection boundaries. When min and/or max edge length not\n";
143   cout << "      specified, the range is chosen based on the mean local edge\n";
144   cout << "      length of the input meshes at the intersection boundaries\n";
145   cout << "      minus/plus :option:`-edge-length-sigma` times the local\n";
146   cout << "      standard deviation of input mesh edge lengths.\n";
147   cout << "  -min-edge-length <min>\n";
148   cout << "      Minimum edge length for remeshing of newly inserted triangles.\n";
149   cout << "  -max-edge-length <min>\n";
150   cout << "      Maximum edge length for remeshing of newly inserted triangles.\n";
151   cout << "  -edge-length-sigma <scale>\n";
152   cout << "      Standard deviation scaling factor of local edge length standard\n";
153   cout << "      deviation used to set min/max edge length range for local remeshing\n";
154   cout << "      of newly inserted triangles at intersection boundaries. (default: 2)\n";
155   cout << "  -smoothing, -smooth [<n>]\n";
156   cout << "      Enable smoothing of merged surface points nearby intersection boundaries.\n";
157   cout << "      When :option:`-smoothing-mu` is zero or NaN, Gaussian weighting function\n";
158   cout << "      is used for the mesh smoothing. Otherwise, a uniform weighting is used. (default: 0)\n";
159   cout << "  -smoothing-iterations, -smooth-iterations\n";
160   cout << "      Number of intersection points smoothing iterations. (default: 0)\n";
161   cout << "  -nosmoothing, -nosmooth\n";
162   cout << "      Disable smoothing of nearby intersection points, same as\n";
163   cout << "      :option:`-smoothing` or :option:`-smoothing-iterations` 0.\n";
164   cout << "  -smoothing-lambda, -smooth-lambda <float>\n";
165   cout << "      Lambda parameter used for odd iterations of Laplacian point :option:`-smoothing`.\n";
166   cout << "      The default value is set based on the :option:`-smooth-mu` parameter. (default: abs(mu) - .01)\n";
167   cout << "      When only :option:`-smoothing-lambda` is given, :option:`-smoothing-mu` is set to\n";
168   cout << "      to the same value and a Gaussian weighting function is used.\n";
169   cout << "  -smoothing-mu, -smooth-mu <float>\n";
170   cout << "      Lambda parameter used for even iterations of Laplacian point :option:`-smoothing`.\n";
171   cout << "      The default smoothing parameters avoid shrinking of the surface mesh\n";
172   cout << "      and serve mainly to relax any small self-intersections that may have\n";
173   cout << "      been introduced while joining the split input surfaces. (default: -.75)\n";
174   cout << "  -neighborhood, -neighbourhood, -radius <int>\n";
175   cout << "      Edge connectivity radius around intersection border cells/points used\n";
176   cout << "      to define the local neighborhood for which to apply the remeshing and\n";
177   cout << "      smoothing operations. See also :option:`-remeshing-neighborhood`.\n";
178   cout << "  -remeshing-neighborhood, -remeshing-radius <int>\n";
179   cout << "      Edge connectivity radius around intersection border cells/points used\n";
180   cout << "      to define neighborhood for which to apply the :option:`-remeshing`. (default: 3)\n";
181   cout << "  -smoothing-neighborhood, -smoothing-radius <int>\n";
182   cout << "      Edge connectivity radius around intersection border cells/points used\n";
183   cout << "      to define neighborhood for which to apply the :option:`-smoothing`. (default: 2)\n";
184   cout << "  -smooth-source, source-smoothing [<n>]\n";
185   cout << "      Number of iterations for which to smooth the :option:`-source-array`\n";
186   cout << "      labels to form smoother boundaries between intersection borders.\n";
187   cout << "  -dividers [on|off], -nodividers\n";
188   cout << "      Enable/disable insertion of segmentation cutting planes at mesh intersections.\n";
189   cout << "      The :option:`-source-array` has unique negative labels for each such inserted\n";
190   cout << "      divider surface patch which enable the extraction or removal of these\n";
191   cout << "      dividers again when a genus-0 surface is required again. (default: off)\n";
192   cout << "  -normals [on|off], -nonormals\n";
193   cout << "      Enable/disable output of surface point and cell normals. (default: on)\n";
194   cout << "  -point-normals [on|off], -nopoint-normals\n";
195   cout << "      Enable/disable output of surface point normals. (default: on)\n";
196   cout << "  -cell-normals [on|off], -nocell-normals\n";
197   cout << "      Enable/disable output of surface cell normals. (default: on)\n";
198   PrintStandardOptions(cout);
199   cout << endl;
200 }
201 
202 // =============================================================================
203 // Constants
204 // =============================================================================
205 
206 const char * const SOURCE_ARRAY_NAME          = "_SourceId";
207 const char * const INTERSECTION_ARRAY_NAME    = "_IntersectionMask";
208 const char * const MIN_EDGE_LENGTH_ARRAY_NAME = "_MinEdgeLength";
209 const char * const MAX_EDGE_LENGTH_ARRAY_NAME = "_MaxEdgeLength";
210 
211 // =============================================================================
212 // Auxiliaries
213 // =============================================================================
214 
215 // -----------------------------------------------------------------------------
216 /// Get binary mask of region enclosed by surface
InsideMask(const ImageAttributes & attr,vtkSmartPointer<vtkPolyData> surface)217 BinaryImage InsideMask(const ImageAttributes &attr, vtkSmartPointer<vtkPolyData> surface)
218 {
219   BinaryImage mask(attr, 1);
220   surface = vtkPolyData::SafeDownCast(WorldToImage(surface, &mask));
221   vtkSmartPointer<vtkImageData>        vtkmask = NewVtkMask(mask.X(), mask.Y(), mask.Z());
222   vtkSmartPointer<vtkImageStencilData> stencil = ImageStencil(vtkmask, surface);
223   ImageStencilToMask(stencil, vtkmask);
224   mask.CopyFrom(reinterpret_cast<BinaryPixel *>(vtkmask->GetScalarPointer()));
225   return mask;
226 }
227 
228 // -----------------------------------------------------------------------------
229 /// Determine dominant surface label
InsideLabels(const GreyImage & labels,double percentage,vtkPolyData * surface)230 UnorderedSet<int> InsideLabels(const GreyImage &labels, double percentage, vtkPolyData *surface)
231 {
232   OrderedMap<int, int> hist;
233   BinaryImage mask = InsideMask(labels.Attributes(), surface);
234   const int nvox = labels.NumberOfSpatialVoxels();
235   int size = 0;
236   for (int vox = 0; vox < nvox; ++vox) {
237     if (mask(vox) != 0) {
238       ++hist[labels(vox)];
239       ++size;
240     }
241   }
242   UnorderedSet<int> label_set;
243   if (size == 0) return label_set;
244   const int min_count = ifloor(percentage * static_cast<double>(size) / 100.);
245   for (const auto &bin : hist) {
246     if (bin.second > min_count) {
247       label_set.insert(bin.first);
248     }
249   }
250   return label_set;
251 }
252 
253 // -----------------------------------------------------------------------------
254 /// Add set of segmentation boundary points
255 vtkSmartPointer<vtkPolyData>
LabelBoundary(const GreyImage & labels,const UnorderedSet<int> & label_set1,const UnorderedSet<int> & label_set2)256 LabelBoundary(const GreyImage &labels, const UnorderedSet<int> &label_set1, const UnorderedSet<int> &label_set2)
257 {
258   const NeighborhoodOffsets offsets(&labels, labels.Z() > 1 ? CONNECTIVITY_6 : CONNECTIVITY_4);
259   const int nvox = labels.NumberOfSpatialVoxels();
260 
261   const GreyPixel *l1, *l2;
262   const GreyPixel * const begin = labels.Data();
263   const GreyPixel * const end   = labels.Data() + nvox;
264 
265   l1 = begin;
266 
267   bool is_boundary_voxel;
268   ByteImage boundary_voxels(labels.Attributes());
269   for (int vox = 0; vox < nvox; ++vox, ++l1) {
270     if (label_set1.find(*l1) != label_set1.end()) {
271       is_boundary_voxel = false;
272       for (int n = 0; n < offsets.Size(); ++n) {
273         l2 = l1 + offsets(n);
274         if (begin <= l2 && l2 < end) {
275           if (*l2 != *l1 && label_set2.find(*l2) != label_set2.end()) {
276             is_boundary_voxel = true;
277             break;
278           }
279         }
280       }
281       if (is_boundary_voxel) {
282         boundary_voxels(vox) = 1;
283       }
284     }
285   }
286 
287   ConnectedComponents<BytePixel> cc;
288   cc.Input (&boundary_voxels);
289   cc.Output(&boundary_voxels);
290   cc.Run();
291 
292   vtkSmartPointer<vtkPoints> points;
293   vtkSmartPointer<vtkCellArray> polys;
294   points = vtkSmartPointer<vtkPoints>::New();
295   polys  = vtkSmartPointer<vtkCellArray>::New();
296 
297   l1 = begin;
298   double p[3], c[3];
299   vtkIdType ptIds[4];
300   int i1, j1, k1, i2, j2, k2;
301   for (int vox = 0; vox < nvox; ++vox, ++l1) {
302     if (boundary_voxels(vox) == 1) {
303       for (int n = 0; n < offsets.Size(); ++n) {
304         l2 = l1 + offsets(n);
305         if (begin <= l2 && l2 < end) {
306           if (*l2 != *l1 && label_set2.find(*l2) != label_set2.end()) {
307             labels.IndexToVoxel(static_cast<int>(l1 - begin), i1, j1, k1);
308             labels.IndexToVoxel(static_cast<int>(l2 - begin), i2, j2, k2);
309             c[0] = i1, c[1] = j1, c[2] = k1;
310             if (i1 != i2) {
311               p[0] = .5 * (i1 + i2);
312               p[1] = c[1] - .5;
313               p[2] = c[2] - .5;
314               labels.ImageToWorld(p[0], p[1], p[2]);
315               ptIds[0] = points->InsertNextPoint(p);
316               p[0] = .5 * (i1 + i2);
317               p[1] = c[1] - .5;
318               p[2] = c[2] + .5;
319               labels.ImageToWorld(p[0], p[1], p[2]);
320               ptIds[1] = points->InsertNextPoint(p);
321               p[0] = .5 * (i1 + i2);
322               p[1] = c[1] + .5;
323               p[2] = c[2] + .5;
324               labels.ImageToWorld(p[0], p[1], p[2]);
325               ptIds[2] = points->InsertNextPoint(p);
326               p[0] = .5 * (i1 + i2);
327               p[1] = c[1] + .5;
328               p[2] = c[2] - .5;
329               labels.ImageToWorld(p[0], p[1], p[2]);
330               ptIds[3] = points->InsertNextPoint(p);
331             } else if (j1 != j2) {
332               p[0] = c[0] - .5;
333               p[1] = .5 * (j1 + j2);
334               p[2] = c[2] - .5;
335               labels.ImageToWorld(p[0], p[1], p[2]);
336               ptIds[0] = points->InsertNextPoint(p);
337               p[0] = c[0] - .5;
338               p[1] = .5 * (j1 + j2);
339               p[2] = c[2] + .5;
340               labels.ImageToWorld(p[0], p[1], p[2]);
341               ptIds[1] = points->InsertNextPoint(p);
342               p[0] = c[0] + .5;
343               p[1] = .5 * (j1 + j2);
344               p[2] = c[2] + .5;
345               labels.ImageToWorld(p[0], p[1], p[2]);
346               ptIds[2] = points->InsertNextPoint(p);
347               p[0] = c[0] + .5;
348               p[1] = .5 * (j1 + j2);
349               p[2] = c[2] - .5;
350               labels.ImageToWorld(p[0], p[1], p[2]);
351               ptIds[3] = points->InsertNextPoint(p);
352             } else {
353               p[0] = c[0] - .5;
354               p[1] = c[1] - .5;
355               p[2] = .5 * (k1 + k2);
356               labels.ImageToWorld(p[0], p[1], p[2]);
357               ptIds[0] = points->InsertNextPoint(p);
358               p[0] = c[0] - .5;
359               p[1] = c[1] + .5;
360               p[2] = .5 * (k1 + k2);
361               labels.ImageToWorld(p[0], p[1], p[2]);
362               ptIds[1] = points->InsertNextPoint(p);
363               p[0] = c[0] + .5;
364               p[1] = c[1] + .5;
365               p[2] = .5 * (k1 + k2);
366               labels.ImageToWorld(p[0], p[1], p[2]);
367               ptIds[2] = points->InsertNextPoint(p);
368               p[0] = c[0] + .5;
369               p[1] = c[1] - .5;
370               p[2] = .5 * (k1 + k2);
371               labels.ImageToWorld(p[0], p[1], p[2]);
372               ptIds[3] = points->InsertNextPoint(p);
373             }
374             polys->InsertNextCell(4, ptIds);
375           }
376         }
377       }
378     }
379   }
380 
381   vtkSmartPointer<vtkPolyData> boundary;
382   boundary = vtkSmartPointer<vtkPolyData>::New();
383   boundary->SetPoints(points);
384   boundary->SetPolys(polys);
385 
386   vtkNew<vtkCleanPolyData> cleaner;
387   SetVTKInput(cleaner, boundary);
388   cleaner->ConvertPolysToLinesOff();
389   cleaner->ConvertLinesToPointsOff();
390   cleaner->ConvertStripsToPolysOff();
391   cleaner->PointMergingOn();
392   cleaner->ToleranceIsAbsoluteOn();
393   cleaner->SetAbsoluteTolerance(1e-9);
394   cleaner->Update();
395   boundary = cleaner->GetOutput();
396 
397   if (debug) {
398     static int ncall = 0; ++ncall;
399     char fname[64];
400     snprintf(fname, 64, "debug_boundary_voxels_%d.nii.gz", ncall);
401     boundary_voxels.Write(fname);
402     snprintf(fname, 64, "debug_boundary_%d.vtp", ncall);
403     WritePolyData(fname, boundary);
404   }
405 
406   return boundary;
407 }
408 
409 // -----------------------------------------------------------------------------
410 /// Minimum squared distance of point to segmentation boundary surface
MinSquaredDistance(vtkAbstractCellLocator * cut,const Point & p)411 inline double MinSquaredDistance(vtkAbstractCellLocator *cut, const Point &p)
412 {
413   double    x[3], dist2;
414   vtkIdType cellId;
415   int       subId;
416   cut->FindClosestPoint(const_cast<Point &>(p), x, cellId, subId, dist2);
417   return dist2;
418 }
419 
420 // -----------------------------------------------------------------------------
421 /// Minimum squared distance of point to surface boundary
MinSquaredDistance(const BoundarySegment & seg,const Point & p)422 inline double MinSquaredDistance(const BoundarySegment &seg, const Point &p)
423 {
424   const auto i = seg.FindClosestPoint(p);
425   return p.SquaredDistance(seg.Point(i));
426 }
427 
428 // -----------------------------------------------------------------------------
429 /// Minimum squared distance of surface boundary segment to segmentation boundary surface
MinSquaredDistance(const BoundarySegment & seg,vtkAbstractCellLocator * cut)430 double MinSquaredDistance(const BoundarySegment &seg, vtkAbstractCellLocator *cut)
431 {
432   double    x[3], dist2, min_dist2 = inf;
433   vtkIdType cellId;
434   int       subId;
435   for (int i = 0; i < seg.NumberOfPoints(); ++i) {
436     cut->FindClosestPoint(seg.Point(i), x, cellId, subId, dist2);
437     if (dist2 < min_dist2) min_dist2 = dist2;
438   }
439   return min_dist2;
440 }
441 
442 // -----------------------------------------------------------------------------
443 /// Hausdorff distance of two boundary segments
HausdorffDistance(const BoundarySegment & a,const BoundarySegment & b)444 double HausdorffDistance(const BoundarySegment &a, const BoundarySegment &b)
445 {
446   double d_ab = 0., d_ba = 0.;
447   for (int i = 0; i < a.NumberOfPoints(); ++i) {
448     d_ab = max(d_ab, MinSquaredDistance(b, a.Point(i)));
449   }
450   for (int i = 0; i < b.NumberOfPoints(); ++i) {
451     d_ba = max(d_ba, MinSquaredDistance(a, b.Point(i)));
452   }
453   return sqrt(max(d_ab, d_ba));
454 }
455 
456 // -----------------------------------------------------------------------------
457 /// Delete boundary points with only one cell
DeleteSingleBoundaryPointCells(SurfaceBoundary & boundary,vtkAbstractCellLocator * cut,double max_dist2)458 void DeleteSingleBoundaryPointCells(SurfaceBoundary &boundary, vtkAbstractCellLocator *cut, double max_dist2)
459 {
460   vtkPolyData * const surface = boundary.Surface();
461 
462   vtkNew<vtkIdList> cellIds;
463 
464   while (true) {
465     int ndel = 0;
466     surface->BuildLinks();
467     for (int j = 0; j < boundary.NumberOfSegments(); ++j) {
468       auto &seg = boundary.Segment(j);
469       if (MinSquaredDistance(seg, cut) < max_dist2) {
470         for (int i = 0; i < seg.NumberOfPoints(); ++i) {
471           surface->GetPointCells(seg.PointId(i), cellIds.GetPointer());
472           if (cellIds->GetNumberOfIds() == 1) {
473             surface->RemoveCellReference(cellIds->GetId(0));
474             surface->DeleteCell(cellIds->GetId(0));
475             ++ndel;
476           }
477         }
478       }
479     }
480     if (ndel == 0) break;
481     surface->RemoveDeletedCells();
482     boundary = SurfaceBoundary(surface);
483   }
484 }
485 
486 // -----------------------------------------------------------------------------
487 /// Smooth intersection boundaries
SmoothBoundaries(SurfaceBoundary & boundary,vtkAbstractCellLocator * cut,double max_dist2,int niter=1)488 void SmoothBoundaries(SurfaceBoundary &boundary, vtkAbstractCellLocator *cut, double max_dist2, int niter = 1)
489 {
490   if (niter <= 0) return;
491   PointSet new_points;
492   for (int j = 0; j < boundary.NumberOfSegments(); ++j) {
493     auto &seg = boundary.Segment(j);
494     if (MinSquaredDistance(seg, cut) < max_dist2) {
495       new_points.Resize(seg.NumberOfPoints());
496       for (int iter = 0; iter < niter; ++iter) {
497         for (int i = 0; i < seg.NumberOfPoints(); ++i) {
498           new_points(i) = (seg.Point(i-1) + seg.Point(i) + seg.Point(i+1)) / 3.;
499         }
500         for (int i = 0; i < seg.NumberOfPoints(); ++i) {
501           seg.Point(i, new_points(i));
502         }
503       }
504     }
505   }
506 }
507 
508 // -----------------------------------------------------------------------------
InterpolateCellData(vtkCellData * cd,vtkPointData * pd,const Array<int> & type,vtkIdType cellId,vtkIdType ptId1,vtkIdType ptId2,vtkIdType ptId3)509 void InterpolateCellData(vtkCellData *cd, vtkPointData *pd, const Array<int> &type,
510                          vtkIdType cellId, vtkIdType ptId1, vtkIdType ptId2, vtkIdType ptId3)
511 {
512   double v1, v2, v3;
513   Array<double> tuple;
514   for (int i = 0; i < cd->GetNumberOfArrays(); ++i) {
515     vtkDataArray * const dst = cd->GetArray(i);
516     vtkDataArray * const src = pd->GetArray(i);
517     tuple.resize(src->GetNumberOfComponents());
518     if (type[i] == 2) {
519       for (int j = 0; j < src->GetNumberOfComponents(); ++j) {
520         tuple[j] = 0.;
521       }
522     } else if (type[i] == 1) {
523       for (int j = 0; j < src->GetNumberOfComponents(); ++j) {
524         v1 = src->GetComponent(ptId1, j);
525         v2 = src->GetComponent(ptId2, j);
526         v3 = src->GetComponent(ptId3, j);
527         tuple[j] = (v2 == v3 ? v2 : v1);
528       }
529     } else {
530       for (int j = 0; j < src->GetNumberOfComponents(); ++j) {
531         v1 = src->GetComponent(ptId1, j);
532         v2 = src->GetComponent(ptId2, j);
533         v3 = src->GetComponent(ptId3, j);
534         tuple[j] = (v1 + v2 + v3) / 3.;
535       }
536     }
537     dst->InsertTuple(cellId, tuple.data());
538   }
539 }
540 
541 // -----------------------------------------------------------------------------
542 /// Convert cell data to point data
CellToPointData(vtkDataSet * dataset,const Array<int> & type)543 vtkSmartPointer<vtkPointData> CellToPointData(vtkDataSet *dataset, const Array<int> &type)
544 {
545   vtkCellData * const cd = dataset->GetCellData();
546   // Use VTK filter to convert cell data to point data using averaging
547   vtkNew<vtkCellDataToPointData> cd_to_pd;
548   SetVTKInput(cd_to_pd, dataset);
549   cd_to_pd->PassCellDataOff();
550   cd_to_pd->Update();
551   vtkSmartPointer<vtkPointData> pd = cd_to_pd->GetOutput()->GetPointData();
552   // Fix label data
553   double v;
554   UnorderedMap<double, int> bins;
555   UnorderedMap<double, int>::iterator bin;
556   vtkNew<vtkIdList> cellIds;
557   for (int i = 0; i < pd->GetNumberOfArrays(); ++i) {
558     if (type[i] == 1) {
559       vtkDataArray * const src = cd->GetArray(i);
560       vtkDataArray * const dst = pd->GetArray(i);
561       for (vtkIdType ptId = 0; ptId < dataset->GetNumberOfPoints(); ++ptId) {
562         dataset->GetPointCells(ptId, cellIds.GetPointer());
563         for (int j = 0; j < src->GetNumberOfComponents(); ++j) {
564           bins.clear();
565           for (vtkIdType k = 0; k < cellIds->GetNumberOfIds(); ++k) {
566             v = src->GetComponent(cellIds->GetId(k), j);
567             bin = bins.find(v);
568             if (bin == bins.end()) bins[v] = 1;
569             else bin->second += 1;
570           }
571           int    max_cnt = 0;
572           double max_val = 0.;
573           for (bin = bins.begin(); bin != bins.end(); ++bin) {
574             if (bin->second > max_cnt) {
575               max_val = bin->first;
576               max_cnt = bin->second;
577             }
578           }
579           dst->SetComponent(ptId, j, max_val);
580         }
581       }
582     }
583   }
584   return pd;
585 }
586 
587 // -----------------------------------------------------------------------------
588 /// Join intersected components at the segmentation boundary
JoinBoundaries(SurfaceBoundary & boundary,vtkAbstractCellLocator * cut,double max_dist2,double max_hdist)589 void JoinBoundaries(SurfaceBoundary &boundary, vtkAbstractCellLocator *cut, double max_dist2, double max_hdist)
590 {
591   vtkIdType cellId, ptIds[3];
592 
593   vtkPolyData  * const surface = boundary.Surface();
594   vtkCellArray * const polys   = surface->GetPolys();
595   vtkCellData  * const cd      = surface->GetCellData();
596 
597   Array<int> cd_type(cd->GetNumberOfArrays(), 0);
598   for (int i = 0; i < cd->GetNumberOfArrays(); ++i) {
599     vtkDataArray * const arr = cd->GetArray(i);
600     if (arr->GetName()) {
601       if (strcmp(arr->GetName(), SOURCE_ARRAY_NAME) == 0) {
602         cd_type[i] = 2;
603       } else if (IsCategoricalArrayName(arr->GetName())) {
604         cd_type[i] = 1;
605       }
606     }
607   }
608   vtkSmartPointer<vtkPointData> cd_as_pd = CellToPointData(surface, cd_type);
609 
610   while (boundary.NumberOfSegments() > 1) {
611 
612     Array<double> dist2(boundary.NumberOfSegments());
613     for (int j = 0; j < boundary.NumberOfSegments(); ++j) {
614       dist2[j] = MinSquaredDistance(boundary.Segment(j), cut);
615     }
616 
617     int idx1, idx2;
618     Array<Array<double>> hdist(boundary.NumberOfSegments());
619     for (idx1 = 0; idx1 < boundary.NumberOfSegments(); ++idx1) {
620       hdist[idx1].resize(boundary.NumberOfSegments(), inf);
621       if (dist2[idx1] <= max_dist2) {
622         const auto &seg1 = boundary.Segment(idx1);
623         if (seg1.NumberOfPoints() > 1) {
624           for (idx2 = idx1 + 1; idx2 < boundary.NumberOfSegments(); ++idx2) {
625             if (dist2[idx2] <= max_dist2) {
626               const auto &seg2 = boundary.Segment(idx2);
627               if (seg2.NumberOfPoints() > 1) {
628                 hdist[idx1][idx2] = HausdorffDistance(seg1, seg2);
629               }
630             }
631           }
632         }
633       }
634     }
635     Array<double> min_hdist(boundary.NumberOfSegments());
636     Array<int>    min_idx2 (boundary.NumberOfSegments());
637     for (idx1 = 0; idx1 < boundary.NumberOfSegments(); ++idx1) {
638       min_idx2 [idx1] = IncreasingOrder(hdist[idx1]).front();
639       min_hdist[idx1] = hdist[idx1][min_idx2[idx1]];
640     }
641     idx1 = IncreasingOrder(min_hdist).front();
642     if (min_hdist[idx1] > max_hdist) break;
643     idx2 = min_idx2[idx1];
644 
645     if (verbose > 1) {
646       cout << "  Joining boundary segments " << idx1+1 << " and " << idx2+1
647            << " out of " << boundary.NumberOfSegments() << " remaining surface boundaries"
648            << " (Hausdorff distance = " << hdist[idx1][idx2] << ")" << endl;
649     }
650 
651     // Get boundary segments to be joined
652     auto &seg1 = boundary.Segment(idx1);
653     auto &seg2 = boundary.Segment(idx2);
654 
655     const int i0 = 0;
656     const int j0 = seg2.FindClosestPoint(seg1.Point(i0));
657 
658     // TODO: Determine in which direction to traverse each boundary segment
659     // such that orientation of newly added triangles is consistent with the
660     // orientation of the boundary triangles
661     const int di = 1;
662 
663     // Construct list of new triangles based on either dj=-1 or dj=+1 traversal
664     // direction and select those triangles with the least cost/error
665     const int max_iter = seg1.NumberOfPoints() + seg2.NumberOfPoints();
666     double l1, l2, cost1 = 0., cost2 = 0.;
667     Array<Vector3D<vtkIdType>> tris1, tris2;
668     for (int dj = -1; dj <= 1; dj += 2) {
669       int i = i0, j = j0;
670       seg1.ClearSelection();
671       seg2.ClearSelection();
672       auto &tris = (dj == -1 ? tris1 : tris2);
673       auto &cost = (dj == -1 ? cost1 : cost2);
674       tris.reserve(max_iter);
675       for (int iter = 0; iter < max_iter; ++iter) {
676         l1 = (seg1.IsSelected(i + di) ? inf : seg2.Point(j).SquaredDistance(seg1.Point(i + di)));
677         l2 = (seg2.IsSelected(j + dj) ? inf : seg1.Point(i).SquaredDistance(seg2.Point(j + dj)));
678         if (IsInf(l1) && IsInf(l2)) break;
679         if (l1 <= l2) {
680           cost += l1;
681           ptIds[0] = seg1.PointId(i);
682           ptIds[1] = seg1.PointId(i + di);
683           ptIds[2] = seg2.PointId(j);
684           i = seg1.IndexModuloNumberOfPoints(i + di);
685           seg1.SelectPoint(i);
686         } else {
687           cost += l2;
688           ptIds[0] = seg1.PointId(i);
689           ptIds[1] = seg2.PointId(j + dj);
690           ptIds[2] = seg2.PointId(j);
691           j = seg2.IndexModuloNumberOfPoints(j + dj);
692           seg2.SelectPoint(j);
693         }
694         tris.push_back(Vector3D<vtkIdType>(ptIds));
695       }
696     }
697 
698     // Add triangles joining the two boundary segments
699     for (auto &tri : (cost1 <= cost2 ? tris1 : tris2)) {
700       cellId = polys->InsertNextCell(3);
701       polys->InsertCellPoint(tri._x);
702       polys->InsertCellPoint(tri._y);
703       polys->InsertCellPoint(tri._z);
704       InterpolateCellData(cd, cd_as_pd, cd_type, cellId, tri._x, tri._y, tri._z);
705     }
706 
707     surface->DeleteLinks();
708     surface->DeleteCells();
709 
710     #if 0
711       boundary = SurfaceBoundary(surface);
712     #else
713       break;
714     #endif
715   }
716 
717   // Free excessive memory
718   polys->Squeeze();
719   cd->Squeeze();
720 }
721 
722 // -----------------------------------------------------------------------------
723 /// Merge two surface meshes at given segmentation boundary
724 vtkSmartPointer<vtkPolyData>
Merge(vtkPolyData * s1,vtkPolyData * s2,vtkPolyData * label_boundary,double tol,int smooth,bool join)725 Merge(vtkPolyData *s1, vtkPolyData *s2, vtkPolyData *label_boundary, double tol, int smooth, bool join)
726 {
727   const double tol2      = tol * tol;
728   const double max_dist2 =  4. * tol2;
729   const double max_hdist = 10. * tol;
730 
731   double     p[3], x[3], dist2;
732   vtkIdType  cellId;
733   int        subId;
734 
735   vtkNew<vtkIdList> cellIds;
736 
737   // Build links
738   s1->BuildLinks();
739   s2->BuildLinks();
740 
741   // Locator to find distance to segmentation boundary
742   vtkSmartPointer<vtkAbstractCellLocator> cut;
743   cut = vtkSmartPointer<vtkCellLocator>::New();
744   cut->SetDataSet(label_boundary);
745   cut->BuildLocator();
746 
747   // Mark cells close to segmentation boundary as deleted
748   for (vtkIdType ptId = 0; ptId < s1->GetNumberOfPoints(); ++ptId) {
749     s1->GetPoint(ptId, p);
750     cut->FindClosestPoint(p, x, cellId, subId, dist2);
751     if (dist2 < tol2) {
752       s1->GetPointCells(ptId, cellIds.GetPointer());
753       for (vtkIdType i = 0; i < cellIds->GetNumberOfIds(); ++i) {
754         s1->DeleteCell(cellIds->GetId(i));
755       }
756       s1->DeletePoint(ptId);
757     }
758   }
759 
760   for (vtkIdType ptId = 0; ptId < s2->GetNumberOfPoints(); ++ptId) {
761     s2->GetPoint(ptId, p);
762     cut->FindClosestPoint(p, x, cellId, subId, dist2);
763     if (dist2 < tol2) {
764       s2->GetPointCells(ptId, cellIds.GetPointer());
765       for (vtkIdType i = 0; i < cellIds->GetNumberOfIds(); ++i) {
766         s2->DeleteCell(cellIds->GetId(i));
767       }
768       s2->DeletePoint(ptId);
769     }
770   }
771 
772   // Remove deleted cells
773   s1->RemoveDeletedCells();
774   s2->RemoveDeletedCells();
775 
776   // Combine surfaces into one polygonal data set
777   vtkNew<vtkAppendPolyData> appender;
778   AddVTKInput(appender, s1);
779   AddVTKInput(appender, s2);
780   appender->Update();
781 
782   // Get combined output data set
783   vtkSmartPointer<vtkPolyData> output;
784   output = appender->GetOutput();
785 
786   // Clean intersection boundaries
787   //
788   // This is in particular needed to avoid issues with these cells when
789   // smoothing and/or joining the intersection boundaries...
790   SurfaceBoundary boundary(output);
791   DeleteSingleBoundaryPointCells(boundary, cut, max_dist2);
792 
793   // Smooth intersection boundaries
794   SmoothBoundaries(boundary, cut, max_dist2, smooth);
795 
796   // Join intersection boundaries
797   if (join) JoinBoundaries(boundary, cut, max_dist2, max_hdist);
798 
799   return output;
800 }
801 
802 // -----------------------------------------------------------------------------
803 /// Add array with given point source label
AddPointSourceArray(vtkPolyData * const surface,size_t idx)804 int AddPointSourceArray(vtkPolyData * const surface, size_t idx)
805 {
806   vtkSmartPointer<vtkDataArray> arr;
807   arr = NewVtkDataArray(VTK_SHORT, surface->GetNumberOfPoints(), 1, SOURCE_ARRAY_NAME);
808   arr->FillComponent(0, static_cast<double>(idx));
809   surface->GetPointData()->RemoveArray(SOURCE_ARRAY_NAME);
810   return surface->GetPointData()->AddArray(arr);
811 }
812 
813 // -----------------------------------------------------------------------------
814 /// Add array with given cell source label
AddCellSourceArray(vtkPolyData * const surface,size_t idx)815 int AddCellSourceArray(vtkPolyData * const surface, size_t idx)
816 {
817   vtkSmartPointer<vtkDataArray> arr;
818   arr = NewVtkDataArray(VTK_SHORT, surface->GetNumberOfCells(), 1, SOURCE_ARRAY_NAME);
819   arr->FillComponent(0, static_cast<double>(idx));
820   surface->GetCellData()->RemoveArray(SOURCE_ARRAY_NAME);
821   return surface->GetCellData()->AddArray(arr);
822 }
823 
824 // -----------------------------------------------------------------------------
825 /// Calculate point normals of surface mesh while fixing vertex order if necessary
826 vtkSmartPointer<vtkPolyData>
CalculateNormals(vtkPolyData * surface,bool point_normals,bool cell_normals)827 CalculateNormals(vtkPolyData *surface, bool point_normals, bool cell_normals)
828 {
829   vtkNew<vtkPolyDataNormals> normals;
830   SetVTKInput(normals, surface);
831   normals->AutoOrientNormalsOff();
832   normals->SplittingOff();
833   normals->NonManifoldTraversalOff();
834   normals->ConsistencyOn();
835   normals->SetComputePointNormals(point_normals);
836   normals->SetComputeCellNormals(cell_normals);
837   normals->Update();
838   return normals->GetOutput();
839 }
840 
841 // -----------------------------------------------------------------------------
842 /// Fix normals of border edge points
FixBorderPointNormals(vtkPolyData * surface)843 void FixBorderPointNormals(vtkPolyData *surface)
844 {
845   vtkDataArray * const cnormals = surface->GetCellData ()->GetNormals();
846   vtkDataArray * const pnormals = surface->GetPointData()->GetNormals();
847   vtkDataArray * const csource  = surface->GetCellData ()->GetArray(SOURCE_ARRAY_NAME);
848   vtkDataArray * const psource  = surface->GetPointData()->GetArray(SOURCE_ARRAY_NAME);
849 
850   vtkIdType n;
851   vtkNew<vtkIdList> cellIds;
852   Vector3 pn, cn;
853 
854   for (vtkIdType ptId = 0; ptId < surface->GetNumberOfPoints(); ++ptId) {
855     if (psource->GetComponent(ptId, 0) == 0.) {
856       surface->GetPointCells(ptId, cellIds.GetPointer());
857       n  = 0;
858       pn = 0.;
859       for (vtkIdType i = 0; i < cellIds->GetNumberOfIds(); ++i) {
860         if (csource->GetComponent(cellIds->GetId(i), 0) >= 0.) {
861           cnormals->GetTuple(cellIds->GetId(i), cn);
862           pn += cn, ++n;
863         }
864       }
865       if (n > 0) {
866         pn /= n;
867         pn.Normalize();
868         pnormals->SetTuple(ptId, pn);
869       }
870     }
871   }
872 }
873 
874 // -----------------------------------------------------------------------------
875 /// Get cell data array with cells within vicinity of joint boundaries masked
IntersectionCellMask(vtkPolyData * const surface,int nconn=3)876 vtkSmartPointer<vtkDataArray> IntersectionCellMask(vtkPolyData * const surface, int nconn = 3)
877 {
878   const vtkIdType n = surface->GetNumberOfCells();
879 
880   vtkCellData  * const cd     = surface->GetCellData();
881   vtkDataArray * const source = cd->GetArray(SOURCE_ARRAY_NAME);
882   vtkNew<vtkIdList> ptIds, cellIds;
883 
884   vtkSmartPointer<vtkDataArray> mask;
885   mask = NewVtkDataArray(VTK_UNSIGNED_CHAR, n, 1, INTERSECTION_ARRAY_NAME);
886   for (vtkIdType cellId = 0; cellId < n; ++cellId) {
887     mask->SetComponent(cellId, 0, source->GetComponent(cellId, 0) == 0. ? 1. : 0.);
888   }
889   for (int iter = 0; iter < nconn; ++iter) {
890     vtkSmartPointer<vtkDataArray> next;
891     next.TakeReference(mask->NewInstance());
892     next->DeepCopy(mask);
893     for (vtkIdType cellId = 0; cellId < n; ++cellId) {
894       if (mask->GetComponent(cellId, 0) != 0.) {
895         surface->GetCellPoints(cellId, ptIds.GetPointer());
896         for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); ++i) {
897           surface->GetPointCells(ptIds->GetId(i), cellIds.GetPointer());
898           for (vtkIdType j = 0; j < cellIds->GetNumberOfIds(); ++j) {
899             next->SetComponent(cellIds->GetId(j), 0, 1.);
900           }
901         }
902       }
903     }
904     mask = next;
905   }
906 
907   return mask;
908 }
909 
910 // -----------------------------------------------------------------------------
911 /// Get point data array with points within vicinity of joint boundaries masked
IntersectionPointMask(vtkPolyData * const surface,int nconn=3)912 vtkSmartPointer<vtkDataArray> IntersectionPointMask(vtkPolyData * const surface, int nconn = 3)
913 {
914   const vtkIdType n = surface->GetNumberOfPoints();
915 
916   vtkCellData  * const cd     = surface->GetCellData();
917   vtkDataArray * const source = cd->GetArray(SOURCE_ARRAY_NAME);
918   vtkNew<vtkIdList> ptIds, cellIds;
919 
920   vtkSmartPointer<vtkDataArray> mask;
921   mask = NewVtkDataArray(VTK_UNSIGNED_CHAR, n, 1, INTERSECTION_ARRAY_NAME);
922   mask->FillComponent(0, 0.);
923   for (vtkIdType ptId = 0; ptId < n; ++ptId) {
924     surface->GetPointCells(ptId, cellIds.GetPointer());
925     for (vtkIdType i = 0; i < cellIds->GetNumberOfIds(); ++i) {
926       if (source->GetComponent(cellIds->GetId(i), 0) == 0.) {
927         mask->SetComponent(ptId, 0, 1.);
928         break;
929       }
930     }
931   }
932   for (int iter = 0; iter < nconn; ++iter) {
933     vtkSmartPointer<vtkDataArray> next;
934     next.TakeReference(mask->NewInstance());
935     next->DeepCopy(mask);
936     for (vtkIdType ptId = 0; ptId < n; ++ptId) {
937       if (mask->GetComponent(ptId, 0) != 0.) {
938         surface->GetPointCells(ptId, cellIds.GetPointer());
939         for (vtkIdType i = 0; i < cellIds->GetNumberOfIds(); ++i) {
940           surface->GetCellPoints(cellIds->GetId(i), ptIds.GetPointer());
941           for (vtkIdType j = 0; j < ptIds->GetNumberOfIds(); ++j) {
942             next->SetComponent(ptIds->GetId(j), 0, 1.);
943           }
944         }
945       }
946     }
947     mask = next;
948   }
949 
950   return mask;
951 }
952 
953 // -----------------------------------------------------------------------------
954 /// Determine edge length range within surface intersection
GetEdgeLengthRange(vtkPolyData * const surface,vtkDataArray * const mask,double & lmin,double & lmax,double sd=2.5)955 void GetEdgeLengthRange(vtkPolyData * const surface, vtkDataArray * const mask,
956                         double &lmin, double &lmax, double sd = 2.5)
957 {
958   vtkCellData  * const cd     = surface->GetCellData();
959   vtkDataArray * const source = cd->GetArray(SOURCE_ARRAY_NAME);
960 
961   double min_edge_length = +inf;
962   double max_edge_length = -inf;
963 
964   int    n = 0;
965   double a[3], b[3], ab[3], l2, lsum = 0., l2sum = 0.;
966   vtkNew<vtkIdList> ptIds;
967 
968   for (vtkIdType cellId = 0; cellId < surface->GetNumberOfCells(); ++cellId) {
969     if (mask->GetComponent(cellId, 0) != 0. && source->GetComponent(cellId, 0) != 0.) {
970       surface->GetCellPoints(cellId, ptIds.GetPointer());
971       for (vtkIdType i = 0, j; i < ptIds->GetNumberOfIds(); ++i) {
972         j = (i + 1) % ptIds->GetNumberOfIds();
973         surface->GetPoint(ptIds->GetId(i), a);
974         surface->GetPoint(ptIds->GetId(j), b);
975         vtkMath::Subtract(b, a, ab);
976         l2 = vtkMath::Dot(ab, ab);
977         lsum  += sqrt(l2);
978         l2sum += l2;
979         ++n;
980       }
981     }
982   }
983   if (n > 0) {
984     const double mean  = lsum / n;
985     const double sigma = sqrt(l2sum / n - mean * mean);
986     min_edge_length = max(0., mean - sd * sigma);
987     max_edge_length = mean + sd * sigma;
988   }
989 
990   if (IsNaN(lmin)) {
991     lmin = (min_edge_length > max_edge_length ? 0. : min_edge_length);
992   }
993   if (IsNaN(lmax)) {
994     lmax = (min_edge_length > max_edge_length ? inf : max_edge_length);
995   }
996 }
997 
998 // -----------------------------------------------------------------------------
999 /// Principal component analysis of point set
PrincipalDirections(vtkPointSet * const points,Vector3 dir[3],bool twod=false)1000 bool PrincipalDirections(vtkPointSet * const points, Vector3 dir[3], bool twod = false)
1001 {
1002   if (points->GetNumberOfPoints() == 0) return false;
1003 
1004   Point c, p;
1005   points->GetCenter(c);
1006 
1007   Matrix3x3 covar(0.);
1008   for (vtkIdType ptId = 0; ptId < points->GetNumberOfPoints(); ++ptId) {
1009     points->GetPoint(ptId, p);
1010     vtkMath::Subtract(p, c, p);
1011     for (int r = 0; r < 3; ++r)
1012     for (int c = 0; c < 3; ++c) {
1013       covar[r][c] += p[r] * p[c];
1014     }
1015   }
1016 
1017   Vector3       axis[3];
1018   Array<double> eigval(3);
1019   covar.EigenSolveSymmetric(eigval.data(), axis);
1020 
1021   eigval[0] = abs(eigval[0]);
1022   eigval[1] = abs(eigval[1]);
1023   eigval[2] = abs(eigval[2]);
1024   Array<int> order = DecreasingOrder(eigval);
1025   if (eigval[order[twod ? 1 : 2]] < 1e-6) return false;
1026 
1027   dir[0] = axis[order[0]], dir[0].Normalize();
1028   dir[1] = axis[order[1]], dir[1].Normalize();
1029   dir[2] = axis[order[2]], dir[2].Normalize();
1030   return true;
1031 }
1032 
1033 // -----------------------------------------------------------------------------
1034 /// Structure with cutting plane parameters
1035 struct PlaneAttributes
1036 {
1037   Point   o; ///< Origin/Center point
1038   Vector3 n; ///< Normal vector
1039   Vector3 x; ///< In-plane x axis
1040   Vector3 y; ///< In-plane y axis
1041   Vector3 &z = n; ///< Out of plane z axis, i.e., plane normal
1042 };
1043 
1044 // -----------------------------------------------------------------------------
1045 /// Get cutting plane from segmentation boundary
CuttingPlaneAttributes(vtkSmartPointer<vtkPointSet> points)1046 PlaneAttributes CuttingPlaneAttributes(vtkSmartPointer<vtkPointSet> points)
1047 {
1048   MIRTK_START_TIMING();
1049   Vector3 dir[3], p;
1050   if (!PrincipalDirections(points, dir)) {
1051     FatalError("Failed to compute principal directions of point set");
1052   }
1053   double u, v, minu = +inf, maxu = -inf, minv = +inf, maxv = -inf;
1054   for (vtkIdType ptId = 0; ptId < points->GetNumberOfPoints(); ++ptId) {
1055     points->GetPoint(ptId, p);
1056     u = dir[0].Dot(p);
1057     v = dir[1].Dot(p);
1058     if (u < minu) minu = u;
1059     if (u > maxu) maxu = u;
1060     if (v < minv) minv = v;
1061     if (v > maxv) maxv = v;
1062   }
1063   dir[0] *= .5 * (maxu - minu);
1064   dir[1] *= .5 * (maxv - minv);
1065   dir[2].Normalize();
1066   PlaneAttributes attr;
1067   points->GetCenter(attr.o);
1068   attr.n = dir[2];
1069   attr.x = dir[0];
1070   attr.y = dir[1];
1071   MIRTK_DEBUG_TIMING(2, "CuttingPlaneAttributes");
1072   return attr;
1073 }
1074 
1075 // -----------------------------------------------------------------------------
1076 /// Get cutting plane from segmentation boundary
CuttingPlane(const PlaneAttributes & attr,double tn,double rx,double ry,double margin)1077 vtkSmartPointer<vtkPolyData> CuttingPlane(const PlaneAttributes &attr, double tn, double rx, double ry, double margin)
1078 {
1079   MIRTK_START_TIMING();
1080 
1081   // Calculate rotation matrices in local plane coordinates
1082   const double cx = cos(rx * rad_per_deg), sx = sin(rx * rad_per_deg);
1083   const double cy = cos(ry * rad_per_deg), sy = sin(ry * rad_per_deg);
1084   const Matrix3x3 Rx(1., 0., 0., 0., cx, -sx, 0., sx, cx);
1085   const Matrix3x3 Ry(cy, 0., sy, 0., 1., 0., -sy, 0., cy);
1086 
1087   // Rotate local plane axes vectors
1088   Vector3 dx(1., 0., 0.), dy(0., 1., 0.), n(0., 0., 1.);
1089   dx = Rx * Ry * dx;
1090   dy = Rx * Ry * dy;
1091   n  = Rx * Ry * n;
1092 
1093   // Map to world vectors of unit length
1094   Vector3 e1(attr.x), e2(attr.y), e3(attr.z);
1095   e1.Normalize(), e2.Normalize(), e3.Normalize();
1096   dx = dx._x * e1 + dx._y * e2 + dx._z * e3;
1097   dy = dy._x * e1 + dy._y * e2 + dy._z * e3;
1098   n  = n ._x * e1 + n ._y * e2 + n ._z * e3;
1099 
1100   // Add plane margin to initial plane extend
1101   dx *= (attr.x.Length() + 2. * margin);
1102   dy *= (attr.y.Length() + 2. * margin);
1103 
1104   // Translate plane along original normal vector
1105   Point o = attr.o + tn * attr.n;
1106 
1107   // vtkPlaneSource origin is in corner, not center
1108   o  -= dx;
1109   o  -= dy;
1110   dx *= 2.;
1111   dy *= 2.;
1112 
1113   // Tesselate finite plane
1114   vtkSmartPointer<vtkPolyData> plane;
1115   vtkNew<vtkPlaneSource> source;
1116   source->SetXResolution(1);
1117   source->SetYResolution(1);
1118   source->SetOrigin(o);
1119   source->SetPoint1(o + dx);
1120   source->SetPoint2(o + dy);
1121   source->Update();
1122   plane = Triangulate(source->GetOutput());
1123 
1124   MIRTK_DEBUG_TIMING(2, "CuttingPlane");
1125   return plane;
1126 }
1127 
1128 // -----------------------------------------------------------------------------
1129 /// Intersect two polygonal data sets and return single intersection polygon
LargestClosedIntersection(vtkPolyData * s1,vtkPolyData * s2)1130 vtkSmartPointer<vtkPolyData> LargestClosedIntersection(vtkPolyData *s1, vtkPolyData *s2)
1131 {
1132   MIRTK_START_TIMING();
1133 
1134   vtkSmartPointer<vtkPolyData> cut;
1135   {
1136     MIRTK_START_TIMING();
1137     vtkNew<vtkIntersectionPolyDataFilter> intersection;
1138     intersection->SplitFirstOutputOff();
1139     intersection->SplitSecondOutputOff();
1140     SetNthVTKInput(intersection, 0, s1);
1141     SetNthVTKInput(intersection, 1, s2);
1142 
1143     vtkNew<vtkCleanPolyData> merger;
1144     SetVTKConnection(merger, intersection);
1145     merger->ConvertStripsToPolysOff();
1146     merger->ConvertPolysToLinesOff();
1147     merger->ConvertLinesToPointsOff();
1148     merger->PointMergingOn();
1149     merger->ToleranceIsAbsoluteOn();
1150     merger->SetAbsoluteTolerance(1e-12);
1151 
1152     merger->Update();
1153     cut = merger->GetOutput();
1154     MIRTK_DEBUG_TIMING(3, "LargestClosedIntersection (intersect)");
1155   }
1156 
1157   if (debug > 2) {
1158     static int iter = 0; ++iter;
1159     char fname[64];
1160     snprintf(fname, 64, "debug_cutting_lines_%d.vtp", iter);
1161     WritePolyData(fname, cut);
1162   }
1163 
1164   {
1165     MIRTK_START_TIMING();
1166     cut->BuildLinks();
1167 
1168     vtkIdType ptId, nbrId, cellId;
1169     vtkNew<vtkIdList> ptIds, cellIds;
1170     ptIds->Allocate(2);
1171 
1172     Stack<vtkIdType> activePtIds;
1173     for (ptId = 0; ptId < cut->GetNumberOfPoints(); ++ptId) {
1174       cut->GetPointCells(ptId, cellIds.GetPointer());
1175       if (cellIds->GetNumberOfIds() == 1) activePtIds.push(ptId);
1176     }
1177     while (!activePtIds.empty()) {
1178       ptId = activePtIds.top(), activePtIds.pop();
1179       cut->GetPointCells(ptId, cellIds.GetPointer());
1180       if (cellIds->GetNumberOfIds() == 1) {
1181         cellId = cellIds->GetId(0);
1182         cut->GetCellPoints(cellId, ptIds.GetPointer());
1183         cut->RemoveCellReference(cellId);
1184         cut->DeleteCell(cellId);
1185         for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); ++i) {
1186           nbrId = ptIds->GetId(i);
1187           if (nbrId != ptId) {
1188             cut->GetPointCells(nbrId, cellIds.GetPointer());
1189             if (cellIds->GetNumberOfIds() == 1) activePtIds.push(nbrId);
1190           }
1191         }
1192       }
1193     }
1194     cut->RemoveDeletedCells();
1195     vtkNew<vtkCleanPolyData> merger;
1196     SetVTKInput(merger, cut);
1197     merger->ConvertStripsToPolysOff();
1198     merger->ConvertPolysToLinesOff();
1199     merger->ConvertLinesToPointsOn();
1200     merger->PointMergingOn();
1201     merger->ToleranceIsAbsoluteOn();
1202     merger->SetAbsoluteTolerance(1e-12);
1203     merger->Update();
1204     merger->GetOutput()->SetVerts(nullptr);
1205     cut = merger->GetOutput();
1206     MIRTK_DEBUG_TIMING(3, "LargestClosedIntersection (clean)");
1207   }
1208 
1209   {
1210     MIRTK_START_TIMING();
1211     vtkNew<vtkPolyDataConnectivityFilter> cc;
1212     SetVTKInput(cc, cut);
1213     cc->ScalarConnectivityOff();
1214     cc->SetExtractionModeToLargestRegion();
1215     cc->Update();
1216     cut = cc->GetOutput();
1217     MIRTK_DEBUG_TIMING(3, "LargestClosedIntersection (largest)");
1218   }
1219 
1220   MIRTK_DEBUG_TIMING(2, "LargestClosedIntersection");
1221   return cut;
1222 }
1223 
1224 // -----------------------------------------------------------------------------
1225 /// Merge points and make line strips
LineStrips(vtkSmartPointer<vtkPolyData> cut)1226 vtkSmartPointer<vtkPolyData> LineStrips(vtkSmartPointer<vtkPolyData> cut)
1227 {
1228   vtkNew<vtkCleanPolyData> cut_merger;
1229   SetVTKInput(cut_merger, cut);
1230   cut_merger->ConvertStripsToPolysOff();
1231   cut_merger->ConvertPolysToLinesOff();
1232   cut_merger->ConvertLinesToPointsOn();
1233   cut_merger->PointMergingOn();
1234   cut_merger->ToleranceIsAbsoluteOn();
1235   cut_merger->SetAbsoluteTolerance(1e-12);
1236   cut_merger->Update();
1237   cut_merger->GetOutput()->SetVerts(nullptr);
1238   cut = cut_merger->GetOutput();
1239 
1240   Stripper stripper;
1241   stripper.Input(cut);
1242   stripper.Run();
1243   return stripper.Output();
1244 }
1245 
1246 // -----------------------------------------------------------------------------
NonBoundaryPointsMask(vtkSmartPointer<vtkPolyData> input)1247 vtkSmartPointer<vtkDataArray> NonBoundaryPointsMask(vtkSmartPointer<vtkPolyData> input)
1248 {
1249   vtkSmartPointer<vtkDataArray> mask;
1250   mask = NewVtkDataArray(VTK_UNSIGNED_CHAR, input->GetNumberOfPoints(), 1);
1251   mask->FillComponent(0, 1.);
1252   UnorderedSet<int> ptIds = BoundaryPoints(input);
1253   for (auto ptId : ptIds) mask->SetComponent(ptId, 0, 0.);
1254   return mask;
1255 }
1256 
1257 // -----------------------------------------------------------------------------
1258 /// Make divider surface from intersection curve
Divider(vtkSmartPointer<vtkPolyData> cut)1259 vtkSmartPointer<vtkPolyData> Divider(vtkSmartPointer<vtkPolyData> cut)
1260 {
1261   vtkSmartPointer<vtkPolyData> divider = LineStrips(cut);
1262   if (debug) {
1263     static int callId = 0; ++callId;
1264     char fname[64];
1265     snprintf(fname, 64, "debug_split_surface_lines_%d.vtp", callId);
1266     WritePolyData(fname, divider);
1267   }
1268   vtkSmartPointer<vtkIdList> ptIds = vtkIdList::New();
1269   vtkSmartPointer<vtkIdList> stripPtIds = vtkIdList::New();
1270   for (vtkIdType cellId = 0; cellId < divider->GetLines()->GetNumberOfCells(); ++cellId) {
1271     divider->GetLines()->GetCell(cellId, ptIds);
1272     if (ptIds->GetNumberOfIds() > stripPtIds->GetNumberOfIds()) {
1273       stripPtIds = ptIds;
1274     }
1275   }
1276   if (stripPtIds->GetNumberOfIds() == 0) {
1277     Throw(ERR_LogicError, __FUNCTION__, "Expected at least one contiguous intersection line");
1278   }
1279   if (stripPtIds->GetNumberOfIds() <= 2) {
1280     Throw(ERR_LogicError, __FUNCTION__, "Expected polygon with more than two points");
1281   }
1282   if (stripPtIds->GetId(0) != stripPtIds->GetId(stripPtIds->GetNumberOfIds() - 1)) {
1283     Throw(ERR_LogicError, __FUNCTION__, "Expected closed intersection polygon");
1284   }
1285   vtkSmartPointer<vtkCellArray> polys = vtkSmartPointer<vtkCellArray>::New();
1286   polys->Allocate(polys->EstimateSize(1, stripPtIds->GetNumberOfIds() - 1));
1287   polys->InsertNextCell(stripPtIds);
1288   divider->SetLines(nullptr);
1289   divider->SetPolys(polys);
1290   divider->DeleteCells();
1291   return divider;
1292 }
1293 
1294 // -----------------------------------------------------------------------------
1295 /// Compute Delaunay triangulation of interior of divider polygon after projection to 2D plane
TesselateDivider(vtkSmartPointer<vtkPolyData> divider,double ds)1296 vtkSmartPointer<vtkPolyData> TesselateDivider(vtkSmartPointer<vtkPolyData> divider, double ds)
1297 {
1298   if (divider->GetNumberOfPoints() < 2) return divider;
1299 
1300   const double min_dist2 = pow(.2 * ds, 2);
1301 
1302   int       subId;
1303   double    dist2, reverse_dist2;
1304   vtkIdType otherId;
1305   Vector3   c, p, q, x, dir[3];
1306 
1307   // Compute principle directions of divider polygon plane
1308   const bool twod = true;
1309   PrincipalDirections(divider, dir, twod);
1310 
1311   // Get center of divider polygon
1312   divider->GetCenter(c);
1313   double offset = 0.;
1314   for (vtkIdType ptId = 0; ptId < divider->GetNumberOfPoints(); ++ptId) {
1315     divider->GetPoint(ptId, p);
1316     offset += dir[2].Dot(p - c);
1317   }
1318   offset /= divider->GetNumberOfPoints();
1319   c += offset * dir[2];
1320 
1321   // Set up homogeneous transformation from 3D world to divider plane
1322   vtkSmartPointer<vtkMatrix4x4> matrix;
1323   matrix = vtkSmartPointer<vtkMatrix4x4>::New();
1324   matrix->Identity();
1325   for (int i = 0; i < 3; ++i) {
1326     matrix->SetElement(i, 0, dir[i]._x);
1327     matrix->SetElement(i, 1, dir[i]._y);
1328     matrix->SetElement(i, 2, dir[i]._z);
1329     matrix->SetElement(i, 3, -dir[i].Dot(c));
1330   }
1331 
1332   vtkSmartPointer<vtkMatrixToLinearTransform> transform;
1333   transform = vtkSmartPointer<vtkMatrixToLinearTransform>::New();
1334   transform->SetInput(matrix);
1335   transform->Update();
1336 
1337   // Determine extent of divider polygon within divider plane
1338   Point minq, maxq;
1339   divider->GetPoint(0, minq);
1340   divider->GetPoint(0, maxq);
1341   for (vtkIdType ptId = 1; ptId < divider->GetNumberOfPoints(); ++ptId) {
1342     divider->GetPoint(ptId, p);
1343     transform->TransformPoint(p, q);
1344     if (q._x < minq._x) minq._x = q._x;
1345     if (q._y < minq._y) minq._y = q._y;
1346     if (q._z < minq._z) minq._z = q._z;
1347     if (q._x > maxq._x) maxq._x = q._x;
1348     if (q._y > maxq._y) maxq._y = q._y;
1349     if (q._z > maxq._z) maxq._z = q._z;
1350   }
1351 
1352   const double margin = ds;
1353   minq._x -= margin, maxq._x += margin;
1354   minq._y -= margin, maxq._y += margin;
1355 
1356   int nx = iceil((maxq._x - minq._x) / ds) + 1;
1357   int ny = iceil((maxq._y - minq._y) / ds) + 1;
1358 
1359   if (nx % 2 == 0) ++nx;
1360   if (ny % 2 == 0) ++ny;
1361 
1362   minq._x = minq._x + .5 * (maxq._x - minq._x) - ((nx - 1) / 2) * ds;
1363   maxq._x = minq._x + (nx - 1) * ds + 1e-12;
1364 
1365   minq._y = minq._y + .5 * (maxq._y - minq._y) - ((ny - 1) / 2) * ds;
1366   maxq._y = minq._y + (ny - 1) * ds + 1e-12;
1367 
1368   // Add additional discrete divider polygon plane grid points
1369   vtkNew<vtkPointLocator> locator;
1370   locator->SetDataSet(divider);
1371   locator->BuildLocator();
1372 
1373   vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
1374   points->SetDataTypeToDouble();
1375   points->Allocate(divider->GetNumberOfPoints() + nx * ny);
1376 
1377   for (vtkIdType ptId = 0; ptId < divider->GetNumberOfPoints(); ++ptId) {
1378     divider->GetPoint(ptId, p);
1379     points->InsertNextPoint(p);
1380   }
1381 
1382   q._z = 0.;
1383   for (q._y = minq._y; q._y <= maxq._y; q._y += ds)
1384   for (q._x = minq._x; q._x <= maxq._x; q._x += ds) {
1385     p = c + q._x * dir[0] + q._y * dir[1];
1386     otherId = locator->FindClosestPoint(p);
1387     if (otherId >= 0) {
1388       divider->GetPoint(otherId, x);
1389       if ((p - x).SquaredLength() < min_dist2) {
1390         p = x;
1391       }
1392     }
1393     points->InsertNextPoint(p);
1394   }
1395 
1396   // Compute constrained Delanauy triangulation of divider polygon
1397   vtkSmartPointer<vtkPolyData> pointset;
1398   pointset = vtkSmartPointer<vtkPolyData>::New();
1399   pointset->SetPoints(points);
1400 
1401   vtkSmartPointer<vtkPolyData> boundary;
1402   boundary = vtkSmartPointer<vtkPolyData>::New();
1403   boundary->SetPoints(points);
1404   boundary->SetPolys(divider->GetPolys());
1405 
1406   vtkNew<vtkDelaunay2D> delaunay;
1407   delaunay->SetInputData(pointset);
1408   delaunay->SetSourceData(boundary);
1409   delaunay->SetAlpha(0.);
1410   delaunay->SetOffset(1.);
1411   delaunay->SetTolerance(0.);
1412   delaunay->SetTransform(transform);
1413   delaunay->Update();
1414 
1415   vtkSmartPointer<vtkPolyData> output;
1416   output = vtkSmartPointer<vtkPolyData>::New();
1417   output->DeepCopy(delaunay->GetOutput());
1418 
1419   // TODO: Determine correct order of polygon points before vtkDelaunay2D::Update
1420   vtkNew<vtkCellLocator> cell_locator;
1421   cell_locator->SetDataSet(delaunay->GetOutput());
1422   cell_locator->BuildLocator();
1423   cell_locator->FindClosestPoint(c, x, otherId, subId, dist2);
1424 
1425   vtkNew<vtkIdList> ptIds, reverse_pts;
1426   boundary->GetPolys()->GetCell(0, ptIds.GetPointer());
1427   reverse_pts->Allocate(ptIds->GetNumberOfIds());
1428   for (vtkIdType i = ptIds->GetNumberOfIds() - 1; i >= 0; --i) {
1429     reverse_pts->InsertNextId(ptIds->GetId(i));
1430   }
1431   vtkSmartPointer<vtkCellArray> reverse_polys;
1432   reverse_polys = vtkSmartPointer<vtkCellArray>::New();
1433   reverse_polys->Allocate(reverse_polys->EstimateSize(1, reverse_pts->GetNumberOfIds()));
1434   reverse_polys->InsertNextCell(reverse_pts.GetPointer());
1435 
1436   vtkSmartPointer<vtkPolyData> reverse_boundary;
1437   reverse_boundary = vtkSmartPointer<vtkPolyData>::New();
1438   reverse_boundary->SetPoints(points);
1439   reverse_boundary->SetPolys(reverse_polys);
1440 
1441   delaunay->SetSourceData(reverse_boundary);
1442   delaunay->Update();
1443 
1444   cell_locator->SetDataSet(delaunay->GetOutput());
1445   cell_locator->BuildLocator();
1446   cell_locator->FindClosestPoint(c, x, otherId, subId, reverse_dist2);
1447 
1448   if (reverse_dist2 < dist2) {
1449     output = delaunay->GetOutput();
1450   }
1451 
1452   // Remove unused points
1453   vtkNew<vtkCleanPolyData> cleaner;
1454   SetVTKInput(cleaner, output);
1455   cleaner->ConvertStripsToPolysOff();
1456   cleaner->ConvertPolysToLinesOff();
1457   cleaner->ConvertLinesToPointsOff();
1458   cleaner->PointMergingOff();
1459   cleaner->Update();
1460   output = cleaner->GetOutput();
1461 
1462   // Smooth divider
1463   // (needed when boundary points were snapped to surface mesh)
1464   MeshSmoothing smoother;
1465   smoother.Input(output);
1466   smoother.Mask(NonBoundaryPointsMask(output));
1467   smoother.Weighting(MeshSmoothing::Combinatorial);
1468   smoother.AdjacentValuesOnlyOn();
1469   smoother.SmoothPointsOn();
1470   smoother.NumberOfIterations(3);
1471   smoother.Lambda(1.);
1472   smoother.Run();
1473   output = smoother.Output();
1474 
1475   return output;
1476 }
1477 
1478 // -----------------------------------------------------------------------------
1479 /// Check if given intersection curve is acceptable
IsValidIntersection(vtkSmartPointer<vtkPolyData> cut,double tol)1480 bool IsValidIntersection(vtkSmartPointer<vtkPolyData> cut, double tol)
1481 {
1482   if (cut->GetNumberOfCells() == 0) return false;
1483   #if 1
1484     return true;
1485   #else
1486     vtkNew<vtkCleanPolyData> merger;
1487     SetVTKInput(merger, cut);
1488     merger->ConvertStripsToPolysOff();
1489     merger->ConvertPolysToLinesOff();
1490     merger->ConvertLinesToPointsOn();
1491     merger->PointMergingOn();
1492     merger->ToleranceIsAbsoluteOn();
1493     merger->SetAbsoluteTolerance(2. * tol);
1494     merger->Update();
1495     merger->GetOutput()->SetVerts(nullptr);
1496     cut = merger->GetOutput();
1497 
1498     cut->BuildLinks();
1499 
1500     vtkNew<vtkIdList> cellIds;
1501     for (vtkIdType ptId = 0; ptId < cut->GetNumberOfPoints(); ++ptId) {
1502       cut->GetPointCells(ptId, cellIds.GetPointer());
1503       if (cellIds->GetNumberOfIds() != 2 ||
1504           cut->GetCellType(cellIds->GetId(0)) != VTK_LINE ||
1505           cut->GetCellType(cellIds->GetId(1)) != VTK_LINE) {
1506         return false;
1507       }
1508     }
1509 
1510     return true;
1511   #endif
1512 }
1513 
1514 // -----------------------------------------------------------------------------
1515 /// Smooth line strip
SmoothLineStrip(vtkSmartPointer<vtkPolyData> cut,int niter=1)1516 void SmoothLineStrip(vtkSmartPointer<vtkPolyData> cut, int niter = 1)
1517 {
1518   vtkNew<vtkIdList> ptIds1, ptIds2, cellIds;
1519 
1520   int   i1, i2;
1521   Point p1, p2, p3;
1522 
1523   cut->BuildLinks();
1524 
1525   vtkSmartPointer<vtkPoints> points;
1526   points = vtkSmartPointer<vtkPoints>::New();
1527   points->SetNumberOfPoints(cut->GetNumberOfPoints());
1528 
1529   for (int iter = 0; iter < niter; ++iter) {
1530     for (vtkIdType ptId = 0; ptId < cut->GetNumberOfPoints(); ++ptId) {
1531       cut->GetPointCells(ptId, cellIds.GetPointer());
1532       if (cellIds->GetNumberOfIds() == 2 &&
1533           cut->GetCellType(cellIds->GetId(0)) == VTK_LINE &&
1534           cut->GetCellType(cellIds->GetId(1)) == VTK_LINE) {
1535         cut->GetCellPoints(cellIds->GetId(0), ptIds1.GetPointer());
1536         cut->GetCellPoints(cellIds->GetId(1), ptIds2.GetPointer());
1537         i1 = (ptIds1->GetId(0) == ptId ? 1 : 0);
1538         i2 = (ptIds2->GetId(0) == ptId ? 1 : 0);
1539         cut->GetPoint(ptIds1->GetId(i1), p1);
1540         cut->GetPoint(ptIds2->GetId(i2), p2);
1541         cut->GetPoint(ptId, p3);
1542         points->SetPoint(ptId, (p1 + p2 + p3) / 3.);
1543       }
1544     }
1545     vtkSmartPointer<vtkPoints> tmp = cut->GetPoints();
1546     cut->SetPoints(points);
1547     points = tmp;
1548   }
1549 }
1550 
1551 // -----------------------------------------------------------------------------
1552 /// Intersection incircle radius
LineStripIncircleRadius(vtkSmartPointer<vtkPolyData> cut)1553 double LineStripIncircleRadius(vtkSmartPointer<vtkPolyData> cut)
1554 {
1555   Point p, c;
1556   cut->GetCenter(c);
1557   double r = inf;
1558   for (vtkIdType ptId = 0; ptId < cut->GetNumberOfPoints(); ++ptId) {
1559     cut->GetPoint(ptId, p);
1560     r = min(r, p.Distance(c));
1561   }
1562   return r;
1563 }
1564 
1565 // -----------------------------------------------------------------------------
1566 /// Intersection incircle radius
AverageLineStripRadius(vtkSmartPointer<vtkPolyData> cut)1567 double AverageLineStripRadius(vtkSmartPointer<vtkPolyData> cut)
1568 {
1569   Point p, c;
1570   cut->GetCenter(c);
1571   double r = 0.;
1572   for (vtkIdType ptId = 0; ptId < cut->GetNumberOfPoints(); ++ptId) {
1573     cut->GetPoint(ptId, p);
1574     r += p.Distance(c);
1575   }
1576   return r / cut->GetNumberOfPoints();
1577 }
1578 
1579 // -----------------------------------------------------------------------------
1580 /// Length of line strip
LineStripLength(vtkSmartPointer<vtkPolyData> cut)1581 double LineStripLength(vtkSmartPointer<vtkPolyData> cut)
1582 {
1583   vtkNew<vtkIdList> ptIds;
1584   Point p1, p2;
1585   double l = 0.;
1586 
1587   cut->BuildLinks();
1588 
1589   for (vtkIdType cellId = 0; cellId < cut->GetNumberOfCells(); ++cellId) {
1590     cut->GetCellPoints(cellId, ptIds.GetPointer());
1591     if (cut->GetCellType(cellId) == VTK_LINE && ptIds->GetNumberOfIds() == 2) {
1592       cut->GetPoint(ptIds->GetId(0), p1);
1593       cut->GetPoint(ptIds->GetId(1), p2);
1594       l += p1.Distance(p2);
1595     }
1596   }
1597 
1598   return l;
1599 }
1600 
1601 // -----------------------------------------------------------------------------
1602 /// Average curvature of line strip
LineStripCurvature(vtkSmartPointer<vtkPolyData> cut)1603 double LineStripCurvature(vtkSmartPointer<vtkPolyData> cut)
1604 {
1605   vtkNew<vtkIdList> cellIds, ptIds1, ptIds2;
1606 
1607   int     i1, i2, n = 0;
1608   double  l1, l2, angle, curv = 0.;
1609   Vector3 e1, e2;
1610   Point   p1, p2, p3;
1611 
1612   cut->GetPoint(cut->GetNumberOfPoints()-1, p1);
1613   cut->GetPoint(0, p2);
1614   e1 = p2 - p1;
1615   l1 = e1.Length();
1616 
1617   cut->BuildLinks();
1618 
1619   for (vtkIdType ptId = 0; ptId < cut->GetNumberOfPoints(); ++ptId) {
1620     cut->GetPointCells(ptId, cellIds.GetPointer());
1621     if (cellIds->GetNumberOfIds() == 2 &&
1622         cut->GetCellType(cellIds->GetId(0)) == VTK_LINE &&
1623         cut->GetCellType(cellIds->GetId(1)) == VTK_LINE) {
1624       cut->GetCellPoints(cellIds->GetId(0), ptIds1.GetPointer());
1625       cut->GetCellPoints(cellIds->GetId(1), ptIds2.GetPointer());
1626       i1 = (ptIds1->GetId(0) == ptId ? 1 : 0);
1627       i2 = (ptIds2->GetId(0) == ptId ? 1 : 0);
1628       cut->GetPoint(ptIds1->GetId(i1), p1);
1629       cut->GetPoint(ptIds2->GetId(i2), p2);
1630       cut->GetPoint(ptId, p3);
1631       e1 = p3 - p1;
1632       e2 = p2 - p3;
1633       l1 = e1.Length();
1634       l2 = e2.Length();
1635       angle = acos(e1.Dot(e2) / (l1 * l2));
1636       curv += angle * angle / (l1 + l2);
1637       ++n;
1638     }
1639   }
1640 
1641   return curv / n;
1642 }
1643 
1644 // -----------------------------------------------------------------------------
1645 /// Maximum angle made up by adjacent line segments
MaxLineStripAngle(vtkSmartPointer<vtkPolyData> cut)1646 double MaxLineStripAngle(vtkSmartPointer<vtkPolyData> cut)
1647 {
1648   vtkNew<vtkIdList> cellIds, ptIds1, ptIds2;
1649 
1650   double  angle = 0.;
1651   int     i1, i2;
1652   Vector3 e1, e2;
1653   Point   p1, p2, p3;
1654 
1655   cut->BuildLinks();
1656 
1657   for (vtkIdType ptId = 0; ptId < cut->GetNumberOfPoints(); ++ptId) {
1658     cut->GetPointCells(ptId, cellIds.GetPointer());
1659     if (cellIds->GetNumberOfIds() == 2 &&
1660         cut->GetCellType(cellIds->GetId(0)) == VTK_LINE &&
1661         cut->GetCellType(cellIds->GetId(1)) == VTK_LINE) {
1662       cut->GetCellPoints(cellIds->GetId(0), ptIds1.GetPointer());
1663       cut->GetCellPoints(cellIds->GetId(1), ptIds2.GetPointer());
1664       i1 = (ptIds1->GetId(0) == ptId ? 1 : 0);
1665       i2 = (ptIds2->GetId(0) == ptId ? 1 : 0);
1666       cut->GetPoint(ptIds1->GetId(i1), p1);
1667       cut->GetPoint(ptIds2->GetId(i2), p2);
1668       cut->GetPoint(ptId, p3);
1669       e1 = p3 - p1;
1670       e2 = p2 - p3;
1671       e1.Normalize();
1672       e2.Normalize();
1673       angle = max(angle, acos(e1.Dot(e2)));
1674     }
1675   }
1676 
1677   return angle;
1678 }
1679 
1680 // -----------------------------------------------------------------------------
DividerArea(vtkSmartPointer<vtkPolyData> divider)1681 double DividerArea(vtkSmartPointer<vtkPolyData> divider)
1682 {
1683   vtkPolygon *polygon = vtkPolygon::SafeDownCast(divider->GetCell(0));
1684   return polygon->ComputeArea();
1685 }
1686 
1687 // -----------------------------------------------------------------------------
DividerError(vtkSmartPointer<vtkPolyData> divider,vtkSmartPointer<vtkPoints> points)1688 double DividerError(vtkSmartPointer<vtkPolyData> divider, vtkSmartPointer<vtkPoints> points)
1689 {
1690   vtkIdType cellId;
1691   int subId;
1692   double sum = 0., dist2;
1693   Point p, x;
1694   vtkNew<vtkCellLocator> locator;
1695   locator->SetDataSet(divider);
1696   locator->BuildLocator();
1697   for (vtkIdType ptId = 0; ptId < points->GetNumberOfPoints(); ++ptId) {
1698     points->GetPoint(ptId, p);
1699     locator->FindClosestPoint(p, x, cellId, subId, dist2);
1700     sum += dist2;
1701   }
1702   return sqrt(sum / points->GetNumberOfPoints());
1703 }
1704 
1705 // -----------------------------------------------------------------------------
1706 /// Find cutting plane which intersects surface with one closed intersection boundary
FindCuttingPlane(vtkSmartPointer<vtkPolyData> surface,vtkSmartPointer<vtkPointSet> boundary,vtkSmartPointer<vtkPolyData> & plane,vtkSmartPointer<vtkPolyData> & cut,double max_offset,double ds=0.,double tol=0.)1707 bool FindCuttingPlane(vtkSmartPointer<vtkPolyData> surface,
1708                       vtkSmartPointer<vtkPointSet> boundary,
1709                       vtkSmartPointer<vtkPolyData> &plane,
1710                       vtkSmartPointer<vtkPolyData> &cut,
1711                       double max_offset, double ds = 0., double tol = 0.)
1712 {
1713   static int call = 0; ++call;
1714 
1715   MIRTK_START_TIMING();
1716 
1717   if (ds <= 0.) ds = AverageEdgeLength(surface);
1718 
1719   // Abort search after the specified number of suitable planes were found
1720   const int  max_suitable_planes  = 10;
1721   const bool prefer_default_plane = false;
1722 
1723   // Compute cutting plane from segmentation boundary points
1724   const PlaneAttributes attr = CuttingPlaneAttributes(boundary);
1725 
1726   const double max_angle   = 20.;
1727   const double delta_angle = 5.;
1728 
1729   const double delta_offset = max_offset / 4.;
1730 
1731   const double margin   =  5. * ds;
1732   const double bbmargin = 20. * ds;
1733 
1734   double    r, l;
1735   double    best_tn, best_rx, best_ry, best_l = inf;
1736   vtkIdType best_ct = 0;
1737 
1738   // Remove cells which are never cut to speed up vtkPolyDataIntersectionFilter
1739   Point center;
1740   double bounds[6], cell_bounds[6];
1741   plane = CuttingPlane(attr, 0., 0., 0., 0.);
1742   plane->GetBounds(bounds);
1743   plane->GetCenter(center);
1744   for (int i = 0; i < 6; i += 2) {
1745     bounds[i  ] -= bbmargin;
1746     bounds[i+1] += bbmargin;
1747   }
1748   plane = nullptr;
1749 
1750   vtkSmartPointer<vtkPolyData> cells;
1751   cells.TakeReference(surface->NewInstance());
1752   cells->ShallowCopy(surface);
1753   cells->DeleteCells();
1754   cells->BuildCells();
1755   for (vtkIdType cellId = 0; cellId < surface->GetNumberOfCells(); ++cellId) {
1756     surface->GetCell(cellId)->GetBounds(cell_bounds);
1757     if (cell_bounds[0] > bounds[1] || cell_bounds[1] < bounds[0] ||
1758         cell_bounds[2] > bounds[3] || cell_bounds[3] < bounds[2] ||
1759         cell_bounds[4] > bounds[5] || cell_bounds[5] < bounds[4]) {
1760       cells->DeleteCell(cellId);
1761     }
1762   }
1763   cells->RemoveDeletedCells();
1764 
1765   if (debug > 1) {
1766     char fname[64];
1767     snprintf(fname, 64, "debug_cutting_cells_%d.vtp", call);
1768     WritePolyData(fname, cells);
1769   }
1770 
1771   const auto prev_cout_precision = cout.precision();
1772   const auto prev_cout_flags     = cout.flags();
1773 
1774   const streamsize offset_precision = 3;
1775   const streamsize angle_precision  = 0;
1776 
1777   // Try different normal offsets and plane rotations in the order of preference
1778   int n = 0, m = 0;
1779   bool abort = false;
1780   for (double angle = 0., rx, ry; angle <= max_angle && !abort; angle += delta_angle) {
1781     for (int dim = (angle > 0. ? 0 : 1); dim < 2 && !abort; ++dim) {
1782       if (dim == 0) {
1783         rx = angle;
1784         ry = 0.;
1785       } else {
1786         rx = 0.;
1787         ry = angle;
1788       }
1789       for (double tn = 0.; tn <= max_offset && !abort; tn += delta_offset) {
1790         for (double sry = (ry > 0. ? -1. : 1.); sry <= 1. && !abort; sry += 2.)
1791         for (double srx = (rx > 0. ? -1. : 1.); srx <= 1. && !abort; srx += 2.)
1792         for (double stn = (tn > 0. ? -1. : 1.); stn <= 1. && !abort; stn += 2.) {
1793           if (verbose > 4) {
1794             cout << "    Cutting plane with: offset = " << fixed
1795                      << setprecision(offset_precision) << setw(offset_precision+3) << stn * tn
1796                      << ", alpha = " << setprecision(angle_precision) << setw(angle_precision+3) << srx * rx
1797                      << ", beta = "  << setprecision(angle_precision) << setw(angle_precision+3) << sry * ry
1798                      << " ...";
1799           }
1800           plane  = CuttingPlane(attr, stn * tn, srx * rx, sry * ry, margin);
1801           cut    = LargestClosedIntersection(cells, plane);
1802           if (IsValidIntersection(cut, tol)) {
1803             if (verbose == 4) {
1804               cout << "    Cutting plane with: offset = " << fixed
1805                    << setprecision(offset_precision) << setw(offset_precision+3) << stn * tn
1806                    << ", alpha = " << setprecision(angle_precision) << setw(angle_precision+3) << srx * rx
1807                    << ", beta = "  << setprecision(angle_precision) << setw(angle_precision+3) << sry * ry
1808                    << " ...";
1809             }
1810             bool accept = false;
1811             SmoothLineStrip(cut, 2);
1812             r = LineStripIncircleRadius(cut);
1813             if (center.Distance(cut->GetCenter()) < r) {
1814               ++m;
1815               if (verbose == 3) {
1816                 cout << "    Cutting plane with: offset = " << fixed
1817                      << setprecision(offset_precision) << setw(offset_precision+3) << stn * tn
1818                      << ", alpha = " << setprecision(angle_precision) << setw(angle_precision+3) << srx * rx
1819                      << ", beta = "  << setprecision(angle_precision) << setw(angle_precision+3) << sry * ry
1820                      << " ...";
1821               }
1822               l = LineStripLength(cut);
1823               if (best_ct == 0 || l < .95 * best_l) {
1824                 accept = true;
1825               }
1826               if (accept) {
1827                 if (verbose > 2) cout << " accepted";
1828                 best_tn = stn * tn;
1829                 best_rx = srx * rx;
1830                 best_ry = sry * ry;
1831                 best_l  = l;
1832                 best_ct = cut->GetNumberOfCells();
1833                 if (prefer_default_plane && best_tn == 0. && best_rx == 0. && best_ry == 0.) {
1834                   abort = true;
1835                 }
1836               } else {
1837                 if (verbose > 2) cout << " rejected";
1838               }
1839               if (verbose > 2) {
1840                 vtkSmartPointer<vtkPolyData> divider = Divider(cut);
1841                 double area  = DividerArea(divider);
1842                 double error = DividerError(divider, boundary->GetPoints());
1843                 cout << ": perimeter = " << setprecision(5) << setw(8) << l;
1844                 cout << ", area = "      << setprecision(5) << setw(8) << area;
1845                 cout << ", RMS = "       << setprecision(5) << setw(8) << error;
1846                 cout << endl;
1847               }
1848               if (m >= max_suitable_planes && best_ct > 0) {
1849                 abort = true;
1850               }
1851             } else {
1852               if (verbose > 3) cout << " rejected" << endl;
1853             }
1854             if (debug > 1) {
1855               if (debug > 2 || center.Distance(cut->GetCenter()) < r) {
1856                 ++n;
1857                 char fname[64];
1858                 const char *suffix = accept ? "accepted" : "rejected";
1859                 snprintf(fname, 64, "debug_cutting_plane_%d_%d_%s.vtp", call, n, suffix);
1860                 WritePolyData(fname, plane);
1861                 snprintf(fname, 64, "debug_cutting_lines_%d_%d_%s.vtp", call, n, suffix);
1862                 WritePolyData(fname, cut);
1863               }
1864             }
1865           } else {
1866             if (verbose > 4) cout << " invalid" << endl;
1867           }
1868         }
1869       }
1870     }
1871   }
1872 
1873   // Pick best plane
1874   if (best_ct > 0) {
1875     if (verbose > 1) {
1876       cout << "    Found cutting plane with: offset = " << fixed
1877            << setprecision(offset_precision) << setw(offset_precision+3) << best_tn
1878            << ", alpha = " << setprecision(angle_precision) << setw(angle_precision+3) << best_rx
1879            << ", beta = "  << setprecision(angle_precision) << setw(angle_precision+3) << best_ry << endl;
1880     }
1881     plane  = CuttingPlane(attr, best_tn, best_rx, best_ry, margin);
1882     cut    = LargestClosedIntersection(surface, plane);
1883   }
1884 
1885   cout.precision(prev_cout_precision);
1886   cout.flags(prev_cout_flags);
1887 
1888   MIRTK_DEBUG_TIMING(2, "FindCuttingPlane");
1889   return best_ct > 0;
1890 }
1891 
1892 // -----------------------------------------------------------------------------
1893 struct EdgeIntersection
1894 {
1895   vtkIdType i; // Edge intersection point index
1896   double    t; // Edge interpolation parameter
1897 
EdgeIntersectionEdgeIntersection1898   EdgeIntersection() : i(-1), t(0.) {}
EdgeIntersectionEdgeIntersection1899   EdgeIntersection(vtkIdType i, double t) : i(i), t(t) {}
1900 };
1901 
1902 // -----------------------------------------------------------------------------
1903 struct TriangleIntersections
1904 {
1905   EdgeIntersection edge[3];
1906 };
1907 
1908 typedef UnorderedMap<vtkIdType, TriangleIntersections> TriangleIntersectionsMap;
1909 
1910 // -----------------------------------------------------------------------------
InsertEdgeIntersection(TriangleIntersectionsMap & intersections,vtkPolyData * surface,vtkIdType cellId,int edge,Vector3 & x,double t)1911 inline void InsertEdgeIntersection(TriangleIntersectionsMap &intersections,
1912                                    vtkPolyData *surface, vtkIdType cellId,
1913                                    int edge, Vector3 &x, double t)
1914 {
1915   vtkNew<vtkIdList> cellIds, cellPtIds, nbrPtIds;
1916   vtkIdType         newPtId, nbrId;
1917   int               nbrEdge;
1918   double            nbrParam;
1919 
1920   auto it = intersections.find(cellId);
1921   if (it == intersections.end()) {
1922     it = intersections.insert(MakePair(cellId, TriangleIntersections())).first;
1923   }
1924   auto &intersection = it->second.edge[edge];
1925   if (intersection.i < 0) {
1926     newPtId = surface->GetPoints()->InsertNextPoint(const_cast<Vector3 &>(x));
1927     intersection.i = newPtId;
1928     intersection.t = t;
1929   } else {
1930     newPtId = intersection.i;
1931     surface->GetPoint(newPtId, x);
1932   }
1933 
1934   surface->GetCellPoints(cellId, cellPtIds.GetPointer());
1935   const vtkIdType ptId1 = cellPtIds->GetId(edge);
1936   const vtkIdType ptId2 = cellPtIds->GetId((edge + 1) % cellPtIds->GetNumberOfIds());
1937   surface->GetCellEdgeNeighbors(cellId, ptId1, ptId2, cellIds.GetPointer());
1938 
1939   for (vtkIdType j = 0; j < cellIds->GetNumberOfIds(); ++j) {
1940     nbrId   = cellIds->GetId(j);
1941     nbrEdge = -1;
1942     surface->GetCellPoints(nbrId, nbrPtIds.GetPointer());
1943     for (vtkIdType k = 0; k < nbrPtIds->GetNumberOfIds(); ++k) {
1944       if (nbrPtIds->GetId(k) == ptId1 && nbrPtIds->GetId((k + 1) % nbrPtIds->GetNumberOfIds()) == ptId2) {
1945         nbrEdge  = k;
1946         nbrParam = t;
1947         break;
1948       }
1949       if (nbrPtIds->GetId(k) == ptId2 && nbrPtIds->GetId((k + 1) % nbrPtIds->GetNumberOfIds()) == ptId1) {
1950         nbrEdge  = k;
1951         nbrParam = 1. - t;
1952         break;
1953       }
1954     }
1955     if (nbrEdge != -1) {
1956       auto it = intersections.find(nbrId);
1957       if (it == intersections.end()) {
1958         it = intersections.insert(MakePair(nbrId, TriangleIntersections())).first;
1959       }
1960       auto &intersection = it->second.edge[nbrEdge];
1961       if (intersection.i < 0) {
1962         intersection.i = newPtId;
1963         intersection.t = nbrParam;
1964       }
1965     }
1966   }
1967 }
1968 
1969 // -----------------------------------------------------------------------------
InterpolateEdge(vtkPointData * outputPD,vtkPointData * inputPD,vtkIdType ptId1,vtkIdType ptId2,const EdgeIntersection & intersection)1970 inline void InterpolateEdge(vtkPointData *outputPD, vtkPointData *inputPD,
1971                             vtkIdType ptId1, vtkIdType ptId2,
1972                             const EdgeIntersection &intersection)
1973 {
1974   outputPD->InterpolateEdge(inputPD, intersection.i, ptId1, ptId2, intersection.t);
1975 }
1976 
1977 // -----------------------------------------------------------------------------
1978 /// Bisect first edge of triangle
Bisect(vtkPolyData * input,vtkIdType cellId,vtkIdType ptId1,vtkIdType ptId2,vtkIdType ptId3,const EdgeIntersection & intersection,vtkPolyData * output)1979 void Bisect(vtkPolyData *input, vtkIdType cellId,
1980             vtkIdType ptId1, vtkIdType ptId2, vtkIdType ptId3,
1981             const EdgeIntersection &intersection, vtkPolyData *output)
1982 {
1983   vtkCellArray * const polys    = output->GetPolys();
1984   vtkPointData * const inputPD  = input ->GetPointData();
1985   vtkPointData * const outputPD = output->GetPointData();
1986   vtkCellData  * const inputCD  = input ->GetCellData();
1987   vtkCellData  * const outputCD = output->GetCellData();
1988 
1989   const vtkIdType &newPtId1 = ptId1;
1990   const vtkIdType &newPtId2 = intersection.i;
1991   const vtkIdType &newPtId3 = ptId2;
1992   const vtkIdType &newPtId4 = ptId3;
1993   vtkIdType       newCellId;
1994 
1995   InterpolateEdge(outputPD, inputPD, ptId1, ptId2, intersection);
1996 
1997   newCellId = polys->InsertNextCell(3);
1998   polys->InsertCellPoint(newPtId1);
1999   polys->InsertCellPoint(newPtId2);
2000   polys->InsertCellPoint(newPtId4);
2001   outputCD->CopyData(inputCD, cellId, newCellId);
2002 
2003   newCellId = polys->InsertNextCell(3);
2004   polys->InsertCellPoint(newPtId2);
2005   polys->InsertCellPoint(newPtId3);
2006   polys->InsertCellPoint(newPtId4);
2007   outputCD->CopyData(inputCD, cellId, newCellId);
2008 
2009   input->DeleteCell(cellId);
2010 }
2011 
2012 // -----------------------------------------------------------------------------
2013 /// Bisect first and second edge of triangle
Trisect(vtkPolyData * input,vtkIdType cellId,vtkIdType ptId1,vtkIdType ptId2,vtkIdType ptId3,const EdgeIntersection & intersection1,const EdgeIntersection & intersection2,vtkPolyData * output)2014 void Trisect(vtkPolyData *input, vtkIdType cellId,
2015              vtkIdType ptId1, vtkIdType ptId2, vtkIdType ptId3,
2016              const EdgeIntersection &intersection1,
2017              const EdgeIntersection &intersection2,
2018              vtkPolyData *output)
2019 {
2020   vtkCellArray * const polys    = output->GetPolys();
2021   vtkPointData * const inputPD  = input ->GetPointData();
2022   vtkPointData * const outputPD = output->GetPointData();
2023   vtkCellData  * const inputCD  = input ->GetCellData();
2024   vtkCellData  * const outputCD = output->GetCellData();
2025 
2026   const vtkIdType &newPtId1 = ptId1;
2027   const vtkIdType &newPtId2 = intersection1.i;
2028   const vtkIdType &newPtId3 = ptId2;
2029   const vtkIdType &newPtId4 = intersection2.i;
2030   const vtkIdType &newPtId5 = ptId3;
2031   vtkIdType       newCellId;
2032 
2033   InterpolateEdge(outputPD, inputPD, ptId1, ptId2, intersection1);
2034   InterpolateEdge(outputPD, inputPD, ptId2, ptId3, intersection2);
2035 
2036   newCellId = polys->InsertNextCell(3);
2037   polys->InsertCellPoint(newPtId1);
2038   polys->InsertCellPoint(newPtId2);
2039   polys->InsertCellPoint(newPtId5);
2040   outputCD->CopyData(inputCD, cellId, newCellId);
2041 
2042   newCellId = polys->InsertNextCell(3);
2043   polys->InsertCellPoint(newPtId2);
2044   polys->InsertCellPoint(newPtId3);
2045   polys->InsertCellPoint(newPtId4);
2046   outputCD->CopyData(inputCD, cellId, newCellId);
2047 
2048   newCellId = polys->InsertNextCell(3);
2049   polys->InsertCellPoint(newPtId4);
2050   polys->InsertCellPoint(newPtId5);
2051   polys->InsertCellPoint(newPtId2);
2052   outputCD->CopyData(inputCD, cellId, newCellId);
2053 
2054   input->DeleteCell(cellId);
2055 }
2056 
2057 // -----------------------------------------------------------------------------
2058 vtkSmartPointer<vtkPolyData>
AddClosedIntersectionDivider(vtkPolyData * surface,vtkPolyData * cut,double tol=0.)2059 AddClosedIntersectionDivider(vtkPolyData *surface, vtkPolyData *cut, double tol = 0.)
2060 {
2061   const double tol2 = tol * tol;
2062 
2063   int       bisect[3], subId, snapId;
2064   double    pcoords[3], weights[3], dist2, t1, t2;
2065   vtkIdType lineId, cellId, ptId;
2066   Vector3   pt[3], x[2], p, q, closestPt;
2067 
2068   Array<double>                 dists;
2069   Array<int>                    order;
2070   vtkNew<vtkIdList>             ptIds, cellIds, cellPtIds, edge1, edge2, linePtIds;
2071   vtkNew<vtkGenericCell>        cell;
2072   vtkSmartPointer<vtkDataArray> arr;
2073 
2074   ptIds->Allocate(10);
2075   edge1->Allocate(2);
2076   edge2->Allocate(2);
2077 
2078   // Determine edge length for tesselation of divider polygon
2079   const double mean_edge_length = AverageEdgeLength(surface);
2080 
2081   // Build needed auxiliary structures
2082   cut->BuildCells();
2083   surface->BuildLinks();
2084 
2085   // Polygonal data set of intersected surface cells
2086   vtkPoints * const points = surface->GetPoints();
2087 
2088   vtkSmartPointer<vtkPolyData>  split;
2089   vtkSmartPointer<vtkCellArray> polys;
2090 
2091   polys = vtkSmartPointer<vtkCellArray>::New();
2092   polys->Allocate(polys->EstimateSize(3 * cut->GetNumberOfCells(), 3));
2093 
2094   split = vtkSmartPointer<vtkPolyData>::New();
2095   split->SetPoints(points);
2096   split->SetPolys(polys);
2097 
2098   split->GetPointData()->InterpolateAllocate(surface->GetPointData());
2099   split->GetCellData()->CopyAllocate(surface->GetCellData());
2100 
2101   // Map (intersected) surface cell ID to intersection line ID(s)
2102   UnorderedMap<vtkIdType, List<vtkIdType>> cellIdToLineIdsMap;
2103   vtkDataArray * const inputIds = cut->GetCellData()->GetArray("Input0CellID");
2104   for (lineId = 0; lineId < cut->GetNumberOfCells(); ++lineId) {
2105     cellId = static_cast<vtkIdType>(inputIds->GetComponent(lineId, 0));
2106     cellIdToLineIdsMap[cellId].push_back(lineId);
2107   }
2108   cut->GetCellData()->RemoveArray("Input0CellID");
2109   cut->GetCellData()->RemoveArray("Input1CellID");
2110 
2111   // Determine and insert new edge intersection points
2112   TriangleIntersectionsMap intersections;
2113   for (auto &entry : cellIdToLineIdsMap) {
2114 
2115     // Get intersected surface cell
2116     cellId = entry.first;
2117     surface->GetCell(cellId, cell.GetPointer());
2118 
2119     // Merge intersection lines at non-edge cell points, these are resulting
2120     // from the triangular tesselation of the intersection plane needed by
2121     // the vtkIntersectionPolyDataFilter
2122     if (entry.second.size() > 1) {
2123       ptIds->Reset();
2124       for (const auto &lineId : entry.second) {
2125         cut->GetCellPoints(lineId, linePtIds.GetPointer());
2126         for (int i = 0; i < linePtIds->GetNumberOfIds(); ++i) {
2127           ptIds->InsertUniqueId(linePtIds->GetId(i));
2128         }
2129       }
2130       dists.resize(ptIds->GetNumberOfIds());
2131       for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); ++i) {
2132         ptId = ptIds->GetId(i);
2133         cut->GetPoint(ptId, p);
2134         cell->EvaluatePosition(p, nullptr, subId, pcoords, dist2, weights);
2135         cell->CellBoundary(subId, pcoords, edge1.GetPointer());
2136         surface->GetPoint(edge1->GetId(0), x[0]);
2137         surface->GetPoint(edge1->GetId(1), x[1]);
2138         dists[i] = vtkLine::DistanceToLine(p, x[0], x[1], t1, closestPt);
2139       }
2140       order = IncreasingOrder(dists);
2141       edge1->SetNumberOfIds(2);
2142       edge1->SetId(0, ptIds->GetId(order[0]));
2143       edge1->SetId(1, ptIds->GetId(order[1]));
2144       auto lineIdIt = entry.second.begin();
2145       cut->ReplaceCell(*lineIdIt, 2, edge1->GetPointer(0));
2146       ++lineIdIt;
2147       while (lineIdIt != entry.second.end()) {
2148         cut->DeleteCell(*lineIdIt);
2149         ++lineIdIt;
2150       }
2151       entry.second.resize(1);
2152     }
2153 
2154     // Determine which edges are being intersected
2155     surface->GetCellPoints(cellId, cellPtIds.GetPointer());
2156     surface->GetPoint(cellPtIds->GetId(0), pt[0]);
2157     surface->GetPoint(cellPtIds->GetId(1), pt[1]);
2158     surface->GetPoint(cellPtIds->GetId(2), pt[2]);
2159 
2160     lineId = entry.second.front();
2161     cut->GetCellPoints(lineId, linePtIds.GetPointer());
2162     cut->GetPoint(linePtIds->GetId(0), p);
2163     cut->GetPoint(linePtIds->GetId(1), q);
2164 
2165     // Snap first intersection line point to either edge point or node
2166     cell->EvaluatePosition(p, nullptr, subId, pcoords, dist2, weights);
2167     cell->CellBoundary(subId, pcoords, edge1.GetPointer());
2168     surface->GetPoint(edge1->GetId(0), x[0]);
2169     surface->GetPoint(edge1->GetId(1), x[1]);
2170 
2171     snapId = -1;
2172     if (tol2 > 0.) {
2173       dists.resize(2);
2174       dists[0] = (p - x[0]).SquaredLength();
2175       dists[1] = (p - x[1]).SquaredLength();
2176       snapId = (dists[0] <= dists[1] ? 0 : 1);
2177       if (dists[snapId] < tol2) {
2178         p = x[snapId], t1 = 0.;
2179         edge1->SetId(snapId == 0 ? 1 : 0, edge1->GetId(snapId));
2180       }
2181     }
2182     if (snapId == -1) {
2183       vtkLine::DistanceToLine(p, x[0], x[1], t1, closestPt);
2184       t1 = clamp(t1, 0., 1.);
2185       p  = closestPt;
2186     }
2187 
2188     // Snap second intersection line point to either edge point or node
2189     cell->EvaluatePosition(q, nullptr, subId, pcoords, dist2, weights);
2190     cell->CellBoundary(subId, pcoords, edge2.GetPointer());
2191     surface->GetPoint(edge2->GetId(0), x[0]);
2192     surface->GetPoint(edge2->GetId(1), x[1]);
2193 
2194     snapId = -1;
2195     if (tol2 > 0.) {
2196       dists.resize(2);
2197       dists[0] = (q - x[0]).SquaredLength();
2198       dists[1] = (q - x[1]).SquaredLength();
2199       snapId = (dists[0] <= dists[1] ? 0 : 1);
2200       if (dists[snapId] < tol2) {
2201         q = x[snapId], t2 = 0.;
2202         edge2->SetId(snapId == 0 ? 1 : 0, edge2->GetId(snapId));
2203       }
2204     }
2205     if (snapId == -1) {
2206       vtkLine::DistanceToLine(q, x[0], x[1], t2, closestPt);
2207       t2 = clamp(t2, 0., 1.);
2208       q  = closestPt;
2209     }
2210 
2211     // Insert (new) edge intersection points
2212     //
2213     // This also updates the edge intersections of neighboring cells,
2214     // including in particular those that are not themselves intersected
2215     // by any of the intersection lines. Edge intersection points are
2216     // inserted only once into the points list, thus ensuring that
2217     // intersection line segments share a common point.
2218     if (cellPtIds->GetNumberOfIds() != 3) {
2219       Throw(ERR_LogicError, __FUNCTION__, "Surface must have triangular faces");
2220     }
2221     if (edge1->GetId(0) == cellPtIds->GetId(0) && edge1->GetId(1) == cellPtIds->GetId(1)) {
2222       InsertEdgeIntersection(intersections, surface, cellId, 0, p, t1);
2223     } else if (edge2->GetId(0) == cellPtIds->GetId(0) && edge2->GetId(1) == cellPtIds->GetId(1)) {
2224       InsertEdgeIntersection(intersections, surface, cellId, 0, q, t2);
2225     }
2226     if (edge1->GetId(0) == cellPtIds->GetId(1) && edge1->GetId(1) == cellPtIds->GetId(2)) {
2227       InsertEdgeIntersection(intersections, surface, cellId, 1, p, t1);
2228     } else if (edge2->GetId(0) == cellPtIds->GetId(1) && edge2->GetId(1) == cellPtIds->GetId(2)) {
2229       InsertEdgeIntersection(intersections, surface, cellId, 1, q, t2);
2230     }
2231     if (edge1->GetId(0) == cellPtIds->GetId(2) && edge1->GetId(1) == cellPtIds->GetId(0)) {
2232       InsertEdgeIntersection(intersections, surface, cellId, 2, p, t1);
2233     } else if (edge2->GetId(0) == cellPtIds->GetId(2) && edge2->GetId(1) == cellPtIds->GetId(0)) {
2234       InsertEdgeIntersection(intersections, surface, cellId, 2, q, t2);
2235     }
2236     if (debug && (t1 != 0. || t2 != 0.) && intersections.find(cellId) == intersections.end()) {
2237       Throw(ERR_LogicError, __FUNCTION__, "No edge intersection recorded for intersected cell ", cellId);
2238     }
2239 
2240     // Update line end points (**after** InsertEdgeIntersection)
2241     cut->GetPoints()->SetPoint(linePtIds->GetId(0), p);
2242     cut->GetPoints()->SetPoint(linePtIds->GetId(1), q);
2243   }
2244   cut->RemoveDeletedCells();
2245 
2246   // Split surface cells at edge intersection points
2247   for (const auto &intersection : intersections) {
2248     cellId           = intersection.first;
2249     const auto &edge = intersection.second.edge;
2250     bisect[0] = (edge[0].i >= 0 ? 1 : 0);
2251     bisect[1] = (edge[1].i >= 0 ? 1 : 0);
2252     bisect[2] = (edge[2].i >= 0 ? 1 : 0);
2253     surface->GetCellPoints(cellId, cellPtIds.GetPointer());
2254     switch (bisect[0] + bisect[1] + bisect[2]) {
2255       case 0: {
2256         Throw(ERR_LogicError, __FUNCTION__, "Exptected at least one edge intersection");
2257       } break;
2258       case 1: {
2259         if      (bisect[0]) Bisect(surface, cellId, cellPtIds->GetId(0), cellPtIds->GetId(1), cellPtIds->GetId(2), edge[0], split);
2260         else if (bisect[1]) Bisect(surface, cellId, cellPtIds->GetId(1), cellPtIds->GetId(2), cellPtIds->GetId(0), edge[1], split);
2261         else                Bisect(surface, cellId, cellPtIds->GetId(2), cellPtIds->GetId(0), cellPtIds->GetId(1), edge[2], split);
2262       } break;
2263       case 2: {
2264         if      (bisect[0] && bisect[1]) Trisect(surface, cellId, cellPtIds->GetId(0), cellPtIds->GetId(1), cellPtIds->GetId(2), edge[0], edge[1], split);
2265         else if (bisect[1] && bisect[2]) Trisect(surface, cellId, cellPtIds->GetId(1), cellPtIds->GetId(2), cellPtIds->GetId(0), edge[1], edge[2], split);
2266         else                             Trisect(surface, cellId, cellPtIds->GetId(2), cellPtIds->GetId(0), cellPtIds->GetId(1), edge[2], edge[0], split);
2267       } break;
2268       case 3: {
2269         Throw(ERR_LogicError, __FUNCTION__, "Quadsection not possible when intersecting triangle with one line");
2270       } break;
2271     }
2272   }
2273   surface->RemoveDeletedCells();
2274 
2275   if (debug) {
2276     static int callId = 0; ++callId;
2277     char fname[64];
2278     snprintf(fname, 64, "debug_split_surface_other_%d.vtp", callId);
2279     WritePolyData(fname, surface);
2280     snprintf(fname, 64, "debug_split_surface_cells_%d.vtp", callId);
2281     WritePolyData(fname, split);
2282     snprintf(fname, 64, "debug_split_surface_cut_%d.vtp", callId);
2283     WritePolyData(fname, cut);
2284   }
2285 
2286   // Create polygonal mesh tesselation of closed intersection polygon
2287   vtkSmartPointer<vtkPolyData> divider = Divider(cut);
2288   if (debug) {
2289     static int callId = 0; ++callId;
2290     char fname[64];
2291     snprintf(fname, 64, "debug_split_surface_polygon_%d.vtp", callId);
2292     WritePolyData(fname, divider);
2293   }
2294   divider = TesselateDivider(divider, mean_edge_length);
2295 
2296   // Prepare point/cell data before appending polygonal data sets
2297   vtkCellData * const surfaceCD = surface->GetCellData();
2298   vtkCellData * const dividerCD = divider->GetCellData();
2299   vtkCellData * const splitCD   = split->GetCellData();
2300 
2301   arr = surfaceCD->GetArray(SOURCE_ARRAY_NAME);
2302   int divider_source_index = min(0, static_cast<int>(arr->GetRange(0)[0])) - 1;
2303 
2304   arr = splitCD->GetArray(SOURCE_ARRAY_NAME);
2305   if (!arr) {
2306     arr = NewVtkDataArray(VTK_SHORT, split->GetNumberOfCells(), 1, SOURCE_ARRAY_NAME);
2307     splitCD->AddArray(arr);
2308   }
2309   arr->FillComponent(0, 0.);
2310 
2311   arr = dividerCD->GetArray(SOURCE_ARRAY_NAME);
2312   if (arr == nullptr) {
2313     arr = NewVtkDataArray(VTK_SHORT, divider->GetNumberOfCells(), 1, SOURCE_ARRAY_NAME);
2314     dividerCD->AddArray(arr);
2315   }
2316   arr->FillComponent(0, static_cast<double>(divider_source_index));
2317 
2318   divider = CalculateNormals(divider, surface->GetPointData()->GetNormals() != nullptr,
2319                                       surface->GetCellData ()->GetNormals() != nullptr);
2320 
2321   if (debug) {
2322     static int callId = 0; ++callId;
2323     char fname[64];
2324     snprintf(fname, 64, "debug_split_surface_divider_%d.vtp", callId);
2325     WritePolyData(fname, divider);
2326   }
2327 
2328   // Merge surface with intersected cells and tesselated divider polygon
2329   vtkNew<vtkAppendPolyData> appender;
2330   AddVTKInput(appender, surface);
2331   AddVTKInput(appender, split);
2332   // divider added as input below
2333 
2334   vtkNew<vtkCleanPolyData> merger;
2335   SetVTKConnection(merger, appender);
2336   merger->ConvertStripsToPolysOff();
2337   merger->ConvertPolysToLinesOff();
2338   merger->ConvertLinesToPointsOff();
2339   merger->PointMergingOn();
2340   merger->ToleranceIsAbsoluteOn();
2341   merger->SetAbsoluteTolerance(1e-12);
2342 
2343   if (debug) {
2344     static int callId = 0; ++callId;
2345     char fname[64];
2346     snprintf(fname, 64, "debug_split_surface_interim_%d.vtp", callId);
2347     merger->Update();
2348     WritePolyData(fname, merger->GetOutput());
2349   }
2350 
2351   AddVTKInput(appender, divider);
2352   merger->Update();
2353   vtkSmartPointer<vtkPolyData> output = merger->GetOutput();
2354 
2355   // Remove those triangles from resulting mesh originating from the tesselated
2356   // divider whose three vertices are all on the divider boundary and that have
2357   // a corresponding cell in the split surface mesh, i.e., a duplicate triangle
2358   output->BuildLinks();
2359   vtkNew<vtkIdList> ptIds1, ptIds2;
2360   for (vtkIdType cellId = 0; cellId < output->GetNumberOfCells(); ++cellId) {
2361     if (output->GetCellType(cellId) != VTK_EMPTY_CELL) {
2362       output->GetCellPoints(cellId, ptIds1.GetPointer());
2363       for (vtkIdType i = 0; i < ptIds1->GetNumberOfIds(); ++i) {
2364         output->GetPointCells(ptIds1->GetId(i), cellIds.GetPointer());
2365         for (vtkIdType j = 0; j < cellIds->GetNumberOfIds(); ++j) {
2366           if (cellIds->GetId(j) > cellId && output->GetCellType(cellIds->GetId(j)) != VTK_EMPTY_CELL) {
2367             output->GetCellPoints(cellIds->GetId(j), ptIds2.GetPointer());
2368             if (ptIds1->GetNumberOfIds() == ptIds2->GetNumberOfIds()) {
2369               ptIds2->IntersectWith(ptIds1.GetPointer());
2370               if (ptIds1->GetNumberOfIds() == ptIds2->GetNumberOfIds()) {
2371                 output->DeleteCell(cellIds->GetId(j));
2372               }
2373             }
2374           }
2375         }
2376       }
2377     }
2378   }
2379   output->RemoveDeletedCells();
2380 
2381   return output;
2382 }
2383 
2384 // -----------------------------------------------------------------------------
LabelSurfacePatches(vtkPolyData * surface)2385 void LabelSurfacePatches(vtkPolyData *surface)
2386 {
2387   SurfacePatches cc;
2388   cc.Input(surface);
2389   cc.Ordering(SurfacePatches::LargestFirst);
2390   cc.Run();
2391 
2392   vtkDataArray * const patch_labels  = cc.GetLabelsArray();
2393   vtkDataArray * const source_labels = surface->GetCellData()->GetArray(SOURCE_ARRAY_NAME);
2394 
2395   UnorderedSet<double>      used;
2396   UnorderedMap<double, int> bins;
2397   decltype(bins)::iterator  bin;
2398   double                    patch_label, label;
2399   int                       count;
2400 
2401   double min_label = source_labels->GetRange(0)[0];
2402   double max_label = source_labels->GetRange(0)[1];
2403 
2404   for (int i = 0; i < cc.NumberOfPatches(); ++i) {
2405     bins.clear();
2406     patch_label = static_cast<double>(i + 1);
2407     for (vtkIdType cellId = 0; cellId < surface->GetNumberOfCells(); ++cellId) {
2408       if (patch_labels->GetComponent(cellId, 0) == patch_label) {
2409         label = source_labels->GetComponent(cellId, 0);
2410         if (label != 0.) {
2411           bin = bins.find(label);
2412           if (bin == bins.end()) bins[label]  = 1;
2413           else                   bin->second += 1;
2414         }
2415       }
2416     }
2417     label = 0.;
2418     count = 0;
2419     for (bin = bins.begin(); bin != bins.end(); ++bin) {
2420       if (bin->second > count) {
2421         label = bin->first;
2422         count = bin->second;
2423       }
2424     }
2425     if (label == 0. || used.find(label) != used.end()) {
2426       if (label < 0.) {
2427         min_label -= 1.;
2428         label = min_label;
2429       } else {
2430         max_label += 1.;
2431         label = max_label;
2432       }
2433     }
2434     for (vtkIdType cellId = 0; cellId < surface->GetNumberOfCells(); ++cellId) {
2435       if (patch_labels->GetComponent(cellId, 0) == patch_label) {
2436         source_labels->SetComponent(cellId, 0, label);
2437       }
2438     }
2439     used.insert(label);
2440   }
2441 }
2442 
2443 // -----------------------------------------------------------------------------
GrowSourceRegion(vtkPolyData * surface,bool ignore_border_edges)2444 void GrowSourceRegion(vtkPolyData *surface, bool ignore_border_edges)
2445 {
2446   vtkDataArray * const source = surface->GetCellData()->GetArray(SOURCE_ARRAY_NAME);
2447 
2448   Queue<vtkIdType>          active;
2449   UnorderedMap<double, int> bins;
2450   decltype(bins)::iterator  bin;
2451   double                    label;
2452   int                       count;
2453   vtkIdType                 cellId, nbrId;
2454   vtkNew<vtkIdList>         cellIds, ptIds;
2455 
2456   for (cellId = 0; cellId < surface->GetNumberOfCells(); ++cellId) {
2457     if (source->GetComponent(cellId, 0) == 0.) {
2458       surface->GetCellPoints(cellId, ptIds.GetPointer());
2459       for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); ++i) {
2460         surface->GetCellEdgeNeighbors(cellId, ptIds->GetId(i), ptIds->GetId((i + 1) % ptIds->GetNumberOfIds()), cellIds.GetPointer());
2461         if (ignore_border_edges || cellIds->GetNumberOfIds() == 1) {
2462           for (vtkIdType j = 0; j < cellIds->GetNumberOfIds(); ++j) {
2463             nbrId = cellIds->GetId(j);
2464             label = source->GetComponent(nbrId, 0);
2465             if (label != 0.) {
2466               active.push(cellId);
2467               i = ptIds->GetNumberOfIds(); // break also cell points loop
2468               break;
2469             }
2470           }
2471         }
2472       }
2473     }
2474   }
2475   while (!active.empty()) {
2476     cellId = active.front();
2477     active.pop();
2478     if (source->GetComponent(cellId, 0) == 0.) {
2479       bins.clear();
2480       surface->GetCellPoints(cellId, ptIds.GetPointer());
2481       for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); ++i) {
2482         surface->GetCellEdgeNeighbors(cellId, ptIds->GetId(i), ptIds->GetId((i + 1) % ptIds->GetNumberOfIds()), cellIds.GetPointer());
2483         if (ignore_border_edges || cellIds->GetNumberOfIds() == 1) {
2484           for (vtkIdType j = 0; j < cellIds->GetNumberOfIds(); ++j) {
2485             nbrId = cellIds->GetId(j);
2486             label = source->GetComponent(nbrId, 0);
2487             if (label == 0.) {
2488               active.push(nbrId);
2489             } else {
2490               bin = bins.find(label);
2491               if (bin == bins.end()) bins[label]  = 1;
2492               else                   bin->second += 1;
2493             }
2494           }
2495         }
2496       }
2497       label = 0.;
2498       count = 0;
2499       for (bin = bins.begin(); bin != bins.end(); ++bin) {
2500         if (bin->second > count) {
2501           label = bin->first;
2502           count = bin->second;
2503         }
2504       }
2505       source->SetComponent(cellId, 0, label);
2506     }
2507   }
2508 }
2509 
2510 // =============================================================================
2511 // Main
2512 // =============================================================================
2513 
2514 // -----------------------------------------------------------------------------
main(int argc,char * argv[])2515 int main(int argc, char *argv[])
2516 {
2517   REQUIRES_POSARGS(0);
2518 
2519   Array<const char *> input_names;
2520   const char *output_name = nullptr;
2521   const char *labels_name = nullptr;
2522 
2523   double labels_percentage    = 33.;
2524   double tolerance            = NaN;
2525   double snap_tolerance       = 0.;
2526   int    smooth_boundaries    = -1;
2527   bool   join_boundaries      = true;
2528   double min_edge_length      = NaN;
2529   double max_edge_length      = NaN;
2530   double edge_length_sd       = 2.;  // SD factor of edge length range about mean
2531   int    max_remesh_steps     = 3;   // max no. of remeshing steps
2532   int    max_smooth_steps     = 0;   // (max) no. of smoothing steps
2533   int    remesh_radius        = 3;   // intersection cell neighborhood connectivity radius
2534   int    smooth_radius        = 2;   // intersection cell neighborhood connectivity radius
2535   double smooth_lambda        = NaN;
2536   double smooth_mu            = NaN;
2537   bool   fill_source_label    = true;
2538   int    max_smooth_source    = 0;
2539   bool   output_largest_comp  = false;
2540   bool   add_dividers         = false;
2541   bool   output_point_normals = true;
2542   bool   output_cell_normals  = true;
2543 
2544   const char *point_source_name = nullptr;
2545   const char *cell_source_name  = nullptr;
2546 
2547   if (NUM_POSARGS == 1) {
2548     output_name = POSARG(1);
2549   } else if (NUM_POSARGS > 1) {
2550     input_names.reserve(NUM_POSARGS-1);
2551     for (int i = 1; i < NUM_POSARGS; ++i) {
2552       input_names.push_back(POSARG(i));
2553     }
2554     output_name = POSARG(NUM_POSARGS);
2555   }
2556 
2557   for (ALL_OPTIONS) {
2558     if (OPTION("-labels")) {
2559       labels_name = ARGUMENT;
2560     }
2561     else if (OPTION("-labels-percentage")) {
2562       PARSE_ARGUMENT(labels_percentage);
2563     }
2564     else if (OPTION("-i") || OPTION("-input")) {
2565       do {
2566         input_names.push_back(ARGUMENT);
2567       } while (HAS_ARGUMENT);
2568     }
2569     else if (OPTION("-o") || OPTION("-output")) {
2570       if (output_name != nullptr) {
2571         input_names.insert(input_names.begin() + NUM_POSARGS - 1, output_name);
2572       }
2573       output_name = ARGUMENT;
2574     }
2575     else if (OPTION("-source-array")) {
2576       point_source_name = cell_source_name = ARGUMENT;
2577     }
2578     else if (OPTION("-point-source-array")) {
2579       point_source_name = ARGUMENT;
2580     }
2581     else if (OPTION("-cell-source-array")) {
2582       cell_source_name = ARGUMENT;
2583     }
2584     else if (OPTION("-tolerance") || OPTION("-tol")) {
2585       PARSE_ARGUMENT(tolerance);
2586     }
2587     else if (OPTION("-snap-tolerance") || OPTION("-snap-tol")) {
2588       PARSE_ARGUMENT(snap_tolerance);
2589     }
2590     else if (OPTION("-smooth-boundaries") || OPTION("-boundary-smoothing")) {
2591       if (HAS_ARGUMENT) PARSE_ARGUMENT(smooth_boundaries);
2592       else smooth_boundaries = 3;
2593     }
2594     else if (OPTION("-nosmooth-boundaries") || OPTION("-noboundary-smoothing")) {
2595       smooth_boundaries = 0;
2596     }
2597     else if (OPTION("-edge-length")) {
2598       PARSE_ARGUMENT(min_edge_length);
2599       if (HAS_ARGUMENT) PARSE_ARGUMENT(max_edge_length);
2600       else max_edge_length = min_edge_length;
2601       if (min_edge_length < max_edge_length) {
2602         swap(min_edge_length, max_edge_length);
2603       }
2604     }
2605     else if (OPTION("-min-edge-length")) {
2606       PARSE_ARGUMENT(min_edge_length);
2607     }
2608     else if (OPTION("-max-edge-length")) {
2609       PARSE_ARGUMENT(max_edge_length);
2610     }
2611     else if (OPTION("-edge-length-sigma") || OPTION("-edge-length-sd")) {
2612       PARSE_ARGUMENT(edge_length_sd);
2613     }
2614     else if (OPTION("-remesh") || OPTION("-remeshing")) {
2615       if (HAS_ARGUMENT) PARSE_ARGUMENT(max_remesh_steps);
2616       else max_remesh_steps = 3;
2617     }
2618     else if (OPTION("-remeshing-iterations") || OPTION("-remesh-iterations")) {
2619       PARSE_ARGUMENT(max_remesh_steps);
2620     }
2621     else if (OPTION("-noremeshing") || OPTION("-noremesh")) {
2622       max_remesh_steps = 0;
2623     }
2624     else if (OPTION("-smooth") || OPTION("-smoothing")) {
2625       if (HAS_ARGUMENT) PARSE_ARGUMENT(max_smooth_steps);
2626       else max_smooth_steps = 100;
2627     }
2628     else if (OPTION("-smoothing-iterations") || OPTION("-smooth-iterations")) {
2629       PARSE_ARGUMENT(max_smooth_steps);
2630     }
2631     else if (OPTION("-smoothing-lambda") || OPTION("-smooth-lambda")) {
2632       PARSE_ARGUMENT(smooth_lambda);
2633     }
2634     else if (OPTION("-smoothing-mu") || OPTION("-smooth-mu")) {
2635       PARSE_ARGUMENT(smooth_mu);
2636     }
2637     else if (OPTION("-nosmoothing") || OPTION("-nosmooth")) {
2638       max_smooth_steps = 0;
2639     }
2640     else if (OPTION("-neighborhood") || OPTION("-neighbourhood") || OPTION("-radius")) {
2641       PARSE_ARGUMENT(remesh_radius);
2642       smooth_radius = remesh_radius;
2643     }
2644     else if (OPTION("-remeshing-neighborhood") || OPTION("-remeshing-neighbourhood") || OPTION("-remeshing-radius") ||
2645              OPTION("-remesh-neighborhood")    || OPTION("-remesh-neighbourhood")    || OPTION("-remesh-radius")) {
2646       PARSE_ARGUMENT(remesh_radius);
2647     }
2648     else if (OPTION("-smoothing-neighborhood") || OPTION("-smoothing-neighbourhood") || OPTION("-smoothing-radius") ||
2649              OPTION("-smooth-neighborhood")    || OPTION("-smooth-neighbourhood")    || OPTION("-smooth-radius")) {
2650       PARSE_ARGUMENT(smooth_radius);
2651     }
2652     else if (OPTION("-smooth-source") || OPTION("-source-smoothing")) {
2653       if (HAS_ARGUMENT) PARSE_ARGUMENT(max_smooth_source);
2654       else max_smooth_source = 10;
2655     }
2656     else if (OPTION("-nosmooth-source") || OPTION("-nosource-smoothing")) {
2657       max_smooth_source = 0;
2658     }
2659     else if (OPTION("-normals")) {
2660       if (HAS_ARGUMENT) {
2661         PARSE_ARGUMENT(output_point_normals);
2662         output_cell_normals = output_point_normals;
2663       } else {
2664         output_cell_normals = output_point_normals = true;
2665       }
2666     }
2667     else if (OPTION("-nonormals")) {
2668       output_cell_normals = output_point_normals = false;
2669     }
2670     else HANDLE_BOOLEAN_OPTION("point-normals", output_point_normals);
2671     else HANDLE_BOOLEAN_OPTION("cell-normals", output_point_normals);
2672     else HANDLE_BOOLEAN_OPTION("join", join_boundaries);
2673     else HANDLE_BOOLEAN_OPTION("largest", output_largest_comp);
2674     else HANDLE_BOOLEAN_OPTION("dividers", add_dividers);
2675     else HANDLE_COMMON_OR_UNKNOWN_OPTION();
2676   }
2677 
2678   if (input_names.size() < 2) {
2679     FatalError("At least two -input surfaces required!");
2680   }
2681   if (output_name == nullptr) {
2682     FatalError("No -output surface file name specified!");
2683   }
2684 
2685   if (join_boundaries) {
2686     if (smooth_boundaries < 0) smooth_boundaries = 3;
2687   } else {
2688     smooth_boundaries = max(smooth_boundaries, 0);
2689     remesh_radius    = smooth_radius    = 0;
2690     max_remesh_steps = max_smooth_steps = 0;
2691   }
2692   if (!IsNaN(min_edge_length) && min_edge_length < 0. &&
2693       !IsNaN(max_edge_length) && IsInf(max_edge_length)) {
2694     remesh_radius    = 0;
2695     max_remesh_steps = 0;
2696   }
2697   if (IsNaN(smooth_lambda)) {
2698     if (IsNaN(smooth_mu)) smooth_mu = -.75;
2699     if (-1. < smooth_mu && smooth_mu < 0.) {
2700       smooth_lambda = abs(smooth_mu) - .01;
2701     } else {
2702       smooth_lambda = smooth_mu;
2703     }
2704   }
2705   if (cell_source_name == nullptr && point_source_name == nullptr) {
2706     fill_source_label = false;
2707     max_smooth_source = 0;
2708   }
2709 
2710   // Read segmentation labels
2711   GreyImage labels_image;
2712   if (labels_name) {
2713     InitializeIOLibrary();
2714     labels_image.Read(labels_name);
2715   } else {
2716     FatalError("Can only merge surfaces with corresponding -labels image at the moment!");
2717   }
2718 
2719   // Read input surfaces
2720   Array<vtkSmartPointer<vtkPolyData> > surfaces;
2721   surfaces.resize(input_names.size());
2722   for (size_t i = 0; i < input_names.size(); ++i) {
2723     surfaces[i] = ReadPolyData(input_names[i]);
2724   }
2725 
2726   // Default tolerance
2727   if (IsNaN(tolerance)) {
2728     tolerance = max(max(labels_image.XSize(),
2729                         labels_image.YSize()),
2730                         labels_image.ZSize());
2731   }
2732 
2733   // Add cell source arrays
2734   size_t source_offset = 1;
2735   vtkSmartPointer<vtkDataArray> cell_source;
2736   if (point_source_name) {
2737     cell_source = surfaces[0]->GetPointData()->GetArray(point_source_name);
2738     if (cell_source && cell_source->GetNumberOfComponents() == 1) {
2739       source_offset = max(source_offset, static_cast<size_t>(ceil(cell_source->GetRange(0)[1])));
2740     }
2741     cell_source = nullptr; // not a cell data array!
2742   }
2743   if (cell_source_name) {
2744     cell_source = surfaces[0]->GetCellData()->GetArray(cell_source_name);
2745     if (cell_source && cell_source->GetNumberOfComponents() == 1) {
2746       source_offset = max(source_offset, static_cast<size_t>(ceil(cell_source->GetRange(0)[1])));
2747     }
2748   }
2749   if (cell_source) {
2750     cell_source->SetName(SOURCE_ARRAY_NAME);
2751     cell_source = nullptr;
2752   } else {
2753     source_offset = 1;
2754     AddCellSourceArray(surfaces[0], source_offset);
2755   }
2756   for (size_t i = 1; i < surfaces.size(); ++i) {
2757     AddCellSourceArray(surfaces[i], source_offset + i);
2758   }
2759 
2760   // Merge surfaces
2761   if (verbose > 0) {
2762     cout << "Merging surfaces at segmentation boundaries...";
2763     if (verbose > 1) cout << "\n";
2764     cout.flush();
2765   }
2766   vtkSmartPointer<vtkPolyData> output = surfaces[0];
2767   Array<vtkSmartPointer<vtkPolyData>> boundaries;
2768   {
2769     vtkSmartPointer<vtkPolyData> boundary;
2770     UnorderedSet<int> output_labels = InsideLabels(labels_image, labels_percentage, output);
2771     if (output_labels.empty()) {
2772       FatalError("Could not determine labels corresponding to the inside of the first input surface!");
2773     }
2774     for (size_t i = 1; i < surfaces.size(); ++i) {
2775       UnorderedSet<int> surface_labels = InsideLabels(labels_image, labels_percentage, surfaces[i]);
2776       if (surface_labels.empty()) {
2777         FatalError("Could not determine labels corresponding to the inside of the "
2778                    << (i == 1 ? "1st" : (i == 2 ? "2nd" : (i == 3 ? "3rd" : ((ToString(i) + "th").c_str()))))
2779                    << " input surface!");
2780       }
2781       boundary = LabelBoundary(labels_image, output_labels, surface_labels);
2782       output = Merge(output, surfaces[i], boundary, tolerance, smooth_boundaries, join_boundaries);
2783       output_labels.insert(surface_labels.begin(), surface_labels.end());
2784       boundaries.push_back(boundary);
2785     }
2786     surfaces.clear();
2787   }
2788   if (verbose > 0) {
2789     if (verbose > 1) cout << "Merging surfaces at segmentation boundaries...";
2790     cout << " done" << endl;
2791   }
2792 
2793   if (join_boundaries) {
2794 
2795     // Extract largest connected component
2796     if (output_largest_comp) {
2797       if (verbose > 0) {
2798         cout << "Extracting largest surface component...";
2799         cout.flush();
2800       }
2801       vtkNew<vtkPolyDataConnectivityFilter> lcc;
2802       SetVTKInput(lcc, output);
2803       lcc->SetExtractionModeToLargestRegion();
2804       lcc->Update();
2805       output = lcc->GetOutput();
2806       if (verbose > 0) cout << " done" << endl;
2807     }
2808 
2809     // Recalculate surface normals and fix vertex order if necessary
2810     if (verbose > 0) {
2811       cout << "Calculating surface normals...";
2812       cout.flush();
2813     }
2814     const bool calc_cell_normals = true;
2815     output = CalculateNormals(output, output_point_normals, calc_cell_normals);
2816     if (verbose > 0) cout << " done" << endl;
2817 
2818     if (debug > 0) {
2819       WritePolyData("debug_joined_boundaries.vtp", output);
2820     }
2821 
2822     // Remesh newly inserted cells which are joining the intersection boundaries
2823     if (remesh_radius > 0 && max_remesh_steps > 0) {
2824       if (verbose > 0) {
2825         cout << "Remeshing surface near intersection boundaries...";
2826         cout.flush();
2827       }
2828 
2829       output = Triangulate(output);
2830       output->BuildLinks();
2831 
2832       vtkSmartPointer<vtkDataArray> mask;
2833       mask = IntersectionCellMask(output, remesh_radius);
2834 
2835       double min_global_length, max_global_length;
2836       GetMinMaxEdgeLength(output, min_global_length, max_global_length);
2837       GetEdgeLengthRange(output, mask, min_edge_length, max_edge_length, edge_length_sd);
2838 
2839       min_global_length -= 1e-12;
2840       max_global_length += 1e-12;
2841 
2842       const vtkIdType ncells = output->GetNumberOfCells();
2843       vtkSmartPointer<vtkDataArray> min_edge_length_arr, max_edge_length_arr;
2844       min_edge_length_arr = NewVtkDataArray(VTK_FLOAT, ncells, 1, MIN_EDGE_LENGTH_ARRAY_NAME);
2845       max_edge_length_arr = NewVtkDataArray(VTK_FLOAT, ncells, 1, MAX_EDGE_LENGTH_ARRAY_NAME);
2846 
2847       int n = 0;
2848       for (vtkIdType cellId = 0; cellId < ncells; ++cellId) {
2849         if (mask->GetComponent(cellId, 0) != 0.) {
2850           min_edge_length_arr->SetComponent(cellId, 0, min_edge_length);
2851           max_edge_length_arr->SetComponent(cellId, 0, max_edge_length);
2852           ++n;
2853         } else {
2854           min_edge_length_arr->SetComponent(cellId, 0, min_global_length);
2855           max_edge_length_arr->SetComponent(cellId, 0, max_global_length);
2856         }
2857       }
2858       mask = nullptr;
2859 
2860       if (n > 0) {
2861         if (verbose > 1) {
2862           cout << "\n";
2863           cout << "  No. of boundary cells = " << n << "\n";
2864           cout << "  Edge length range     = [" << min_edge_length << ", " << max_edge_length << "]\n";
2865           if (debug_time > 0) cout << "\n";
2866           cout.flush();
2867         }
2868         int nmelt = 0, ninvs = 0, nsubd = 0;
2869         for (int iter = 0; iter < max_remesh_steps; ++iter) {
2870           SurfaceRemeshing remesher;
2871           remesher.MeltTrianglesOff();
2872           remesher.MeltNodesOff();
2873           remesher.InvertTrianglesToIncreaseMinHeightOff();
2874           remesher.Input(output);
2875           if (iter == 0) {
2876             remesher.MinCellEdgeLengthArray(min_edge_length_arr);
2877             remesher.MaxCellEdgeLengthArray(max_edge_length_arr);
2878             min_edge_length_arr = nullptr;
2879             max_edge_length_arr = nullptr;
2880           }
2881           remesher.Run();
2882           if (remesher.NumberOfChanges() == 0) break;
2883           output = remesher.Output();
2884           if (debug > 0) {
2885             char fname[64];
2886             snprintf(fname, 64, "debug_remeshed_%d.vtp", iter+1);
2887             WritePolyData(fname, output);
2888           }
2889           nmelt += remesher.NumberOfMeltedEdges();
2890           ninvs += remesher.NumberOfInversions();
2891           nsubd += remesher.NumberOfBisections();
2892           nsubd += 2 * remesher.NumberOfTrisections();
2893           nsubd += 3 * remesher.NumberOfQuadsections();
2894         }
2895         if (verbose > 1) {
2896           if (debug_time > 0) cout << "\n";
2897           cout << "  No. of edge-meltings  = " << nmelt << "\n";
2898           cout << "  No. of inversions     = " << ninvs << "\n";
2899           cout << "  No. of subdivisions   = " << nsubd << "\n";
2900           cout.flush();
2901         }
2902         output->GetPointData()->RemoveArray(SurfaceRemeshing::MIN_EDGE_LENGTH);
2903         output->GetPointData()->RemoveArray(SurfaceRemeshing::MAX_EDGE_LENGTH);
2904       }
2905 
2906       if (verbose > 0) {
2907         if (verbose > 1) {
2908           cout << "Remeshing surface near intersection boundaries...";
2909         }
2910         cout << " done" << endl;
2911       }
2912     }
2913 
2914     // Smooth surface in vicinity of joined intersection boundaries to remove
2915     // small self-intersections introduced by joining and/or remeshing steps
2916     if (smooth_radius > 0 && max_smooth_steps > 0) {
2917       if (verbose > 0) {
2918         cout << "Smoothing surface near intersection boundaries...";
2919         cout.flush();
2920       }
2921       output->BuildLinks();
2922       MeshSmoothing smoother;
2923       smoother.Input(output);
2924       smoother.Mask(IntersectionPointMask(output, smooth_radius));
2925       smoother.Lambda(smooth_lambda);
2926       smoother.Mu(smooth_mu);
2927       smoother.NumberOfIterations(max_smooth_steps);
2928       if (IsNaN(smooth_mu) || fequal(smooth_lambda, smooth_mu)) {
2929         smoother.Weighting(MeshSmoothing::Gaussian);
2930         smoother.AdjacentValuesOnlyOff();
2931       } else {
2932         smoother.Weighting(MeshSmoothing::Combinatorial);
2933         smoother.AdjacentValuesOnlyOn();
2934       }
2935       smoother.SmoothPointsOn();
2936       smoother.Run();
2937       output->SetPoints(smoother.Output()->GetPoints());
2938       if (verbose > 0) cout << " done" << endl;
2939       if (debug > 0) {
2940         const int i = output->GetPointData()->AddArray(smoother.Mask());
2941         WritePolyData("debug_smoothed_transition.vtp", output);
2942         output->GetPointData()->RemoveArray(i);
2943       }
2944     }
2945 
2946     // Insert cutting planes in reverse order
2947     if (add_dividers) {
2948       if (verbose > 0) {
2949         cout << "Adding internal division planes...";
2950         if (verbose > 1) cout << "\n";
2951         cout.flush();
2952       }
2953       const EdgeTable edgeTable(output);
2954       const double ds = AverageEdgeLength(output->GetPoints(), edgeTable);
2955       vtkSmartPointer<vtkPolyData> plane, polygon, cut;
2956       for (int i = static_cast<int>(boundaries.size()-1); i >= 0; --i) {
2957         string msg;
2958         if (verbose > 1) {
2959           const int n = i + 1;
2960           msg = "  Adding division plane for ";
2961           if      (n == 1) msg += "1st";
2962           else if (n == 2) msg += "2nd";
2963           else if (n == 3) msg += "3rd";
2964           else             msg += ToString(n) + "th";
2965           msg += " boundary";
2966           cout << msg << "..." << endl;
2967         }
2968         if (FindCuttingPlane(output, boundaries[i], plane, cut, 2. * tolerance, ds, snap_tolerance)) {
2969           if (debug > 0) {
2970             char fname[64];
2971             snprintf(fname, 64, "debug_cutting_plane_%d.vtp", static_cast<int>(boundaries.size()) - i);
2972             WritePolyData(fname, plane);
2973           }
2974           if (debug > 0) {
2975             char fname[64];
2976             snprintf(fname, 64, "debug_cutting_polygon_%d.vtp", static_cast<int>(boundaries.size()) - i);
2977             WritePolyData(fname, cut);
2978           }
2979           output = AddClosedIntersectionDivider(output, cut, snap_tolerance);
2980           if (debug > 0) {
2981             char fname[64];
2982             snprintf(fname, 64, "debug_output+divider_%d.vtp", static_cast<int>(boundaries.size()) - i);
2983             WritePolyData(fname, output);
2984           }
2985           if (!msg.empty()) cout << msg << "... done" << endl;
2986         } else {
2987           if (verbose > 0) {
2988             if (!msg.empty()) cout << msg << "...";
2989             cout << " failed\n" << endl;
2990           }
2991           FatalError("Could not find a closed intersection with finite cutting plane near segmentation boundary!");
2992         }
2993       }
2994       if (verbose > 0) {
2995         if (verbose > 1) cout << "Adding internal division planes...";
2996         cout << " done" << endl;
2997       }
2998     }
2999 
3000   } // if (join_boundaries)
3001 
3002   // Clean up and rename or remove data source arrays
3003   output->BuildLinks();
3004 
3005   if (fill_source_label) {
3006     if (verbose > 0) {
3007       cout << "Filling cell source labels...";
3008       cout.flush();
3009     }
3010     if (add_dividers) {
3011       LabelSurfacePatches(output);
3012     } else {
3013       GrowSourceRegion(output, false);
3014       GrowSourceRegion(output, true); // just in case... usually NOP
3015     }
3016     if (verbose > 0) cout << " done" << endl;
3017   }
3018 
3019   if (max_smooth_source > 0) {
3020     if (verbose > 0) {
3021       cout << "Smoothing cell source labels...";
3022       cout.flush();
3023     }
3024 
3025     UnorderedMap<double, int> bins;
3026     decltype(bins)::iterator  bin;
3027     double                    label;
3028     int                       count;
3029 
3030     vtkNew<vtkIdList> cellIds, ptIds;
3031     vtkSmartPointer<vtkDataArray> cell_source;
3032     cell_source = output->GetCellData()->GetArray(SOURCE_ARRAY_NAME);
3033 
3034     for (int iter = 0; iter < max_smooth_source; ++iter) {
3035       bool modified = false;
3036       for (vtkIdType cellId = 0; cellId < output->GetNumberOfCells(); ++cellId) {
3037         bins.clear();
3038         output->GetCellPoints(cellId, ptIds.GetPointer());
3039         for (vtkIdType i = 0; i < ptIds->GetNumberOfIds(); ++i) {
3040           output->GetPointCells(ptIds->GetId(i), cellIds.GetPointer());
3041           for (vtkIdType j = 0; j < cellIds->GetNumberOfIds(); ++j) {
3042             if (cellIds->GetId(j) != cellId) {
3043               label = cell_source->GetComponent(cellIds->GetId(j), 0);
3044               bin   = bins.find(label);
3045               if (bin == bins.end()) bins[label]  = 1;
3046               else                   bin->second += 1;
3047             }
3048           }
3049         }
3050         label = cell_source->GetComponent(cellId, 0);
3051         bin   = bins.find(label);
3052         count = (bin == bins.end() ? 0 : bin->second);
3053         for (bin = bins.begin(); bin != bins.end(); ++bin) {
3054           if (bin->second > count) {
3055             label    = bin->first;
3056             count    = bin->second;
3057             modified = true;
3058           }
3059         }
3060         cell_source->SetComponent(cellId, 0, label);
3061       }
3062       if (!modified) break;
3063     }
3064     if (verbose > 0) cout << " done" << endl;
3065   }
3066 
3067   if ((output_point_normals && add_dividers) || point_source_name) {
3068     if (verbose > 0) {
3069       cout << "Generating point source labels...";
3070       cout.flush();
3071     }
3072 
3073     double label;
3074     vtkNew<vtkIdList> cellIds;
3075     vtkSmartPointer<vtkDataArray> cell_source, point_source;
3076 
3077     cell_source  = output->GetCellData()->GetArray(SOURCE_ARRAY_NAME);
3078     point_source = NewVtkDataArray(cell_source->GetDataType(),
3079                                    output->GetNumberOfPoints(), 1,
3080                                    SOURCE_ARRAY_NAME); // renamed later
3081     output->GetPointData()->AddArray(point_source);
3082 
3083     for (vtkIdType ptId = 0; ptId < output->GetNumberOfPoints(); ++ptId) {
3084       output->GetPointCells(ptId, cellIds.GetPointer());
3085       if (cellIds->GetNumberOfIds() == 0) {
3086         label = 0.;
3087       } else {
3088         label = cell_source->GetComponent(cellIds->GetId(0), 0);
3089         for (vtkIdType i = 1; i < cellIds->GetNumberOfIds(); ++i) {
3090           if (cell_source->GetComponent(cellIds->GetId(i), 0) != label) {
3091             label = 0.;
3092             break;
3093           }
3094         }
3095       }
3096       point_source->SetComponent(ptId, 0, label);
3097     }
3098 
3099     if (verbose > 0) cout << " done" << endl;
3100   }
3101 
3102   // Only now rename/remove not requested data arrays
3103   if (output_point_normals) {
3104     if (add_dividers) FixBorderPointNormals(output); // **before** renaming the source arrays
3105   } else {
3106     output->GetPointData()->SetNormals(nullptr);
3107   }
3108   if (!output_cell_normals) {
3109     output->GetCellData()->SetNormals(nullptr);
3110   }
3111   if (cell_source_name) {
3112     output->GetCellData()->GetArray(SOURCE_ARRAY_NAME)->SetName(cell_source_name);
3113   } else {
3114     output->GetCellData()->RemoveArray(SOURCE_ARRAY_NAME);
3115   }
3116   if (point_source_name) {
3117     output->GetPointData()->GetArray(SOURCE_ARRAY_NAME)->SetName(point_source_name);
3118   }
3119 
3120   // Write output surface mesh
3121   if (!WritePolyData(output_name, output)) {
3122     FatalError("Failed to write output surface to " << output_name);
3123   }
3124 
3125   return 0;
3126 }
3127