Добавил фильтров и общий рефакторинг
This commit is contained in:
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user