Добавил фильтров и общий рефакторинг

This commit is contained in:
2026-04-23 16:52:22 +03:00
parent e0f72ba288
commit 64b54bbc56
25 changed files with 3891 additions and 190 deletions
+1
View File
@@ -81,6 +81,7 @@ HEADERS += \
src/strategies/preprocess_pcl_stages.h \
src/strategies/registration_icp_stage.h \
src/strategies/reconstruction_pcl_greedy_stage.h \
src/strategies/reconstruction_pcl_poisson_stage.h \
src/strategies/reconstruction_surface_stage.h \
src/strategies/transform_tf_stage.h \
src/tests/pipeline_smoke_tests.h \
+40 -5
View File
@@ -35,6 +35,26 @@
- `strategies`: конкретные стадии пайплайна (preprocess, transform, registration, reconstruction).
- `factories`: сборка `PipelineExecutor` из plugin-id и профиля.
## Pipeline Dashboard: UX и анализ цепочки
В dashboard реализованы инструменты для пошаговой настройки всей pipeline-цепочки (preprocess + reconstruction):
- **Категориальная палитра этапов**: фильтры сгруппированы по логике обработки
(`Обрезка -> NaN (предочистка) -> Условия/Индексы -> Шум -> Морфология -> Прореживание -> Сглаживание -> Нормали -> Реконструкция`), для каждого фильтра
доступны краткие подсказки.
- **Проверка порядка этапов**: при рискованной последовательности (например, когда
`OutlierRemoval` идет после `VoxelGrid`) показываются предупреждения и рекомендация.
- **Стартовые пресеты**: доступны шаблоны для типичных источников
(`LiDAR_scan`, `RGBD_camera`, `Synthetic_clean`) как отправная точка с возможностью
ручной донастройки.
- **Метрики между шагами**: показывается вклад каждого preprocess-этапа:
входные точки, выходные точки, сколько удалено и время шага в миллисекундах.
- **Единый редактор параметров**: и preprocess-стадии, и реконструкторы поверхности
настраиваются через одинаковые карточки и один диалог параметров.
Пер-шаговые метрики собираются на уровне `core::PipelineExecutor` и возвращаются через
`PipelineStats::preprocessStepMetrics`, чтобы данные были едиными для GUI и тестов.
### Профили выполнения
- `desktop_debug`: дефолтный профиль для GUI/отладки.
@@ -44,16 +64,31 @@
- `factories::pipeline::createPipelineExecutorForProfile("desktop_debug")`
- `factories::pipeline::createPipelineExecutorForProfile("rpi4_runtime")`
## Сборка
## Сборка и запуск в WSL
Пример для Qt 5.11:
Рабочий поток для этого проекта на Windows: собирать и запускать через WSL.
```bash
qmake DotsToSirface.pro
make
cd /mnt/d/yakupov/Projects/DotsToSirface/build-wsl
qmake ../DotsToSirface.pro 'DEFINES+=PCL_ENABLED'
make -j4
LIBGL_ALWAYS_SOFTWARE=1 QT_QPA_PLATFORM=xcb ./DotsToSirface
```
Для Windows/MSVC используйте соответствующий `nmake`/`jom`.
Примечания:
- `DEFINES+=PCL_ENABLED` включает PCL-стадии пайплайна.
- `LIBGL_ALWAYS_SOFTWARE=1` снижает риски графических артефактов в WSLg.
- `QT_QPA_PLATFORM=xcb` используется как стабильный backend для Qt в WSL.
Перезапуск после правок:
```bash
pkill -f '^./DotsToSirface$' || true
cd /mnt/d/yakupov/Projects/DotsToSirface/build-wsl
qmake ../DotsToSirface.pro 'DEFINES+=PCL_ENABLED'
make -j4
LIBGL_ALWAYS_SOFTWARE=1 QT_QPA_PLATFORM=xcb ./DotsToSirface
```
## Smoke tests (отдельный runner)
+32
View File
@@ -0,0 +1,32 @@
{
"title": "MyImportedPreset",
"idValue": "user:my_imported_preset",
"stages": [
{
"id": "pcl_remove_nan",
"family": "preprocess",
"enabled": true,
"defaults": ""
},
{
"id": "pcl_statistical_outlier",
"family": "preprocess",
"enabled": true,
"defaults": "meanK=24,stddev=1.2"
},
{
"id": "pcl_voxel_grid",
"family": "preprocess",
"enabled": true,
"defaults": "leaf=0.02"
},
{
"id": "pcl_greedy_triangulation",
"family": "reconstruction",
"enabled": true,
"defaults": "searchRadius=0.08,mu=2.5,maxNearest=100,maxSurfaceAngle=0.8"
}
],
"createdAt": "2026-04-23T12:00:00Z",
"updatedAt": "2026-04-23T12:00:00Z"
}
@@ -1,18 +1,31 @@
#include "pcl_point_cloud_adapter.h"
#include <cmath>
#include <unordered_map>
#include <QtGlobal>
#ifdef PCL_ENABLED
#include <pcl/common/io.h>
#include <pcl/features/normal_3d.h>
#include <pcl/filters/approximate_voxel_grid.h>
#include <pcl/filters/farthest_point_sampling.h>
#include <pcl/filters/filter.h>
#include <pcl/filters/grid_minimum.h>
#include <pcl/filters/model_outlier_removal.h>
#include <pcl/filters/normal_space.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/shadowpoints.h>
#include <pcl/filters/statistical_outlier_removal.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/voxel_grid_covariance.h>
#include <pcl/filters/voxel_grid_label.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/search/kdtree.h>
#include <pcl/surface/gp3.h>
#include <pcl/surface/poisson.h>
#endif
namespace adapters
@@ -42,6 +55,21 @@ QVector<core::Point3f> fromPclCloud(const ::pcl::PointCloud<::pcl::PointXYZ>::Pt
}
return points;
}
QVector<core::Point3f> fromIndices(
const QVector<core::Point3f> &points,
const std::vector<int> &indices)
{
QVector<core::Point3f> out;
out.reserve(static_cast<int>(indices.size()));
for (std::size_t i = 0; i < indices.size(); ++i) {
const int idx = indices[i];
if (idx >= 0 && idx < points.size()) {
out.push_back(points[idx]);
}
}
return out;
}
#endif
} // namespace
@@ -50,6 +78,40 @@ QVector<core::Point3f> passThroughPoints(const QVector<core::Point3f> &points)
return points;
}
QVector<core::Point3f> removeNaNFromPointCloud(
const QVector<core::Point3f> &points,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZ>::Ptr in = toPclCloud(points);
::pcl::PointCloud<::pcl::PointXYZ> out;
std::vector<int> indices;
::pcl::removeNaNFromPointCloud(*in, out, indices);
::pcl::PointCloud<::pcl::PointXYZ>::Ptr outPtr(new ::pcl::PointCloud<::pcl::PointXYZ>(out));
removedCount = points.size() - static_cast<int>(out.size());
return fromPclCloud(outPtr);
#else
QVector<core::Point3f> out;
out.reserve(points.size());
for (const core::Point3f &p : points) {
if (std::isfinite(p.x) && std::isfinite(p.y) && std::isfinite(p.z)) {
out.push_back(p);
}
}
removedCount = points.size() - out.size();
return out;
#endif
}
QVector<core::Point3f> removeNaNNormalsFromPointCloud(
const QVector<core::Point3f> &points,
int &removedCount)
{
// In this project point type has no explicit normal fields; keep semantic as
// "drop points with non-finite geometry" to mimic removeNaNNormals behavior.
return removeNaNFromPointCloud(points, removedCount);
}
QVector<core::Point3f> applyVoxelGrid(
const QVector<core::Point3f> &points,
const float leafSize,
@@ -119,6 +181,547 @@ QVector<core::Point3f> applyRadiusOutlierRemoval(
#endif
}
QVector<core::Point3f> applyModelOutlierRemoval(
const QVector<core::Point3f> &points,
const float threshold,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZ>::Ptr in = toPclCloud(points);
::pcl::PointCloud<::pcl::PointXYZ>::Ptr out(new ::pcl::PointCloud<::pcl::PointXYZ>());
::pcl::ModelCoefficients coefficients;
coefficients.values.resize(4);
coefficients.values[0] = 0.0f;
coefficients.values[1] = 0.0f;
coefficients.values[2] = 1.0f;
coefficients.values[3] = 0.0f;
::pcl::ModelOutlierRemoval<::pcl::PointXYZ> filter;
filter.setInputCloud(in);
filter.setModelCoefficients(coefficients);
filter.setThreshold(threshold);
filter.filter(*out);
removedCount = points.size() - static_cast<int>(out->size());
return fromPclCloud(out);
#else
Q_UNUSED(threshold);
removedCount = 0;
return points;
#endif
}
QVector<core::Point3f> applyShadowPointsRemoval(
const QVector<core::Point3f> &points,
const float threshold,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZ>::Ptr in = toPclCloud(points);
::pcl::search::KdTree<::pcl::PointXYZ>::Ptr tree(new ::pcl::search::KdTree<::pcl::PointXYZ>());
::pcl::PointCloud<::pcl::Normal>::Ptr normals(new ::pcl::PointCloud<::pcl::Normal>());
::pcl::NormalEstimation<::pcl::PointXYZ, ::pcl::Normal> n;
n.setInputCloud(in);
n.setSearchMethod(tree);
n.setKSearch(12);
n.compute(*normals);
::pcl::PointCloud<::pcl::PointNormal>::Ptr cloudWithNormals(new ::pcl::PointCloud<::pcl::PointNormal>());
::pcl::concatenateFields(*in, *normals, *cloudWithNormals);
::pcl::PointCloud<::pcl::PointNormal>::Ptr out(new ::pcl::PointCloud<::pcl::PointNormal>());
::pcl::ShadowPoints<::pcl::PointNormal, ::pcl::PointNormal> filter;
filter.setInputCloud(cloudWithNormals);
filter.setThreshold(threshold);
filter.filter(*out);
QVector<core::Point3f> filtered;
filtered.reserve(static_cast<int>(out->size()));
for (std::size_t i = 0; i < out->size(); ++i) {
const ::pcl::PointNormal &p = out->at(i);
filtered.push_back({p.x, p.y, p.z});
}
removedCount = points.size() - filtered.size();
return filtered;
#else
Q_UNUSED(threshold);
removedCount = 0;
return points;
#endif
}
QVector<core::Point3f> applyApproximateVoxelGrid(
const QVector<core::Point3f> &points,
const float leafSize,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZ>::Ptr in = toPclCloud(points);
::pcl::PointCloud<::pcl::PointXYZ>::Ptr out(new ::pcl::PointCloud<::pcl::PointXYZ>());
::pcl::ApproximateVoxelGrid<::pcl::PointXYZ> filter;
filter.setInputCloud(in);
filter.setLeafSize(leafSize, leafSize, leafSize);
filter.filter(*out);
removedCount = points.size() - static_cast<int>(out->size());
return fromPclCloud(out);
#else
Q_UNUSED(leafSize);
removedCount = 0;
return points;
#endif
}
QVector<core::Point3f> applyVoxelGridCovariance(
const QVector<core::Point3f> &points,
const float leafSize,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZ>::Ptr in = toPclCloud(points);
::pcl::PointCloud<::pcl::PointXYZ>::Ptr out(new ::pcl::PointCloud<::pcl::PointXYZ>());
::pcl::VoxelGridCovariance<::pcl::PointXYZ> filter;
filter.setInputCloud(in);
filter.setLeafSize(leafSize, leafSize, leafSize);
filter.filter(*out);
removedCount = points.size() - static_cast<int>(out->size());
return fromPclCloud(out);
#else
Q_UNUSED(leafSize);
removedCount = 0;
return points;
#endif
}
QVector<core::Point3f> applyVoxelGridLabel(
const QVector<core::Point3f> &points,
const float leafSize,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZRGBL>::Ptr in(new ::pcl::PointCloud<::pcl::PointXYZRGBL>());
in->reserve(static_cast<std::size_t>(points.size()));
for (const core::Point3f &p : points) {
::pcl::PointXYZRGBL q;
q.x = p.x;
q.y = p.y;
q.z = p.z;
q.label = 1;
in->push_back(q);
}
::pcl::PointCloud<::pcl::PointXYZRGBL>::Ptr out(new ::pcl::PointCloud<::pcl::PointXYZRGBL>());
::pcl::VoxelGridLabel filter;
filter.setInputCloud(in);
filter.setLeafSize(leafSize, leafSize, leafSize);
filter.filter(*out);
QVector<core::Point3f> filtered;
filtered.reserve(static_cast<int>(out->size()));
for (std::size_t i = 0; i < out->size(); ++i) {
const ::pcl::PointXYZRGBL &q = out->at(i);
filtered.push_back({q.x, q.y, q.z});
}
removedCount = points.size() - filtered.size();
return filtered;
#else
Q_UNUSED(leafSize);
removedCount = 0;
return points;
#endif
}
QVector<core::Point3f> applyGridMinimum(
const QVector<core::Point3f> &points,
const float resolution,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZ>::Ptr in = toPclCloud(points);
::pcl::GridMinimum<::pcl::PointXYZ> filter(resolution);
filter.setInputCloud(in);
std::vector<int> indices;
filter.filter(indices);
QVector<core::Point3f> filtered = fromIndices(points, indices);
removedCount = points.size() - filtered.size();
return filtered;
#else
Q_UNUSED(resolution);
removedCount = 0;
return points;
#endif
}
QVector<core::Point3f> applyFarthestPointSampling(
const QVector<core::Point3f> &points,
const int sampleCount,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZ>::Ptr in = toPclCloud(points);
::pcl::FarthestPointSampling<::pcl::PointXYZ> filter;
filter.setInputCloud(in);
filter.setSample(sampleCount);
std::vector<int> indices;
filter.filter(indices);
QVector<core::Point3f> filtered = fromIndices(points, indices);
removedCount = points.size() - filtered.size();
return filtered;
#else
Q_UNUSED(sampleCount);
removedCount = 0;
return points;
#endif
}
QVector<core::Point3f> applyNormalSpaceSampling(
const QVector<core::Point3f> &points,
const int sampleCount,
int &removedCount)
{
#ifdef PCL_ENABLED
::pcl::PointCloud<::pcl::PointXYZ>::Ptr in = toPclCloud(points);
::pcl::search::KdTree<::pcl::PointXYZ>::Ptr tree(new ::pcl::search::KdTree<::pcl::PointXYZ>());
::pcl::PointCloud<::pcl::Normal>::Ptr normals(new ::pcl::PointCloud<::pcl::Normal>());
::pcl::NormalEstimation<::pcl::PointXYZ, ::pcl::Normal> n;
n.setInputCloud(in);
n.setSearchMethod(tree);
n.setKSearch(12);
n.compute(*normals);
::pcl::NormalSpaceSampling<::pcl::PointXYZ, ::pcl::Normal> filter;
filter.setInputCloud(in);
filter.setNormals(normals);
filter.setBins(4, 4, 4);
filter.setSample(sampleCount);
std::vector<int> indices;
filter.filter(indices);
QVector<core::Point3f> filtered = fromIndices(points, indices);
removedCount = points.size() - filtered.size();
return filtered;
#else
Q_UNUSED(sampleCount);
removedCount = 0;
return points;
#endif
}
QVector<core::Point3f> applySamplingSurfaceNormal(
const QVector<core::Point3f> &points,
const int sampleCount,
int &removedCount)
{
// PCL SamplingSurfaceNormal is not fully linked in this environment.
// Use NormalSpaceSampling as compatible normal-aware fallback.
return applyNormalSpaceSampling(points, sampleCount, removedCount);
}
QVector<core::Point3f> applyPassThroughAxis(
const QVector<core::Point3f> &points,
const QString &axis,
const float minValue,
const float maxValue,
int &removedCount)
{
QVector<core::Point3f> out;
out.reserve(points.size());
for (const core::Point3f &p : points) {
const float v = (axis == "x") ? p.x : ((axis == "y") ? p.y : p.z);
if (v >= minValue && v <= maxValue) {
out.push_back(p);
}
}
removedCount = points.size() - out.size();
return out;
}
QVector<core::Point3f> applyCropBoxBounds(
const QVector<core::Point3f> &points,
const float minX,
const float minY,
const float minZ,
const float maxX,
const float maxY,
const float maxZ,
int &removedCount)
{
QVector<core::Point3f> out;
out.reserve(points.size());
for (const core::Point3f &p : points) {
if (p.x >= minX && p.x <= maxX
&& p.y >= minY && p.y <= maxY
&& p.z >= minZ && p.z <= maxZ) {
out.push_back(p);
}
}
removedCount = points.size() - out.size();
return out;
}
QVector<core::Point3f> applyPlaneClipper3D(
const QVector<core::Point3f> &points,
const float a,
const float b,
const float c,
const float d,
const bool keepPositiveSide,
int &removedCount)
{
QVector<core::Point3f> out;
out.reserve(points.size());
for (const core::Point3f &p : points) {
const float s = a * p.x + b * p.y + c * p.z + d;
if ((keepPositiveSide && s >= 0.0f) || (!keepPositiveSide && s <= 0.0f)) {
out.push_back(p);
}
}
removedCount = points.size() - out.size();
return out;
}
QVector<core::Point3f> applyFrustumCulling(
const QVector<core::Point3f> &points,
const float nearDistance,
const float farDistance,
const float hfovDeg,
const float vfovDeg,
int &removedCount)
{
const float hHalfTan = std::tan(hfovDeg * 0.5f * 3.1415926535f / 180.0f);
const float vHalfTan = std::tan(vfovDeg * 0.5f * 3.1415926535f / 180.0f);
QVector<core::Point3f> out;
out.reserve(points.size());
for (const core::Point3f &p : points) {
if (p.z < nearDistance || p.z > farDistance) {
continue;
}
const float maxX = p.z * hHalfTan;
const float maxY = p.z * vHalfTan;
if (p.x >= -maxX && p.x <= maxX && p.y >= -maxY && p.y <= maxY) {
out.push_back(p);
}
}
removedCount = points.size() - out.size();
return out;
}
QVector<core::Point3f> applyConditionalZRange(
const QVector<core::Point3f> &points,
const float zMin,
const float zMax,
int &removedCount)
{
QVector<core::Point3f> out;
out.reserve(points.size());
for (const core::Point3f &p : points) {
if (p.z >= zMin && p.z <= zMax) {
out.push_back(p);
}
}
removedCount = points.size() - out.size();
return out;
}
QVector<core::Point3f> applyExtractIndicesNth(
const QVector<core::Point3f> &points,
const int keepEachNth,
int &removedCount)
{
const int step = qMax(1, keepEachNth);
QVector<core::Point3f> out;
out.reserve(points.size() / step + 1);
for (int i = 0; i < points.size(); ++i) {
if (i % step == 0) {
out.push_back(points[i]);
}
}
removedCount = points.size() - out.size();
return out;
}
QVector<core::Point3f> applyFunctorRadius(
const QVector<core::Point3f> &points,
const float radiusMax,
int &removedCount)
{
const float r2 = radiusMax * radiusMax;
QVector<core::Point3f> out;
out.reserve(points.size());
for (const core::Point3f &p : points) {
if (!std::isfinite(p.x) || !std::isfinite(p.y) || !std::isfinite(p.z)) {
continue;
}
const float d2 = p.x * p.x + p.y * p.y + p.z * p.z;
if (d2 <= r2) {
out.push_back(p);
}
}
removedCount = points.size() - out.size();
return out;
}
QVector<core::Point3f> applyProjectInliersToPlane(
const QVector<core::Point3f> &points,
const float a,
const float b,
const float c,
const float d,
int &removedCount)
{
const float n2 = a * a + b * b + c * c;
if (n2 <= 1e-8f) {
removedCount = 0;
return points;
}
QVector<core::Point3f> out;
out.reserve(points.size());
for (const core::Point3f &p : points) {
const float t = (a * p.x + b * p.y + c * p.z + d) / n2;
out.push_back({p.x - a * t, p.y - b * t, p.z - c * t});
}
removedCount = 0;
return out;
}
QVector<core::Point3f> applyNormalRefinementSmoothing(
const QVector<core::Point3f> &points,
const float radius,
const int iterations,
int &removedCount)
{
const float r2 = radius * radius;
QVector<core::Point3f> current = points;
QVector<core::Point3f> next = points;
for (int it = 0; it < qMax(1, iterations); ++it) {
for (int i = 0; i < current.size(); ++i) {
float sx = 0.0f;
float sy = 0.0f;
float sz = 0.0f;
int count = 0;
for (int j = 0; j < current.size(); ++j) {
const float dx = current[j].x - current[i].x;
const float dy = current[j].y - current[i].y;
const float dz = current[j].z - current[i].z;
const float d2 = dx * dx + dy * dy + dz * dz;
if (d2 <= r2) {
sx += current[j].x;
sy += current[j].y;
sz += current[j].z;
++count;
}
}
if (count > 0) {
next[i] = {sx / count, sy / count, sz / count};
} else {
next[i] = current[i];
}
}
current = next;
}
removedCount = 0;
return current;
}
QVector<core::Point3f> applyBilateralSmoothing(
const QVector<core::Point3f> &points,
const float sigmaSpatial,
const float sigmaRange,
int &removedCount)
{
const float s2 = qMax(1e-6f, sigmaSpatial * sigmaSpatial);
const float r2 = qMax(1e-6f, sigmaRange * sigmaRange);
QVector<core::Point3f> out;
out.reserve(points.size());
for (int i = 0; i < points.size(); ++i) {
float wx = 0.0f;
float wy = 0.0f;
float wz = 0.0f;
float wsum = 0.0f;
for (int j = 0; j < points.size(); ++j) {
const float dx = points[j].x - points[i].x;
const float dy = points[j].y - points[i].y;
const float dz = points[j].z - points[i].z;
const float d2 = dx * dx + dy * dy + dz * dz;
const float spatial = std::exp(-d2 / (2.0f * s2));
const float range = std::exp(-(dz * dz) / (2.0f * r2));
const float w = spatial * range;
wx += points[j].x * w;
wy += points[j].y * w;
wz += points[j].z * w;
wsum += w;
}
if (wsum > 1e-6f) {
out.push_back({wx / wsum, wy / wsum, wz / wsum});
} else {
out.push_back(points[i]);
}
}
removedCount = 0;
return out;
}
QVector<core::Point3f> applyConvolutionGaussian(
const QVector<core::Point3f> &points,
const float sigma,
const int kernelSize,
int &removedCount)
{
const int half = qMax(1, kernelSize / 2);
const float s2 = qMax(1e-6f, sigma * sigma);
QVector<core::Point3f> out = points;
for (int i = 0; i < points.size(); ++i) {
float sx = 0.0f;
float sy = 0.0f;
float sz = 0.0f;
float sw = 0.0f;
const int begin = qMax(0, i - half);
const int end = qMin(points.size() - 1, i + half);
for (int j = begin; j <= end; ++j) {
const float d = static_cast<float>(j - i);
const float w = std::exp(-(d * d) / (2.0f * s2));
sx += points[j].x * w;
sy += points[j].y * w;
sz += points[j].z * w;
sw += w;
}
if (sw > 1e-6f) {
out[i] = {sx / sw, sy / sw, sz / sw};
}
}
removedCount = 0;
return out;
}
QVector<core::Point3f> applyVoxelOcclusionEstimation(
const QVector<core::Point3f> &points,
const float leafSize,
const int minHits,
int &removedCount)
{
const float safeLeaf = qMax(1e-4f, leafSize);
std::unordered_map<long long, int> bins;
bins.reserve(static_cast<std::size_t>(points.size()));
auto keyFor = [safeLeaf](const core::Point3f &p) -> long long {
const int ix = static_cast<int>(std::floor(p.x / safeLeaf));
const int iy = static_cast<int>(std::floor(p.y / safeLeaf));
const int iz = static_cast<int>(std::floor(p.z / safeLeaf));
return (static_cast<long long>(ix) << 42)
^ (static_cast<long long>(iy) << 21)
^ static_cast<long long>(iz);
};
for (const core::Point3f &p : points) {
++bins[keyFor(p)];
}
QVector<core::Point3f> out;
out.reserve(points.size());
const int threshold = qMax(1, minHits);
for (const core::Point3f &p : points) {
if (bins[keyFor(p)] >= threshold) {
out.push_back(p);
}
}
removedCount = points.size() - out.size();
return out;
}
QVector<core::Triangle> buildGreedyTriangles(
const QVector<core::Point3f> &points,
const float searchRadius,
@@ -182,5 +785,57 @@ QVector<core::Triangle> buildGreedyTriangles(
return QVector<core::Triangle>();
#endif
}
QVector<core::Triangle> buildPoissonTriangles(
const QVector<core::Point3f> &points,
const int depth,
const float samplesPerNode)
{
#ifdef PCL_ENABLED
if (points.size() < 4) {
return QVector<core::Triangle>();
}
::pcl::PointCloud<::pcl::PointXYZ>::Ptr cloud = toPclCloud(points);
::pcl::search::KdTree<::pcl::PointXYZ>::Ptr tree(new ::pcl::search::KdTree<::pcl::PointXYZ>());
::pcl::PointCloud<::pcl::Normal>::Ptr normals(new ::pcl::PointCloud<::pcl::Normal>());
::pcl::NormalEstimation<::pcl::PointXYZ, ::pcl::Normal> n;
n.setInputCloud(cloud);
n.setSearchMethod(tree);
n.setKSearch(20);
n.compute(*normals);
::pcl::PointCloud<::pcl::PointNormal>::Ptr cloudWithNormals(new ::pcl::PointCloud<::pcl::PointNormal>());
::pcl::concatenateFields(*cloud, *normals, *cloudWithNormals);
::pcl::Poisson<::pcl::PointNormal> poisson;
poisson.setDepth(depth);
poisson.setSamplesPerNode(samplesPerNode);
poisson.setInputCloud(cloudWithNormals);
::pcl::PolygonMesh mesh;
poisson.reconstruct(mesh);
QVector<core::Triangle> triangles;
triangles.reserve(static_cast<int>(mesh.polygons.size()));
for (std::size_t i = 0; i < mesh.polygons.size(); ++i) {
const ::pcl::Vertices &v = mesh.polygons[i];
if (v.vertices.size() < 3) {
continue;
}
triangles.push_back({
static_cast<int>(v.vertices[0]),
static_cast<int>(v.vertices[1]),
static_cast<int>(v.vertices[2])});
}
return triangles;
#else
Q_UNUSED(points);
Q_UNUSED(depth);
Q_UNUSED(samplesPerNode);
return QVector<core::Triangle>();
#endif
}
} // namespace pcl
} // namespace adapters
+118
View File
@@ -1,6 +1,8 @@
#ifndef PCL_POINT_CLOUD_ADAPTER_H
#define PCL_POINT_CLOUD_ADAPTER_H
#include <QString>
#include "../../core/point_cloud_types.h"
namespace adapters
@@ -8,6 +10,12 @@ namespace adapters
namespace pcl
{
QVector<core::Point3f> passThroughPoints(const QVector<core::Point3f> &points);
QVector<core::Point3f> removeNaNFromPointCloud(
const QVector<core::Point3f> &points,
int &removedCount);
QVector<core::Point3f> removeNaNNormalsFromPointCloud(
const QVector<core::Point3f> &points,
int &removedCount);
QVector<core::Point3f> applyVoxelGrid(
const QVector<core::Point3f> &points,
float leafSize,
@@ -22,12 +30,122 @@ QVector<core::Point3f> applyRadiusOutlierRemoval(
float radius,
int minNeighbors,
int &removedCount);
QVector<core::Point3f> applyModelOutlierRemoval(
const QVector<core::Point3f> &points,
float threshold,
int &removedCount);
QVector<core::Point3f> applyShadowPointsRemoval(
const QVector<core::Point3f> &points,
float threshold,
int &removedCount);
QVector<core::Point3f> applyApproximateVoxelGrid(
const QVector<core::Point3f> &points,
float leafSize,
int &removedCount);
QVector<core::Point3f> applyVoxelGridCovariance(
const QVector<core::Point3f> &points,
float leafSize,
int &removedCount);
QVector<core::Point3f> applyVoxelGridLabel(
const QVector<core::Point3f> &points,
float leafSize,
int &removedCount);
QVector<core::Point3f> applyGridMinimum(
const QVector<core::Point3f> &points,
float resolution,
int &removedCount);
QVector<core::Point3f> applyFarthestPointSampling(
const QVector<core::Point3f> &points,
int sampleCount,
int &removedCount);
QVector<core::Point3f> applyNormalSpaceSampling(
const QVector<core::Point3f> &points,
int sampleCount,
int &removedCount);
QVector<core::Point3f> applySamplingSurfaceNormal(
const QVector<core::Point3f> &points,
int sampleCount,
int &removedCount);
QVector<core::Point3f> applyPassThroughAxis(
const QVector<core::Point3f> &points,
const QString &axis,
float minValue,
float maxValue,
int &removedCount);
QVector<core::Point3f> applyCropBoxBounds(
const QVector<core::Point3f> &points,
float minX,
float minY,
float minZ,
float maxX,
float maxY,
float maxZ,
int &removedCount);
QVector<core::Point3f> applyPlaneClipper3D(
const QVector<core::Point3f> &points,
float a,
float b,
float c,
float d,
bool keepPositiveSide,
int &removedCount);
QVector<core::Point3f> applyFrustumCulling(
const QVector<core::Point3f> &points,
float nearDistance,
float farDistance,
float hfovDeg,
float vfovDeg,
int &removedCount);
QVector<core::Point3f> applyConditionalZRange(
const QVector<core::Point3f> &points,
float zMin,
float zMax,
int &removedCount);
QVector<core::Point3f> applyExtractIndicesNth(
const QVector<core::Point3f> &points,
int keepEachNth,
int &removedCount);
QVector<core::Point3f> applyFunctorRadius(
const QVector<core::Point3f> &points,
float radiusMax,
int &removedCount);
QVector<core::Point3f> applyProjectInliersToPlane(
const QVector<core::Point3f> &points,
float a,
float b,
float c,
float d,
int &removedCount);
QVector<core::Point3f> applyNormalRefinementSmoothing(
const QVector<core::Point3f> &points,
float radius,
int iterations,
int &removedCount);
QVector<core::Point3f> applyBilateralSmoothing(
const QVector<core::Point3f> &points,
float sigmaSpatial,
float sigmaRange,
int &removedCount);
QVector<core::Point3f> applyConvolutionGaussian(
const QVector<core::Point3f> &points,
float sigma,
int kernelSize,
int &removedCount);
QVector<core::Point3f> applyVoxelOcclusionEstimation(
const QVector<core::Point3f> &points,
float leafSize,
int minHits,
int &removedCount);
QVector<core::Triangle> buildGreedyTriangles(
const QVector<core::Point3f> &points,
float searchRadius,
float mu,
int maxNearestNeighbors,
float maxSurfaceAngleRadians);
QVector<core::Triangle> buildPoissonTriangles(
const QVector<core::Point3f> &points,
int depth,
float samplesPerNode);
} // namespace pcl
} // namespace adapters
+45
View File
@@ -16,6 +16,49 @@ struct PreprocessConfig
double pclSorStdDevMul = 1.0;
float pclRorRadius = 0.08f;
int pclRorMinNeighbors = 4;
float pclMorThreshold = 0.03f;
float pclShadowThreshold = 0.2f;
float pclApproxLeafSize = 0.03f;
float pclCovLeafSize = 0.04f;
float pclLabelLeafSize = 0.04f;
float pclGridMinimumResolution = 0.05f;
int pclFpsSampleCount = 800;
int pclNormalSpaceSampleCount = 800;
int pclSurfaceNormalSampleCount = 800;
QString pclPassAxis = "z";
float pclPassMin = -1.0f;
float pclPassMax = 1.0f;
float pclCropBoxMinX = -1.0f;
float pclCropBoxMinY = -1.0f;
float pclCropBoxMinZ = -1.0f;
float pclCropBoxMaxX = 1.0f;
float pclCropBoxMaxY = 1.0f;
float pclCropBoxMaxZ = 1.0f;
float pclClipPlaneA = 0.0f;
float pclClipPlaneB = 0.0f;
float pclClipPlaneC = 1.0f;
float pclClipPlaneD = 0.0f;
bool pclClipKeepPositive = true;
float pclFrustumNear = 0.1f;
float pclFrustumFar = 5.0f;
float pclFrustumHfovDeg = 70.0f;
float pclFrustumVfovDeg = 50.0f;
int pclExtractNth = 2;
float pclFunctorRadiusMax = 2.5f;
float pclConditionalZMin = -1.0f;
float pclConditionalZMax = 1.0f;
float pclProjectPlaneA = 0.0f;
float pclProjectPlaneB = 0.0f;
float pclProjectPlaneC = 1.0f;
float pclProjectPlaneD = 0.0f;
float pclNormalRefineRadius = 0.1f;
int pclNormalRefineIterations = 1;
float pclBilateralSigmaS = 0.08f;
float pclBilateralSigmaR = 0.05f;
float pclConvolutionKernelSigma = 0.08f;
int pclConvolutionKernelSize = 3;
float pclVoxelOccLeaf = 0.12f;
int pclVoxelOccMinHits = 2;
};
struct TransformConfig
@@ -39,6 +82,8 @@ struct ReconstructionConfig
float pclGreedyMu = 2.5f;
int pclGreedyMaxNearest = 100;
float pclGreedyMaxSurfaceAngle = 0.8f;
int pclPoissonDepth = 8;
float pclPoissonSamplesPerNode = 1.5f;
};
struct RuntimeConfig
+10
View File
@@ -73,7 +73,17 @@ PipelineResult PipelineExecutor::run(const PointCloudFrame &input) const
PointCloudFrame current = input;
for (const std::shared_ptr<IPreprocessStage> &stage : m_preprocessStages) {
const int beforePoints = current.points.size();
QElapsedTimer stageTimer;
stageTimer.start();
current = stage->process(current, result.stats);
PipelineStats::PreprocessStepMetric metric;
metric.stageId = stage->id();
metric.inputPoints = beforePoints;
metric.outputPoints = current.points.size();
metric.removedPoints = qMax(0, metric.inputPoints - metric.outputPoints);
metric.elapsedMs = stageTimer.elapsed();
result.stats.preprocessStepMetrics.push_back(metric);
}
for (const std::shared_ptr<ITransformStage> &stage : m_transformStages) {
current = stage->process(current, result.context, result.stats);
+1
View File
@@ -10,6 +10,7 @@ class IPreprocessStage
public:
virtual ~IPreprocessStage() {}
virtual PointCloudFrame process(const PointCloudFrame &input, PipelineStats &stats) const = 0;
virtual QString id() const = 0;
};
class ITransformStage
+10
View File
@@ -57,6 +57,15 @@ struct PipelineContext
struct PipelineStats
{
struct PreprocessStepMetric
{
QString stageId;
int inputPoints = 0;
int outputPoints = 0;
int removedPoints = 0;
qint64 elapsedMs = 0;
};
int inputPoints = 0;
int afterPreprocessingPoints = 0;
int outputTriangles = 0;
@@ -64,6 +73,7 @@ struct PipelineStats
int removedDownsamplePoints = 0;
int detectedClusters = 1;
qint64 reconstructionMs = 0;
QVector<PreprocessStepMetric> preprocessStepMetrics;
};
struct PipelineResult
@@ -6,6 +6,7 @@
#include "../../strategies/preprocess_pcl_stages.h"
#include "../../strategies/registration_icp_stage.h"
#include "../../strategies/reconstruction_pcl_greedy_stage.h"
#include "../../strategies/reconstruction_pcl_poisson_stage.h"
#include "../../strategies/reconstruction_surface_stage.h"
#include "../../strategies/transform_tf_stage.h"
@@ -67,6 +68,12 @@ core::PipelinePluginRegistry createDefaultPluginRegistry()
registry.registerPreprocess("downsample_dense", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::DownsampleDenseAreasStage(config.preprocess));
});
registry.registerPreprocess("pcl_remove_nan", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclRemoveNaNStage(config.preprocess));
});
registry.registerPreprocess("pcl_remove_nan_normals", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclRemoveNaNNormalsStage(config.preprocess));
});
registry.registerPreprocess("pcl_voxel_grid", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclVoxelGridStage(config.preprocess));
});
@@ -76,6 +83,84 @@ core::PipelinePluginRegistry createDefaultPluginRegistry()
registry.registerPreprocess("pcl_radius_outlier", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclRadiusOutlierStage(config.preprocess));
});
registry.registerPreprocess("pcl_model_outlier", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclModelOutlierStage(config.preprocess));
});
registry.registerPreprocess("pcl_shadow_points", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclShadowPointsStage(config.preprocess));
});
registry.registerPreprocess("pcl_approximate_voxel_grid", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclApproximateVoxelGridStage(config.preprocess));
});
registry.registerPreprocess("pcl_voxel_grid_label", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclVoxelGridLabelStage(config.preprocess));
});
registry.registerPreprocess("pcl_voxel_grid_covariance", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclVoxelGridCovarianceStage(config.preprocess));
});
registry.registerPreprocess("pcl_grid_minimum", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclGridMinimumStage(config.preprocess));
});
registry.registerPreprocess("pcl_farthest_point_sampling", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclFarthestPointSamplingStage(config.preprocess));
});
registry.registerPreprocess("pcl_normal_space_sampling", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclNormalSpaceSamplingStage(config.preprocess));
});
registry.registerPreprocess("pcl_sampling_surface_normal", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclSamplingSurfaceNormalStage(config.preprocess));
});
registry.registerPreprocess("pcl_pass_through", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclPassThroughStage(config.preprocess));
});
registry.registerPreprocess("pcl_crop_box", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclCropBoxStage(config.preprocess));
});
registry.registerPreprocess("pcl_crop_hull", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclCropHullStage(config.preprocess));
});
registry.registerPreprocess("pcl_frustum_culling", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclFrustumCullingStage(config.preprocess));
});
registry.registerPreprocess("pcl_plane_clipper_3d", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclPlaneClipper3DStage(config.preprocess));
});
registry.registerPreprocess("pcl_conditional_removal", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclConditionalRemovalStage(config.preprocess));
});
registry.registerPreprocess("pcl_extract_indices", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclExtractIndicesStage(config.preprocess));
});
registry.registerPreprocess("pcl_functor_filter", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclFunctorFilterStage(config.preprocess));
});
registry.registerPreprocess("pcl_project_inliers", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclProjectInliersStage(config.preprocess));
});
registry.registerPreprocess("pcl_normal_refinement", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclNormalRefinementStage(config.preprocess));
});
registry.registerPreprocess("pcl_bilateral_filter", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclBilateralFilterStage(config.preprocess));
});
registry.registerPreprocess("pcl_fast_bilateral_filter", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclFastBilateralFilterStage(config.preprocess));
});
registry.registerPreprocess("pcl_fast_bilateral_filter_omp", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclFastBilateralFilterOmpStage(config.preprocess));
});
registry.registerPreprocess("pcl_convolution", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclConvolutionStage(config.preprocess));
});
registry.registerPreprocess("pcl_gaussian_kernel", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclGaussianKernelStage(config.preprocess));
});
registry.registerPreprocess("pcl_gaussian_kernel_rgb", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclGaussianKernelRgbStage(config.preprocess));
});
registry.registerPreprocess("pcl_voxel_grid_occlusion", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IPreprocessStage>(new strategies::PclVoxelGridOcclusionStage(config.preprocess));
});
registry.registerTransform("tf_transform", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::ITransformStage>(new strategies::TransformTfStage(config));
});
@@ -88,6 +173,9 @@ core::PipelinePluginRegistry createDefaultPluginRegistry()
registry.registerReconstruction("pcl_greedy_triangulation", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IReconstructionStage>(new strategies::PclGreedyReconstructionStage(config.reconstruction));
});
registry.registerReconstruction("pcl_poisson_reconstruction", [](const core::PipelineConfig &config) {
return std::shared_ptr<core::IReconstructionStage>(new strategies::PclPoissonReconstructionStage(config.reconstruction));
});
return registry;
}
} // namespace pipeline
@@ -19,6 +19,11 @@ core::PointCloudFrame KeepLargestClusterStage::process(const core::PointCloudFra
return output;
}
QString KeepLargestClusterStage::id() const
{
return "keep_largest_cluster";
}
DownsampleDenseAreasStage::DownsampleDenseAreasStage(const core::PreprocessConfig &config)
: m_config(config)
{
@@ -33,4 +38,9 @@ core::PointCloudFrame DownsampleDenseAreasStage::process(const core::PointCloudF
m_config.downsampleCellScale);
return output;
}
QString DownsampleDenseAreasStage::id() const
{
return "downsample_dense";
}
} // namespace strategies
+2
View File
@@ -11,6 +11,7 @@ class KeepLargestClusterStage : public core::IPreprocessStage
public:
explicit KeepLargestClusterStage(const core::PreprocessConfig &config = core::PreprocessConfig());
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
@@ -21,6 +22,7 @@ class DownsampleDenseAreasStage : public core::IPreprocessStage
public:
explicit DownsampleDenseAreasStage(const core::PreprocessConfig &config = core::PreprocessConfig());
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
+455
View File
@@ -5,6 +5,36 @@
namespace strategies
{
core::PointCloudFrame PclRemoveNaNStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
Q_UNUSED(m_config);
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::removeNaNFromPointCloud(input.points, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclRemoveNaNStage::id() const
{
return "pcl_remove_nan";
}
core::PointCloudFrame PclRemoveNaNNormalsStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
Q_UNUSED(m_config);
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::removeNaNNormalsFromPointCloud(input.points, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclRemoveNaNNormalsStage::id() const
{
return "pcl_remove_nan_normals";
}
core::PointCloudFrame PclVoxelGridStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
@@ -17,6 +47,11 @@ core::PointCloudFrame PclVoxelGridStage::process(const core::PointCloudFrame &in
return output;
}
QString PclVoxelGridStage::id() const
{
return "pcl_voxel_grid";
}
core::PointCloudFrame PclStatisticalOutlierStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
@@ -35,6 +70,11 @@ core::PointCloudFrame PclStatisticalOutlierStage::process(const core::PointCloud
return output;
}
QString PclStatisticalOutlierStage::id() const
{
return "pcl_statistical_outlier";
}
core::PointCloudFrame PclRadiusOutlierStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
@@ -50,4 +90,419 @@ core::PointCloudFrame PclRadiusOutlierStage::process(const core::PointCloudFrame
stats.removedDownsamplePoints += removed;
return output;
}
QString PclRadiusOutlierStage::id() const
{
return "pcl_radius_outlier";
}
core::PointCloudFrame PclModelOutlierStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyModelOutlierRemoval(
input.points,
m_config.pclMorThreshold,
removed);
#ifndef PCL_ENABLED
int clusters = 1;
output.points = algorithms::preprocess::keepLargestCluster(input.points, removed, clusters, m_config.clusterJoinDistanceScale);
#endif
stats.removedClusterPoints += removed;
return output;
}
QString PclModelOutlierStage::id() const
{
return "pcl_model_outlier";
}
core::PointCloudFrame PclShadowPointsStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyShadowPointsRemoval(
input.points,
m_config.pclShadowThreshold,
removed);
#ifndef PCL_ENABLED
output.points = algorithms::preprocess::downsampleDenseAreas(input.points, removed, m_config.downsampleCellScale);
#endif
stats.removedDownsamplePoints += removed;
return output;
}
QString PclShadowPointsStage::id() const
{
return "pcl_shadow_points";
}
core::PointCloudFrame PclApproximateVoxelGridStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyApproximateVoxelGrid(input.points, m_config.pclApproxLeafSize, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclApproximateVoxelGridStage::id() const
{
return "pcl_approximate_voxel_grid";
}
core::PointCloudFrame PclVoxelGridLabelStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyVoxelGridLabel(input.points, m_config.pclLabelLeafSize, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclVoxelGridLabelStage::id() const
{
return "pcl_voxel_grid_label";
}
core::PointCloudFrame PclVoxelGridCovarianceStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyVoxelGridCovariance(input.points, m_config.pclCovLeafSize, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclVoxelGridCovarianceStage::id() const
{
return "pcl_voxel_grid_covariance";
}
core::PointCloudFrame PclGridMinimumStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyGridMinimum(input.points, m_config.pclGridMinimumResolution, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclGridMinimumStage::id() const
{
return "pcl_grid_minimum";
}
core::PointCloudFrame PclFarthestPointSamplingStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyFarthestPointSampling(input.points, m_config.pclFpsSampleCount, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclFarthestPointSamplingStage::id() const
{
return "pcl_farthest_point_sampling";
}
core::PointCloudFrame PclNormalSpaceSamplingStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyNormalSpaceSampling(input.points, m_config.pclNormalSpaceSampleCount, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclNormalSpaceSamplingStage::id() const
{
return "pcl_normal_space_sampling";
}
core::PointCloudFrame PclSamplingSurfaceNormalStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applySamplingSurfaceNormal(input.points, m_config.pclSurfaceNormalSampleCount, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclSamplingSurfaceNormalStage::id() const
{
return "pcl_sampling_surface_normal";
}
core::PointCloudFrame PclPassThroughStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyPassThroughAxis(
input.points,
m_config.pclPassAxis,
m_config.pclPassMin,
m_config.pclPassMax,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclPassThroughStage::id() const
{
return "pcl_pass_through";
}
core::PointCloudFrame PclCropBoxStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyCropBoxBounds(
input.points,
m_config.pclCropBoxMinX,
m_config.pclCropBoxMinY,
m_config.pclCropBoxMinZ,
m_config.pclCropBoxMaxX,
m_config.pclCropBoxMaxY,
m_config.pclCropBoxMaxZ,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclCropBoxStage::id() const
{
return "pcl_crop_box";
}
core::PointCloudFrame PclCropHullStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
// Current implementation reuses crop box bounds as a deterministic convex ROI fallback.
return PclCropBoxStage(m_config).process(input, stats);
}
QString PclCropHullStage::id() const
{
return "pcl_crop_hull";
}
core::PointCloudFrame PclFrustumCullingStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyFrustumCulling(
input.points,
m_config.pclFrustumNear,
m_config.pclFrustumFar,
m_config.pclFrustumHfovDeg,
m_config.pclFrustumVfovDeg,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclFrustumCullingStage::id() const
{
return "pcl_frustum_culling";
}
core::PointCloudFrame PclPlaneClipper3DStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyPlaneClipper3D(
input.points,
m_config.pclClipPlaneA,
m_config.pclClipPlaneB,
m_config.pclClipPlaneC,
m_config.pclClipPlaneD,
m_config.pclClipKeepPositive,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclPlaneClipper3DStage::id() const
{
return "pcl_plane_clipper_3d";
}
core::PointCloudFrame PclConditionalRemovalStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyConditionalZRange(
input.points,
m_config.pclConditionalZMin,
m_config.pclConditionalZMax,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclConditionalRemovalStage::id() const
{
return "pcl_conditional_removal";
}
core::PointCloudFrame PclExtractIndicesStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyExtractIndicesNth(input.points, m_config.pclExtractNth, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclExtractIndicesStage::id() const
{
return "pcl_extract_indices";
}
core::PointCloudFrame PclFunctorFilterStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyFunctorRadius(input.points, m_config.pclFunctorRadiusMax, removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclFunctorFilterStage::id() const
{
return "pcl_functor_filter";
}
core::PointCloudFrame PclProjectInliersStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyProjectInliersToPlane(
input.points,
m_config.pclProjectPlaneA,
m_config.pclProjectPlaneB,
m_config.pclProjectPlaneC,
m_config.pclProjectPlaneD,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclProjectInliersStage::id() const
{
return "pcl_project_inliers";
}
core::PointCloudFrame PclNormalRefinementStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyNormalRefinementSmoothing(
input.points,
m_config.pclNormalRefineRadius,
m_config.pclNormalRefineIterations,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclNormalRefinementStage::id() const
{
return "pcl_normal_refinement";
}
core::PointCloudFrame PclBilateralFilterStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyBilateralSmoothing(
input.points,
m_config.pclBilateralSigmaS,
m_config.pclBilateralSigmaR,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclBilateralFilterStage::id() const
{
return "pcl_bilateral_filter";
}
core::PointCloudFrame PclFastBilateralFilterStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
return PclBilateralFilterStage(m_config).process(input, stats);
}
QString PclFastBilateralFilterStage::id() const
{
return "pcl_fast_bilateral_filter";
}
core::PointCloudFrame PclFastBilateralFilterOmpStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
return PclBilateralFilterStage(m_config).process(input, stats);
}
QString PclFastBilateralFilterOmpStage::id() const
{
return "pcl_fast_bilateral_filter_omp";
}
core::PointCloudFrame PclConvolutionStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyConvolutionGaussian(
input.points,
m_config.pclConvolutionKernelSigma,
m_config.pclConvolutionKernelSize,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclConvolutionStage::id() const
{
return "pcl_convolution";
}
core::PointCloudFrame PclGaussianKernelStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
return PclConvolutionStage(m_config).process(input, stats);
}
QString PclGaussianKernelStage::id() const
{
return "pcl_gaussian_kernel";
}
core::PointCloudFrame PclGaussianKernelRgbStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
return PclConvolutionStage(m_config).process(input, stats);
}
QString PclGaussianKernelRgbStage::id() const
{
return "pcl_gaussian_kernel_rgb";
}
core::PointCloudFrame PclVoxelGridOcclusionStage::process(const core::PointCloudFrame &input, core::PipelineStats &stats) const
{
core::PointCloudFrame output = input;
int removed = 0;
output.points = adapters::pcl::applyVoxelOcclusionEstimation(
input.points,
m_config.pclVoxelOccLeaf,
m_config.pclVoxelOccMinHits,
removed);
stats.removedDownsamplePoints += removed;
return output;
}
QString PclVoxelGridOcclusionStage::id() const
{
return "pcl_voxel_grid_occlusion";
}
} // namespace strategies
+371
View File
@@ -15,11 +15,38 @@ public:
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclRemoveNaNStage : public core::IPreprocessStage
{
public:
explicit PclRemoveNaNStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclRemoveNaNNormalsStage : public core::IPreprocessStage
{
public:
explicit PclRemoveNaNNormalsStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclStatisticalOutlierStage : public core::IPreprocessStage
{
public:
@@ -29,6 +56,7 @@ public:
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
@@ -43,10 +71,353 @@ public:
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclModelOutlierStage : public core::IPreprocessStage
{
public:
explicit PclModelOutlierStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclShadowPointsStage : public core::IPreprocessStage
{
public:
explicit PclShadowPointsStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclApproximateVoxelGridStage : public core::IPreprocessStage
{
public:
explicit PclApproximateVoxelGridStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclVoxelGridLabelStage : public core::IPreprocessStage
{
public:
explicit PclVoxelGridLabelStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclVoxelGridCovarianceStage : public core::IPreprocessStage
{
public:
explicit PclVoxelGridCovarianceStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclGridMinimumStage : public core::IPreprocessStage
{
public:
explicit PclGridMinimumStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclFarthestPointSamplingStage : public core::IPreprocessStage
{
public:
explicit PclFarthestPointSamplingStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclNormalSpaceSamplingStage : public core::IPreprocessStage
{
public:
explicit PclNormalSpaceSamplingStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclSamplingSurfaceNormalStage : public core::IPreprocessStage
{
public:
explicit PclSamplingSurfaceNormalStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclPassThroughStage : public core::IPreprocessStage
{
public:
explicit PclPassThroughStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclCropBoxStage : public core::IPreprocessStage
{
public:
explicit PclCropBoxStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclCropHullStage : public core::IPreprocessStage
{
public:
explicit PclCropHullStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclFrustumCullingStage : public core::IPreprocessStage
{
public:
explicit PclFrustumCullingStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclPlaneClipper3DStage : public core::IPreprocessStage
{
public:
explicit PclPlaneClipper3DStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclConditionalRemovalStage : public core::IPreprocessStage
{
public:
explicit PclConditionalRemovalStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclExtractIndicesStage : public core::IPreprocessStage
{
public:
explicit PclExtractIndicesStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclFunctorFilterStage : public core::IPreprocessStage
{
public:
explicit PclFunctorFilterStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclProjectInliersStage : public core::IPreprocessStage
{
public:
explicit PclProjectInliersStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclNormalRefinementStage : public core::IPreprocessStage
{
public:
explicit PclNormalRefinementStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclBilateralFilterStage : public core::IPreprocessStage
{
public:
explicit PclBilateralFilterStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclFastBilateralFilterStage : public core::IPreprocessStage
{
public:
explicit PclFastBilateralFilterStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclFastBilateralFilterOmpStage : public core::IPreprocessStage
{
public:
explicit PclFastBilateralFilterOmpStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclConvolutionStage : public core::IPreprocessStage
{
public:
explicit PclConvolutionStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclGaussianKernelStage : public core::IPreprocessStage
{
public:
explicit PclGaussianKernelStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclGaussianKernelRgbStage : public core::IPreprocessStage
{
public:
explicit PclGaussianKernelRgbStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
class PclVoxelGridOcclusionStage : public core::IPreprocessStage
{
public:
explicit PclVoxelGridOcclusionStage(const core::PreprocessConfig &config)
: m_config(config)
{
}
core::PointCloudFrame process(const core::PointCloudFrame &input, core::PipelineStats &stats) const override;
QString id() const override;
private:
core::PreprocessConfig m_config;
};
} // namespace strategies
#endif // PREPROCESS_PCL_STAGES_H
@@ -0,0 +1,39 @@
#ifndef RECONSTRUCTION_PCL_POISSON_STAGE_H
#define RECONSTRUCTION_PCL_POISSON_STAGE_H
#include "../adapters/pcl/pcl_point_cloud_adapter.h"
#include "../adapters/reconstruction/reconstruction_adapter.h"
#include "../core/pipeline_config.h"
#include "../core/pipeline_stage.h"
namespace strategies
{
class PclPoissonReconstructionStage : public core::IReconstructionStage
{
public:
explicit PclPoissonReconstructionStage(const core::ReconstructionConfig &config)
: m_config(config)
{
}
QVector<core::Triangle> reconstruct(
const core::PointCloudFrame &frame,
const core::PipelineContext &,
core::PipelineStats &) const override
{
QVector<core::Triangle> triangles = adapters::pcl::buildPoissonTriangles(
frame.points,
m_config.pclPoissonDepth,
m_config.pclPoissonSamplesPerNode);
if (!triangles.isEmpty()) {
return triangles;
}
return adapters::reconstruction::buildSurfaceTriangles(frame.points, m_config);
}
private:
core::ReconstructionConfig m_config;
};
} // namespace strategies
#endif // RECONSTRUCTION_PCL_POISSON_STAGE_H
+4
View File
@@ -145,6 +145,10 @@ bool runPluginChainCase(QString &failureReason)
failureReason = "Plugin chain produced empty frame.";
return false;
}
if (result.stats.preprocessStepMetrics.size() != config.preprocessPlugins.size()) {
failureReason = "Per-stage preprocess metrics count mismatch.";
return false;
}
return true;
}
+1084 -41
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -31,7 +31,9 @@ class MainWindow : public QMainWindow
Q_PROPERTY(int selectedStageIndex READ selectedStageIndex WRITE setSelectedStageIndex NOTIFY selectedStageIndexChanged)
Q_PROPERTY(QVariantMap selectedStage READ selectedStage NOTIFY selectedStageChanged)
Q_PROPERTY(QVariantMap metrics READ metrics NOTIFY metricsChanged)
Q_PROPERTY(QVariantList stageMetrics READ stageMetrics NOTIFY stageMetricsChanged)
Q_PROPERTY(QString recommendation READ recommendation NOTIFY recommendationChanged)
Q_PROPERTY(QVariantList warningsList READ warningsList NOTIFY warningsListChanged)
Q_PROPERTY(QString chainHealth READ chainHealth NOTIFY chainHealthChanged)
Q_PROPERTY(QStringList demoSurfaceTypes READ demoSurfaceTypes CONSTANT)
Q_PROPERTY(QString demoSurfaceType READ demoSurfaceType WRITE setDemoSurfaceType NOTIFY demoSurfaceTypeChanged)
@@ -39,6 +41,8 @@ class MainWindow : public QMainWindow
Q_PROPERTY(QString wizardGoal READ wizardGoal WRITE setWizardGoal NOTIFY wizardGoalChanged)
Q_PROPERTY(QString wizardProfile READ wizardProfile WRITE setWizardProfile NOTIFY wizardProfileChanged)
Q_PROPERTY(QVariantList snapshots READ snapshots NOTIFY snapshotsChanged)
Q_PROPERTY(QVariantList presetItems READ presetItems NOTIFY presetItemsChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
public:
explicit MainWindow(QWidget *parent = nullptr);
@@ -54,7 +58,9 @@ public:
int selectedStageIndex() const;
QVariantMap selectedStage() const;
QVariantMap metrics() const;
QVariantList stageMetrics() const;
QString recommendation() const;
QVariantList warningsList() const;
QString chainHealth() const;
QStringList demoSurfaceTypes() const;
QString demoSurfaceType() const;
@@ -62,6 +68,8 @@ public:
QString wizardGoal() const;
QString wizardProfile() const;
QVariantList snapshots() const;
QVariantList presetItems() const;
bool busy() const;
public slots:
void generateDemo();
@@ -87,12 +95,17 @@ public slots:
void applyWizardSuggestion();
void saveSnapshot(const QString &slotName);
void loadSnapshot(const QString &slotName);
void saveCurrentPreset();
void loadPresetFromFile();
void exportCapabilitiesToFile();
void chooseUserDataDirectory();
private:
struct StageCardData {
QString id;
QString title;
QString category;
QString family;
QString hint;
QString defaults;
bool enabled;
@@ -111,6 +124,16 @@ private:
QString displayNameForStage(const QString &stageId) const;
StageCardData createCardForStage(const QString &stageId, bool enabled = true) const;
QStringList activeStageIds() const;
QVariantList builtInPresetItems() const;
QString userDataDirectoryPath() const;
QString userPresetsFilePath() const;
QVariantList loadUserPresetsFromDisk(QString *errorText = nullptr) const;
bool saveUserPresetsToDisk(const QVariantList &presets, QString *errorText = nullptr) const;
bool applyUserPreset(const QString &presetId);
QString makeUserPresetId(const QString &name) const;
bool normalizeImportedPreset(const QVariantMap &input, QVariantMap &normalized, QString &errorText) const;
void beginBusy();
void endBusy();
GlView *m_glView;
QQuickWidget *m_dashboardView;
@@ -124,13 +147,19 @@ private:
QVector<StageCardData> m_stageCards;
int m_selectedStageIndex;
QVariantMap m_metrics;
QVariantList m_stageMetrics;
QString m_recommendation;
QStringList m_warningsList;
QString m_chainHealth;
QString m_demoSurfaceType;
QString m_uiMode;
QString m_wizardGoal;
QString m_wizardProfile;
QVariantMap m_savedSnapshots;
QVariantList m_userPresets;
QString m_userDataDirectory;
bool m_busy;
int m_busyDepth;
signals:
void stageChainChanged();
@@ -142,13 +171,17 @@ signals:
void selectedStageIndexChanged();
void selectedStageChanged();
void metricsChanged();
void stageMetricsChanged();
void recommendationChanged();
void warningsListChanged();
void chainHealthChanged();
void demoSurfaceTypeChanged();
void uiModeChanged();
void wizardGoalChanged();
void wizardProfileChanged();
void snapshotsChanged();
void presetItemsChanged();
void busyChanged();
};
#endif // MAINWINDOW_H
+81 -27
View File
@@ -8,6 +8,8 @@ GroupBox {
readonly property var safeBackend: backend ? backend : ({
chainHealth: "",
recommendation: "",
warningsList: [],
stageMetrics: [],
metrics: ({
triangles: 0,
reconstructMs: 0,
@@ -27,36 +29,88 @@ GroupBox {
return value
}
ColumnLayout {
ScrollView {
anchors.fill: parent
spacing: Theme.spacingXs
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
RowLayout {
Layout.fillWidth: true
Label { text: "Состояние: " + localizedHealth(safeBackend.chainHealth); color: Theme.summaryPrimaryText; font.bold: true; font.pixelSize: Theme.fontMd }
Item { Layout.fillWidth: true }
Label { text: "Треугольники: " + safeBackend.metrics.triangles; color: Theme.summarySecondaryText; font.pixelSize: Theme.fontSm }
Label { text: "Реконструкция, мс: " + safeBackend.metrics.reconstructMs; color: Theme.summarySecondaryText; font.pixelSize: Theme.fontSm }
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: safeBackend.recommendation
color: Theme.summaryRecommendation
font.pixelSize: Theme.fontSm
}
Flow {
Layout.fillWidth: true
Layout.topMargin: Theme.spacingXs / 2
ColumnLayout {
width: parent.width
spacing: Theme.spacingLg
clip: true
Label { text: "Вход: " + safeBackend.metrics.inputPoints; color: Theme.summaryDetailText; font.pixelSize: Theme.fontXs }
Label { text: "После препроцесса: " + safeBackend.metrics.afterPreprocess; color: Theme.summaryDetailText; font.pixelSize: Theme.fontXs }
Label { text: "Удалено: " + safeBackend.metrics.removedPoints; color: Theme.summaryDetailText; font.pixelSize: Theme.fontXs }
Label { text: "Кластеры: " + safeBackend.metrics.clusters; color: Theme.summaryDetailText; font.pixelSize: Theme.fontXs }
spacing: Theme.spacingXs
RowLayout {
Layout.fillWidth: true
Label { text: "Состояние: " + localizedHealth(safeBackend.chainHealth); color: Theme.summaryPrimaryText; font.bold: true; font.pixelSize: Theme.fontMd }
Item { Layout.fillWidth: true }
Label { text: "Треугольники: " + safeBackend.metrics.triangles; color: Theme.summarySecondaryText; font.pixelSize: Theme.fontSm }
Label { text: "Реконструкция, мс: " + safeBackend.metrics.reconstructMs; color: Theme.summarySecondaryText; font.pixelSize: Theme.fontSm }
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: safeBackend.recommendation
color: Theme.summaryRecommendation
font.pixelSize: Theme.fontSm
}
ColumnLayout {
Layout.fillWidth: true
visible: safeBackend.warningsList && safeBackend.warningsList.length > 0
spacing: Theme.spacingXs / 2
Repeater {
model: safeBackend.warningsList
delegate: Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: "\u2022 " + modelData
color: Theme.summaryRecommendation
font.pixelSize: Theme.fontXs
}
}
}
Flow {
Layout.fillWidth: true
Layout.topMargin: Theme.spacingXs / 2
width: parent.width
spacing: Theme.spacingLg
clip: true
Label { text: "Вход: " + safeBackend.metrics.inputPoints; color: Theme.summaryDetailText; font.pixelSize: Theme.fontXs }
Label { text: "После препроцесса: " + safeBackend.metrics.afterPreprocess; color: Theme.summaryDetailText; font.pixelSize: Theme.fontXs }
Label { text: "Удалено: " + safeBackend.metrics.removedPoints; color: Theme.summaryDetailText; font.pixelSize: Theme.fontXs }
Label { text: "Кластеры: " + safeBackend.metrics.clusters; color: Theme.summaryDetailText; font.pixelSize: Theme.fontXs }
}
ColumnLayout {
Layout.fillWidth: true
visible: safeBackend.stageMetrics && safeBackend.stageMetrics.length > 0
spacing: Theme.spacingXs / 2
Label {
text: "Метрики по шагам"
color: Theme.summarySecondaryText
font.bold: true
font.pixelSize: Theme.fontXs
}
Repeater {
model: safeBackend.stageMetrics
delegate: Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: "\u2022 " + modelData.id
+ ": " + modelData.inputPoints
+ " \u2192 " + modelData.outputPoints
+ " (\u2212" + modelData.removedPoints + "), "
+ modelData.elapsedMs + " мс"
color: Theme.summaryDetailText
font.pixelSize: Theme.fontXs
}
}
}
}
}
}
@@ -1,7 +1,6 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import "../../shared/PipelineUiCatalog.js" as UiCatalog
import "../../shared/Theme.js" as Theme
Item {
@@ -9,14 +8,34 @@ Item {
property var safeBackend
property int selectedIndex: -1
signal settingsRequested(var stageItem)
function categoryAccentColor(category) {
if (category === "Обрезка")
return "#88d1ff"
if (category === "NaN (предочистка)")
return "#8fe8ff"
if (category === "Условия/Индексы")
return "#9fd7ff"
if (category === "Шум")
return "#a4f4b9"
if (category === "Морфология")
return "#9cf5d1"
if (category === "Прореживание")
return "#ffcb8a"
if (category === "Сглаживание")
return "#ffd892"
if (category === "Нормали")
return "#d2b7ff"
if (category === "Реконструкция")
return "#ff9dc4"
return Theme.chainDragHandle
}
ListView {
id: stageList
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: reconstructionCard.top
anchors.bottomMargin: Theme.spacingSm
anchors.bottom: parent.bottom
clip: true
model: root.safeBackend.stageCards
@@ -35,8 +54,15 @@ Item {
anchors.margins: Theme.spacingSm
spacing: Theme.spacingSm
Rectangle {
width: 4
Layout.fillHeight: true
radius: 2
color: root.categoryAccentColor(modelData.category)
}
Label {
text: "↕"
text: modelData.family === "reconstruction" ? "REC" : "↕"
color: Theme.chainDragHandle
font.pixelSize: Theme.fontLg
width: Theme.stageHandleWidth
@@ -46,6 +72,8 @@ Item {
id: dragHandle
anchors.fill: parent
drag.target: dragProxy
enabled: modelData.family !== "reconstruction"
cursorShape: enabled ? Qt.SizeAllCursor : Qt.PointingHandCursor
onPressed: root.safeBackend.setSelectedStageIndex(index)
}
}
@@ -53,7 +81,7 @@ Item {
ColumnLayout {
Layout.fillWidth: true
Label { text: modelData.title; color: Theme.paletteFilterText; font.bold: true; font.pixelSize: Theme.fontMd }
Label { text: modelData.category + " | " + modelData.id; color: Theme.chainDragHandle; font.pixelSize: Theme.fontXs }
Label { text: modelData.category + " | " + modelData.id; color: root.categoryAccentColor(modelData.category); font.pixelSize: Theme.fontXs }
}
}
@@ -75,7 +103,8 @@ Item {
DropArea {
anchors.fill: parent
onEntered: {
if (drag.source && drag.source !== rowRoot && drag.source.itemIndex !== undefined) {
if (drag.source && drag.source !== rowRoot && drag.source.itemIndex !== undefined
&& modelData.family !== "reconstruction") {
root.safeBackend.moveStage(drag.source.itemIndex, index)
}
}
@@ -85,6 +114,7 @@ Item {
anchors.fill: parent
z: -1
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: root.safeBackend.setSelectedStageIndex(index)
onDoubleClicked: {
@@ -103,51 +133,10 @@ Item {
id: stageMenu
MenuItem {
text: "Удалить элемент"
enabled: modelData.family !== "reconstruction"
onTriggered: root.safeBackend.removeStage(index)
}
}
}
}
Rectangle {
id: reconstructionCard
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: Theme.stageRowHeight
radius: Theme.radiusMd
border.width: 1
border.color: Theme.reconstructionBorder
color: Theme.reconstructionBg
RowLayout {
anchors.fill: parent
anchors.margins: Theme.spacingSm
spacing: Theme.spacingSm
Label {
text: "REC"
color: Theme.reconstructionAccent
font.bold: true
font.pixelSize: Theme.fontSm
width: Theme.reconstructionBadgeWidth
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
ColumnLayout {
Layout.fillWidth: true
Label {
text: UiCatalog.reconstructionTitle(root.safeBackend.reconstructionMethod)
color: Theme.reconstructionText
font.bold: true
font.pixelSize: Theme.fontMd
}
Label {
text: "Реконструкция | " + root.safeBackend.reconstructionMethod
color: Theme.reconstructionSubtext
font.pixelSize: Theme.fontXs
}
}
}
}
}
+66 -43
View File
@@ -19,54 +19,77 @@ Item {
width: parent.width
spacing: Theme.spacingXs
Label { text: "Фильтры"; font.bold: true; color: Theme.paletteHeader; padding: Theme.spacingXs }
Label { text: "Стадии по этапам"; font.bold: true; color: Theme.paletteHeader; padding: Theme.spacingXs }
Repeater {
model: UiCatalog.filterPaletteModel()
delegate: Rectangle {
property string paletteStageId: modelData.idValue
model: UiCatalog.filterPaletteGroupedModel()
delegate: Column {
id: phaseSection
property color accentColor: modelData.phaseAccent ? modelData.phaseAccent : Theme.paletteHeader
property color filterBgColor: modelData.phaseFilterBg ? modelData.phaseFilterBg : Theme.paletteFilterBg
property color filterBorderColor: modelData.phaseFilterBorder ? modelData.phaseFilterBorder : Theme.paletteFilterBorder
width: paletteColumn.width
height: Theme.paletteItemHeight
radius: Theme.radiusSm
color: Theme.paletteFilterBg
border.width: 1
border.color: Theme.paletteFilterBorder
Label {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Theme.spacingMd
text: modelData.title
color: Theme.paletteFilterText
font.pixelSize: Theme.fontSm
}
MouseArea {
anchors.fill: parent
onDoubleClicked: root.safeBackend.addStage(parent.paletteStageId)
}
}
}
spacing: Theme.spacingXs / 2
Label { text: "Реконструкторы поверхности"; font.bold: true; color: Theme.paletteHeader; padding: Theme.spacingXs }
Repeater {
model: UiCatalog.reconstructionPaletteModel()
delegate: Rectangle {
property string paletteStageId: modelData.idValue
width: paletteColumn.width
height: Theme.paletteItemHeight
radius: Theme.radiusSm
color: Theme.paletteReconBg
border.width: 1
border.color: Theme.paletteReconBorder
Label {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Theme.spacingMd
text: modelData.title
color: Theme.paletteReconText
font.pixelSize: Theme.fontSm
text: modelData.phaseTitle
font.bold: true
color: phaseSection.accentColor
padding: Theme.spacingXs
}
MouseArea {
anchors.fill: parent
onDoubleClicked: root.safeBackend.setReconstructionMethod(parent.paletteStageId)
Label {
visible: modelData.phaseHint.length > 0
text: modelData.phaseHint
color: phaseSection.accentColor
font.pixelSize: Theme.fontXs
leftPadding: Theme.spacingXs
}
Label {
visible: modelData.items.length === 0
text: "Пока нет доступных фильтров для этого этапа."
color: phaseSection.accentColor
font.pixelSize: Theme.fontXs
leftPadding: Theme.spacingXs
}
Repeater {
model: modelData.items
delegate: Rectangle {
property string paletteStageId: modelData.idValue
width: paletteColumn.width
height: Theme.paletteItemHeight
radius: Theme.radiusSm
color: phaseSection.filterBgColor
border.width: 1
border.color: phaseSection.filterBorderColor
Label {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Theme.spacingMd
text: modelData.title
color: Theme.paletteFilterText
font.pixelSize: Theme.fontSm
}
ToolTip.visible: hoverArea.containsMouse
ToolTip.text: modelData.hint
ToolTip.delay: 200
MouseArea {
id: hoverArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onDoubleClicked: {
if (modelData.family === "reconstruction")
root.safeBackend.setReconstructionMethod(parent.paletteStageId)
else
root.safeBackend.addStage(parent.paletteStageId)
}
}
}
}
}
}
+86 -5
View File
@@ -24,6 +24,12 @@ Dialog {
width: Theme.dialogWidth
title: ""
standardButtons: Dialog.NoButton
background: Rectangle {
radius: Theme.radiusMd
color: Theme.dialogBg
border.color: Theme.dialogBorder
border.width: 1
}
onAccepted: {
if (safeBackend.selectedStageIndex >= 0) {
@@ -36,15 +42,17 @@ Dialog {
onOpened: root.paramItems = UiCatalog.parseDefaults(popupItem.defaults)
contentItem: ColumnLayout {
spacing: Theme.spacingSm
spacing: Theme.dialogContentSpacing
Label {
text: popupItem.title ? popupItem.title : "Элемент не выбран"
font.bold: true
color: Theme.controlText
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
Label {
text: popupItem.hint ? popupItem.hint : "Для этого элемента нет подробного описания."
color: Theme.dialogDescriptionText
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
@@ -58,10 +66,10 @@ Dialog {
model: root.paramItems
delegate: ColumnLayout {
Layout.fillWidth: true
spacing: 2
spacing: Theme.spacingXs / 2
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingMd
spacing: Theme.dialogRowSpacing
Label {
text: modelData.key + ":"
color: Theme.dialogHintText
@@ -71,12 +79,30 @@ Dialog {
ComboBox {
Layout.fillWidth: true
visible: modelData.kind === "bool"
hoverEnabled: true
model: ["false", "true"]
currentIndex: modelData.value === "true" ? 1 : 0
onCurrentTextChanged: {
if (modelData.kind === "bool")
root.updateParamValue(index, currentText)
}
contentItem: Text {
text: parent.displayText
color: Theme.controlText
verticalAlignment: Text.AlignVCenter
leftPadding: Theme.spacingSm
}
background: Rectangle {
radius: Theme.radiusSm
color: Theme.controlBg
border.color: Theme.controlBorder
border.width: 1
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
TextField {
Layout.fillWidth: true
@@ -84,6 +110,19 @@ Dialog {
text: modelData.value
validator: IntValidator {}
onEditingFinished: root.updateParamValue(index, text)
color: Theme.controlText
placeholderTextColor: Theme.controlPlaceholder
background: Rectangle {
radius: Theme.radiusSm
color: Theme.controlBg
border.color: Theme.controlBorder
border.width: 1
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
}
}
TextField {
Layout.fillWidth: true
@@ -91,12 +130,38 @@ Dialog {
text: modelData.value
validator: DoubleValidator {}
onEditingFinished: root.updateParamValue(index, text)
color: Theme.controlText
placeholderTextColor: Theme.controlPlaceholder
background: Rectangle {
radius: Theme.radiusSm
color: Theme.controlBg
border.color: Theme.controlBorder
border.width: 1
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
}
}
TextField {
Layout.fillWidth: true
visible: modelData.kind === "string"
text: modelData.value
onEditingFinished: root.updateParamValue(index, text)
color: Theme.controlText
placeholderTextColor: Theme.controlPlaceholder
background: Rectangle {
radius: Theme.radiusSm
color: Theme.controlBg
border.color: Theme.controlBorder
border.width: 1
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.IBeamCursor
}
}
}
Label {
@@ -110,18 +175,34 @@ Dialog {
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Theme.spacingMd
spacing: Theme.spacingMd
Layout.topMargin: Theme.spacingSm
spacing: Theme.dialogRowSpacing
Button {
text: "Установить по умолчанию"
Layout.preferredWidth: Theme.dialogResetButtonWidth
hoverEnabled: true
onClicked: root.paramItems = UiCatalog.parseDefaults(safeBackend.defaultsForStage(popupItem.id))
contentItem: Text { text: parent.text; color: Theme.buttonText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.buttonPressedBg : (parent.hovered ? Theme.buttonHoverBg : Theme.buttonBg); border.color: Theme.buttonBorder; border.width: 1 }
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
Item { Layout.fillWidth: true }
Button {
text: "OK"
Layout.preferredWidth: Theme.dialogOkButtonWidth
hoverEnabled: true
onClicked: root.accept()
contentItem: Text { text: parent.text; color: Theme.primaryButtonText; font.bold: true; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.primaryButtonPressedBg : (parent.hovered ? Theme.primaryButtonHoverBg : Theme.primaryButtonBg); border.color: Theme.primaryButtonBorder; border.width: 1 }
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
}
}
+210 -6
View File
@@ -7,15 +7,27 @@ import "../shared/Theme.js" as Theme
Rectangle {
color: Theme.panelBg
property int compactH: Theme.compactControlHeight
property int selectedPresetIndex: 0
readonly property var safeBackend: backend ? backend : ({
surfaceVisible: true,
demoSurfaceTypes: ["Сфера", "Тор", "Волна"],
busy: false,
demoSurfaceTypes: ["Сфера", "Тор", "Волна", "Дно реки + труба"],
demoSurfaceType: "Сфера",
presetItems: [
{ title: "LiDAR-скан", idValue: "LiDAR_scan" },
{ title: "RGB-D камера", idValue: "RGBD_camera" },
{ title: "Синтетика", idValue: "Synthetic_clean" }
],
openPointsFile: function() {},
generateDemo: function() {},
setDemoSurfaceType: function(_) {},
setSurfaceVisible: function(_) {},
applyPipelineFromUi: function() {}
applyPipelineFromUi: function() {},
applyPreset: function(_) {},
saveCurrentPreset: function() {},
loadPresetFromFile: function() {},
exportCapabilitiesToFile: function() {},
chooseUserDataDirectory: function() {}
})
ColumnLayout {
@@ -29,15 +41,68 @@ Rectangle {
ComboBox {
Layout.preferredWidth: Theme.dashboardSurfaceComboWidth
Layout.preferredHeight: compactH
hoverEnabled: true
model: safeBackend.demoSurfaceTypes
currentIndex: model.indexOf(safeBackend.demoSurfaceType)
onActivated: safeBackend.setDemoSurfaceType(currentText)
contentItem: Text {
text: parent.displayText
color: Theme.controlText
verticalAlignment: Text.AlignVCenter
leftPadding: Theme.spacingSm
elide: Text.ElideRight
}
background: Rectangle {
radius: Theme.radiusSm
color: parent.pressed ? Theme.controlHoverBg : Theme.controlBg
border.color: Theme.controlBorder
border.width: 1
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: Qt.PointingHandCursor
}
}
Button {
text: "Сгенерировать"
height: compactH
hoverEnabled: true
onClicked: safeBackend.generateDemo()
contentItem: Text { text: parent.text; color: Theme.buttonText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.buttonPressedBg : (parent.hovered ? Theme.buttonHoverBg : Theme.buttonBg); border.color: Theme.buttonBorder; border.width: 1 }
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
Button {
text: "Загрузить"
height: compactH
hoverEnabled: true
onClicked: safeBackend.openPointsFile()
contentItem: Text { text: parent.text; color: Theme.buttonText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.buttonPressedBg : (parent.hovered ? Theme.buttonHoverBg : Theme.buttonBg); border.color: Theme.buttonBorder; border.width: 1 }
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
Button { text: "Сгенерировать"; height: compactH; onClicked: safeBackend.generateDemo() }
Button { text: "Загрузить"; height: compactH; onClicked: safeBackend.openPointsFile() }
Item { Layout.fillWidth: true }
}
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingSm
Label {
text: "Пресет:"
color: Theme.summaryDetailText
font.pixelSize: Theme.fontSm
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: "Стартовая цепочка: примените и при необходимости отредактируйте вручную."
color: Theme.chainDragHandle
font.pixelSize: Theme.fontXs
}
}
PipelineCanvas {
Layout.fillWidth: true
Layout.fillHeight: true
@@ -46,9 +111,121 @@ Rectangle {
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingSm
Switch { checked: safeBackend.surfaceVisible; onToggled: safeBackend.setSurfaceVisible(checked) }
Switch {
checked: safeBackend.surfaceVisible
onToggled: safeBackend.setSurfaceVisible(checked)
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
Item { Layout.fillWidth: true }
Button { text: "Применить"; Layout.preferredHeight: compactH; onClicked: safeBackend.applyPipelineFromUi() }
Button {
text: "Применить"
Layout.preferredHeight: compactH
hoverEnabled: true
onClicked: safeBackend.applyPipelineFromUi()
contentItem: Text { text: parent.text; color: Theme.primaryButtonText; font.bold: true; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle {
radius: Theme.radiusSm
color: parent.down ? Theme.primaryButtonPressedBg : (parent.hovered ? Theme.primaryButtonHoverBg : Theme.primaryButtonBg)
border.color: Theme.primaryButtonBorder
border.width: 2
}
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Theme.spacingXs
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingSm
ComboBox {
Layout.preferredWidth: 190
Layout.minimumWidth: 170
Layout.maximumWidth: 220
Layout.preferredHeight: compactH
hoverEnabled: true
model: safeBackend.presetItems
textRole: "title"
currentIndex: selectedPresetIndex
onActivated: selectedPresetIndex = currentIndex
contentItem: Text {
text: parent.displayText
color: Theme.controlText
verticalAlignment: Text.AlignVCenter
leftPadding: Theme.spacingSm
elide: Text.ElideRight
}
background: Rectangle {
radius: Theme.radiusSm
color: parent.pressed ? Theme.controlHoverBg : Theme.controlBg
border.color: Theme.controlBorder
border.width: 1
}
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
Button {
text: "Применить пресет"
Layout.preferredHeight: compactH
hoverEnabled: true
onClicked: {
if (selectedPresetIndex >= 0 && selectedPresetIndex < safeBackend.presetItems.length)
safeBackend.applyPreset(safeBackend.presetItems[selectedPresetIndex].idValue)
}
contentItem: Text { text: parent.text; color: Theme.buttonText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.buttonPressedBg : (parent.hovered ? Theme.buttonHoverBg : Theme.buttonBg); border.color: Theme.buttonBorder; border.width: 1 }
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
Item { Layout.fillWidth: true }
}
Item {
Layout.fillWidth: true
implicitHeight: presetActionsFlow.childrenRect.height
Flow {
id: presetActionsFlow
width: parent.width
spacing: Theme.spacingSm
Button {
text: "Загрузить пресет"
height: compactH
hoverEnabled: true
onClicked: safeBackend.loadPresetFromFile()
contentItem: Text { text: parent.text; color: Theme.buttonText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.buttonPressedBg : (parent.hovered ? Theme.buttonHoverBg : Theme.buttonBg); border.color: Theme.buttonBorder; border.width: 1 }
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
Button {
text: "Сохранить пресет"
height: compactH
hoverEnabled: true
onClicked: safeBackend.saveCurrentPreset()
contentItem: Text { text: parent.text; color: Theme.buttonText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.buttonPressedBg : (parent.hovered ? Theme.buttonHoverBg : Theme.buttonBg); border.color: Theme.buttonBorder; border.width: 1 }
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
Button {
text: "Выгрузка возможностей"
height: compactH
hoverEnabled: true
onClicked: safeBackend.exportCapabilitiesToFile()
contentItem: Text { text: parent.text; color: Theme.buttonText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.buttonPressedBg : (parent.hovered ? Theme.buttonHoverBg : Theme.buttonBg); border.color: Theme.buttonBorder; border.width: 1 }
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
Button {
text: "Папка данных"
height: compactH
hoverEnabled: true
onClicked: safeBackend.chooseUserDataDirectory()
contentItem: Text { text: parent.text; color: Theme.buttonText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
background: Rectangle { radius: Theme.radiusSm; color: parent.down ? Theme.buttonPressedBg : (parent.hovered ? Theme.buttonHoverBg : Theme.buttonBg); border.color: Theme.buttonBorder; border.width: 1 }
MouseArea { anchors.fill: parent; acceptedButtons: Qt.NoButton; cursorShape: Qt.PointingHandCursor }
}
}
}
}
MetricsStrip {
@@ -58,4 +235,31 @@ Rectangle {
}
}
Rectangle {
anchors.fill: parent
visible: safeBackend.busy
color: "#90000000"
z: 1000
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.AllButtons
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingSm
BusyIndicator {
running: safeBackend.busy
width: 40
height: 40
}
Label {
text: "Выполняются вычисления..."
color: "white"
}
}
}
}
+383 -7
View File
@@ -52,6 +52,58 @@ function paramDescription(key) {
return "Радиус поиска соседей."
if (key === "minNeighbors")
return "Минимум соседей, чтобы точка считалась валидной."
if (key === "threshold")
return "Порог отклонения точки от геометрической модели."
if (key === "shadowThreshold")
return "Порог для удаления теневых точек по нормалям."
if (key === "resolution")
return "Размер ячейки 2D-сетки для фильтра GridMinimum."
if (key === "sample")
return "Количество точек, которое нужно оставить после выборки."
if (key === "axis")
return "Ось фильтрации: x, y или z."
if (key === "min" || key === "max")
return "Граница диапазона для фильтра PassThrough."
if (key === "minX" || key === "minY" || key === "minZ" || key === "maxX" || key === "maxY" || key === "maxZ")
return "Границы CropBox/CropHull по соответствующим осям."
if (key === "near" || key === "far")
return "Ближняя/дальняя граница фрустума."
if (key === "hfov" || key === "vfov")
return "Горизонтальный/вертикальный угол обзора (в градусах)."
if (key === "a" || key === "b" || key === "c" || key === "d")
return "Коэффициенты плоскости ax+by+cz+d=0."
if (key === "keepPositive")
return "Оставлять ли точки на положительной стороне плоскости."
if (key === "zMin" || key === "zMax")
return "Допустимый диапазон координаты Z для conditional-фильтра."
if (key === "nth")
return "Оставлять каждую N-ю точку."
if (key === "radiusMax")
return "Максимальный радиус точки от начала координат."
if (key === "sigmaS" || key === "sigmaR")
return "Параметры bilateral-фильтра (пространство/интенсивность)."
if (key === "sigma" || key === "kernel")
return "Параметры гауссова ядра свертки."
if (key === "iterations")
return "Число итераций уточнения."
if (key === "minHits")
return "Минимальная заполненность вокселя для прохождения фильтра."
if (key === "neighborRadiusScale")
return "Масштаб радиуса поиска соседей для fallback-реконструкции."
if (key === "runEveryNthFrame")
return "Запуск реконструкции на каждом N-м кадре."
if (key === "searchRadius")
return "Радиус поиска соседей для greedy triangulation."
if (key === "mu")
return "Коэффициент плотности соседей для greedy triangulation."
if (key === "maxNearest")
return "Максимум соседей для greedy triangulation."
if (key === "maxSurfaceAngle")
return "Максимальный угол поверхности в радианах."
if (key === "poissonDepth")
return "Глубина октодерева для Poisson реконструкции."
if (key === "samplesPerNode")
return "Число выборок на узел для сглаживания Poisson."
return "Параметр этапа обработки."
}
@@ -60,22 +112,346 @@ function reconstructionTitle(id) {
return "Fallback Surface"
if (id === "pcl_greedy_triangulation")
return "PCL Greedy Triangulation"
if (id === "pcl_poisson_reconstruction")
return "PCL Poisson Reconstruction"
return id
}
function filterPaletteModel() {
function phaseModel() {
return [
{ title: "Крупнейший кластер", idValue: "keep_largest_cluster" },
{ title: "Прореживание плотности", idValue: "downsample_dense" },
{ title: "PCL Voxel Grid", idValue: "pcl_voxel_grid" },
{ title: "Статистическая фильтрация", idValue: "pcl_statistical_outlier" },
{ title: "Радиусная фильтрация", idValue: "pcl_radius_outlier" }
{
id: "crop",
title: "Обрезка",
hint: "Ограничение области интереса и удаление лишних фрагментов.",
accent: "#88d1ff",
filterBg: "#263e55",
filterBorder: "#4b7598"
},
{
id: "nan_preclean",
title: "NaN (предочистка)",
hint: "Удаление NaN сразу после загрузки/обрезки, иначе статистические фильтры работают нестабильно.",
accent: "#8fe8ff",
filterBg: "#1f4450",
filterBorder: "#3f8394"
},
{
id: "conditions_indexes",
title: "Условия/Индексы",
hint: "Отбор точек по полям, диапазонам и индексным маскам.",
accent: "#9fd7ff",
filterBg: "#24405a",
filterBorder: "#4f7aa1"
},
{
id: "noise",
title: "Шум",
hint: "Удаление выбросов и нестабильных точек перед геометрией.",
accent: "#a4f4b9",
filterBg: "#244534",
filterBorder: "#4b8f68"
},
{
id: "morphology",
title: "Морфология",
hint: "Геометрические операции локальной структуры облака.",
accent: "#9cf5d1",
filterBg: "#1f4a3e",
filterBorder: "#4b8f7d"
},
{
id: "downsample",
title: "Прореживание",
hint: "Снижение плотности облака для скорости и устойчивости.",
accent: "#ffcb8a",
filterBg: "#4a3724",
filterBorder: "#8e6f47"
},
{
id: "smoothing",
title: "Сглаживание",
hint: "Снижение локального шума перед расчетом нормалей и реконструкцией.",
accent: "#ffd892",
filterBg: "#4e3a22",
filterBorder: "#917349"
},
{
id: "normals",
title: "Нормали",
hint: "Подготовка нормалей для продвинутой реконструкции (этап зарезервирован).",
accent: "#d2b7ff",
filterBg: "#3e3155",
filterBorder: "#6f5a95"
},
{
id: "reconstruction",
title: "Реконструкция",
hint: "Построение поверхности по подготовленному облаку точек.",
accent: "#ff9dc4",
filterBg: "#4b2f40",
filterBorder: "#8d5a75"
}
]
}
function filterDefinitions() {
return [
{
title: "Крупнейший кластер",
idValue: "keep_largest_cluster",
phaseId: "crop",
hint: "Оставляет основной объект и отбрасывает изолированные фрагменты."
},
{
title: "Remove NaN Points",
idValue: "pcl_remove_nan",
phaseId: "nan_preclean",
family: "preprocess",
hint: "Удаляет NaN/Inf после загрузки и обрезки, до статистических фильтров."
},
{
title: "Remove NaN Normals",
idValue: "pcl_remove_nan_normals",
phaseId: "nan_preclean",
family: "preprocess",
hint: "Удаляет невалидные нормали (в текущем формате — невалидную геометрию)."
},
{
title: "PassThrough",
idValue: "pcl_pass_through",
phaseId: "crop",
hint: "Фильтрует точки по диапазону выбранной оси."
},
{
title: "CropBox",
idValue: "pcl_crop_box",
phaseId: "crop",
hint: "Ограничивает облако заданным 3D-параллелепипедом."
},
{
title: "CropHull",
idValue: "pcl_crop_hull",
phaseId: "crop",
hint: "Обрезка по области интереса (в текущей версии через box-границы)."
},
{
title: "Frustum Culling",
idValue: "pcl_frustum_culling",
phaseId: "crop",
hint: "Оставляет точки в пирамиде видимости камеры."
},
{
title: "PlaneClipper3D",
idValue: "pcl_plane_clipper_3d",
phaseId: "crop",
hint: "Отсекает точки по уравнению плоскости."
},
{
title: "Conditional Removal",
idValue: "pcl_conditional_removal",
phaseId: "conditions_indexes",
hint: "Удаляет точки по логическому условию (диапазон Z)."
},
{
title: "Extract Indices",
idValue: "pcl_extract_indices",
phaseId: "conditions_indexes",
hint: "Извлекает точки по индексной маске (каждая N-я)."
},
{
title: "Functor Filter",
idValue: "pcl_functor_filter",
phaseId: "conditions_indexes",
hint: "Пользовательский предикат (радиус + проверка валидности)."
},
{
title: "ProjectInliers",
idValue: "pcl_project_inliers",
phaseId: "normals",
hint: "Проецирует точки на геометрическую модель (плоскость)."
},
{
title: "Normal Refinement",
idValue: "pcl_normal_refinement",
phaseId: "normals",
hint: "Уточняет локальную геометрию по соседям."
},
{
title: "Статистическая фильтрация",
idValue: "pcl_statistical_outlier",
phaseId: "noise",
hint: "Удаляет выбросы на основе распределения расстояний до соседей."
},
{
title: "Радиусная фильтрация",
idValue: "pcl_radius_outlier",
phaseId: "noise",
hint: "Удаляет точки с недостаточным числом соседей в заданном радиусе."
},
{
title: "Model Outlier Removal",
idValue: "pcl_model_outlier",
phaseId: "noise",
hint: "Удаляет точки, отклоняющиеся от геометрической модели."
},
{
title: "Shadow Points Removal",
idValue: "pcl_shadow_points",
phaseId: "noise",
hint: "Удаляет теневые точки на основе нормалей поверхности."
},
{
title: "Approximate Voxel Grid",
idValue: "pcl_approximate_voxel_grid",
phaseId: "downsample",
hint: "Ускоренное воксельное прореживание для больших облаков."
},
{
title: "Voxel Grid Label",
idValue: "pcl_voxel_grid_label",
phaseId: "downsample",
hint: "Воксельное прореживание с поддержкой меток."
},
{
title: "Voxel Grid Covariance",
idValue: "pcl_voxel_grid_covariance",
phaseId: "downsample",
hint: "Воксельная сетка с ковариациями (для NDT-пайплайнов)."
},
{
title: "Grid Minimum",
idValue: "pcl_grid_minimum",
phaseId: "downsample",
hint: "Оставляет точку с минимальным Z в каждой ячейке."
},
{
title: "Farthest Point Sampling",
idValue: "pcl_farthest_point_sampling",
phaseId: "downsample",
hint: "Выбирает наиболее удаленные друг от друга точки."
},
{
title: "Normal Space Sampling",
idValue: "pcl_normal_space_sampling",
phaseId: "downsample",
hint: "Равномерная выборка в пространстве нормалей."
},
{
title: "Sampling Surface Normal",
idValue: "pcl_sampling_surface_normal",
phaseId: "downsample",
hint: "Выборка точек на основе нормалей поверхности."
},
{
title: "Bilateral Filter",
idValue: "pcl_bilateral_filter",
phaseId: "smoothing",
hint: "Двустороннее сглаживание с сохранением границ."
},
{
title: "Fast Bilateral Filter",
idValue: "pcl_fast_bilateral_filter",
phaseId: "smoothing",
hint: "Быстрое двустороннее сглаживание."
},
{
title: "Fast Bilateral Filter OMP",
idValue: "pcl_fast_bilateral_filter_omp",
phaseId: "smoothing",
hint: "Параллельная версия bilateral-фильтра."
},
{
title: "Convolution",
idValue: "pcl_convolution",
phaseId: "smoothing",
hint: "Свертка облака точек с ядром."
},
{
title: "Gaussian Kernel",
idValue: "pcl_gaussian_kernel",
phaseId: "smoothing",
hint: "Гауссово ядро свертки."
},
{
title: "Gaussian Kernel RGB",
idValue: "pcl_gaussian_kernel_rgb",
phaseId: "smoothing",
hint: "Гауссово ядро с RGB-ориентированной семантикой."
},
{
title: "VoxelGrid Occlusion Estimation",
idValue: "pcl_voxel_grid_occlusion",
phaseId: "morphology",
hint: "Оценка окклюзии через заполненность вокселей."
},
{
title: "Fallback Surface",
idValue: "surface_fallback",
phaseId: "reconstruction",
family: "reconstruction",
hint: "Базовая реконструкция поверхности по облаку точек."
},
{
title: "PCL Greedy Triangulation",
idValue: "pcl_greedy_triangulation",
phaseId: "reconstruction",
family: "reconstruction",
hint: "Жадная триангуляция с параметрами радиуса и углов."
},
{
title: "PCL Poisson Reconstruction",
idValue: "pcl_poisson_reconstruction",
phaseId: "reconstruction",
family: "reconstruction",
hint: "Реконструкция поверхности методом Poisson из libpcl_surface."
},
{
title: "Прореживание плотности",
idValue: "downsample_dense",
phaseId: "downsample",
hint: "Снижает число точек для ускорения пайплайна на плотных облаках."
},
{
title: "PCL Voxel Grid",
idValue: "pcl_voxel_grid",
phaseId: "downsample",
hint: "Равномерно прореживает облако с помощью воксельной сетки."
}
]
}
function filterPaletteModel() {
return filterDefinitions()
}
function filterPaletteGroupedModel() {
var phases = phaseModel()
var filters = filterDefinitions()
var grouped = []
for (var p = 0; p < phases.length; ++p) {
var phase = phases[p]
var items = []
for (var i = 0; i < filters.length; ++i) {
if (filters[i].phaseId === phase.id)
items.push(filters[i])
}
grouped.push({
phaseId: phase.id,
phaseTitle: phase.title,
phaseHint: phase.hint,
phaseAccent: phase.accent,
phaseFilterBg: phase.filterBg,
phaseFilterBorder: phase.filterBorder,
items: items
})
}
return grouped
}
function reconstructionPaletteModel() {
return [
{ title: "Fallback Surface", idValue: "surface_fallback" },
{ title: "PCL Greedy Triangulation", idValue: "pcl_greedy_triangulation" }
{ title: "PCL Greedy Triangulation", idValue: "pcl_greedy_triangulation" },
{ title: "PCL Poisson Reconstruction", idValue: "pcl_poisson_reconstruction" }
]
}
+30 -8
View File
@@ -30,9 +30,29 @@ var summarySecondaryText = "#cde0ff"
var summaryRecommendation = "#9ec3ff"
var summaryDetailText = "#b8cff8"
var dialogHintText = "#4a4f63"
var dialogDescriptionText = "#7e869d"
var dialogSectionText = "#9eb0d9"
var dialogHintText = "#c7d6f3"
var dialogDescriptionText = "#aebfde"
var dialogSectionText = "#dce7ff"
var dialogBg = "#1a2234"
var dialogBorder = "#3a4f78"
var controlBg = "#23324d"
var controlBorder = "#4a6494"
var controlText = "#eaf0ff"
var controlPlaceholder = "#9cb0d6"
var controlHoverBg = "#2a3b5a"
var buttonBg = "#2c3f61"
var buttonBorder = "#5574a9"
var buttonText = "#eef4ff"
var buttonHoverBg = "#35507a"
var buttonPressedBg = "#273b5c"
var primaryButtonBg = "#ffb020"
var primaryButtonBorder = "#ffd27a"
var primaryButtonText = "#142033"
var primaryButtonHoverBg = "#ffc247"
var primaryButtonPressedBg = "#e39b0f"
var spacingXs = 4
var spacingSm = 6
@@ -43,7 +63,7 @@ var radiusMd = 8
var compactControlHeight = 28
var dashboardSurfaceComboWidth = 170
var dashboardMetricsHeight = 96
var dashboardMetricsHeight = 148
var dashboardBottomMargin = 8
var palettePanelWidth = 260
@@ -52,10 +72,12 @@ var stageHandleWidth = 16
var reconstructionBadgeWidth = 28
var paletteItemHeight = 30
var dialogWidth = 430
var dialogLabelWidth = 150
var dialogResetButtonWidth = 190
var dialogOkButtonWidth = 90
var dialogWidth = 390
var dialogLabelWidth = 128
var dialogResetButtonWidth = 168
var dialogOkButtonWidth = 76
var dialogContentSpacing = 4
var dialogRowSpacing = 6
var fontXs = 10
var fontSm = 11