From 26b48edf93fe4ad74164a5991e88cc4df02dac3a Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Tue, 14 Apr 2026 06:23:50 -0500 Subject: [PATCH 1/2] DOC: Sync spell-check dictionary with main branch Backport the itk_dict.txt word list from upstream/main to release-5.4, then add 20 words found in release-5.4 source that are not in main's dictionary (code identifiers in ThirdParty headers, legitimate terms like Hounsfield and Dask, and variable names in comments that cannot be changed on the release branch). Uses main's sort order to minimize future merge diffs. --- .github/workflows/itk_dict.txt | 1035 +++++++++++++++++++++++--------- 1 file changed, 745 insertions(+), 290 deletions(-) diff --git a/.github/workflows/itk_dict.txt b/.github/workflows/itk_dict.txt index 48d86e0ca31..a15fdaf508e 100644 --- a/.github/workflows/itk_dict.txt +++ b/.github/workflows/itk_dict.txt @@ -1,352 +1,237 @@ +abdo Abramowitz Abscissae +acq Acq +acqp +adj +adjNeigh +advective Aerosp +aff Agard Aghion +agui Ai -Alsabti -Armijo -AssembleFforTimeStep -AssembleKandM -Austern -Bankman -Beucher -Beucher's -BiDxz -Bigler -Bioimaging -Bioinformatics -Biomedicine -Blas -Bo -Borland -Bracewell -Brents -Burstein -Calc -Cardano's -Chrisochoides -Chu -CiDxy -Clatz -Clunie -Clunies -CommandIterationUpdatev -Conners -ConvertAnitkImageTovtkImageData -ConvertRGBvtkImageDataToAnitkImage -Cortesi's -Coverity -Creade -CreateListOfSamplesWithIDs -Crofton's -Cuadra -Cz -DDataFrameobject -Dasarathy -Dask -Deref -Dij -Dinstein -DownsamplerType -Dufour -Dxx -Dyx -Dyy -Dyz -Dz -Dzx -Dzy -Dzz -Eberhart -Eigen -Elmaghraby -Englewood -Eun -Evol -FEMExceptionItpackSparseMatrixSbagn -FEMExceptionItpackSparseMatrixSbsij -FEMSpatialOPbject -Fedorov -Fei -Fillhole -Fletch -Freesurfer -Frenet -GEAdw -GPUSuperclass's -Gao -Gibbsian -Gifti -Golby -Graphviz -Greenleaf -Guillen -Haar -Hackathon -Haralick -Haralick's -Hauke -Heapsort -Heibel -Hideaki -Hierarchique -Hilden -Hiraki -Hirschberg -Hostname -IOregion -ITKIOMeshGifti -ITKImageToIplImage -ITKOptimizersv -Intelli -Irfan -Irix -Isocenter -Jacobians -Jianfeng -Joly -Josuttis -Kalman -Kanungo -Ke -Kishore -KittlerIllingworth's -Kitware -LBFGSBOptimizerHelperv -LBFGSOptimizerBaseHelperv -LBFGSOptimizerBasev -Lagrange -Laurienti -LeBihan's -Levenberg -Lewy -Liao -Liu -LoadGrav -LoadGravConst -Lz -Magnotta -Malaterre -Mahalanobian -Mahalanobis -Marquardt -Mastronarde -Meijster -Methodv -Minc -MincTransformIO -Minkowski -Musser -NOLINTBEGIN -NOLINTEND -NVidia -Negahdaripour -Newmark -Nicolai -Nicolson -Niftilib -Nikos -Nocedal -Nonunit -NumbxHdrer -Octant -Olivo -Originv -Oxy -Oxyz -Oxz -Oy -Oyz -Pelleg -Pgrad -Pimpl -PimplGlobals -Pluggable -PointSetToPointSetMetricWithIndexv -Polak -Poynton's -Preallocate -PredType -Prefetch -Presaturation -Prev -Propag -Pyarali -Qa -Rajagopalan -RCSfile -RegistrationMethodv -Req -Resizeable -resizeable -Resizes -Ribiere -Rician -Rightarrow -Rosenbrock -SeQuence -Sehgal -SetBranchness -SetMedialness -SetRidgeness -Shanhbag -Shanmugam -Shinin -SinglephaseChanAndVeseSparseFieldLevelSetSegmentation -SingleValuedVnlCostFunctionAdaptorv -Smolikova -Sobel -Soille's -Sonka -Spall -Sprengel -Stegun -Stiehl -Synopsys -Syst -Tajbakhsh -Talos -Tannenbaum -Templateless -Tfar -Thiran -Thuente -Tnear -Tobias -Tra -Trivedi -Tunete -UIDs -Vec -Vectorpixel -Vicomtech -Vidya -Vitreo -Vkept -Vox -Wachowiak -Wanlin -Wdeprecated -Wi -Wtautological -Xu -Y'CbCr -Yager's -Yixun -Yhis -Yuanting -Zeiss -Zosso -Zurada -aPrioris -acq -acqp -advective aic algo alloc +Alsabti analyse anisotropies annuli anticausal antiraster +antonin ap +aparc apath +approx +apps +aPrioris archetypical +argc argidx +arithmetics +Armijo +arr ascii +AssembleFforTimeStep +AssembleKandM asymptote asymptotes atfork attenuator +Austern autocrop +autodetect +autogenerating +aux +avg +avi +axi +axii backend backends +Bankman bayes bb +bcc +BeginGeomDnext +BeginGeomLnext +beginthreadex bestdistortion +Beucher +Beucher's bezier +bg +bidirectionality +BiDxz +Bigler +bigtiff binarization binsize +bio +Bioimaging +Bioinformatics +Biomedicine bistoury -bg +bitmask +bitpix +Blas blockings +Bo +Borland +bot +bpm +bpp +Bracewell brainweb branchness +Brents bresenham bsplice btw buf bugfix +bugtracker +Burstein bw +bx +Bx +byteswap byu bzip calc +Calc +CalcDirnVector calloc callosum -centerline +Cardano's +cbmag +ccaster cdf cdfs +cellbuffersize +celldata +celllinks +centerline cert cfl cgi cha +chamferoutput +changemap chebycoefs chebyshev +checkboard +checksum +childrens chokkan +Chrisochoides chroma +Chu +chunked +chunking ci +CiDxy +cimage +cinfo +cit classvtk +Clatz +clEnqueueNDRangeKernel +Clerc +clib cline +Clunie +Clunies +clusterer +cmap cme cms +cnt codec +codestream coeff coeffP -composedness +CommandIterationUpdatev +commandline +comp complainty +composedness +Composedness computable +confocal connectedness +Conners +consec +ConvertAnitkImageTovtkImageData +ConvertRGBvtkImageDataToAnitkImage convolves -confocal cooccurrence +Cortesi's +costfunction costless +coverity +Coverity cp cpp cr cranio +Creade +CreateListOfSamplesWithIDs creativecommons +Crofton's +cronwake cryo cryosection cryosectional ctwd +Cuadra +cuboidal +cum +cumGaussianArrayCopySize +cumGaussianArraySize cumulate cumulated +curdist curvilinear +cuthill cv -cvCvtColor cvals +cvCvtColor cx cy +cyclicity +Cygwin +Cz d'Attente -dDwWmMyY da +Danielsson +dapi +Dasarathy dask +Dask dat -dcl -dcmimage -dcmtk datablock +datafile dataig dataobject datapipeline dataraces dbg +dblVec +dcl +dcmimage +dcmtk +dd +DDataFrameobject ddu +dDwWmMyY deallocated decompositions +decompressor decrementing decubitus defacto @@ -354,75 +239,123 @@ defe defem deformably deformer +deg degr +demangling demark denoiser denormal +Dept +Deref deriv +desc descr detections +devicetype +devs dgr +diast dic +dicoimg dicoopxt dicts +diff +Dij dilations dima dimb dimensionally dimensioned +Dinfo +Dinstein dirn dirns +div +Div +DivFloor +DivReal dl docstring dodecahedron +doesAttrExist dof +doitkEllipsoidInteriorExteriorSpatialFunctionTest +doitkSymmetricEllipsoidInteriorExteriorSpatialFunctionTest dom +doublereal downlist downlists -doublereal +DownsamplerType +dparameters drand +ds dseq dtnrm dtwd +Dufour duplications dwi dxx +Dxx dxy dxz +dyn +Dyx dyy +Dyy dyz +Dyz +dz +Dz +Dzx +Dzy dzz +Dzz e'e +eaa +Eberhart echall edgecell eg -eigenVecs +eigen +Eigen +eigenlib eigensolve eigenvec +eigenVecs +eigenvector +eigenvectors eix eiy eiz el -elt +Elmaghraby elmts +elt emph endlink +Englewood ent entropies eps epsmch epsmcj +eqi eqn +eqrt equalities equispaced -eqi -eqrt equs erf erfc +errid +est etd +Eun evaluable +Evol excitations +ExhaustiveOptimizedMetricV4 explicits extensionkey extradiagonal @@ -430,86 +363,177 @@ extrapolators extremum ey ez +faceGeometry factorable +FactorylessNewMacro fah failsafe +failsafes fastmarching favourite +fclose fcv fdinbuf fdistream fdostream fdstream +featureimage +fedisableexcept +Fedorov +feenableexcept +fegetenv +fegetexcept +Fei +fem +FEMExceptionItpackSparseMatrixSbagn +FEMExceptionItpackSparseMatrixSbsij femobject -fh +femparray +FEMSpatialOPbject +fenv +feraiseexcept +fesetenv +fexcept +ffmpeg fftinput fftoutput +fftpad fgm +fh fi fia fid fifo +filepath +fillch +fillhole +Fillhole +fixme +fixup +Fletch floatingfigure +floatRepresentationdx +floatRepresentationfx +fmag +fmri fn fnorm +foo +fopen formatter formatters fortran +FourCCtoEncoderType fourier +fov +fprintf +fps +fpu framebox framerate freesurfer +Freesurfer +Frenet +freq frobenius fsa fsb fstream ftol +fullx functors fw +fwrite fx fy ga galist gamold +Gao gauss gaussians +Gawedzki gb gc gdcm gdef +GEAdw geometrydata gerrit +GetQEGeom +getSint +Gibbsian gifti +Gifti gifticlib gii glehmann gnuplot +Golby +gotchas gp gpu +GPUSuperclass's +Grandin +Graphviz +Greenleaf +grep greyval -guid +gridsize +grindpeak gtol +guid +Guido +Guillen +gws gzip +Haar +Hackathon halfwidth +Hannu haralick +Haralick +Haralick's hardcoding +Hauke +hd hdfgroup +hdim +headerless +Heapsort +Heibel +Helminen +hermitian hexa hexahedral hh hhs +Hideaki +Hierarchique highrange +Hilden hipaa +Hiraki +Hirschberg +hist +hk +Homebrew hor +Hostname hough Hounsfield +hr hval hvector +hypercubic +hypercuboidal +hyperthreading ia ibmth icall icnt icosahedron +idet idtype idx'th iems @@ -519,172 +543,309 @@ ierr'th ifac ifstream ifti +Ignacy +ii +iix ijk +im +Im imagedimension +imagekey +imager +Imager +imgfile imit immunofluorescent +imread imsl imthd imthdd +incl includegraphics incx incy indct indir +informations inhomogeneous +initialisation +initialisations initialise initialised -initialisation +initialization initializations +initialize +initialized initnorm inpMax inpMin inputreader +inria +insideregion +Intelli intensityrescaler interactor interdependencies interop interslice +intmax intra invariance invariants +iobase +ioregion +IOregion +ip +iparm ipl -isValidJacobianCalcLocat +ipstr +Irfan +Irix +isk +Isocenter isocontouring isol -isk +isoshells +isValidJacobianCalcLocat +isw iter iterator's -itkGEAdwImageIO -itkFEMLoadGrav itkcaption +itkFEMLoadGrav +itkGEAdwImageIO +ITKImageToIplImage +ITKIOMeshGifti +ITKOptimizersv itmax itpack -ip -iparm -ipstr -isw itr +iv ival iwksp iwork iwskp +Ix ja jacobi jacobians +Jacobians jcd jcg jh +Jianfeng jj +Joly +Josuttis +jph +jpt jsi jvm k'th +Kalman +Kanungo +Ke +kg kHd -kwargs -kwstyle +kiran +Kishore +KittlerIllingworth's +Kitware kmeans kth +Ku +kwargs +kwstyle +kwsys labeler labelized +labeller +labeloutIt +Lagrange +lapl +Las lastx +Laurienti lbfgs lbfgsb +LBFGSBOptimizerHelperv +LBFGSOptimizerBaseHelperv +LBFGSOptimizerBasev lbg +LeBihan's leftarrow levell levenberg +Levenberg +Lewy +lh +Liao liblbfgs +libpngs +libsrc +libtiff lin lina lins +Liu lm lnt +LoadGrav +LoadGravConst loc localizer lockings logfixedImageMarginalPDFValue +longjmp +losslessly +lowercase lowpass lowrange +lpvReserved lradius lsw +lv lventricle +lws +Lz +macbook +macports +Macports +mag +Magnotta +Mahalanobian +Mahalanobis +Malaterre +mallinfo malloc mappable marquardt +Marquardt +Mastronarde +materiel mathrm matlab matricial +maxerr maxf +MaximumCityblockDistance maxm maxnorm maxs mebibytes medialness +Meijster +memcmp memcpy memmove memset merchantability +mersenne meshpixperelt metacharacters +metadatadictionary +metadataobject +metadatas +metadictionary +Methodv metricb metricx +micopy microgauss midslice migr minc +Minc +MincTransformIO +minf +mingw minimizer minimizers minimizes -minf -mingw +Minkowski minm minmod mins mis misalignments +misc +miset mmm +modulo +Moebius +morph +MorphFunction +mrf msec +msg mskIt +mtime +mtimes +mtype +mult multicolumn multicomponent multicomponents +multiframe multimetric multimodal multipage multiphase multivalued +multivisitor +Musser mutator mutexes +mutexing mv +MvtSunf +mx myhistogram +Myofibers +myoutput myreferenceimage -mx +natively nbOfDirections nbOfPixels +nc ncodewords ndarray ndof ndofpernode +Negahdaripour neighb +nel +nelements ness neuroimaging +newlines +Newmark nex +Nicolai +Nicolson niftilib +Niftilib +niix +Nikos nint nn nnb nocedal +Nocedal noded noface noindent +NOLINTBEGIN +NOLINTEND nonboundary nonterminal +Nonunit noreturn normalise normalised normalizations normalizer normalsize +nothrow nout noutt nr @@ -692,154 +853,291 @@ nreal nrl nrrdreader ns +Nslew +NumbxHdrer +numComp numerics numiter numnodesperelt +NVidia +nvox nw nx nxy -nxz nxyz +nxz ny nyz nz nzelts +objectif obo octahedron octant +Octant octants -oindex +offcentre ofstream +oindex +oitr oldnrm +Olivo +Onate +onedegree +op +opencl +opencvIO +openjpeg +oper +opj +optimisation +optimization optimizersv +orientable orienter +orig +Originv +Oring +Orings orthant orthantwise ortho +orthog orthogonalize orthonormality -opencl -openjpeg -oper -outJacobian's +ostringstream outdir +outJacobian's +outpixeltype +outputimage +outputpixeltype +outstream oversegmentation +Oxy +Oxyz +Oxz +Oy +Oyz padder pageref -pathologies palletized parallelizes +pathologies pc pca +pcoords pdata pde +Pelleg +Pgrad +Photometrics +pij +pijk +Pimpl +PimplGlobals pipelined pipelining piter +pixdim +PixelMed pixelspacing +pixeltypes +pkg plugable +Pluggable +pmap pmatrix +pointdata pointIDs pointItr pointsets +PointSetToPointSetMetricWithIndexv poisson's +Polak +polygondata polylines ponderation posn postcondition posteriori +Poynton's +pp +ppc +pq +pqr pragmas +prbndx +Preallocate preallocated +prec precalculate precalculating precompute preconditioner +predef +PredType +Prefetch prepend +Presaturation prescan -priorityqueue +prev +Prev printf printself +priorityqueue procs +Proj +Propag propagQueue +prvalue +prvalues +Prvalues psd +pset pshapeD pso pth pthread pthreads +pts +pw px pxp +Pyarali pythonprepend qa +Qa +QEcell +QEGeom qform -quantized +qfrom +qm +qr +qsort qto +QuadEdgeGeom +QuadEdgeMeshConstIteratorGeom +QuadEdgeMeshIteratorGeom +Quaing +quantized +rad radiographs radiological +Rajagopalan randgen +rasterizable rasterization -rasterized rasterize -rasterizable +rasterized rayleigh rcol -rgba +RCSfile reco reconstructer rectus recurse redeclared +redistributor +refactored +regiongrow regionprops regionsize +RegistrationMethodv +reinitializer reinitializes reinitializing +rel relabeler renderers renormalization renormalize renormalizing representable +Req +res resampler rescaler rescales rescaling +resizeable +Resizeable +Resizes +resLevel resubstitute +ret rethrow +rgba rhoold +Ribiere +Rician ridgeness +Rightarrow rightharpoonup +ritr rl rlp rof +Rosenbrock rotationally +rotplus +rowsperstrip rparm rr rrr rscg +RSGv rssi +rt rtc samedirection savemean savevar +saxi +sbend scalarndarr scaler scalingfactor scalings -scanMem scanf scanlines +scanlinesize +scanMem +scantype +scatt +scl scol +scomponents sct +sdk sdt sed +seedimage seek'd seekable +segfault +segfaults segmenter +Sehgal seimens seminumerical +SeQuence +serie +SerieHelper +SetBranchness +setjmp +setLibverBounds +SetMedialness +SetRidgeness +Sf sform -sholdImageCalculator +sforms +Sg +Shanhbag +Shanmugam shapeDgl +SharedMorphUtilities +Shinin +sholdImageCalculator shrinker shrinkings si @@ -848,41 +1146,78 @@ sigmad sigmoids signa signproduct +sim +SinglephaseChanAndVeseSparseFieldLevelSetSegmentation +SingleValuedVnlCostFunctionAdaptorv situ sizeable +sizez slicessorted smartpointers sme +Smolikova snprintf +Sobel +Soille's solmat +soln +Sonka sor sourceforge sourcelist +Spall spatialobject spdMatrix spdMatrixB +specificities +speedimage +sphereality splenium +splittable +splitted +Splus spr +Sprengel sptr +sshfs ssor ssorcg ssorsi +sstreamState stackrel +Starcam +startz +stat +statBuf +Stegun steplength +stepsize +Stepsize stereographic +Stiehl stod stoi streamable +stringstream +strncpy stype subarray subblocks subcube +subdomain +subdomains +Subdomains subdoxygen +subfile +subfiles subimages subindex subitems +submap +Submap submatch submatrix +subprocess subrefining subsampled subsampler @@ -890,62 +1225,121 @@ subsamplers subsamples substring substrings +subterraneous subtransform subtype summands superclass sym sympy +syn +Synopsys +sysroot +syst +Syst tagkey +tagkeys tagvalue +Tajbakhsh +Talos +Tannenbaum tardis +tbb +tcdf tcol +td +tdo +tech +teletype +Templateless +Testcode textheight textit texttt textwidth +Tfar +tfrequency tgz +thetamin +thetaplus thewtex +Thiran thisDic threadIDs threadsafe thresholder +thruview +Thuente thusly +tiffinfo +tiffiop +timestamp +timestamps tinuous -tfrequency tmpimage +tname +Tnear +Tobias +toffset tol +tolower tomerge +toplevel topologies +topoTest +topoTestpoints +toupper +tpdf tpl +tpo +Tra +transformIO tridimensional +Trivedi tunable +Tunete +twould txf +txx typedef'ed +typeless typemaps ub +UIDs +uiig +uk +umich +un unallocated -uncor +unattachable unbalancedness unbuffered +uncomment +Uncomment uncompiled +uncompress +uncor undeformed underbrace underflowed +Unencoded unfinalize unfinalizing uninitVar uninverted -unistd uniqLabel +unistd +unix unlink unmapped +unpacker unpadded unregister unrotated unselect unselected unsmoothed +Unsort unvisited upcast updatable @@ -956,59 +1350,120 @@ upsampler upwinding usec userdata +usleep +valgrind vall vals -valgrind +valuediff +valuev +vanal variates vd +ve +Vec +vecsize vectorial +Vectorpixel venc +ver +versa +vert +Vicomtech vidl +Vidya viewport +viewports visu vitrea +Vitreo +viz +Vkept +Vol +vop +vox +Vox voxelwise +vp +vr +vsize vv vxl vxl's +Wachowiak +waitid +walkthrough +Wanlin +warnable warper wb +wchar wctad +Wdeprecated weakgroup +webcam westin westinMEDIA +Wextra wfopen +Wheter +Wi winnls wisdoms +wk +wkjeong wksp wmatter +Wmaybe wolfe wopen workgroup worksize +Wrange +writability wstring +Wtautological +wunlink +Wunused wv xarray xfff xfrequency +xfull +xfullx +ximg xlen xlmda +xlv xspace +Xu +xyzt +Y'CbCr +Yager's yfrequency +Yhis +Yixun youngs yspace +Yuanting yx zagging zbrent +zeiss +Zeiss zero'th zerocrossings zeroflux zeroin -zig zfrequency -zmin +Zhao +zig +zlib zmax +zmin +Zosso +zspace +Zurada +Zurich zx zy zymatrix -zspace From 6b157cf47b1c1bd3e385aa5761540ce56d07164d Mon Sep 17 00:00:00 2001 From: Hans Johnson Date: Tue, 7 Apr 2026 18:59:23 +0000 Subject: [PATCH 2/2] BUG: Fix infinite loop in VoronoiDiagram2DGenerator::ConstructDiagram Backport from main (3b3f3c24d4). Adds stall detection and degenerate edge filtering to prevent infinite loops in ConstructDiagram when Fortune's algorithm produces near-zero-length edges. Adds itkVoronoiDiagram2DInfiniteLoopTest regression test. (cherry picked from commit 3b3f3c24d4) --- .../include/itkVoronoiDiagram2DGenerator.hxx | 74 ++++++++++++++++-- .../Segmentation/Voronoi/test/CMakeLists.txt | 8 ++ .../itkVoronoiDiagram2DInfiniteLoopTest.cxx | 78 +++++++++++++++++++ 3 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 Modules/Segmentation/Voronoi/test/itkVoronoiDiagram2DInfiniteLoopTest.cxx diff --git a/Modules/Segmentation/Voronoi/include/itkVoronoiDiagram2DGenerator.hxx b/Modules/Segmentation/Voronoi/include/itkVoronoiDiagram2DGenerator.hxx index c7600faad3e..32a40f70bba 100644 --- a/Modules/Segmentation/Voronoi/include/itkVoronoiDiagram2DGenerator.hxx +++ b/Modules/Segmentation/Voronoi/include/itkVoronoiDiagram2DGenerator.hxx @@ -272,8 +272,6 @@ VoronoiDiagram2DGenerator::ConstructDiagram() EdgeInfo curr1; EdgeInfo curr2; - unsigned char frontbnd; - unsigned char backbnd; std::vector cellPoints; for (unsigned int i = 0; i < m_NumberOfSeeds; ++i) { @@ -283,12 +281,56 @@ VoronoiDiagram2DGenerator::ConstructDiagram() buildEdges.push_back(curr); EdgeInfo front = curr; EdgeInfo back = curr; - while (!(rawEdges[i].empty())) + // Assemble raw edges into a connected chain for this Voronoi cell. + // Each iteration pops an edge from the deque and attempts to attach + // it to the front or back of the growing chain. Edges that cannot + // attach are pushed back for retry, because later attachments change + // the chain endpoints and may make previously unattachable edges + // attachable. + // + // A stall counter tracks progress: it resets whenever an edge + // attaches, and terminates the loop when a full pass through the + // deque makes no progress. Without this, certain degenerate seed + // configurations (near-collinear seeds, ITK issue #4386) cause an + // infinite loop because Fortune's algorithm produces near-zero-length + // edges whose endpoints differ by less than floating-point tolerance + // but have different vertex IDs. These degenerate edges cannot + // attach because: + // 1. Their endpoints don't match any chain vertex by ID. + // 2. The chain may already be closed (front[0] == back[1]). + // 3. The boundary-bridging logic doesn't apply when the chain + // endpoints are interior (not on the domain boundary). + // Such edges are safely dropped — they represent floating-point + // artifacts where two boundary intersection points should be + // identical in exact arithmetic. + auto remainingBeforeStall = rawEdges[i].size(); + while (!(rawEdges[i].empty()) && (remainingBeforeStall != 0)) { + --remainingBeforeStall; curr = rawEdges[i].front(); rawEdges[i].pop_front(); - frontbnd = Pointonbnd(front[0]); - backbnd = Pointonbnd(back[1]); + + // Check if this edge is a degenerate near-zero-length artifact. + // Fortune's algorithm can produce edges whose two endpoints map + // to the same geometric point (within DIFF_TOLERENCE) but have + // different vertex IDs. These carry no geometric information + // and can be safely discarded. + const PointType & edgeStart = m_OutputVD->GetVertex(curr[0]); + const PointType & edgeEnd = m_OutputVD->GetVertex(curr[1]); + if (!differentPoint(edgeStart, edgeEnd)) + { + itkDebugMacro("Dropping degenerate near-zero-length edge [" + << curr[0] << " (" << edgeStart[0] << "," << edgeStart[1] << ") -> " << curr[1] << " (" + << edgeEnd[0] << "," << edgeEnd[1] << ")]" + << " for cell " << i << ": endpoints within DIFF_TOLERENCE=" << DIFF_TOLERENCE); + // Count as progress — this edge is resolved (discarded). + remainingBeforeStall = rawEdges[i].size(); + continue; + } + + unsigned char frontbnd = Pointonbnd(front[0]); + unsigned char backbnd = Pointonbnd(back[1]); + bool edgeAttached = true; if (curr[0] == back[1]) { buildEdges.push_back(curr); @@ -357,20 +399,38 @@ VoronoiDiagram2DGenerator::ConstructDiagram() else { rawEdges[i].push_back(curr); + edgeAttached = false; } } else { rawEdges[i].push_back(curr); + edgeAttached = false; } + if (edgeAttached) + { + // Progress was made — chain endpoints changed, so previously + // unattachable edges may now be attachable. + remainingBeforeStall = rawEdges[i].size(); + } + } + // After assembly, all edges for this cell should have been either + // attached to the chain or identified as degenerate artifacts. + // Any remaining edges indicate an unexpected algorithmic failure. + if (!rawEdges[i].empty()) + { + itkExceptionMacro("VoronoiDiagram2DGenerator::ConstructDiagram: " + << rawEdges[i].size() << " non-degenerate edge(s) could not be " + << "assembled into cell " << i << " boundary chain. " + << "This indicates an unexpected geometric configuration."); } curr = buildEdges.front(); curr1 = buildEdges.back(); if (curr[0] != curr1[1]) { - frontbnd = Pointonbnd(curr[0]); - backbnd = Pointonbnd(curr1[1]); + unsigned char frontbnd = Pointonbnd(curr[0]); + unsigned char backbnd = Pointonbnd(curr1[1]); if ((frontbnd != 0) && (backbnd != 0)) { if (frontbnd == backbnd) diff --git a/Modules/Segmentation/Voronoi/test/CMakeLists.txt b/Modules/Segmentation/Voronoi/test/CMakeLists.txt index 944d3580a38..a3103f22309 100644 --- a/Modules/Segmentation/Voronoi/test/CMakeLists.txt +++ b/Modules/Segmentation/Voronoi/test/CMakeLists.txt @@ -3,10 +3,18 @@ set(ITKVoronoiTests itkVoronoiSegmentationImageFilterTest.cxx itkVoronoiSegmentationRGBImageFilterTest.cxx itkVoronoiDiagram2DTest.cxx + itkVoronoiDiagram2DInfiniteLoopTest.cxx itkVoronoiPartitioningImageFilterTest.cxx) createtestdriver(ITKVoronoi "${ITKVoronoi-Test_LIBRARIES}" "${ITKVoronoiTests}") +itk_add_test( + NAME + itkVoronoiDiagram2DInfiniteLoopTest + COMMAND + ITKVoronoiTestDriver + itkVoronoiDiagram2DInfiniteLoopTest) + itk_add_test( NAME itkVoronoiSegmentationImageFilterTest diff --git a/Modules/Segmentation/Voronoi/test/itkVoronoiDiagram2DInfiniteLoopTest.cxx b/Modules/Segmentation/Voronoi/test/itkVoronoiDiagram2DInfiniteLoopTest.cxx new file mode 100644 index 00000000000..754fc6db3f1 --- /dev/null +++ b/Modules/Segmentation/Voronoi/test/itkVoronoiDiagram2DInfiniteLoopTest.cxx @@ -0,0 +1,78 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkVoronoiDiagram2DGenerator.h" +#include "itkTestingMacros.h" + +// Regression test for ITK issue #4386: near-collinear seed configurations +// caused an infinite loop in VoronoiDiagram2DGenerator::ConstructDiagram(). +// +// Root cause: Fortune's algorithm produces near-zero-length edges when +// boundary intersection points coincide within floating-point tolerance. +// These degenerate edges have different vertex IDs but geometrically +// identical endpoints (within DIFF_TOLERENCE = 0.001). They cannot be +// attached to the growing boundary chain because: +// 1. Their vertex IDs don't match any chain endpoint. +// 2. The chain may already be closed (front == back). +// 3. Boundary-bridging logic doesn't apply when chain endpoints are +// interior (not on the domain boundary). +// The fix explicitly detects and drops these degenerate edges using the +// existing differentPoint() tolerance check. +int +itkVoronoiDiagram2DInfiniteLoopTest(int argc, char * argv[]) +{ + if (argc != 1) + { + std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << std::endl; + return EXIT_FAILURE; + } + + using VoronoiDiagramType = itk::VoronoiDiagram2D; + using VoronoiGeneratorType = itk::VoronoiDiagram2DGenerator; + using PointType = VoronoiDiagramType::PointType; + + // Six near-collinear seeds (x in [-1.40, -1.21]) that produce a + // degenerate Voronoi edge on the left domain boundary where two + // intersection points are ~0.00003 apart. + auto vg = VoronoiGeneratorType::New(); + vg->SetOrigin(PointType{ { -1.61569, -1.76726 } }); + vg->SetBoundary(PointType{ { 1.60174, 1.76345 } }); + vg->AddOneSeed(PointType{ { -1.39649, 0.322212 } }); + vg->AddOneSeed(PointType{ { -1.30128, 0.231786 } }); + vg->AddOneSeed(PointType{ { -1.21509, 0.0515039 } }); + vg->AddOneSeed(PointType{ { -1.22364, -0.030281 } }); + vg->AddOneSeed(PointType{ { -1.22125, -0.120815 } }); + vg->AddOneSeed(PointType{ { -1.25159, -0.23593 } }); + + // Without the fix, this call loops forever. With the fix, the + // degenerate near-zero-length edge is detected and dropped, and + // the exception guard ensures no non-degenerate edges are lost. + ITK_TRY_EXPECT_NO_EXCEPTION(vg->Update()); + + // Verify all 6 cells were constructed with valid boundaries + auto vd = vg->GetOutput(); + for (unsigned int i = 0; i < 6; ++i) + { + VoronoiDiagramType::CellAutoPointer cellPtr; + vd->GetCellId(i, cellPtr); + ITK_TEST_EXPECT_TRUE(cellPtr->GetNumberOfPoints() >= 2); + } + + std::cout << "Test passed." << std::endl; + return EXIT_SUCCESS; +}