#include "LocalizationService.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { struct Surf { double length{0.0}; double hausdorff{0.0}; double bounds[6]{0, 0, 0, 0, 0, 0}; double center[3]{0, 0, 0}; double confronto_lati{0.0}; int regionId{-1}; Point3d centroid; }; static Point3d centroidFromPoints(vtkPoints* pts) { Point3d c; if (!pts) return c; const vtkIdType n = pts->GetNumberOfPoints(); if (n <= 0) return c; double sx = 0.0, sy = 0.0, sz = 0.0; double p[3]; for (vtkIdType i = 0; i < n; ++i) { // vtkPoints::GetPoint is non-const in VTK 9.x, so keep pts non-const here. pts->GetPoint(i, p); sx += p[0]; sy += p[1]; sz += p[2]; } c.x = sx / static_cast(n); c.y = sy / static_cast(n); c.z = sz / static_cast(n); return c; } static double sideDifferenceMetric(const double b[6]) { // Legacy "lati" metric: compare extents. const double dx = std::abs(b[1] - b[0]); const double dy = std::abs(b[3] - b[2]); const double dz = std::abs(b[5] - b[4]); // difference between the two largest extents (simple, stable) const double a = std::max({dx, dy, dz}); const double c = std::min({dx, dy, dz}); const double mid = dx + dy + dz - a - c; return std::max({std::abs(a - mid), std::abs(mid - c), std::abs(a - c)}); } static double hausdorffToSphere(vtkPolyData* surface, const double center[3]) { if (!surface || surface->GetNumberOfPoints() == 0) return 0.0; vtkNew source_sphere; source_sphere->SetCenter(center[0], center[1], center[2]); source_sphere->SetRadius(5.0); source_sphere->SetThetaResolution(24); source_sphere->SetPhiResolution(24); source_sphere->Update(); vtkPolyData* sphere = source_sphere->GetOutput(); if (!sphere || sphere->GetNumberOfPoints() == 0) return 0.0; const vtkIdType nSource = surface->GetNumberOfPoints(); const vtkIdType nTarget = sphere->GetNumberOfPoints(); double maxMin = 0.0; double p1[3], p2[3]; for (vtkIdType i = 0; i < nSource; ++i) { surface->GetPoint(i, p1); double smallest = std::numeric_limits::infinity(); for (vtkIdType j = 0; j < nTarget; ++j) { sphere->GetPoint(j, p2); const double dx = p1[0] - p2[0]; const double dy = p1[1] - p2[1]; const double dz = p1[2] - p2[2]; const double dist2 = dx * dx + dy * dy + dz * dz; if (dist2 < smallest) smallest = dist2; } if (smallest > maxMin) maxMin = smallest; } return maxMin; } } // namespace MarkerList LocalizationService::localize( vtkImageData* volume, const LocalizationParams& params, const ProgressFn& onProgress, const AbortFn& shouldAbort) { MarkerList result; if (!volume) return result; // Marching cubes vtkNew marchingcubes; marchingcubes->SetInputData(volume); marchingcubes->SetValue(0, params.marchThreshold); marchingcubes->SetComputeNormals(0); marchingcubes->Update(); if (marchingcubes->GetNumberOfContours() <= 0) return result; // Smoothing vtkNew smoother; smoother->SetInputConnection(marchingcubes->GetOutputPort()); smoother->SetNumberOfIterations(20); smoother->NormalizeCoordinatesOn(); smoother->SetPassBand(0.01); smoother->BoundarySmoothingOn(); smoother->Update(); // Normals vtkNew normals; normals->SetInputConnection(smoother->GetOutputPort()); normals->Update(); // 1) Label / count regions vtkNew connAll; connAll->SetInputConnection(normals->GetOutputPort()); connAll->SetExtractionModeToAllRegions(); connAll->ColorRegionsOn(); connAll->Update(); const int nRegions = connAll->GetNumberOfExtractedRegions(); std::cout << "nRegions " << nRegions << std::endl; if (nRegions <= 0) return result; // 2) Extract each region vtkNew connOne; connOne->SetInputConnection(normals->GetOutputPort()); connOne->SetExtractionModeToSpecifiedRegions(); connOne->ColorRegionsOff(); connOne->Update(); for (int regionId = 0; regionId < nRegions; ++regionId) { if (shouldAbort && shouldAbort()) break; if (onProgress) onProgress(100.0 * double(regionId + 1) / double(nRegions)); connOne->InitializeSpecifiedRegionList(); connOne->AddSpecifiedRegion(regionId); connOne->Modified(); connOne->Update(); vtkPolyData* raw = connOne->GetOutput(); if (!raw || raw->GetNumberOfCells() == 0) { std::cout << "regionId " << regionId << " => cells 0\n"; continue; } // IMPORTANT: compact points so bounds/length reflect ONLY this region vtkNew clean; clean->SetInputData(raw); clean->PointMergingOff(); // keep geometry identical; just drop unused points clean->Update(); vtkPolyData* out = clean->GetOutput(); if (!out || out->GetNumberOfCells() == 0 || out->GetNumberOfPoints() == 0) { std::cout << "regionId " << regionId << " => empty after clean\n"; continue; } out->ComputeBounds(); double b[6]; out->GetBounds(b); const double len = out->GetLength(); std::cout << "regionId " << regionId << " => cells " << out->GetNumberOfCells() << " points " << out->GetNumberOfPoints() << " len " << len << " bounds " << b[0] << " " << b[1] << " " << b[2] << " " << b[3] << " " << b[4] << " " << b[5] << "\n"; bool ok = true; // 0: bounding-box diagonal length if (params.selectedFilters[0]) { if (len < params.thrDown_D || len > params.thrUp_D) ok = false; } // 1: Hausdorff if (ok && params.selectedFilters[1]) { double center[3]; out->GetCenter(center); const double hd = hausdorffToSphere(out, center); if (hd > params.thr_HAUSD) ok = false; } // 2: side-difference if (ok && params.selectedFilters[2]) { const double metric = sideDifferenceMetric(b); if (metric > params.thr_S) ok = false; } if (!ok) continue; // Centroid from edges, to match legacy behavior. vtkNew edge_extractor; edge_extractor->SetInputData(out); edge_extractor->Update(); vtkPolyData* edges = edge_extractor->GetOutput(); if (!edges || edges->GetNumberOfPoints() == 0) continue; const Point3d c_mm = centroidFromPoints(edges->GetPoints()); Marker m; m.regionId = regionId; m.centroid = Point3d{c_mm.x / 1000.0, c_mm.y / 1000.0, c_mm.z / 1000.0}; result.push_back(m); } return result; }