batch processing

This commit is contained in:
farmakis
2025-07-27 13:49:48 +03:00
parent aeee15a0f6
commit ca9ee52da2
3 changed files with 233 additions and 93 deletions
+3 -3
View File
@@ -72,11 +72,11 @@ class qVoxFallTools
{
public:
static std::vector<Tuple3i> FindAdjacents(Tuple3i V, CCVector3 steps, bool facetsOnly);
static std::vector<Tuple3i> FindAdjacents(Tuple3i V, Tuple3i steps, bool facetsOnly);
static int Grid2Index(Tuple3i n, CCVector3 steps);
static int Grid2Index(Tuple3i n, Tuple3i steps);
static Tuple3i Index2Grid(unsigned index, CCVector3 steps);
static Tuple3i Index2Grid(unsigned index, Tuple3i steps);
};
+227 -83
View File
@@ -59,7 +59,6 @@ using namespace CCCoreLib;
//! Default name for VoxFall scalar fields
static const char OCCUPANCY_SF_NAME[] = "Occupancy";
static const char CLUSTER_SF_NAME[] = "Cluster ID";
static const char CHANGE_TYPE_SF_NAME[] = "Loss/gain";
static const char VOLUME_SF_NAME[] = "Volume (m3)";
@@ -77,12 +76,16 @@ struct VoxFallParams
bool genarateReport = false;
bool exportBlocksAsMeshes = false;
bool exportLossGain = false;
CCVector3 minBound, maxBound, extent, steps;
Tuple3i size;
CCVector3 minBound, maxBound, extent;
//helpers
int nonEmptyCount = 0;
int exteriorCount = 0;
std::vector<std::vector<int>> nbs;
std::vector<bool> isEmpty;
std::vector<bool> isEmptyBefore;
std::vector<bool> isExterior;
std::vector<bool> nonEmptyVoxelsVisited;
std::vector<int> clusters;
int emptyVoxelCount = 0;
@@ -128,9 +131,9 @@ bool InitializeOutputCloud(int voxelCount, GenericProgressCallback* progressCb =
float voxelSize = s_VoxFallParams.voxelSize;
CCVector3 minBound = s_VoxFallParams.minBound;
for (int index = 0; index < voxelCount; ++index)
for (unsigned index = 0; index < voxelCount; ++index)
{
Tuple3i V = qVoxFallTools::Index2Grid(index, s_VoxFallParams.steps);
Tuple3i V = qVoxFallTools::Index2Grid(index, s_VoxFallParams.size);
CCVector3 P(static_cast<PointCoordinateType>(V.x * voxelSize + minBound.x),
static_cast<PointCoordinateType>(V.y * voxelSize + minBound.y),
static_cast<PointCoordinateType>(V.z * voxelSize + minBound.z));
@@ -149,18 +152,126 @@ bool InitializeOutputCloud(int voxelCount, GenericProgressCallback* progressCb =
void GetVoxelOccupancy(const Tuple3i& cellPos, unsigned n)
{
int index = qVoxFallTools::Grid2Index(cellPos, s_VoxFallParams.steps);
int index = qVoxFallTools::Grid2Index(cellPos, s_VoxFallParams.size);
s_VoxFallParams.isEmpty[index] = false;
s_VoxFallParams.nonEmptyCount++;
}
void GetVoxelOccupancyBefore(const Tuple3i& cellPos, unsigned n)
{
int index = qVoxFallTools::Grid2Index(cellPos, s_VoxFallParams.steps);
int index = qVoxFallTools::Grid2Index(cellPos, s_VoxFallParams.size);
s_VoxFallParams.isEmptyBefore[index] = false;
}
bool BuildAdjacency(int maxThreads, int voxelCount, GenericProgressCallback* progressCb = nullptr)
{
//progress notification
NormalizedProgress nProgress(progressCb, voxelCount);
if (progressCb)
{
if (progressCb->textCanBeEdited())
{
char buffer[64];
snprintf(buffer, 64, "Bulding adjacency \n Voxels: %u", voxelCount);
progressCb->setInfo(buffer);
progressCb->setMethodTitle("VoxFall Detection");
}
progressCb->update(0);
progressCb->start();
}
auto size = s_VoxFallParams.size;
s_VoxFallParams.nbs.resize(voxelCount);
//#if defined(_OPENMP)
//#pragma omp parallel for schedule(static) \
// num_threads(maxThreads)
//#endif
for (int index = 0; index < voxelCount; ++index) {
auto V = qVoxFallTools::Index2Grid(index, size);
auto NN = qVoxFallTools::FindAdjacents(V, size, false);
if (V.x == 0 || V.y == 0 || V.z == 0 || V.x == size.x - 1 || V.y == size.y - 1 || V.z == size.z - 1)
{
if (s_VoxFallParams.isEmpty[index])
{
s_VoxFallParams.isExterior[index] = true;
s_VoxFallParams.exteriorCount++;
s_VoxFallParams.clusterSF->setValue(index, static_cast<ScalarType>(0.0));
}
}
for (auto const& n : NN)
{
int nIdx = qVoxFallTools::Grid2Index(n, size);
s_VoxFallParams.nbs[index].push_back(nIdx);
}
nProgress.oneStep();
//#if defined(_OPENMP)
//#pragma omp critical(BuildAdjacency)
// { nProgress.oneStep(); }
//#endif
}
return true;
}
bool FloodFillExterior(int maxThreads, int voxelCount, GenericProgressCallback* progressCb = nullptr)
{
//progress notification
NormalizedProgress nProgress(progressCb, voxelCount);
if (progressCb)
{
if (progressCb->textCanBeEdited())
{
char buffer[64];
int voxelsToProcess = voxelCount - s_VoxFallParams.nonEmptyCount;
snprintf(buffer, 64, "Filtering exterior space \n Voxels: %u", voxelsToProcess);
progressCb->setInfo(buffer);
progressCb->setMethodTitle("VoxFall Detection");
}
progressCb->update(0);
progressCb->start();
}
auto size = s_VoxFallParams.size;
//#if defined(_OPENMP)
//#pragma omp parallel for schedule(dynamic) \
// num_threads(maxThreads)
//#endif
for (int index = 0; index < voxelCount; ++index)
{
// if the voxel is not exterior air, skip it
if (!(s_VoxFallParams.isExterior[index]))
{
continue;
}
//get the grid position and neighbors of the voxel
auto V = qVoxFallTools::Index2Grid(index, size);
auto NN = qVoxFallTools::FindAdjacents(V, size, false);
for (auto const& n : NN)
{
int nIdx = qVoxFallTools::Grid2Index(n, size);
if (s_VoxFallParams.isEmpty[nIdx])
{
s_VoxFallParams.isExterior[nIdx] = true;
s_VoxFallParams.exteriorCount++;
s_VoxFallParams.clusterSF->setValue(nIdx, static_cast<ScalarType>(0.0));
}
}
nProgress.oneStep();
//#if defined(_OPENMP)
//#pragma omp critical(FloodFillExterior)
// { nProgress.oneStep(); }
//#endif
}
return true;
}
bool ClusterEmptySpace(int maxThreads, int voxelCount, GenericProgressCallback* progressCb = nullptr)
{
//progress notification
@@ -170,7 +281,8 @@ bool ClusterEmptySpace(int maxThreads, int voxelCount, GenericProgressCallback*
if (progressCb->textCanBeEdited())
{
char buffer[64];
snprintf(buffer, 64, "Clustering empty space \n Voxels: %u", voxelCount);
int voxelsToProcess = voxelCount - s_VoxFallParams.nonEmptyCount - s_VoxFallParams.exteriorCount;
snprintf(buffer, 64, "Clustering empty space \n Voxels: %u", voxelsToProcess);
progressCb->setInfo(buffer);
progressCb->setMethodTitle("VoxFall Detection");
}
@@ -178,30 +290,10 @@ bool ClusterEmptySpace(int maxThreads, int voxelCount, GenericProgressCallback*
progressCb->start();
}
auto steps = s_VoxFallParams.steps;
s_VoxFallParams.nbs.resize(voxelCount);
#if defined(_OPENMP)
#pragma omp parallel for schedule(static) \
num_threads(maxThreads)
#endif
for (int index = 0; index < voxelCount; ++index) {
auto V = qVoxFallTools::Index2Grid(index, steps);
auto NN = qVoxFallTools::FindAdjacents(V, steps, false);
for (auto const& n : NN)
{
int nIdx = qVoxFallTools::Grid2Index(n, steps);
s_VoxFallParams.nbs[index].push_back(nIdx);
}
//nProgress.oneStep();
#if defined(_OPENMP)
#pragma omp critical(ClusterEmptySpace)
{ nProgress.oneStep(); }
#endif
}
for (int index = 0; index < voxelCount; ++index)
{
// Check if voxel is empty.
if (!s_VoxFallParams.isEmpty[index])
// Check if voxel is occupied or exterior air.
if ((!s_VoxFallParams.isEmpty[index]) || s_VoxFallParams.isExterior[index])
continue;
// Label is not undefined.
@@ -238,7 +330,7 @@ bool ClusterEmptySpace(int maxThreads, int voxelCount, GenericProgressCallback*
unsigned nb = *nbs_next.begin();
nbs_next.erase(nbs_next.begin());
// Check empty neighbor.
if (!s_VoxFallParams.isEmpty[nb])
if ((!s_VoxFallParams.isEmpty[nb]) || s_VoxFallParams.isExterior[nb])
{
continue;
}
@@ -329,10 +421,10 @@ bool ComputeClusterVolume(int maxThreads, int clusterCount, ccHObject* clusterGr
if (s_VoxFallParams.exportLossGain)
{
Tuple3i V = qVoxFallTools::Index2Grid(nb, s_VoxFallParams.steps);
Tuple3i V = qVoxFallTools::Index2Grid(nb, s_VoxFallParams.size);
CCVector3 voxel(static_cast<PointCoordinateType>(V.x * s_VoxFallParams.voxelSize + s_VoxFallParams.minBound.x),
static_cast<PointCoordinateType>(V.y * s_VoxFallParams.voxelSize + s_VoxFallParams.minBound.y),
static_cast<PointCoordinateType>(V.z * s_VoxFallParams.voxelSize + s_VoxFallParams.minBound.z));
static_cast<PointCoordinateType>(V.y * s_VoxFallParams.voxelSize + s_VoxFallParams.minBound.y),
static_cast<PointCoordinateType>(V.z * s_VoxFallParams.voxelSize + s_VoxFallParams.minBound.z));
if (voxel.x > maxBound.x) maxBound.x = static_cast<PointCoordinateType>(voxel.x);
if (voxel.y > maxBound.y) maxBound.y = static_cast<PointCoordinateType>(voxel.y);
@@ -425,38 +517,31 @@ bool qVoxFallProcess::Compute(const qVoxFallDialog& dlg, QString& errorMessage,
s_VoxFallParams.minBound = mesh->getOwnBB().minCorner();
s_VoxFallParams.maxBound = mesh->getOwnBB().maxCorner();
s_VoxFallParams.extent = s_VoxFallParams.maxBound - s_VoxFallParams.minBound;
s_VoxFallParams.steps = (s_VoxFallParams.extent / s_VoxFallParams.voxelSize) + Vector3Tpl<float>(1, 1, 1);
s_VoxFallParams.size = Tuple3i( static_cast<int>(ceil(s_VoxFallParams.extent.x / s_VoxFallParams.voxelSize)),
static_cast<int>(ceil(s_VoxFallParams.extent.y / s_VoxFallParams.voxelSize)),
static_cast<int>(ceil(s_VoxFallParams.extent.z / s_VoxFallParams.voxelSize)));
s_VoxFallParams.genarateReport = dlg.getGenerateReportActivation();
s_VoxFallParams.exportBlocksAsMeshes = dlg.getExportMeshesActivation();
s_VoxFallParams.exportLossGain = dlg.getLossGainActivation();
s_VoxFallParams.groupName = mesh1->getName() + "_to_" + mesh2->getName() + QString(" [VoxFall] (voxel %1m)").arg(s_VoxFallParams.voxelSize);
s_VoxFallParams.voxfall = new ccPointCloud(s_VoxFallParams.groupName);
//Initialize voxel grid
auto voxelGrid = CCCoreLib::Grid3D<int>();
if (!voxelGrid.init( int(s_VoxFallParams.steps.x),
int(s_VoxFallParams.steps.y),
int(s_VoxFallParams.steps.z),
0 )) //margin
{
errorMessage = "Failed to initialize voxel grid!";
return false;
}
// Initialize heplpers
s_VoxFallParams.voxfall->reserve(voxelGrid.innerCellCount());
s_VoxFallParams.nbs.resize(voxelGrid.innerCellCount());
s_VoxFallParams.isEmpty.resize(voxelGrid.innerCellCount(), true);
s_VoxFallParams.isEmptyBefore.resize(voxelGrid.innerCellCount(), true);
int voxelCount = s_VoxFallParams.size.x * s_VoxFallParams.size.y * s_VoxFallParams.size.z;
s_VoxFallParams.voxfall->reserve(voxelCount);
s_VoxFallParams.nbs.resize(voxelCount);
s_VoxFallParams.isEmpty.resize(voxelCount, true);
s_VoxFallParams.isEmptyBefore.resize(voxelCount, true);
s_VoxFallParams.isExterior.resize(voxelCount, false);
if (s_VoxFallParams.exportBlocksAsMeshes)
{
s_VoxFallParams.clusters.resize(voxelGrid.innerCellCount(), 0);
s_VoxFallParams.clusters.resize(voxelCount, 0);
}
//allocate cluster ID SF
s_VoxFallParams.clusterSF = new ccScalarField(CLUSTER_SF_NAME);
s_VoxFallParams.clusterSF->link();
if (!s_VoxFallParams.clusterSF->resizeSafe(voxelGrid.innerCellCount(), true, static_cast<ScalarType>(-1.0)))
if (!s_VoxFallParams.clusterSF->resizeSafe(voxelCount, true, static_cast<ScalarType>(-1.0)))
{
errorMessage = "Failed to allocate memory for cluster ID values!";
return false;
@@ -467,7 +552,7 @@ bool qVoxFallProcess::Compute(const qVoxFallDialog& dlg, QString& errorMessage,
//allocate change type SF
s_VoxFallParams.changeTypeSF = new ccScalarField(CHANGE_TYPE_SF_NAME);
s_VoxFallParams.changeTypeSF->link();
if (!s_VoxFallParams.changeTypeSF->resizeSafe(voxelGrid.innerCellCount(), true, CCCoreLib::NAN_VALUE))
if (!s_VoxFallParams.changeTypeSF->resizeSafe(voxelCount, true, CCCoreLib::NAN_VALUE))
{
errorMessage = "Failed to allocate memory for change type values!";
return false;
@@ -476,7 +561,7 @@ bool qVoxFallProcess::Compute(const qVoxFallDialog& dlg, QString& errorMessage,
//allocate volume SF
s_VoxFallParams.volumeSF = new ccScalarField(VOLUME_SF_NAME);
s_VoxFallParams.volumeSF->link();
if (!s_VoxFallParams.volumeSF->resizeSafe(voxelGrid.innerCellCount(), true, CCCoreLib::NAN_VALUE))
if (!s_VoxFallParams.volumeSF->resizeSafe(voxelCount, true, CCCoreLib::NAN_VALUE))
{
errorMessage = "Failed to allocate memory for volume values!";
return false;
@@ -484,14 +569,14 @@ bool qVoxFallProcess::Compute(const qVoxFallDialog& dlg, QString& errorMessage,
//allocate volume uncertainty SF
s_VoxFallParams.uncertaintySF = new ccScalarField(UNCERTAINTY_SF_NAME);
s_VoxFallParams.uncertaintySF->link();
if (!s_VoxFallParams.uncertaintySF->resizeSafe(voxelGrid.innerCellCount(), true, CCCoreLib::NAN_VALUE))
if (!s_VoxFallParams.uncertaintySF->resizeSafe(voxelCount, true, CCCoreLib::NAN_VALUE))
{
errorMessage = "Failed to allocate memory for volume uncertainty values!";
return false;
}
// Initialize output cloud
if (!InitializeOutputCloud(voxelGrid.innerCellCount(), &pDlg))
if (!InitializeOutputCloud(voxelCount, &pDlg))
{
errorMessage = "Failed to initialize output data!";
return false;
@@ -506,6 +591,89 @@ bool qVoxFallProcess::Compute(const qVoxFallDialog& dlg, QString& errorMessage,
}
// GRID COMPUTATION
//=======================================================================================================================
//Duration: grid computation
QElapsedTimer gridTimer;
gridTimer.start();
//Initialize voxel grid
auto voxelGrid = CCCoreLib::Grid3D<int>();
if (!voxelGrid.init(s_VoxFallParams.size.x,
s_VoxFallParams.size.y,
s_VoxFallParams.size.z,
0)) //margin
{
errorMessage = "Failed to initialize voxel grid!";
return false;
}
//compute occupancy
if (!voxelGrid.intersectWith(mesh,
s_VoxFallParams.voxelSize,
s_VoxFallParams.minBound,
GetVoxelOccupancy,
&pDlg))
{
errorMessage = "Failed to compute grid occupancy!";
return false;
}
if (s_VoxFallParams.exportLossGain)
{
if (!voxelGrid.intersectWith(mesh1,
s_VoxFallParams.voxelSize,
s_VoxFallParams.minBound,
GetVoxelOccupancyBefore,
&pDlg))
{
errorMessage = "Failed to compute grid occupancy!";
return false;
}
}
//Build adjacency
if (!BuildAdjacency(maxThreadCount,
voxelCount,
&pDlg))
{
errorMessage = "Failed to build adjacency!";
return false;
}
qint64 gridTime_ms = gridTimer.elapsed();
//we display init. timing only if no error occurred!
if (app)
{
app->dispToConsole(QString("[VoxFall] Grid computation: %1 s").arg(gridTime_ms / 1000.0, 0, 'f', 3),
ccMainAppInterface::STD_CONSOLE_MESSAGE);
}
// EXTERIOR AIR FILTERING
//=======================================================================================================================
//Duration: Exterior filtering
QElapsedTimer exterTimer;
exterTimer.start();
//filter exterior air
if (!FloodFillExterior( maxThreadCount,
voxelCount,
&pDlg))
{
errorMessage = "Failed to compelete exterior air filtering!";
return false;
}
qint64 exterTime_ms = exterTimer.elapsed();
//we display block extraction timing only if no error occurred!
if (app)
{
app->dispToConsole(QString("[VoxFall] Exterior filtering: %1 s").arg(exterTime_ms / 1000.0, 0, 'f', 3),
ccMainAppInterface::STD_CONSOLE_MESSAGE);
}
// BLOCK DETECTION
//=======================================================================================================================
@@ -513,36 +681,12 @@ bool qVoxFallProcess::Compute(const qVoxFallDialog& dlg, QString& errorMessage,
QElapsedTimer detectTimer;
detectTimer.start();
if (!voxelGrid.intersectWith( mesh,
s_VoxFallParams.voxelSize,
s_VoxFallParams.minBound,
GetVoxelOccupancy,
&pDlg ))
{
errorMessage = "Failed to compute grid occupancy!";
return false;
}
if (s_VoxFallParams.exportLossGain)
{
if (!voxelGrid.intersectWith(mesh1,
s_VoxFallParams.voxelSize,
s_VoxFallParams.minBound,
GetVoxelOccupancyBefore,
&pDlg))
{
errorMessage = "Failed to compute grid occupancy!";
return false;
}
}
//cluster DBSCAN
if (!ClusterEmptySpace( maxThreadCount,
voxelGrid.innerCellCount(),
voxelCount,
&pDlg ))
{
errorMessage = "Failed to compute grid occupancy!";
errorMessage = "Failed to complete empty space clustering!";
return false;
}
@@ -753,7 +897,7 @@ bool qVoxFallProcess::Compute(const qVoxFallDialog& dlg, QString& errorMessage,
// OUTPUT FORMATION
//=======================================================================================================================
//associate cluster ID scalar fields to the voxel grid
//associate cluster ID scalar field to the voxel grid
int sfIdx = -1;
if (s_VoxFallParams.clusterSF)
{
@@ -763,7 +907,7 @@ bool qVoxFallProcess::Compute(const qVoxFallDialog& dlg, QString& errorMessage,
}
if (s_VoxFallParams.exportLossGain)
{
//associate change type scalar fields to the voxel grid
//associate change type scalar field to the voxel grid
if (s_VoxFallParams.changeTypeSF)
{
//add cluster ID SF to voxel grid
+3 -7
View File
@@ -50,9 +50,7 @@ ccBox* qVoxFallTransform::CreateVoxelMesh(CCVector3 V, float voxelSize, int voxe
return voxel;
}
std::vector<Tuple3i> qVoxFallTools::FindAdjacents(Tuple3i V, CCVector3 steps, bool facetsOnly=false)
std::vector<Tuple3i> qVoxFallTools::FindAdjacents(Tuple3i V, Tuple3i steps, bool facetsOnly=false)
{
std::vector<Tuple3i> set;
std::vector<std::vector<int>> adjacencyMatrix;
@@ -97,8 +95,7 @@ std::vector<Tuple3i> qVoxFallTools::FindAdjacents(Tuple3i V, CCVector3 steps, bo
return set;
}
int qVoxFallTools::Grid2Index(Tuple3i n, CCVector3 steps)
int qVoxFallTools::Grid2Index(Tuple3i n, Tuple3i steps)
{
int i = n.x;
int j = n.y;
@@ -108,8 +105,7 @@ int qVoxFallTools::Grid2Index(Tuple3i n, CCVector3 steps)
return index;
}
Tuple3i qVoxFallTools::Index2Grid(unsigned index, CCVector3 steps)
Tuple3i qVoxFallTools::Index2Grid(unsigned index, Tuple3i steps)
{
int k = std::floor(index / (int(steps.y) * int(steps.x)));
int remain = index - (int(steps.y) * int(steps.x) * k);