├── BoundBox.cpp ├── BoundBox.h ├── Cloud.cpp ├── Cloud.h ├── CloudWorker.cpp ├── CloudWorker.h ├── Cover-Tree ├── CoverTreePoint.h ├── Cover_Tree.h ├── Makefile └── README ├── GLWidget.cpp ├── GLWidget.h ├── LICENSE ├── MainWindow.cpp ├── MainWindow.h ├── Makefile ├── Makefile.Debug ├── Makefile.Release ├── MessageLogger.cpp ├── MessageLogger.h ├── PCReconstruct.pro ├── PCReconstruct.pro.user ├── PCReconstruct.qrc ├── README.md ├── Window.cpp ├── Window.h ├── alignment.h ├── constants.h ├── dialogs ├── BoundBoxDialog.cpp ├── BoundBoxDialog.h ├── DecimateDialog.cpp ├── DecimateDialog.h ├── NormalsDialog.cpp ├── NormalsDialog.h ├── OptionsDialog.cpp ├── OptionsDialog.h ├── RandomSurfDialog.cpp ├── RandomSurfDialog.h ├── ReconstructDialog.cpp ├── ReconstructDialog.h ├── SparsifyDialog.cpp ├── SparsifyDialog.h ├── get_field.cpp └── get_field.h ├── dictionarylearning ├── MatchingPursuit.cpp ├── MatchingPursuit.h ├── OrthogonalPursuit.cpp ├── OrthogonalPursuit.h ├── cosine_transform.cpp ├── cosine_transform.h ├── ksvd.cpp ├── ksvd.h ├── ksvd_dct2D.cpp └── ksvd_dct2D.h ├── images ├── Capture1a.PNG ├── Capture1b.PNG ├── Capture1c.PNG ├── Capture1d.PNG ├── Capture1e.PNG ├── Capture2a.PNG ├── Capture2a2.PNG ├── Capture2b.PNG ├── Capture2b2.PNG ├── Capture2c.PNG ├── Capture2d.PNG ├── Capture2d2.PNG ├── Capture2e.PNG ├── Capture2e2.PNG ├── Capture2f.PNG ├── Capture2f2.PNG ├── Capture5a.PNG ├── Capture5b.PNG ├── Capture5c.PNG ├── Capture5d.PNG ├── Capture8a.PNG ├── Capture8b.PNG ├── Capture8c.PNG ├── Capture8d.PNG ├── copy.png ├── cut.png ├── new.png ├── open.png ├── paste.png ├── save.png └── undo.png ├── main.cpp ├── release ├── OpenNI2.dll ├── PCReconstruct.exe ├── bunny.pcd ├── pcl_common_release.dll ├── pcl_io_ply_release.dll └── pcl_io_release.dll ├── stable.h └── utils ├── Plane.cpp ├── Plane.h ├── cloud_normal.cpp ├── cloud_normal.h ├── ensure_buffer_size.h ├── pt_to_pt_distsq.cpp ├── pt_to_pt_distsq.h ├── rotations.cpp └── rotations.h /BoundBox.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #include "BoundBox.h" 6 | #include "Cloud.h" 7 | #include "MessageLogger.h" 8 | #include "constants.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | using Vector3f = Eigen::Vector3f; 15 | 16 | 17 | constexpr const std::array BoundBox::m_elemGL; 18 | 19 | //--------------------------------------------------------- 20 | 21 | BoundBox::BoundBox(const float minBBox[3], const float maxBBox[3], 22 | MessageLogger* msgLogger) 23 | { 24 | m_msgLogger = msgLogger; 25 | set(minBBox, maxBBox); 26 | } 27 | 28 | //--------------------------------------------------------- 29 | 30 | BoundBox::BoundBox(const Cloud& cloud, MessageLogger* msgLogger) 31 | { 32 | m_msgLogger = msgLogger; 33 | set(cloud); 34 | } 35 | 36 | //--------------------------------------------------------- 37 | 38 | void BoundBox::set(const Cloud& cloud) 39 | { 40 | float minBBox[3] = {float_infinity, float_infinity, float_infinity}; 41 | float maxBBox[3] = {-float_infinity, -float_infinity, -float_infinity}; 42 | 43 | size_t numPoints = cloud.pointCount(); 44 | for(size_t i = 0; i < numPoints; ++i){ 45 | Vector3f v = cloud.point(i); 46 | for(int j=0; j<3; ++j) { 47 | minBBox[j] = std::min(v[j], minBBox[j]); 48 | maxBBox[j] = std::max(v[j], maxBBox[j]); 49 | } 50 | } 51 | 52 | set(minBBox, maxBBox); 53 | } 54 | 55 | //--------------------------------------------------------- 56 | 57 | void BoundBox::set(const float minBBox[3], const float maxBBox[3]) 58 | { 59 | for(int i=0; i<3; ++i) { 60 | m_minBBox[i] = minBBox[i]; 61 | m_maxBBox[i] = maxBBox[i]; 62 | } 63 | m_vertCount = 8; 64 | 65 | if( m_cloud != nullptr ) m_cloud->invalidateCT(); 66 | 67 | } 68 | 69 | 70 | //--------------------------------------------------------- 71 | 72 | void BoundBox::pad(float padX, float padY, float padZ) 73 | { 74 | float padding[3] = {padX, padY, padZ}; 75 | for(int j=0; j<3; ++j) { 76 | m_minBBox[j] -= padding[j]; 77 | m_maxBBox[j] += padding[j]; 78 | } 79 | 80 | if( m_cloud != nullptr ) m_cloud->invalidateCT(); 81 | 82 | } 83 | 84 | //--------------------------------------------------------- 85 | 86 | void BoundBox::rescale(float frac) 87 | { 88 | 89 | for(int j=0; j<3; ++j) { 90 | float padding = frac*(m_maxBBox[j] - m_minBBox[j]); 91 | m_minBBox[j] -= padding; 92 | m_maxBBox[j] += padding; 93 | } 94 | 95 | if( m_cloud != nullptr ) m_cloud->invalidateCT(); 96 | } 97 | 98 | //--------------------------------------------------------- 99 | 100 | void BoundBox::setParentCloud(Cloud *cloud) 101 | { 102 | 103 | m_cloud = cloud; 104 | if( m_cloud != nullptr ) m_cloud->invalidateCT(); 105 | } 106 | 107 | 108 | //--------------------------------------------------------- 109 | 110 | const GLfloat *BoundBox::vertGLData() 111 | { 112 | for(int i = 0, j = 0; i>k)&1) == 0) ? m_minBBox[k] : m_maxBBox[k]; 116 | 117 | } 118 | m_vertGL[j+3] = 1.0f; 119 | m_vertGL[j+4] = 0.0f; 120 | m_vertGL[j+5] = 0.0f; 121 | j += 6; 122 | } 123 | return static_cast(m_vertGL.data()); 124 | } 125 | //--------------------------------------------------------- 126 | 127 | void BoundBox::logMessageBBox() const 128 | { 129 | if(m_msgLogger != nullptr) { 130 | m_msgLogger->logMessage( 131 | QString("Bounding box minimum extent:\n") + 132 | QString("(") + 133 | QString::number(m_minBBox[0]) + 134 | QString(", ") + QString::number(m_minBBox[1]) + 135 | QString(", ") + QString::number(m_minBBox[2]) + 136 | QString(")\n") + 137 | QString("Bounding box maximum extent:\n") + 138 | QString("(") + 139 | QString::number(m_maxBBox[0]) + 140 | QString(", ") + QString::number(m_maxBBox[1]) + 141 | QString(", ") + QString::number(m_maxBBox[2]) + 142 | QString(")\n")); 143 | } 144 | } 145 | //--------------------------------------------------------- 146 | -------------------------------------------------------------------------------- /BoundBox.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #ifndef BOUNDBOX_H 6 | #define BOUNDBOX_H 7 | 8 | #include "pt_to_pt_distsq.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | class MessageLogger; 15 | class Cloud; 16 | 17 | class BoundBox 18 | { 19 | using Index = Eigen::Index; 20 | using Vector3f = Eigen::Vector3f; 21 | 22 | public: 23 | BoundBox(MessageLogger* msgLogger = nullptr): m_msgLogger(msgLogger){} 24 | BoundBox( 25 | const float minBBox[3], const float maxBBox[3], 26 | MessageLogger* msgLogger = nullptr); 27 | BoundBox(const Cloud& cloud, MessageLogger* msgLogger = nullptr); 28 | 29 | void set(const float minBBox[3], const float maxBBox[3]); 30 | void set(const Cloud& cloud); 31 | void pad(float padX, float padY, float padZ); 32 | void rescale(float frac); 33 | void setParentCloud(Cloud *cloud); 34 | 35 | int vertCount() const { return m_vertCount; } 36 | void getExtents(float minBBox[], float maxBBox[]) const { 37 | for(int i=0; i < 3; ++i){ 38 | minBBox[i] = m_minBBox[i]; 39 | maxBBox[i] = m_maxBBox[i]; 40 | } 41 | } 42 | float diagonalSize() const { 43 | return sqrt(pt_to_pt_distsq(m_minBBox, m_maxBBox)); 44 | } 45 | 46 | const GLfloat *vertGLData(); 47 | const GLuint *elemGLData() const 48 | { return static_cast(m_elemGL.data()); } 49 | 50 | bool pointInBBox(const Vector3f& p) const { 51 | return p(0) >= m_minBBox[0] && p(0) <= m_maxBBox[0] && 52 | p(1) >= m_minBBox[1] && p(1) <= m_maxBBox[1] && 53 | p(2) >= m_minBBox[2] && p(2) <= m_maxBBox[2]; 54 | } 55 | 56 | float ballInBBox(const Vector3f& p, float radius) const { 57 | return p(0) - m_minBBox[0] >= radius && 58 | p(1) - m_minBBox[1] >= radius && 59 | p(2) - m_minBBox[2] >= radius && 60 | m_maxBBox[0] - p(0) >= radius && 61 | m_maxBBox[1] - p(1) >= radius && 62 | m_maxBBox[2] - p(2) >= radius; 63 | } 64 | 65 | void logMessageBBox() const; 66 | 67 | 68 | private: 69 | float m_minBBox[3] = {0.0f,0.0f,0.0f}; 70 | float m_maxBBox[3] = {0.0f,0.0f,0.0f}; 71 | int m_vertCount = 0; 72 | std::array m_vertGL; 73 | Cloud* m_cloud = nullptr; 74 | 75 | static constexpr std::array m_elemGL = { 76 | 0, 1, 77 | 0, 2, 78 | 0, 4, 79 | 1, 3, 80 | 1, 5, 81 | 2, 3, 82 | 2, 6, 83 | 3, 7, 84 | 4, 5, 85 | 4, 6, 86 | 5, 7, 87 | 6, 7 88 | }; 89 | 90 | MessageLogger* m_msgLogger; 91 | 92 | 93 | }; 94 | 95 | #endif // BOUNDBOX_H 96 | -------------------------------------------------------------------------------- /Cloud.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #ifndef CLOUD_H 6 | #define CLOUD_H 7 | 8 | #include "Cover_Tree.h" 9 | #include "CoverTreePoint.h" 10 | #include "BoundBox.h" 11 | #include "constants.h" 12 | //#include "MessageLogger.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | //#include 21 | #include 22 | #include 23 | 24 | class MessageLogger; 25 | class BoundBox; 26 | 27 | class Cloud : public QObject 28 | { 29 | Q_OBJECT 30 | 31 | template using vector = std::vector; 32 | using Index = Eigen::Index; 33 | using Vector3f = Eigen::Vector3f; 34 | using Matrix3f = Eigen::Matrix3f; 35 | typedef pcl::PointCloud::Ptr CloudPtr; 36 | 37 | public: 38 | Cloud(MessageLogger* msgLogger = nullptr, QObject *parent = nullptr); 39 | ~Cloud(); 40 | const Vector3f point(size_t idx) const { return m_cloud[idx]; } 41 | const GLfloat* vertGLData(); 42 | const GLfloat* normGLData(float scale); 43 | const GLfloat* debugGLData(); 44 | 45 | size_t pointCount() const { return m_cloud.size(); } 46 | size_t pointCountOrig() const { return m_npointsOrig; } 47 | size_t debugCount() const { return m_debug.size(); } 48 | 49 | void setBoundBox(BoundBox *bBox); 50 | void invalidateCT(); 51 | void clear(); 52 | void backup(); 53 | void restore(); 54 | void fromPCL(CloudPtr cloud); 55 | void toPCL(CloudPtr& cloud); 56 | void fromRandomPlanePoints( 57 | Vector3f norm, size_t npoints, 58 | const std::function heightFun = nullptr); 59 | 60 | size_t addPoint(const Vector3f& v, const Vector3f &n, 61 | bool threadSafe = false); 62 | void replacePoint( 63 | size_t idx, const Vector3f& v, const Vector3f &n, 64 | bool threadSafe = false); 65 | 66 | Vector3f approxNorm( 67 | const Vector3f& p, int iters, 68 | const vector>& neighs, 69 | vector& vneighs, 70 | vector& vwork) const; 71 | void pointKNN( 72 | const Vector3f& p, size_t kNN, 73 | vector>& neighs) const; 74 | 75 | void buildSpatialIndex(bool useBBox = true); 76 | void approxCloudNorms(int iters=25, size_t kNN=25); 77 | void decimate(size_t nHoles, size_t kNN); 78 | void sparsify(float percent); 79 | void reconstruct( 80 | int kSVDIters, size_t kNN, size_t nfreq, float densify, 81 | size_t natm, size_t latm, size_t maxNewPoints, bool looseBBox, 82 | SparseApprox method = SparseApprox::OrthogonalPursuit); 83 | 84 | private: 85 | void getNeighVects(const Vector3f& p, 86 | const vector>& neighs, 87 | vector& vneighs) const; 88 | 89 | vector m_cloud; 90 | vector m_norms; 91 | vector m_cloud_bak; 92 | vector m_norms_bak; 93 | vector> m_debug; 94 | vector m_vertGL; 95 | vector m_normGL; 96 | vector m_debugGL; 97 | BoundBox* m_bBox = nullptr; 98 | CoverTree> *m_CT; 99 | bool m_CTStale = true; 100 | size_t m_npointsCT = 0; 101 | MessageLogger* m_msgLogger; 102 | QRecursiveMutex m_recMutex; 103 | size_t m_npointsOrig = 0; 104 | 105 | }; 106 | 107 | 108 | #endif // CLOUD_H 109 | -------------------------------------------------------------------------------- /CloudWorker.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #include "CloudWorker.h" 6 | #include "Cloud.h" 7 | #include "constants.h" 8 | 9 | //------------------------------------------------------------------------- 10 | CloudWorker::CloudWorker(Cloud& cloud, QObject *parent) : 11 | QObject(parent) 12 | { 13 | m_cloud = &cloud; 14 | } 15 | 16 | //------------------------------------------------------------------------- 17 | CloudWorker::~CloudWorker() 18 | { 19 | } 20 | 21 | //------------------------------------------------------------------------- 22 | 23 | void CloudWorker::approxCloudNorms(int nIters, size_t kNN) 24 | { 25 | if(m_cloud->pointCount() == 0) return; 26 | m_cloud->approxCloudNorms(nIters, kNN); 27 | emit finished(false); 28 | 29 | } 30 | 31 | //------------------------------------------------------------------------- 32 | void CloudWorker::decimateCloud(size_t nHoles, size_t kNN) 33 | { 34 | if(m_cloud->pointCount() == 0) return; 35 | m_cloud->backup(); 36 | m_cloud->decimate(nHoles, kNN); 37 | 38 | emit finished(false); 39 | } 40 | 41 | //------------------------------------------------------------------------- 42 | void CloudWorker::sparsifyCloud(float percent) 43 | { 44 | if(m_cloud->pointCount() == 0) return; 45 | m_cloud->backup(); 46 | m_cloud->sparsify(percent); 47 | 48 | emit finished(false); 49 | } 50 | 51 | //------------------------------------------------------------------------- 52 | 53 | void CloudWorker::reconstructCloud( 54 | int kSVDIters, size_t kNN, size_t nfreq, float densify, 55 | size_t natm, size_t latm, size_t maxNewPoints, bool looseBBox, 56 | SparseApprox method) 57 | { 58 | if(m_cloud->pointCount() == 0) return; 59 | m_cloud->backup(); 60 | m_cloud->reconstruct( 61 | kSVDIters, kNN, nfreq, densify, natm, latm, 62 | maxNewPoints, looseBBox, method); 63 | 64 | emit finished(false); 65 | 66 | } 67 | 68 | 69 | //------------------------------------------------------------------------- 70 | -------------------------------------------------------------------------------- /CloudWorker.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #ifndef RECONSTRUCTTHREAD_H 6 | #define RECONSTRUCTTHREAD_H 7 | 8 | #include "constants.h" 9 | 10 | #include 11 | #include 12 | 13 | class Cloud; 14 | class BoundBox; 15 | 16 | 17 | class CloudWorker : public QObject 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit CloudWorker( 23 | Cloud& cloud, QObject *parent = nullptr); 24 | ~CloudWorker(); 25 | 26 | public slots: 27 | void approxCloudNorms(int nIters, size_t kNN); 28 | void decimateCloud(size_t nHoles, size_t kNN); 29 | void sparsifyCloud(float percent); 30 | void reconstructCloud( 31 | int kSVDIters, size_t kNN, size_t nfreq, float densify, 32 | size_t natm, size_t latm, size_t maxNewPoints, bool looseBBox, 33 | SparseApprox method); 34 | 35 | 36 | signals: 37 | void finished(bool updateBBox); 38 | 39 | private: 40 | Cloud *m_cloud; 41 | QMutex m_mutex; 42 | }; 43 | 44 | 45 | #endif // RECONSTRUCTTHREAD_H 46 | -------------------------------------------------------------------------------- /Cover-Tree/CoverTreePoint.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | 7 | // Brief description 8 | // Point on a cover tree stored as a vector in Eigen. 9 | 10 | #ifndef _COVERTREEPOINT_H 11 | #define _COVERTREEPOINT_H 12 | 13 | #include "pt_to_pt_distsq.h" 14 | #include 15 | 16 | 17 | template 18 | class CoverTreePoint { 19 | private: 20 | T _vec; 21 | size_t _id; 22 | public: 23 | CoverTreePoint(T v, size_t id) : _vec(v), _id(id) {} 24 | CoverTreePoint(){} 25 | ~CoverTreePoint(){} 26 | double distance(const CoverTreePoint& p) const; 27 | const T& getVec() const; 28 | size_t getId() const; 29 | void set(const T& v, size_t id); 30 | void print() const; 31 | bool operator==(const CoverTreePoint&) const; 32 | }; 33 | 34 | template 35 | double CoverTreePoint::distance(const CoverTreePoint& p) const { 36 | const T& vec2 = p.getVec(); 37 | assert(vec2.size() == _vec.size()); 38 | double distsq = 0.0; 39 | size_t n = size_t(_vec.size()); 40 | for(size_t i=0; i < n; ++i) { 41 | distsq += (_vec[i]-vec2[i])*(_vec[i]-vec2[i]); 42 | } 43 | return distsq; 44 | } 45 | 46 | template 47 | const T& CoverTreePoint::getVec() const { 48 | return _vec; 49 | } 50 | 51 | template 52 | size_t CoverTreePoint::getId() const { 53 | return _id; 54 | } 55 | 56 | template 57 | void CoverTreePoint::set(const T& v, size_t id) { 58 | _vec = v; 59 | _id = id; 60 | } 61 | 62 | template 63 | void CoverTreePoint::print() const { 64 | std::cout << "point " << _id << ": " << _vec << "\n"; 65 | } 66 | 67 | template 68 | bool CoverTreePoint::operator==(const CoverTreePoint& p) const { 69 | return (_id==p.getId()); 70 | } 71 | 72 | 73 | #endif // _COVERTREEPOINT_H 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Cover-Tree/Makefile: -------------------------------------------------------------------------------- 1 | CXX ?= g++ 2 | CXXFLAGS ?= -Wall -O3 -ffast-math -funroll-loops 3 | 4 | all: test 5 | 6 | Cover_Tree_Point.o: Cover_Tree_Point.h Cover_Tree_Point.cc 7 | $(CXX) -c $(CXXFLAGS) Cover_Tree_Point.cc 8 | 9 | test: test.cc Cover_Tree.h Cover_Tree_Point.o Cover_Tree_Point.h Cover_Tree_Point.cc 10 | $(CXX) test.cc $(CXXFLAGS) -o test Cover_Tree_Point.o 11 | 12 | clean: 13 | rm *.o test 14 | -------------------------------------------------------------------------------- /Cover-Tree/README: -------------------------------------------------------------------------------- 1 | This is a C++ implementation of the cover tree datastructure. Implements the 2 | cover tree algorithms for insert, removal, and k-nearest-neighbor search. 3 | 4 | To build simply type make in the terminal from the project directory. Do 5 | ./test to run the tests. Look in test.cc for example code of how to use the 6 | cover tree. 7 | 8 | Relevant links: 9 | https://secure.wikimedia.org/wikipedia/en/wiki/Cover_tree - Wikipedia's page 10 | on cover trees. 11 | http://hunch.net/~jl/projects/cover_tree/cover_tree.html - John Langford's (one 12 | of the inventors of cover trees) page on cover trees with links to papers. 13 | 14 | To use the Cover Tree, you must implement your own Point class. CoverTreePoint 15 | is provided for testing and as an example. Your Point class must implement the 16 | following functions: 17 | 18 | double YourPoint::distance(const YourPoint& p); 19 | bool YourPoint::operator==(const YourPoint& p); 20 | and optionally (for debugging/printing only): 21 | void YourPoint::print(); 22 | 23 | The distance function must be a Metric, meaning (from Wikipedia): 24 | 1: d(x, y) = 0 if and only if x = y 25 | 2: d(x, y) = d(y, x) (symmetry) 26 | 3: d(x, z) =< d(x, y) + d(y, z) (subadditivity / triangle inequality). 27 | 28 | See https://secure.wikimedia.org/wikipedia/en/wiki/Metric_%28mathematics%29 29 | for details. 30 | 31 | Actually, 1 does not exactly need to hold for this implementation; you can 32 | provide, for example, names for your points which are unrelated to distance 33 | but important for equality. You can insert multiple points with distance 0 to 34 | each other and the tree will keep track of them, but you cannot insert multiple 35 | points that are equal to each other; attempting to insert a point that 36 | already exists in the tree will not alter the tree at all. 37 | 38 | If you do not want to allow multiple nodes with distance 0, then just make 39 | your equality operator always return true when distance is 0. 40 | 41 | TODO: 42 | -The papers describe batch insert and batch-nearest-neighbors algorithms which 43 | may be worth implementing. 44 | -Try using a third "upper bound" argument for distance functions, beyond which 45 | the distance does not need to be calculated, to improve efficiency in practice. 46 | -------------------------------------------------------------------------------- /GLWidget.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | /**************************************************************************** 51 | ** Peter Beben: heavily modified this file for the purpose of point 52 | ** cloud visualization in this project. 53 | ** Views ALL points in the point cloud without any pruning (so it 54 | ** must be small enough to fit into video memory!!). 55 | ****************************************************************************/ 56 | 57 | #include "GLWidget.h" 58 | #include "BoundBox.h" 59 | #include "Cloud.h" 60 | #include "CloudWorker.h" 61 | #include "MessageLogger.h" 62 | #include "constants.h" 63 | 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | 73 | //#define SHOW_CLOUD_DBUG 74 | 75 | 76 | bool GLWidget::m_transparent = false; 77 | 78 | //--------------------------------------------------------- 79 | 80 | GLWidget::GLWidget(QWidget *parent, MessageLogger* msgLogger) 81 | : QOpenGLWidget(parent), 82 | m_msgLogger(msgLogger), 83 | m_vRot(0), 84 | m_program(0), 85 | m_rotVect(0.0f,0.0f,0.0f), 86 | m_movVect(0.0f,0.0f,0.0f), 87 | m_pointSize(5.0f), 88 | m_showCloudNorms(false), 89 | m_aspectRatio(1.0f), 90 | m_farPlane(100.0f), m_nearPlane(0.01f), 91 | m_modelSize(1.0f), 92 | m_cloudVbo(QOpenGLBuffer::VertexBuffer), 93 | m_cloudBBoxVbo(QOpenGLBuffer::VertexBuffer), 94 | m_cloudBBoxEbo(QOpenGLBuffer::IndexBuffer), 95 | m_cloudNormsVbo(QOpenGLBuffer::VertexBuffer), 96 | m_cloudDebugVbo(QOpenGLBuffer::VertexBuffer), 97 | m_cloud(msgLogger, parent), m_cloudBBox(msgLogger) 98 | { 99 | m_core = QSurfaceFormat::defaultFormat().profile() == QSurfaceFormat::CoreProfile; 100 | // --transparent causes the clear color to be transparent. Therefore, on systems that 101 | // support it, the widget will become transparent apart from the cloud. 102 | if (m_transparent) { 103 | QSurfaceFormat fmt = format(); 104 | fmt.setAlphaBufferSize(8); 105 | setFormat(fmt); 106 | } 107 | 108 | m_cloudThread = new QThread(this); 109 | m_cloudWorker = new CloudWorker(m_cloud); 110 | m_cloudWorker->moveToThread(m_cloudThread); 111 | 112 | connect(this, &GLWidget::cloudApproxNorms, 113 | m_cloudWorker, &CloudWorker::approxCloudNorms); 114 | 115 | connect(this, &GLWidget::cloudDecimate, 116 | m_cloudWorker, &CloudWorker::decimateCloud); 117 | 118 | connect(this, &GLWidget::cloudSparsify, 119 | m_cloudWorker, &CloudWorker::sparsifyCloud); 120 | 121 | connect(this, &GLWidget::cloudReconstruct, 122 | m_cloudWorker, &CloudWorker::reconstructCloud); 123 | 124 | connect(m_cloudWorker, &CloudWorker::finished, 125 | this, &GLWidget::updateCloud); 126 | 127 | connect(m_cloudThread, &QThread::finished, 128 | m_cloudWorker, &QObject::deleteLater); 129 | 130 | m_cloudThread->start(); 131 | 132 | } 133 | //--------------------------------------------------------- 134 | 135 | GLWidget::~GLWidget() 136 | { 137 | m_cloudThread->quit(); 138 | m_cloudThread->wait(); 139 | // delete m_cloudThread; 140 | delete m_cloudWorker; 141 | cleanup(); 142 | } 143 | //--------------------------------------------------------- 144 | 145 | QSize GLWidget::minimumSizeHint() const 146 | { 147 | return QSize(50, 50); 148 | } 149 | //--------------------------------------------------------- 150 | 151 | QSize GLWidget::sizeHint() const 152 | { 153 | return QSize(400, 400); 154 | } 155 | //--------------------------------------------------------- 156 | 157 | static void qNormalizeAngle(int &angle) 158 | { 159 | while (angle < 0) 160 | angle += 360 * 16; 161 | while (angle > 360 * 16) 162 | angle -= 360 * 16; 163 | } 164 | 165 | //--------------------------------------------------------- 166 | 167 | void GLWidget::setVectRotation(int angle, QVector3D v) 168 | { 169 | qNormalizeAngle(angle); 170 | m_vRot = angle; 171 | m_rotVect = v; 172 | m_movVect = QVector3D(0.0f,0.0f,0.0f); 173 | emit vectRotationChanged(angle, v); 174 | update(); 175 | } 176 | //--------------------------------------------------------- 177 | 178 | void GLWidget::setVectTranslation(QVector3D v) 179 | { 180 | m_vRot = 0.0f; 181 | m_rotVect = QVector3D(0.0f,0.0f,0.0f); 182 | m_movVect = v; 183 | emit vectTranslationChanged(v); 184 | update(); 185 | } 186 | //--------------------------------------------------------- 187 | 188 | void GLWidget::cleanup() 189 | { 190 | if (m_program == nullptr) 191 | return; 192 | makeCurrent(); 193 | m_cloudVbo.destroy(); 194 | m_cloudNormsVbo.destroy(); 195 | m_cloudBBoxVbo.destroy(); 196 | m_cloudDebugVbo.destroy(); 197 | delete m_program; 198 | m_program = 0; 199 | doneCurrent(); 200 | } 201 | //--------------------------------------------------------- 202 | 203 | static const char *vertexShaderSourceCore = 204 | "#version 150\n" 205 | "in vec4 vertex;\n" 206 | "in vec3 normal;\n" 207 | "out vec3 vert;\n" 208 | "out vec3 vertNormal;\n" 209 | "uniform mat4 projMatrix;\n" 210 | "uniform mat4 mvMatrix;\n" 211 | "uniform mat3 normalMatrix;\n" 212 | "uniform float pointSize;\n" 213 | "void main() {\n" 214 | " vert = vertex.xyz;\n" 215 | " vertNormal = normalMatrix * normal;\n" 216 | " gl_Position = projMatrix * mvMatrix * vertex;\n" 217 | " gl_PointSize = pointSize/(0.1+10.0*abs(gl_Position.z));\n" 218 | // " gl_PointSize = 50.0;\n" 219 | "}\n"; 220 | 221 | static const char *fragmentShaderSourceCore = 222 | "#version 150\n" 223 | "in highp vec3 vert;\n" 224 | "in highp vec3 vertNormal;\n" 225 | "out highp vec4 fragColor;\n" 226 | "uniform highp vec3 lightPos;\n" 227 | "uniform vec3 vertColor;\n" 228 | "void main() {\n" 229 | " highp vec3 L = normalize(lightPos - vert);\n" 230 | " highp float NL = max(abs(dot(normalize(vertNormal), L)), 0.0);\n" 231 | " highp vec3 color = vertColor;\n" 232 | " highp vec3 col = clamp(color * (0.3 + 0.8*NL), 0.0, 1.0);\n" 233 | // " highp vec3 col = color;\n" 234 | " fragColor = vec4(col, 1.0);\n" 235 | "}\n"; 236 | 237 | static const char *vertexShaderSource = 238 | "attribute vec4 vertex;\n" 239 | "attribute vec3 normal;\n" 240 | "varying vec3 vert;\n" 241 | "varying vec3 vertNormal;\n" 242 | "uniform mat4 projMatrix;\n" 243 | "uniform mat4 mvMatrix;\n" 244 | "uniform mat3 normalMatrix;\n" 245 | "uniform float pointSize;\n" 246 | "void main() {\n" 247 | " vert = vertex.xyz;\n" 248 | " vertNormal = normalMatrix * normal;\n" 249 | " gl_Position = projMatrix * mvMatrix * vertex;\n" 250 | " gl_PointSize = pointSize/(0.1+10.0*abs(gl_Position.z));\n" 251 | // " gl_PointSize = 500.0;\n" 252 | "}\n"; 253 | 254 | static const char *fragmentShaderSource = 255 | "varying highp vec3 vert;\n" 256 | "varying highp vec3 vertNormal;\n" 257 | "uniform highp vec3 lightPos;\n" 258 | "uniform highp vec3 vertColor;\n" 259 | "void main() {\n" 260 | " highp vec3 L = normalize(lightPos - vert);\n" 261 | " highp float NL = max(abs(dot(normalize(vertNormal), L)), 0.0);\n" 262 | " highp vec3 color = vertColor;\n" 263 | " highp vec3 col = clamp(color * (0.3 + 0.8*NL), 0.0, 1.0);\n" 264 | // " highp vec3 col = color;\n" 265 | " gl_FragColor = vec4(col, 1.0);\n" 266 | "}\n"; 267 | 268 | 269 | //--------------------------------------------------------- 270 | 271 | void GLWidget::initializeGL() 272 | { 273 | connect(context(), &QOpenGLContext::aboutToBeDestroyed, 274 | this, &GLWidget::cleanup); 275 | 276 | initializeOpenGLFunctions(); 277 | glClearColor(0, 0, 0, m_transparent ? 0 : 1); 278 | 279 | m_program = new QOpenGLShaderProgram; 280 | m_program->addShaderFromSourceCode( 281 | QOpenGLShader::Vertex, m_core ? 282 | vertexShaderSourceCore : vertexShaderSource); 283 | m_program->addShaderFromSourceCode( 284 | QOpenGLShader::Fragment, m_core ? 285 | fragmentShaderSourceCore : fragmentShaderSource); 286 | m_program->bindAttributeLocation("vertex", 0); 287 | m_program->bindAttributeLocation("normal", 1); 288 | m_program->link(); 289 | 290 | m_program->bind(); 291 | m_projMatrixLoc = m_program->uniformLocation("projMatrix"); 292 | m_mvMatrixLoc = m_program->uniformLocation("mvMatrix"); 293 | m_normalMatrixLoc = m_program->uniformLocation("normalMatrix"); 294 | m_lightPosLoc = m_program->uniformLocation("lightPos"); 295 | m_colorLoc = m_program->uniformLocation("vertColor"); 296 | m_pointSizeLoc = m_program->uniformLocation("pointSize"); 297 | 298 | m_cloudVao.create(); 299 | QOpenGLVertexArrayObject::Binder vaoBinderCloud(&m_cloudVao); 300 | setGLCloud(); 301 | 302 | m_cloudBBoxVao.create(); 303 | QOpenGLVertexArrayObject::Binder vaoBinderCloudBBox(&m_cloudBBoxVao); 304 | setGLBBox(m_cloudBBox, m_cloudBBoxVbo, m_cloudBBoxEbo); 305 | 306 | m_cloudNormsVao.create(); 307 | QOpenGLVertexArrayObject::Binder vaoBinderCloudNorms(&m_cloudNormsVao); 308 | setGLCloudNorms(1.0f); 309 | 310 | #ifdef SHOW_CLOUD_DBUG 311 | m_cloudDebugVao.create(); 312 | QOpenGLVertexArrayObject::Binder vaoBinderCloudDebug(&m_cloudDebugVao); 313 | setGLCloudDebug(); 314 | #endif 315 | 316 | setGLView(); 317 | 318 | m_program->setUniformValue(m_lightPosLoc, QVector3D(0, 0, 2*m_modelSize)); 319 | 320 | m_program->release(); 321 | 322 | } 323 | 324 | //--------------------------------------------------------- 325 | void GLWidget::paintGL() 326 | { 327 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 328 | glEnable(GL_DEPTH_TEST); 329 | glEnable(GL_CULL_FACE); 330 | glEnable(GL_PROGRAM_POINT_SIZE); 331 | 332 | m_world.rotate(-m_vRot / 8.0f, m_rotVect); 333 | m_camera.translate(-m_movVect / 2000.0f); 334 | 335 | m_program->bind(); 336 | m_program->setUniformValue(m_projMatrixLoc, m_proj); 337 | m_program->setUniformValue(m_mvMatrixLoc, m_camera * m_world.transposed()); 338 | QMatrix3x3 normalMatrix = m_world.normalMatrix(); 339 | m_program->setUniformValue(m_normalMatrixLoc, normalMatrix); 340 | m_program->setUniformValue(m_pointSizeLoc, m_pointSize); 341 | m_program->setUniformValue(m_lightPosLoc, QVector3D(0, 0, 2*m_modelSize)); 342 | 343 | size_t npoints = m_cloud.pointCount(); 344 | size_t npointsOrig = m_cloud.pointCountOrig(); 345 | size_t npointsNew = npoints - npointsOrig; 346 | 347 | m_program->setUniformValue(m_colorLoc, QVector3D(0.4f, 1.0f, 0.0f)); 348 | QOpenGLVertexArrayObject::Binder vaoBinder(&m_cloudVao); 349 | glDrawArrays(GL_POINTS, 0, npointsOrig); 350 | 351 | if( npointsNew > 0 ){ 352 | m_program->setUniformValue(m_colorLoc, QVector3D(1.0f, 0.2f, 1.0f)); 353 | glDrawArrays(GL_POINTS, npointsOrig-1, npointsNew); 354 | } 355 | 356 | m_program->setUniformValue(m_colorLoc, QVector3D(1.0f, 0.5f, 0.0f)); 357 | QOpenGLVertexArrayObject::Binder vaoBinder2(&m_cloudBBoxVao); 358 | int idxCount = (m_cloudBBox.vertCount()==0) ? 0 : 24; 359 | glDrawElements(GL_LINES, idxCount, GL_UNSIGNED_INT, 0); 360 | // glDrawElements(GL_LINES, idxCount, GL_UNSIGNED_INT, m_cloudBBox.elemGLData()); 361 | 362 | if( m_showCloudNorms ){ 363 | m_program->setUniformValue(m_colorLoc, QVector3D(1.0f, 0.0f, 1.0f)); 364 | QOpenGLVertexArrayObject::Binder vaoBinder3(&m_cloudNormsVao); 365 | glDrawArrays(GL_LINES, 0, 2*npoints); 366 | } 367 | 368 | #ifdef SHOW_CLOUD_DBUG 369 | m_program->setUniformValue(m_colorLoc, QVector3D(0.0f, 0.0f, 0.5f)); 370 | QOpenGLVertexArrayObject::Binder vaoBinder4(&m_cloudDebugVao); 371 | glDrawArrays(GL_LINES, 0, 2*m_cloud.debugCount()); 372 | #endif 373 | 374 | m_program->release(); 375 | } 376 | //--------------------------------------------------------- 377 | 378 | void GLWidget::resizeGL(int w, int h) 379 | { 380 | m_aspectRatio = float(w)/h; 381 | m_proj.setToIdentity(); 382 | m_proj.perspective(45.0f, m_aspectRatio, m_nearPlane, m_farPlane); 383 | } 384 | 385 | //--------------------------------------------------------- 386 | 387 | void GLWidget::setGLView() 388 | { 389 | m_rotVect = QVector3D(0.0f,0.0f,0.0f); 390 | m_movVect = QVector3D(0.0f,0.0f,0.0f); 391 | 392 | m_nearPlane = 1e-5*m_modelSize; 393 | m_farPlane = 100.0f*m_modelSize; 394 | m_proj.setToIdentity(); 395 | m_proj.perspective(45.0f, m_aspectRatio, m_nearPlane, m_farPlane); 396 | 397 | m_world.setToIdentity(); 398 | m_camera.setToIdentity(); 399 | m_camera.translate(0, 0, -m_modelSize); 400 | } 401 | //--------------------------------------------------------- 402 | 403 | void GLWidget::setGLCloud() 404 | { 405 | size_t npoints = m_cloud.pointCount(); 406 | 407 | makeCurrent(); 408 | 409 | // Setup our vertex buffer object for point cloud. 410 | m_cloudVbo.create(); 411 | m_cloudVbo.bind(); 412 | m_cloudVbo.allocate( 413 | m_cloud.vertGLData(), 6*npoints*sizeof(GLfloat)); 414 | // Store the vertex attribute bindings for the program. 415 | setupVertexAttribs(m_cloudVbo); 416 | 417 | } 418 | 419 | //--------------------------------------------------------- 420 | 421 | void GLWidget::setGLCloudNorms(float scale) 422 | { 423 | size_t npoints = m_cloud.pointCount(); 424 | 425 | makeCurrent(); 426 | 427 | m_cloudNormsVbo.create(); 428 | m_cloudNormsVbo.bind(); 429 | m_cloudNormsVbo.allocate( 430 | m_cloud.normGLData(scale), 431 | 12*npoints*sizeof(GLfloat)); 432 | setupVertexAttribs(m_cloudNormsVbo); 433 | 434 | } 435 | 436 | //--------------------------------------------------------- 437 | 438 | void GLWidget::setGLCloudDebug() 439 | { 440 | size_t ndebug = m_cloud.debugCount(); 441 | 442 | makeCurrent(); 443 | 444 | m_cloudDebugVbo.create(); 445 | m_cloudDebugVbo.bind(); 446 | m_cloudDebugVbo.allocate( 447 | m_cloud.debugGLData(), 448 | 12*ndebug*sizeof(GLfloat)); 449 | setupVertexAttribs(m_cloudDebugVbo); 450 | 451 | } 452 | 453 | //--------------------------------------------------------- 454 | 455 | void GLWidget::setGLBBox( 456 | BoundBox bBox, QOpenGLBuffer vbo, QOpenGLBuffer ebo) 457 | { 458 | makeCurrent(); 459 | 460 | vbo.create(); 461 | vbo.bind(); 462 | vbo.allocate(bBox.vertGLData(), 6*bBox.vertCount()*sizeof(GLfloat)); 463 | ebo.create(); 464 | ebo.bind(); 465 | int idxCount = (bBox.vertCount()==0) ? 0 : 24; 466 | ebo.allocate(bBox.elemGLData(), idxCount*sizeof(GLuint)); 467 | setupVertexAttribs(vbo); 468 | 469 | } 470 | 471 | //--------------------------------------------------------- 472 | 473 | void GLWidget::setupVertexAttribs(QOpenGLBuffer vbo) 474 | { 475 | vbo.bind(); 476 | QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); 477 | f->glEnableVertexAttribArray(0); 478 | f->glEnableVertexAttribArray(1); 479 | f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), 0); 480 | f->glVertexAttribPointer( 481 | 1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), 482 | reinterpret_cast(3 * sizeof(GLfloat))); 483 | vbo.release(); 484 | 485 | } 486 | 487 | //--------------------------------------------------------- 488 | 489 | void GLWidget::mousePressEvent(QMouseEvent *event) 490 | { 491 | m_lastMousePos = event->pos(); 492 | //*** 493 | // if(event->buttons() & Qt::MidButton){ 494 | // setRandomCloud(35000); 495 | // } 496 | 497 | } 498 | //--------------------------------------------------------- 499 | 500 | void GLWidget::mouseMoveEvent(QMouseEvent *event) 501 | { 502 | int dx = event->x() - m_lastMousePos.x(); 503 | int dy = event->y() - m_lastMousePos.y(); 504 | 505 | bool leftButton = (event->buttons() & Qt::LeftButton); 506 | bool rightButton = (event->buttons() & Qt::RightButton); 507 | 508 | if ( leftButton && rightButton ) { 509 | float norm = frobeniusNorm4x4(m_camera); 510 | setVectTranslation(QVector3D(-norm*dx,norm*dy,0.0f)); 511 | } 512 | else if ( leftButton ) { 513 | int angle = abs(dx) + abs(dy); 514 | setVectRotation(angle, QVector3D(dy,dx,0.0f)); 515 | } 516 | else if ( rightButton ) { 517 | int angle = abs(dx) + abs(dy); 518 | setVectRotation(angle, QVector3D(dy,0.0f,-dx)); 519 | } 520 | 521 | m_lastMousePos = event->pos(); 522 | } 523 | //--------------------------------------------------------- 524 | 525 | void GLWidget::wheelEvent(QWheelEvent *event) 526 | { 527 | float norm = frobeniusNorm4x4(m_camera) + 1e-3; 528 | setVectTranslation(QVector3D(0.0f,0.0f,norm*event->delta())); 529 | } 530 | //--------------------------------------------------------- 531 | 532 | void GLWidget::setCloud(CloudPtr cloud) 533 | { 534 | QMutexLocker locker(&m_recMutex); 535 | 536 | m_cloud.fromPCL(cloud); 537 | 538 | updateCloud(true); 539 | setGLView(); 540 | } 541 | 542 | 543 | //--------------------------------------------------------- 544 | 545 | void GLWidget::setRandomCloud(size_t nPoints) 546 | { 547 | QMutexLocker locker(&m_recMutex); 548 | 549 | Eigen::VectorXf C = Eigen::VectorXf::Random(5); 550 | Eigen::VectorXf D = Eigen::VectorXf::Random(8); 551 | Eigen::VectorXf E = Eigen::VectorXf::Random(5); 552 | Eigen::VectorXf F = Eigen::VectorXf::Random(8); 553 | 554 | auto heightFun = [&C, &D, &E, &F](float xu, float xv){ 555 | float pu = 15*xu, pv = 15*xv; 556 | float height = 557 | C(0)*cos(D(0)*pu) + 558 | C(1)*cos(D(1)*pv) + 559 | C(2)*cos(D(2)*pu)*cos(D(3)*pu) + 560 | C(3)*cos(D(4)*pu)*cos(D(5)*pv) + 561 | C(4)*cos(D(6)*pv)*cos(D(7)*pv) + 562 | E(0)*sin(F(0)*pu) + 563 | E(1)*sin(F(1)*pv) + 564 | E(2)*sin(F(2)*pu)*sin(F(3)*pu) + 565 | E(3)*sin(F(4)*pu)*sin(F(5)*pv) + 566 | E(4)*sin(F(6)*pv)*sin(F(7)*pv); 567 | return 0.05f*height; 568 | }; 569 | 570 | Eigen::Vector3f norm(0.0f,1.0f,0.0f); 571 | m_cloud.fromRandomPlanePoints(norm, nPoints, heightFun); 572 | 573 | updateCloud(true); 574 | 575 | setGLView(); 576 | } 577 | 578 | 579 | //--------------------------------------------------------- 580 | 581 | void GLWidget::getCloud(CloudPtr& cloud) 582 | { 583 | QMutexLocker locker(&m_recMutex); 584 | 585 | m_cloud.toPCL(cloud); 586 | } 587 | 588 | //--------------------------------------------------------- 589 | 590 | void GLWidget::undoCloud() 591 | { 592 | QMutexLocker locker(&m_recMutex); 593 | 594 | m_cloud.restore(); 595 | 596 | updateCloud(false); 597 | } 598 | 599 | 600 | //--------------------------------------------------------- 601 | 602 | void GLWidget::setCloudBBox(float minBBox[3], float maxBBox[3]) 603 | { 604 | QMutexLocker locker(&m_recMutex); 605 | 606 | m_cloudBBox.set(minBBox, maxBBox); 607 | m_cloudBBox.logMessageBBox(); 608 | emit bBoxFieldsChanged(minBBox, maxBBox); 609 | m_cloud.setBoundBox(&m_cloudBBox); 610 | setGLBBox(m_cloudBBox, m_cloudBBoxVbo, m_cloudBBoxEbo); 611 | update(); 612 | 613 | } 614 | 615 | //--------------------------------------------------------- 616 | 617 | void GLWidget::viewGLCloudNorms(bool enabled) 618 | { 619 | m_showCloudNorms = enabled; 620 | update(); 621 | } 622 | 623 | //--------------------------------------------------------- 624 | 625 | void GLWidget::updateCloud(bool updateBBox) 626 | { 627 | QMutexLocker locker(&m_recMutex); 628 | 629 | if( updateBBox ){ 630 | float minBBox[3], maxBBox[3]; 631 | m_cloudBBox.set(m_cloud); 632 | m_cloudBBox.rescale(0.01f); 633 | m_cloudBBox.logMessageBBox(); 634 | m_cloudBBox.getExtents(minBBox, maxBBox); 635 | emit bBoxFieldsChanged(minBBox, maxBBox); 636 | m_cloud.setBoundBox(&m_cloudBBox); 637 | m_modelSize = m_cloudBBox.diagonalSize(); 638 | setGLBBox(m_cloudBBox, m_cloudBBoxVbo, m_cloudBBoxEbo); 639 | } 640 | 641 | setGLCloud(); 642 | setGLCloudNorms(1e-2*m_modelSize); 643 | 644 | #ifdef SHOW_CLOUD_DBUG 645 | setGLCloudDebug(); 646 | #endif 647 | 648 | update(); 649 | } 650 | 651 | //--------------------------------------------------------- 652 | 653 | float GLWidget::frobeniusNorm4x4(QMatrix4x4 M) 654 | { 655 | float normSq = 656 | M(0,0)*M(0,0) + 657 | M(1,0)*M(1,0) + 658 | M(2,0)*M(2,0) + 659 | M(3,0)*M(3,0) + 660 | M(0,1)*M(0,1) + 661 | M(1,1)*M(1,1) + 662 | M(2,1)*M(2,1) + 663 | M(3,1)*M(3,1) + 664 | M(0,2)*M(0,2) + 665 | M(1,2)*M(1,2) + 666 | M(2,2)*M(2,2) + 667 | M(3,2)*M(3,2) + 668 | M(0,3)*M(0,3) + 669 | M(1,3)*M(1,3) + 670 | M(2,3)*M(2,3) + 671 | M(3,3)*M(3,3); 672 | return sqrt(normSq); 673 | } 674 | //--------------------------------------------------------- 675 | -------------------------------------------------------------------------------- /GLWidget.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | /**************************************************************************** 51 | ** Peter Beben: heavily modified this file for the purpose of point 52 | ** cloud visualization in this project. 53 | ** Views ALL points in the point cloud without any pruning (so it 54 | ** must be small enough to fit into video memory!!). 55 | ****************************************************************************/ 56 | 57 | #ifndef GLWIDGET_H 58 | #define GLWIDGET_H 59 | 60 | #include "BoundBox.h" 61 | #include "Cloud.h" 62 | #include "CloudWorker.h" 63 | //#include "MessageLogger.h" 64 | 65 | #include 66 | #include 67 | 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | 75 | class MessageLogger; 76 | class CloudWorker; 77 | 78 | QT_BEGIN_NAMESPACE 79 | class QThread; 80 | QT_END_NAMESPACE 81 | 82 | QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram) 83 | 84 | class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions 85 | { 86 | Q_OBJECT 87 | 88 | typedef pcl::PointCloud::Ptr CloudPtr; 89 | 90 | public: 91 | GLWidget(QWidget *parent = nullptr, MessageLogger* msgLogger = nullptr); 92 | ~GLWidget(); 93 | 94 | static bool isTransparent() { return m_transparent; } 95 | static void setTransparent(bool t) { m_transparent = t; } 96 | 97 | QSize minimumSizeHint() const override; 98 | QSize sizeHint() const override; 99 | 100 | public slots: 101 | void setVectRotation(int angle, QVector3D v); 102 | void setVectTranslation(QVector3D v); 103 | void cleanup(); 104 | void setCloud(CloudPtr cloud); 105 | void setRandomCloud(size_t nPoints); 106 | void getCloud(CloudPtr& cloud); 107 | void undoCloud(); 108 | void setCloudBBox(float minBBox[3], float maxBBox[3]); 109 | void viewGLCloudNorms(bool enabled); 110 | void approxCloudNorms(int nIters, size_t kNN) 111 | { emit cloudApproxNorms(nIters, kNN); } 112 | void decimateCloud(size_t nHoles, size_t kNN) 113 | { emit cloudDecimate(nHoles,kNN); } 114 | void sparsifyCloud(float percent) 115 | { emit cloudSparsify(percent); } 116 | void reconstructCloud( 117 | int kSVDIters, size_t kNN, size_t nfreq, float densify, 118 | size_t natm, size_t latm, size_t maxNewPoints, bool looseBBox, 119 | SparseApprox method) 120 | { 121 | emit cloudReconstruct( 122 | kSVDIters, kNN, nfreq, densify, natm, latm, 123 | maxNewPoints, looseBBox, method); 124 | } 125 | void setPointSize(float size) { m_pointSize = size; } 126 | void setNormScale(float scale) { setGLCloudNorms(scale*m_modelSize); } 127 | void updateCloud(bool updateBBox); 128 | 129 | signals: 130 | void vectRotationChanged(int angle, QVector3D v); 131 | void vectTranslationChanged(QVector3D v); 132 | //void cloudSetRandom(size_t nPoints); 133 | void cloudApproxNorms(int nIters, size_t kNN); 134 | void cloudDecimate(size_t nHoles, size_t kNN); 135 | void cloudSparsify(float percent); 136 | void cloudReconstruct( 137 | int kSVDIters, size_t kNN, size_t nfreq, float densify, 138 | size_t natm, size_t latm, size_t maxNewPoints, bool looseBBox, 139 | SparseApprox method); 140 | void bBoxFieldsChanged(float minBBox[3], float maxBBox[3]); 141 | void logMessage(const QString& text); 142 | 143 | protected: 144 | void initializeGL() override; 145 | void paintGL() override; 146 | void resizeGL(int width, int height) override; 147 | void mousePressEvent(QMouseEvent *event) override; 148 | void mouseMoveEvent(QMouseEvent *event) override; 149 | void wheelEvent(QWheelEvent *event) override; 150 | 151 | private: 152 | void setupVertexAttribs(QOpenGLBuffer vbo); 153 | void setGLView(); 154 | void setGLCloud(); 155 | void setGLCloudNorms(float scale); 156 | void setGLCloudDebug(); 157 | void setGLBBox( 158 | BoundBox bBox, QOpenGLBuffer vbo, QOpenGLBuffer ebo); 159 | float frobeniusNorm4x4(QMatrix4x4 M); 160 | 161 | bool m_core; 162 | int m_vRot; 163 | QPoint m_lastMousePos; 164 | //QPoint m_lastWheelPos; 165 | Cloud m_cloud; 166 | BoundBox m_cloudBBox; 167 | QOpenGLVertexArrayObject m_cloudVao; 168 | QOpenGLVertexArrayObject m_cloudBBoxVao; 169 | QOpenGLVertexArrayObject m_cloudNormsVao; 170 | QOpenGLVertexArrayObject m_cloudDebugVao; 171 | QOpenGLBuffer m_cloudVbo; 172 | QOpenGLBuffer m_cloudBBoxVbo; 173 | QOpenGLBuffer m_cloudBBoxEbo; 174 | QOpenGLBuffer m_cloudNormsVbo; 175 | QOpenGLBuffer m_cloudDebugVbo; 176 | QOpenGLShaderProgram *m_program; 177 | int m_projMatrixLoc; 178 | float m_aspectRatio; 179 | float m_nearPlane; 180 | float m_farPlane; 181 | float m_modelSize; 182 | int m_mvMatrixLoc; 183 | int m_normalMatrixLoc; 184 | int m_lightPosLoc; 185 | int m_colorLoc; 186 | int m_pointSizeLoc; 187 | float m_pointSize; 188 | float m_normScale; 189 | bool m_showCloudNorms; 190 | QMatrix4x4 m_proj; 191 | QMatrix4x4 m_camera; 192 | QMatrix4x4 m_world; 193 | QVector3D m_rotVect; 194 | QVector3D m_movVect; 195 | static bool m_transparent; 196 | MessageLogger* m_msgLogger; 197 | QThread* m_cloudThread; 198 | CloudWorker* m_cloudWorker; 199 | QRecursiveMutex m_recMutex; 200 | 201 | }; 202 | 203 | #endif 204 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /MainWindow.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | 5 | #include "MainWindow.h" 6 | #include "Window.h" 7 | #include "MessageLogger.h" 8 | #include "RandomSurfDialog.h" 9 | #include "BoundBoxDialog.h" 10 | #include "NormalsDialog.h" 11 | #include "DecimateDialog.h" 12 | #include "SparsifyDialog.h" 13 | #include "ReconstructDialog.h" 14 | #include "OptionsDialog.h" 15 | #include "constants.h" 16 | 17 | #include 18 | //#include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | 35 | MainWindow::MainWindow(QWidget *parent) : 36 | QMainWindow(parent) 37 | { 38 | if (objectName().isEmpty()) 39 | setObjectName(QString::fromUtf8("MainWindow")); 40 | resize(1204, 640); 41 | 42 | setWindowTitle(QCoreApplication::translate("MainWindow", "PCReconstruct", nullptr)); 43 | 44 | //------ 45 | // Add docks 46 | QDockWidget *dock = new QDockWidget("Log Window", this); 47 | dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); 48 | logText = new QPlainTextEdit; 49 | logText->setReadOnly(true); 50 | dock->setWidget(logText); 51 | addDockWidget(Qt::RightDockWidgetArea, dock); 52 | 53 | //------ 54 | // Add actions 55 | QMenu *fileMenu = menuBar()->addMenu("&File"); 56 | QMenu *editMenu = menuBar()->addMenu("&Edit"); 57 | QMenu *viewMenu = menuBar()->addMenu("&View"); 58 | QMenu *toolsMenu = menuBar()->addMenu("&Tools"); 59 | QMenu *helpMenu = menuBar()->addMenu("&Help"); 60 | 61 | 62 | QToolBar *fileToolBar = addToolBar("File"); 63 | 64 | const QIcon openIcon = 65 | QIcon::fromTheme("document-open", QIcon(":/images/open.png")); 66 | QAction *openAct = new QAction(openIcon, "&Open...", this); 67 | openAct->setShortcuts(QKeySequence::Open); 68 | openAct->setStatusTip("Open an existing PCD file"); 69 | connect(openAct, &QAction::triggered, this, &MainWindow::open); 70 | fileMenu->addAction(openAct); 71 | fileToolBar->addAction(openAct); 72 | 73 | const QIcon saveAsIcon = 74 | QIcon::fromTheme("document-save-as", QIcon(":/images/save.png")); 75 | QAction *saveAsAct = new QAction(saveAsIcon, "Save &As...", this); 76 | saveAsAct->setShortcuts(QKeySequence::SaveAs); 77 | saveAsAct->setStatusTip("Save PCD to disk"); 78 | connect(saveAsAct, &QAction::triggered, this, &MainWindow::saveAs); 79 | fileMenu->addAction(saveAsAct); 80 | fileToolBar->addAction(saveAsAct); 81 | 82 | const QIcon exitIcon = QIcon::fromTheme("application-exit"); 83 | QAction *exitAct = 84 | fileMenu->addAction(exitIcon, "E&xit", this, &QWidget::close); 85 | exitAct->setShortcuts(QKeySequence::Quit); 86 | exitAct->setStatusTip("Exit PCReconstruct"); 87 | 88 | 89 | QToolBar *editToolBar = addToolBar("Edit"); 90 | 91 | const QIcon undoIcon = 92 | QIcon::fromTheme("application-undo", QIcon(":/images/undo.png")); 93 | QAction *undoAct = new QAction(undoIcon, "&Undo", this); 94 | undoAct->setShortcuts(QKeySequence::Undo); 95 | undoAct->setStatusTip("Undo"); 96 | connect(undoAct, &QAction::triggered, this, &MainWindow::undo); 97 | editMenu->addAction(undoAct); 98 | editToolBar->addAction(undoAct); 99 | 100 | 101 | viewMenu->addAction(dock->toggleViewAction()); 102 | 103 | QAction *viewNormsAct = new QAction("&View normals", this); 104 | viewNormsAct->setStatusTip("View point cloud normals."); 105 | viewNormsAct->setCheckable(true); 106 | connect(viewNormsAct, &QAction::triggered, this, &MainWindow::viewGLNorms); 107 | viewMenu->addAction(viewNormsAct); 108 | 109 | QAction *randomSurfAct = new QAction("&Random surface", this); 110 | randomSurfAct->setStatusTip( 111 | "Sample a point cloud randomly from a random surface"); 112 | connect(randomSurfAct, &QAction::triggered, this, &MainWindow::setRandom); 113 | toolsMenu->addAction(randomSurfAct); 114 | 115 | QAction *boundBoxAct = new QAction("&Set bounding box", this); 116 | boundBoxAct->setStatusTip( 117 | "Set bounding box, inside which operations are performed"); 118 | connect(boundBoxAct, &QAction::triggered, this, &MainWindow::setBBox); 119 | toolsMenu->addAction(boundBoxAct); 120 | 121 | QAction *normalsAct = new QAction("&Approx. Normals", this); 122 | normalsAct->setStatusTip( 123 | "Approximate cloud surface normals"); 124 | connect(normalsAct, &QAction::triggered, this, &MainWindow::approxNorms); 125 | toolsMenu->addAction(normalsAct); 126 | 127 | QAction *decimateAct = new QAction("&Decimate", this); 128 | decimateAct->setStatusTip("Generate random holes in point cloud"); 129 | connect(decimateAct, &QAction::triggered, this, &MainWindow::decimate); 130 | toolsMenu->addAction(decimateAct); 131 | 132 | QAction *sparsifyAct = new QAction("&Sparsify", this); 133 | sparsifyAct->setStatusTip("Take a random subset of the point cloud"); 134 | connect(sparsifyAct, &QAction::triggered, this, &MainWindow::sparsify); 135 | toolsMenu->addAction(sparsifyAct); 136 | 137 | QAction *reconstructAct = new QAction("&Reconstruct", this); 138 | reconstructAct->setStatusTip("Reconstruct point cloud"); 139 | connect(reconstructAct, &QAction::triggered, this, &MainWindow::reconstruct); 140 | toolsMenu->addAction(reconstructAct); 141 | 142 | toolsMenu->addSeparator(); 143 | 144 | QAction *optionsAct = new QAction("&Options", this); 145 | optionsAct->setStatusTip("Change app settings."); 146 | connect(optionsAct, &QAction::triggered, this, &MainWindow::options); 147 | toolsMenu->addAction(optionsAct); 148 | 149 | 150 | QAction *aboutAct = 151 | helpMenu->addAction("&About", this, &MainWindow::about); 152 | aboutAct->setStatusTip("About"); 153 | 154 | QAction *aboutQtAct = 155 | helpMenu->addAction("About &Qt", qApp, &QApplication::aboutQt); 156 | aboutQtAct->setStatusTip("About Qt"); 157 | 158 | 159 | //------ 160 | // Central widget 161 | 162 | msgLogger = new MessageLogger(logText); 163 | centralWidget = new Window(this, msgLogger); 164 | centralWidget->setObjectName(QString::fromUtf8("centralWidget")); 165 | setCentralWidget(centralWidget); 166 | 167 | connect(msgLogger, &MessageLogger::logTextAppend, 168 | this, &MainWindow::appendLogText); 169 | 170 | connect(msgLogger, &MessageLogger::logTextInsert, 171 | this, &MainWindow::insertLogText); 172 | 173 | connect(this, &MainWindow::cloudChanged, 174 | centralWidget, &Window::setCloud); 175 | 176 | connect(this, &MainWindow::cloudQueried, 177 | centralWidget, &Window::getCloud); 178 | 179 | connect(this, &MainWindow::cloudUndo, 180 | centralWidget, &Window::undoCloud); 181 | 182 | connect(this, &MainWindow::cloudNormsViewGL, 183 | centralWidget, &Window::viewGLCloudNorms); 184 | 185 | connect(this, &MainWindow::cloudSetRandom, 186 | centralWidget, &Window::setRandomCloud); 187 | 188 | connect(this, &MainWindow::cloudSetBBox, 189 | centralWidget, &Window::setCloudBBox); 190 | 191 | connect(this, &MainWindow::cloudApproxNorms, 192 | centralWidget, &Window::approxCloudNorms); 193 | 194 | connect(this, &MainWindow::cloudDecimate, 195 | centralWidget, &Window::decimateCloud); 196 | 197 | connect(this, &MainWindow::cloudSparsify, 198 | centralWidget, &Window::sparsifyCloud); 199 | 200 | connect(this, &MainWindow::cloudReconstruct, 201 | centralWidget, &Window::reconstructCloud); 202 | 203 | connect(this, &MainWindow::pointSizeChanged, 204 | centralWidget, &Window::setPointSize); 205 | 206 | connect(this, &MainWindow::normScaleChanged, 207 | centralWidget, &Window::setNormScale); 208 | 209 | connect(centralWidget, &Window::bBoxFieldsChanged, 210 | this, &MainWindow::changeBBoxFields); 211 | 212 | 213 | //------ 214 | // Dialogs 215 | 216 | randomSurfDialog = new RandomSurfDialog(this); 217 | boundBoxDialog = new BoundBoxDialog(this); 218 | normalsDialog = new NormalsDialog(this); 219 | decimateDialog = new DecimateDialog(this); 220 | sparsifyDialog = new SparsifyDialog(this); 221 | reconstructDialog = new ReconstructDialog(this); 222 | optionsDialog = new OptionsDialog(this); 223 | 224 | 225 | //------ 226 | 227 | QMetaObject::connectSlotsByName(this); 228 | 229 | } 230 | 231 | //--------------------------------------------------------- 232 | 233 | void MainWindow::open() 234 | { 235 | 236 | // QString q_path = QFileDialog::getOpenFileName( 237 | // this, "Open File", "", "(*.pcd *.ply)" 238 | // ); 239 | 240 | QString q_path = QFileDialog::getOpenFileName( 241 | this, "Open File", "", "PCD (*.pcd)" 242 | ); 243 | 244 | std::string path = q_path.toStdString(); 245 | pcl::PointCloud::Ptr cloud(new pcl::PointCloud); 246 | 247 | int success = 0; 248 | try{ 249 | // if( q_path.endsWith("pcd", Qt::CaseInsensitive) ){ 250 | success = pcl::io::loadPCDFile(path, *cloud); 251 | // } 252 | // else if( q_path.endsWith("ply", Qt::CaseInsensitive) ){ 253 | // success = pcl::io::loadPLYFile(path, *cloud); 254 | // if( success == -1 ){ 255 | // success = pcl::io::loadPolygonFilePLY(path, *cloud); 256 | // } 257 | // } 258 | // else{ 259 | // success = -1; 260 | // } 261 | 262 | } 263 | catch (...){ 264 | success = -1; 265 | } 266 | 267 | if (success == -1) { 268 | appendLogText("Couldn't read PCD file\n"); 269 | return; 270 | } 271 | else{ 272 | appendLogText("Opened: " + q_path + "\n"); 273 | } 274 | 275 | emit cloudChanged(cloud); 276 | 277 | } 278 | 279 | //--------------------------------------------------------- 280 | 281 | void MainWindow::saveAs() 282 | { 283 | QString q_pcdPath = QFileDialog::getSaveFileName( 284 | this, "Open File", "", "PCD (*.pcd)" 285 | ); 286 | std::string pcdPath = q_pcdPath.toStdString(); 287 | 288 | pcl::PointCloud::Ptr cloud(new pcl::PointCloud); 289 | emit cloudQueried(cloud); 290 | 291 | int success = 0; 292 | try{ 293 | success = pcl::io::savePCDFile(pcdPath, *cloud); 294 | } 295 | catch (...){ 296 | success = -1; 297 | } 298 | 299 | if (success == -1) { 300 | //PCL_ERROR ("Couldn't save PCD file\n"); 301 | appendLogText("Couldn't save PCD file\n"); 302 | return; 303 | } 304 | else{ 305 | appendLogText("Saved: " + q_pcdPath + "\n"); 306 | } 307 | 308 | 309 | } 310 | 311 | //--------------------------------------------------------- 312 | 313 | void MainWindow::undo() 314 | { 315 | emit cloudUndo(); 316 | } 317 | 318 | //--------------------------------------------------------- 319 | 320 | void MainWindow::viewGLNorms(bool enabled) 321 | { 322 | emit cloudNormsViewGL(enabled); 323 | } 324 | 325 | //--------------------------------------------------------- 326 | 327 | void MainWindow::setRandom() 328 | { 329 | // Show the dialog as modal 330 | if(randomSurfDialog->exec() == QDialog::Accepted){ 331 | size_t nPoints; 332 | bool ok = randomSurfDialog->getFields(nPoints); 333 | if(!ok){ 334 | badInputMessageBox("All fields should be integers bigger than zero."); 335 | return; 336 | } 337 | emit cloudSetRandom(nPoints); 338 | } 339 | 340 | } 341 | 342 | 343 | //--------------------------------------------------------- 344 | 345 | void MainWindow::setBBox() 346 | { 347 | // Show the dialog as modal 348 | if(boundBoxDialog->exec() == QDialog::Accepted){ 349 | float minBBox[3], maxBBox[3]; 350 | bool ok = boundBoxDialog->getFields(minBBox, maxBBox); 351 | if(!ok){ 352 | badInputMessageBox( 353 | QString("All fields should be bigger than zero,\n") + 354 | QString("and min. extents less than max. extents.")); 355 | return; 356 | } 357 | emit cloudSetBBox(minBBox, maxBBox); 358 | } 359 | 360 | } 361 | 362 | 363 | //--------------------------------------------------------- 364 | 365 | void MainWindow::approxNorms() 366 | { 367 | // Show the dialog as modal 368 | if(normalsDialog->exec() == QDialog::Accepted){ 369 | int nIters; 370 | size_t kNN; 371 | bool ok = normalsDialog->getFields(nIters, kNN); 372 | if(!ok){ 373 | badInputMessageBox( 374 | QString("All fields should be bigger than zero.")); 375 | return; 376 | } 377 | emit cloudApproxNorms(nIters, kNN); 378 | } 379 | 380 | } 381 | 382 | 383 | //--------------------------------------------------------- 384 | 385 | void MainWindow::decimate() 386 | { 387 | // Show the dialog as modal 388 | if(decimateDialog->exec() == QDialog::Accepted){ 389 | size_t nHoles, kNN; 390 | bool ok = decimateDialog->getFields(nHoles, kNN); 391 | if(!ok){ 392 | badInputMessageBox("All fields should be integers bigger than zero."); 393 | return; 394 | } 395 | emit cloudDecimate(nHoles,kNN); 396 | } 397 | 398 | } 399 | 400 | //--------------------------------------------------------- 401 | 402 | void MainWindow::sparsify() 403 | { 404 | // Show the dialog as modal 405 | if(sparsifyDialog->exec() == QDialog::Accepted){ 406 | float percent; 407 | bool ok = sparsifyDialog->getFields(percent); 408 | if(!ok){ 409 | badInputMessageBox("Percent field should be between 0 and 100."); 410 | return; 411 | } 412 | emit cloudSparsify(percent); 413 | } 414 | 415 | } 416 | 417 | 418 | //--------------------------------------------------------- 419 | 420 | void MainWindow::reconstruct() 421 | { 422 | // Show the dialog as modal 423 | if(reconstructDialog->exec() == QDialog::Accepted){ 424 | int kSVDIters; 425 | size_t kNN, nfreq, natm, latm, maxNewPoints; 426 | float densify; 427 | bool looseBBox; 428 | SparseApprox method; 429 | 430 | int ok = reconstructDialog->getFields( 431 | kSVDIters, kNN, nfreq, densify, natm, latm, 432 | maxNewPoints, looseBBox, method); 433 | switch( ok ){ 434 | case -1: 435 | badInputMessageBox( 436 | "Number of iterations field must be bigger than zero."); 437 | break; 438 | case -2: 439 | badInputMessageBox( 440 | "Patch size field must be bigger than zero."); 441 | break; 442 | case -3: 443 | badInputMessageBox( 444 | "Frequency field must be bigger than zero."); 445 | break; 446 | case -4: 447 | badInputMessageBox( 448 | "Densification field must be bigger than zero."); 449 | break; 450 | case -5: 451 | badInputMessageBox( 452 | "Number of atoms field must be bigger than zero."); 453 | break; 454 | case -6: 455 | badInputMessageBox( 456 | QString("Sparsity constraint must be bigger than zero, ") + 457 | QString("and no bigger than the number of atoms field.") 458 | ); 459 | break; 460 | case -7: 461 | badInputMessageBox( 462 | "Max. new points field should be bigger than zero."); 463 | break; 464 | default: 465 | emit cloudReconstruct( 466 | kSVDIters, kNN, nfreq, densify, natm, latm, 467 | maxNewPoints, looseBBox, method); 468 | } 469 | } 470 | 471 | } 472 | 473 | 474 | //--------------------------------------------------------- 475 | 476 | void MainWindow::options() 477 | { 478 | // Show the dialog as modal 479 | if(optionsDialog->exec() == QDialog::Accepted){ 480 | float pointSize, normScale; 481 | bool ok = optionsDialog->getFields(pointSize, normScale); 482 | if(!ok){ 483 | badInputMessageBox("All field should be greater than zero."); 484 | return; 485 | } 486 | emit pointSizeChanged(pointSize); 487 | emit normScaleChanged(normScale); 488 | } 489 | 490 | } 491 | 492 | //--------------------------------------------------------- 493 | 494 | void MainWindow::about() 495 | { 496 | QMessageBox::about(this, "About", 497 | QString("PCReconstruct.\n") + 498 | QString("Copyright 2019 Piotr (Peter) Beben.\n") + 499 | QString("pdbcas@gmail.com\n")); 500 | } 501 | 502 | //--------------------------------------------------------- 503 | 504 | void MainWindow::badInputMessageBox(const QString& info) 505 | { 506 | QMessageBox msgBox; 507 | msgBox.setText("Invalid input."); 508 | msgBox.setInformativeText(info); 509 | msgBox.setStandardButtons(QMessageBox::Ok); 510 | msgBox.setDefaultButton(QMessageBox::Ok); 511 | msgBox.setIcon(QMessageBox::Information); 512 | msgBox.exec(); 513 | } 514 | //--------------------------------------------------------- 515 | void MainWindow::changeBBoxFields(float minBBox[3], float maxBBox[3]) 516 | { 517 | boundBoxDialog->setFields(minBBox, maxBBox); 518 | } 519 | 520 | //--------------------------------------------------------- 521 | 522 | void MainWindow::appendLogText(const QString& text) 523 | { 524 | logText->appendPlainText(text); 525 | logText->verticalScrollBar()->setValue( 526 | logText->verticalScrollBar()->maximum()); 527 | } 528 | 529 | //--------------------------------------------------------- 530 | 531 | void MainWindow::insertLogText(const QString& text) 532 | { 533 | logText->undo(); 534 | logText->appendPlainText(text); 535 | logText->verticalScrollBar()->setValue( 536 | logText->verticalScrollBar()->maximum()); 537 | } 538 | 539 | //--------------------------------------------------------- 540 | -------------------------------------------------------------------------------- /MainWindow.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef MAINWINDOW_H 5 | #define MAINWINDOW_H 6 | 7 | #include "constants.h" 8 | 9 | //#include 10 | #include 11 | #include 12 | #include 13 | 14 | QT_BEGIN_NAMESPACE 15 | class QAction; 16 | class QMenu; 17 | class QPlainTextEdit; 18 | class QListWidget; 19 | QT_END_NAMESPACE 20 | 21 | class GLWidget; 22 | class Window; 23 | class MessageLogger; 24 | class RandomSurfDialog; 25 | class BoundBoxDialog; 26 | class NormalsDialog; 27 | class DecimateDialog; 28 | class SparsifyDialog; 29 | class ReconstructDialog; 30 | class OptionsDialog; 31 | 32 | class MainWindow : public QMainWindow 33 | { 34 | Q_OBJECT 35 | 36 | typedef pcl::PointCloud::Ptr CloudPtr; 37 | 38 | public: 39 | explicit MainWindow(QWidget *parent = nullptr); 40 | void badInputMessageBox(const QString& info); 41 | 42 | private: 43 | void open(); 44 | void saveAs(); 45 | void undo(); 46 | void viewGLNorms(bool enabled); 47 | void setRandom(); 48 | void setBBox(); 49 | void approxNorms(); 50 | void decimate(); 51 | void sparsify(); 52 | void reconstruct(); 53 | void options(); 54 | void about(); 55 | 56 | public slots: 57 | void changeBBoxFields(float minBBox[3], float maxBBox[3]); 58 | void appendLogText(const QString& text); 59 | void insertLogText(const QString& text); 60 | 61 | signals: 62 | void cloudChanged(CloudPtr cloud); 63 | void cloudQueried(CloudPtr& cloud); 64 | void cloudUndo(); 65 | void cloudNormsViewGL(bool enabled); 66 | void cloudSetRandom(size_t nPoints); 67 | void cloudSetBBox(float minBBox[3], float maxBBox[3]); 68 | void cloudApproxNorms(int nIters, size_t kNN); 69 | void cloudDecimate(size_t nHoles, size_t kNN); 70 | void cloudSparsify(float percent); 71 | void cloudReconstruct( 72 | int kSVDIters, size_t kNN, size_t nfreq, float densify, 73 | size_t natm, size_t latm, size_t maxNewPoints, bool looseBBox, 74 | SparseApprox method); 75 | void pointSizeChanged(float size); 76 | void normScaleChanged(float scale); 77 | 78 | private: 79 | Window *centralWidget; 80 | QToolBar *mainToolBar; 81 | QStatusBar *statusBar; 82 | QToolBar *toolBar; 83 | QPlainTextEdit *logText; 84 | RandomSurfDialog *randomSurfDialog; 85 | BoundBoxDialog *boundBoxDialog; 86 | NormalsDialog *normalsDialog; 87 | DecimateDialog *decimateDialog; 88 | SparsifyDialog *sparsifyDialog; 89 | ReconstructDialog *reconstructDialog; 90 | OptionsDialog *optionsDialog; 91 | 92 | MessageLogger *msgLogger; 93 | 94 | }; 95 | 96 | #endif // MAINWINDOW_H 97 | -------------------------------------------------------------------------------- /MessageLogger.cpp: -------------------------------------------------------------------------------- 1 | #include "MessageLogger.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | MessageLogger::MessageLogger(QObject *parent) : 9 | QObject(parent) 10 | { 11 | 12 | } 13 | 14 | 15 | MessageLogger::~MessageLogger() 16 | { 17 | 18 | } 19 | 20 | void MessageLogger::logMessage(const QString& text, bool append) { 21 | 22 | QMutexLocker locker(&m_recMutex); 23 | 24 | if( append ){ 25 | emit logTextAppend(text); 26 | ++m_lastPos; 27 | } 28 | else{ 29 | emit logTextInsert(text); 30 | } 31 | 32 | } 33 | 34 | void MessageLogger::logProgress( 35 | const QString& msgPrefix, size_t i, size_t n, int infreq, 36 | size_t& threshold, size_t& lastPos) { 37 | float percent = (100.0f*i)/n; 38 | if(percent >= threshold) { 39 | QMutexLocker locker(&m_recMutex); 40 | bool append = threshold > 0 ? (lastPos < m_lastPos) : true; 41 | logMessage(msgPrefix + ": " + QString::number(int(percent)) + "%...", 42 | append); 43 | threshold += infreq; 44 | lastPos = m_lastPos; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MessageLogger.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #ifndef MESSAGELOGGER_H 6 | #define MESSAGELOGGER_H 7 | 8 | #include 9 | #include 10 | 11 | //QT_BEGIN_NAMESPACE 12 | //class QPlainTextEdit; 13 | //QT_END_NAMESPACE 14 | 15 | class MessageLogger : public QObject 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit MessageLogger(QObject *parent = nullptr); 21 | ~MessageLogger(); 22 | 23 | void logMessage(const QString& text, bool append = true); 24 | 25 | void logProgress(const QString& msgPrefix, size_t i, size_t n, 26 | int infreq, size_t& threshold, size_t& lastPos); 27 | 28 | signals: 29 | void undo(); 30 | void logTextAppend(const QString& text); 31 | void logTextInsert(const QString& text); 32 | 33 | private: 34 | //QPlainTextEdit *m_logText; 35 | size_t m_lastPos = 0; 36 | QRecursiveMutex m_recMutex; 37 | }; 38 | 39 | #endif // MESSAGELOGGER_H 40 | -------------------------------------------------------------------------------- /PCReconstruct.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2019-09-28T10:50:52 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = PCReconstruct 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | DEFINES += EIGEN_NO_MALLOC 20 | DEFINES += EIGEN_NO_DEBUG 21 | DEFINES += _USE_MATH_DEFINES 22 | 23 | # You can also make your code fail to compile if you use deprecated APIs. 24 | # In order to do so, uncomment the following line. 25 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 26 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 27 | 28 | CONFIG += c++11 29 | 30 | PRECOMPILED_HEADER = stable.h 31 | 32 | SOURCES += \ 33 | BoundBox.cpp \ 34 | Cloud.cpp \ 35 | CloudWorker.cpp \ 36 | GLWidget.cpp \ 37 | MainWindow.cpp \ 38 | MessageLogger.cpp \ 39 | Window.cpp \ 40 | dialogs/BoundBoxDialog.cpp \ 41 | dialogs/DecimateDialog.cpp \ 42 | dialogs/NormalsDialog.cpp \ 43 | dialogs/OptionsDialog.cpp \ 44 | dialogs/RandomSurfDialog.cpp \ 45 | dialogs/ReconstructDialog.cpp \ 46 | dialogs/SparsifyDialog.cpp \ 47 | dialogs/get_field.cpp \ 48 | dictionarylearning/MatchingPursuit.cpp \ 49 | dictionarylearning/OrthogonalPursuit.cpp \ 50 | dictionarylearning/cosine_transform.cpp \ 51 | dictionarylearning/ksvd.cpp \ 52 | dictionarylearning/ksvd_dct2D.cpp \ 53 | main.cpp \ 54 | utils/Plane.cpp \ 55 | utils/cloud_normal.cpp \ 56 | utils/pt_to_pt_distsq.cpp \ 57 | utils/rotations.cpp 58 | 59 | HEADERS += \ 60 | BoundBox.h \ 61 | Cloud.h \ 62 | CloudWorker.h \ 63 | Cover-Tree/CoverTreePoint.h \ 64 | Cover-Tree/Cover_Tree.h \ 65 | GLWidget.h \ 66 | MainWindow.h \ 67 | MessageLogger.h \ 68 | Window.h \ 69 | alignment.h \ 70 | constants.h \ 71 | dialogs/BoundBoxDialog.h \ 72 | dialogs/DecimateDialog.h \ 73 | dialogs/NormalsDialog.h \ 74 | dialogs/OptionsDialog.h \ 75 | dialogs/RandomSurfDialog.h \ 76 | dialogs/ReconstructDialog.h \ 77 | dialogs/SparsifyDialog.h \ 78 | dialogs/get_field.h \ 79 | dictionarylearning/MatchingPursuit.h \ 80 | dictionarylearning/OrthogonalPursuit.h \ 81 | dictionarylearning/cosine_transform.h \ 82 | dictionarylearning/ksvd.h \ 83 | dictionarylearning/ksvd_dct2D.h \ 84 | stable.h \ 85 | \ \ 86 | utils/Plane.h \ 87 | utils/cloud_normal.h \ 88 | utils/ensure_buffer_size.h \ 89 | utils/pt_to_pt_distsq.h \ 90 | utils/rotations.h 91 | 92 | FORMS += 93 | 94 | # Default rules for deployment. 95 | qnx: target.path = /tmp/$${TARGET}/bin 96 | else: unix:!android: target.path = /opt/$${TARGET}/bin 97 | !isEmpty(target.path): INSTALLS += target 98 | 99 | INCLUDEPATH += $$PWD/'../../lib/PCL 1.9.1/include/pcl-1.9' \ 100 | $$PWD/'../../lib/eigen3' \ 101 | $$PWD/'../../lib/Boost/include/boost-1_68' \ 102 | $$PWD/'../../lib/OpenNI2/Include' \ 103 | $$PWD/'../../lib/FLANN/include' \ 104 | $$PWD/'Cover-Tree' \ 105 | $$PWD/'dialogs' \ 106 | $$PWD/'dictionarylearning' \ 107 | $$PWD/'utils' 108 | 109 | LIBS += -L$$PWD/'../../lib/PCL 1.9.1/lib/' 110 | CONFIG( debug, debug|release ) { 111 | ## debug 112 | LIBS += \ 113 | -lpcl_common_debug \ 114 | -lpcl_filters_debug \ 115 | -lpcl_kdtree_debug \ 116 | -lpcl_search_debug \ 117 | -lpcl_io_debug \ 118 | -lpcl_io_ply_debug 119 | } 120 | else { 121 | # release 122 | LIBS += \ 123 | -lpcl_common_release \ 124 | -lpcl_filters_release \ 125 | -lpcl_kdtree_release \ 126 | -lpcl_search_release \ 127 | -lpcl_io_release \ 128 | -lpcl_io_ply_release 129 | } 130 | 131 | LIBS += -L$$PWD/'../../lib/Boost/lib/' 132 | LIBS += -L$$PWD/'../../lib/OpenNI2/Lib/' -lOpenNI2 133 | 134 | RESOURCES += \ 135 | PCReconstruct.qrc 136 | 137 | msvc{ 138 | QMAKE_CXXFLAGS += -openmp 139 | } 140 | 141 | gcc{ 142 | QMAKE_CXXFLAGS += -fopenmp -Wno-attributes -Wno-unused-parameter 143 | QMAKE_CXXFLAGS += -Wno-ignored-attributes 144 | QMAKE_LFLAGS += -fopenmp 145 | LIBS += -fopenmp 146 | } 147 | -------------------------------------------------------------------------------- /PCReconstruct.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/copy.png 4 | images/cut.png 5 | images/new.png 6 | images/open.png 7 | images/paste.png 8 | images/save.png 9 | images/undo.png 10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PCReconstruct 2 | 3 | Point cloud completion tool based on dictionary learning. 4 | 5 | Takes a PCL point cloud surface and fills in gaps or densifies sparse regions by learning from the various surface features of the cloud. 6 | 7 | This is done using a variation of the k-SVD dictionary learning algorithm that allows for continuous atoms and dealing with unstructured point cloud data. 8 | 9 | Written in Qt C++, using Eigen, OpenMP, and OpenGL. 10 | 11 | ## TODO: 12 | * Still struggles in areas of very high curvature, producing artifacts that go off in random directions. 13 | * If original point cloud normals are provided, take them into account (especially at parallel near approaches between two surfaces). 14 | * Dictionary learning could be off-loaded to GPU. 15 | * Potential OpenMP parallelization in point creation loop. 16 | * Take point colour values into account. 17 | 18 | ## Sparsified Stanford Bunny: 19 | Original Stanford bunny sampled with 35847 points: 20 | 21 | ![1a](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture1a.PNG) 22 | 23 | Sparsifying the mid section to 4% after shrinking the bounding box to exclude 24 | the ears (there are not enough points around the ear tips to approximate normals 25 | after sparsifying, and the curvature there is too high as well). 26 | 27 | ![1b](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture1b.PNG) 28 | ![1c](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture1c.PNG) 29 | 30 | We increase the default densification field to 1.5 to get a denser reconstruction, 31 | and set patch frequency to 2 since we dont expect much bumpiness in the final result 32 | within a patch size of 50. We also want the "use points outside bounding box" 33 | field set "True" to get better agreement around the boundary of the bounding box: 34 | 35 | ![1d](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture1d.PNG) 36 | 37 | The resulting completion of the sparsified bunny: 38 | 39 | ![1e](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture1e.PNG) 40 | 41 | ## Decimated Armadillo: 42 | Original armadillo sampled with 172,974 points: 43 | 44 | ![2a](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2a.PNG) 45 | ![2a2](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2a2.PNG) 46 | 47 | Decimating it's mid body with random holes: 48 | 49 | ![2b](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2b.PNG) 50 | ![2b2](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2b2.PNG) 51 | 52 | We increase the default patch size field to 300 to take into account the large gaps relative 53 | to sampling density: 54 | 55 | ![2c](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2c.PNG) 56 | 57 | The resulting reconstruction of the decimated armadillo, with reconstructed points highlighted: 58 | 59 | ![2d](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2d.PNG) 60 | ![2d2](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2d2.PNG) 61 | 62 | When we unhighlight the newly added points, the rear shell looks unnaturally smooth 63 | 64 | ![2e](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2e.PNG) 65 | 66 | But this is because of the normals need updating using a smaller patch size 67 | 68 | ![2e2](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2e2.PNG) 69 | 70 | Doing this we get the final repaired armadillo 71 | 72 | ![2f2](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2f2.PNG) 73 | ![2f](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture2f.PNG) 74 | 75 | 76 | ## Sparsified Random Surface: 77 | Original surface sampled with 35000 points: 78 | 79 | ![5a](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture5a.PNG) 80 | 81 | Sparsified down to 1225 points: 82 | 83 | ![5b](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture5b.PNG) 84 | 85 | Increasing the default densification field to 1.5 to get a denser reconstruction: 86 | 87 | ![5c](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture5c.PNG) 88 | 89 | The resulting reconstruction of the sparsified point cloud: 90 | 91 | ![5d](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture5d.PNG) 92 | 93 | 94 | ## Decimated Random Surface: 95 | Original surface sampled with 15000 points: 96 | 97 | ![8a](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture8a.PNG) 98 | 99 | Heavily decimated with random holes: 100 | 101 | ![8b](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture8b.PNG) 102 | 103 | Increasing the default patch size field to 250 to take into account the large gaps relative to sampling density: 104 | 105 | ![8c](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture8c.PNG) 106 | 107 | The resulting reconstruction of the decimated point cloud: 108 | 109 | ![8d](https://github.com/codearxiv/PCReconstruct/blob/master/images/Capture8d.PNG) 110 | 111 | -------------------------------------------------------------------------------- /Window.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the examples of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "MainWindow.h" 52 | #include "Window.h" 53 | #include "GLWidget.h" 54 | #include "MessageLogger.h" 55 | 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | 65 | 66 | Window::Window(QMainWindow *mw, MessageLogger* msgLogger) 67 | : mainWindow(mw), m_msgLogger(msgLogger) 68 | { 69 | glWidget = new GLWidget(this, msgLogger); 70 | QWidget *w = new QWidget; 71 | //QVBoxLayout *mainLayout = new QVBoxLayout; 72 | QHBoxLayout *container = new QHBoxLayout; 73 | //LogWindow *logWindow = new LogWindow; 74 | 75 | container->addWidget(glWidget); 76 | w->setLayout(container); 77 | //mainLayout->addWidget(w); 78 | //setLayout(mainLayout); 79 | setLayout(container); 80 | 81 | setWindowTitle("PCReconstruct"); 82 | 83 | connect(this, &Window::cloudChanged, 84 | glWidget, &GLWidget::setCloud); 85 | 86 | connect(this, &Window::cloudQueried, 87 | glWidget, &GLWidget::getCloud); 88 | 89 | connect(this, &Window::cloudUndo, 90 | glWidget, &GLWidget::undoCloud); 91 | 92 | connect(this, &Window::cloudNormsViewGL, 93 | glWidget, &GLWidget::viewGLCloudNorms); 94 | 95 | connect(this, &Window::cloudSetRandom, 96 | glWidget, &GLWidget::setRandomCloud); 97 | 98 | connect(this, &Window::cloudSetBBox, 99 | glWidget, &GLWidget::setCloudBBox); 100 | 101 | connect(this, &Window::cloudApproxNorms, 102 | glWidget, &GLWidget::approxCloudNorms); 103 | 104 | connect(this, &Window::cloudDecimate, 105 | glWidget, &GLWidget::decimateCloud); 106 | 107 | connect(this, &Window::cloudSparsify, 108 | glWidget, &GLWidget::sparsifyCloud); 109 | 110 | connect(this, &Window::cloudReconstruct, 111 | glWidget, &GLWidget::reconstructCloud); 112 | 113 | connect(this, &Window::pointSizeChanged, 114 | glWidget, &GLWidget::setPointSize); 115 | 116 | connect(this, &Window::normScaleChanged, 117 | glWidget, &GLWidget::setNormScale); 118 | 119 | connect(glWidget, &GLWidget::bBoxFieldsChanged, 120 | this, &Window::changeBBoxFields); 121 | 122 | } 123 | 124 | 125 | void Window::keyPressEvent(QKeyEvent *e) 126 | { 127 | if (e->key() == Qt::Key_Escape) 128 | close(); 129 | else 130 | QWidget::keyPressEvent(e); 131 | } 132 | 133 | -------------------------------------------------------------------------------- /Window.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #ifndef WINDOW_H 6 | #define WINDOW_H 7 | 8 | #include "MessageLogger.h" 9 | #include "constants.h" 10 | 11 | //#include 12 | #include 13 | #include 14 | #include 15 | 16 | QT_BEGIN_NAMESPACE 17 | class QSlider; 18 | class QPushButton; 19 | class QMainWindow; 20 | QT_END_NAMESPACE 21 | 22 | class GLWidget; 23 | 24 | 25 | class Window : public QWidget 26 | { 27 | Q_OBJECT 28 | 29 | typedef pcl::PointCloud::Ptr CloudPtr; 30 | 31 | public: 32 | Window(QMainWindow *mw, MessageLogger* msgLogger = nullptr); 33 | 34 | public slots: 35 | void setCloud(CloudPtr cloud) 36 | { emit cloudChanged(cloud); } 37 | 38 | void getCloud(CloudPtr& cloud) 39 | { emit cloudQueried(cloud); } 40 | 41 | void undoCloud() { emit cloudUndo(); } 42 | 43 | void viewGLCloudNorms(bool enabled) 44 | { emit cloudNormsViewGL(enabled); } 45 | 46 | void setRandomCloud(size_t nPoints) 47 | { emit cloudSetRandom(nPoints); } 48 | 49 | void setCloudBBox(float minBBox[3], float maxBBox[3]) 50 | { emit cloudSetBBox(minBBox, maxBBox); } 51 | 52 | void approxCloudNorms(int nIters, size_t kNN) 53 | { emit cloudApproxNorms(nIters, kNN); } 54 | 55 | void decimateCloud(size_t nHoles, size_t kNN) 56 | { emit cloudDecimate(nHoles, kNN); } 57 | 58 | void sparsifyCloud(float percent) 59 | { emit cloudSparsify(percent); } 60 | 61 | void reconstructCloud( 62 | int kSVDIters, size_t kNN, size_t nfreq, float densify, 63 | size_t natm, size_t latm, size_t maxNewPoints, bool looseBBox, 64 | SparseApprox method) 65 | { 66 | emit cloudReconstruct( 67 | kSVDIters, kNN, nfreq, densify, natm, latm, 68 | maxNewPoints, looseBBox, method); 69 | } 70 | 71 | void setPointSize(float size) 72 | { emit pointSizeChanged(size); } 73 | 74 | void setNormScale(float scale) 75 | { emit normScaleChanged(scale); } 76 | 77 | void changeBBoxFields(float minBBox[3], float maxBBox[3]) 78 | { emit bBoxFieldsChanged(minBBox, maxBBox);} 79 | 80 | 81 | signals: 82 | void cloudChanged(CloudPtr cloud); 83 | void cloudQueried(CloudPtr& cloud); 84 | void cloudUndo(); 85 | void cloudNormsViewGL(bool enabled); 86 | void cloudSetRandom(size_t nPoints); 87 | void cloudSetBBox(float minBBox[3], float maxBBox[3]); 88 | void cloudApproxNorms(int nIters, size_t kNN); 89 | void cloudDecimate(size_t nHoles, size_t kNN); 90 | void cloudSparsify(float percent); 91 | void cloudReconstruct( 92 | int kSVDIters, size_t kNN, size_t nfreq, float densify, 93 | size_t natm, size_t latm, size_t maxNewPoints, bool looseBBox, 94 | SparseApprox method); 95 | void pointSizeChanged(float size); 96 | void normScaleChanged(float scale); 97 | void bBoxFieldsChanged(float minBBox[3], float maxBBox[3]); 98 | 99 | protected: 100 | void keyPressEvent(QKeyEvent *event) override; 101 | 102 | private: 103 | QSlider *createSlider(); 104 | 105 | GLWidget *glWidget; 106 | QMainWindow *mainWindow; 107 | MessageLogger* m_msgLogger; 108 | 109 | }; 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /alignment.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #ifndef ALIGNMENT_H 6 | #define ALIGNMENT_H 7 | 8 | #include 9 | 10 | 11 | #define ALIGNED_MEMORY 12 | 13 | 14 | #ifdef ALIGNED_MEMORY 15 | const size_t ALIGNEDX = Eigen::Aligned16; 16 | inline size_t align_padded(size_t n) { 17 | return ALIGNEDX > 0 ? ALIGNEDX*(1+((n-1)/ALIGNEDX)) : n; 18 | } 19 | #else 20 | const size_t ALIGNEDX = Eigen::Unaligned; 21 | inline size_t align_padded(size_t n) { return n; } 22 | #endif 23 | 24 | #endif // ALIGNMENT_H 25 | -------------------------------------------------------------------------------- /constants.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #ifndef CONSTANTS_H 6 | #define CONSTANTS_H 7 | 8 | #include 9 | 10 | const float float_infinity = std::numeric_limits::infinity(); 11 | const float float_tiny = std::numeric_limits::min(); 12 | const double double_infinity = std::numeric_limits::infinity(); 13 | const int int_infinity = std::numeric_limits::max(); 14 | 15 | enum class SparseApprox { OrthogonalPursuit = 0, MatchingPursuit = 1 }; 16 | 17 | #endif // CONSTANTS_H 18 | -------------------------------------------------------------------------------- /dialogs/BoundBoxDialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | 5 | #include "BoundBoxDialog.h" 6 | #include "get_field.h" 7 | #include "constants.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | BoundBoxDialog::BoundBoxDialog(QWidget *parent) : QDialog(parent) 19 | { 20 | validator = new QDoubleValidator(-double_infinity, double_infinity, 8, this); 21 | 22 | QString label[3] = {"X","Y","Z"}; 23 | 24 | form = new QFormLayout(this); 25 | form->addRow(new QLabel( 26 | "Set bounding box to perform operations within")); 27 | for(int i=0; i < 3; ++i){ 28 | minLineEdit[i] = new QLineEdit(this); 29 | minLineEdit[i]->setValidator(validator); 30 | minLineEdit[i]->setToolTip( 31 | QString("The smallest ") + label[i] + 32 | QString(" coordinate in bounding box.")); 33 | form->addRow(QString("Minimum ") + label[i] + QString(" coordinate:"), 34 | minLineEdit[i]); 35 | maxLineEdit[i] = new QLineEdit(this); 36 | maxLineEdit[i]->setValidator(validator); 37 | maxLineEdit[i]->setToolTip( 38 | QString("The largest ") + label[i] + 39 | QString(" coordinate in bounding box.")); 40 | form->addRow(QString("Maximum ") + label[i] + QString(" coordinate:"), 41 | maxLineEdit[i]); 42 | } 43 | 44 | 45 | buttonBox = new QDialogButtonBox( 46 | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, 47 | Qt::Horizontal, this); 48 | 49 | form->addRow(buttonBox); 50 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 51 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 52 | } 53 | 54 | //----------------------------------------------------------- 55 | bool BoundBoxDialog::getFields(float minBBox[], float maxBBox[]) const 56 | { 57 | bool ok; 58 | 59 | for(int i=0; i < 3; ++i){ 60 | ok = get_float_field(minLineEdit[i], validator, minBBox[i]); 61 | if(!ok) return false; 62 | 63 | ok = get_float_field(maxLineEdit[i], validator, maxBBox[i]); 64 | if(!ok) return false; 65 | } 66 | 67 | return true; 68 | } 69 | //----------------------------------------------------------- 70 | void BoundBoxDialog::setFields(const float minBBox[3], const float maxBBox[3]) 71 | { 72 | for(int i=0; i < 3; ++i){ 73 | minLineEdit[i]->setText(QString::number(minBBox[i])); 74 | maxLineEdit[i]->setText(QString::number(maxBBox[i])); 75 | } 76 | 77 | } 78 | 79 | //----------------------------------------------------------- 80 | -------------------------------------------------------------------------------- /dialogs/BoundBoxDialog.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef BOUNDBOXDIALOG_H 5 | #define BOUNDBOXDIALOG_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | QT_BEGIN_NAMESPACE 12 | class QFormLayout; 13 | class QLineEdit; 14 | class QDialogButtonBox; 15 | class QDoubleValidator; 16 | QT_END_NAMESPACE 17 | 18 | class BoundBoxDialog : public QDialog 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | BoundBoxDialog(QWidget *parent = nullptr); 24 | bool getFields(float minBBox[], float maxBBox[]) const; 25 | void setFields(const float minBBox[3], const float maxBBox[3]); 26 | 27 | private: 28 | QFormLayout *form; 29 | QLineEdit* minLineEdit[3]; 30 | QLineEdit* maxLineEdit[3]; 31 | QDialogButtonBox *buttonBox; 32 | QDoubleValidator *validator; 33 | 34 | }; 35 | 36 | 37 | #endif // BOUNDBOXDIALOG_H 38 | -------------------------------------------------------------------------------- /dialogs/DecimateDialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #include "DecimateDialog.h" 5 | #include "get_field.h" 6 | #include "constants.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | DecimateDialog::DecimateDialog(QWidget *parent) : QDialog(parent) 19 | { 20 | validator = new QIntValidator(1, int_infinity, this); 21 | 22 | form = new QFormLayout(this); 23 | form->addRow(new QLabel("Create random holes within bounding box")); 24 | nHolesLineEdit = new QLineEdit(this); 25 | nHolesLineEdit->setValidator(validator); 26 | nHolesLineEdit->setText("50"); 27 | form->addRow(QString("Number of holes:"), nHolesLineEdit); 28 | nPointsLineEdit = new QLineEdit(this); 29 | nPointsLineEdit->setValidator(validator); 30 | nPointsLineEdit->setText("100"); 31 | form->addRow(QString("Number of points per hole:"), nPointsLineEdit); 32 | 33 | buttonBox = new QDialogButtonBox( 34 | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, 35 | Qt::Horizontal, this); 36 | 37 | form->addRow(buttonBox); 38 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 39 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 40 | 41 | } 42 | 43 | 44 | 45 | bool DecimateDialog::getFields(size_t& nHoles, size_t& kNN) const 46 | { 47 | bool ok; 48 | 49 | ok = get_integer_field(nHolesLineEdit, validator, nHoles); 50 | if(!ok) return false; 51 | 52 | ok = get_integer_field(nPointsLineEdit, validator, kNN); 53 | if(!ok) return false; 54 | 55 | return true; 56 | } 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /dialogs/DecimateDialog.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef DECIMATEDIALOG_H 5 | #define DECIMATEDIALOG_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | QT_BEGIN_NAMESPACE 12 | class QFormLayout; 13 | class QLineEdit; 14 | class QDialogButtonBox; 15 | class QIntValidator; 16 | QT_END_NAMESPACE 17 | 18 | class DecimateDialog : public QDialog 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | DecimateDialog(QWidget *parent = nullptr); 24 | bool getFields(size_t& nHoles, size_t& kNN) const; 25 | 26 | private: 27 | QFormLayout *form; 28 | QLineEdit *nHolesLineEdit; 29 | QLineEdit *nPointsLineEdit; 30 | QDialogButtonBox *buttonBox; 31 | QIntValidator *validator; 32 | 33 | }; 34 | 35 | 36 | #endif // DECIMATEDIALOG_H 37 | -------------------------------------------------------------------------------- /dialogs/NormalsDialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #include "NormalsDialog.h" 5 | #include "get_field.h" 6 | #include "constants.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | NormalsDialog::NormalsDialog(QWidget *parent) : QDialog(parent) 19 | { 20 | validator = new QIntValidator(1, int_infinity, this); 21 | 22 | form = new QFormLayout(this); 23 | form->addRow(new QLabel("Approximate cloud normals")); 24 | nItersLineEdit = new QLineEdit(this); 25 | nItersLineEdit->setValidator(validator); 26 | nItersLineEdit->setText("25"); 27 | form->addRow(QString("Number of iterations:"), nItersLineEdit); 28 | kNNLineEdit = new QLineEdit(this); 29 | kNNLineEdit->setValidator(validator); 30 | kNNLineEdit->setText("25"); 31 | form->addRow(QString("Number of neighbouring points used:"), kNNLineEdit); 32 | 33 | buttonBox = new QDialogButtonBox( 34 | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, 35 | Qt::Horizontal, this); 36 | 37 | form->addRow(buttonBox); 38 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 39 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 40 | 41 | } 42 | 43 | 44 | 45 | bool NormalsDialog::getFields(int& nIters, size_t& kNN) const 46 | { 47 | bool ok; 48 | size_t temp; 49 | 50 | ok = get_integer_field(nItersLineEdit, validator, temp); 51 | if(!ok) return false; 52 | nIters = int(temp); 53 | 54 | ok = get_integer_field(kNNLineEdit, validator, kNN); 55 | if(!ok) return false; 56 | 57 | return true; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /dialogs/NormalsDialog.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef NORMALSDIALOG_H 5 | #define NORMALSDIALOG_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | QT_BEGIN_NAMESPACE 12 | class QFormLayout; 13 | class QLineEdit; 14 | class QDialogButtonBox; 15 | class QIntValidator; 16 | QT_END_NAMESPACE 17 | 18 | class NormalsDialog : public QDialog 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | NormalsDialog(QWidget *parent = nullptr); 24 | bool getFields(int& nIters, size_t& kNN) const; 25 | 26 | private: 27 | QFormLayout *form; 28 | QLineEdit *nItersLineEdit; 29 | QLineEdit *kNNLineEdit; 30 | QDialogButtonBox *buttonBox; 31 | QIntValidator *validator; 32 | 33 | }; 34 | 35 | 36 | #endif // NORMALSDIALOG_H 37 | -------------------------------------------------------------------------------- /dialogs/OptionsDialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #include "OptionsDialog.h" 5 | #include "get_field.h" 6 | #include "constants.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | OptionsDialog::OptionsDialog(QWidget *parent) : QDialog(parent) 18 | { 19 | validator = new QDoubleValidator(0.0, double_infinity, 5, this); 20 | 21 | form = new QFormLayout(this); 22 | form->addRow(new QLabel("Change app settings")); 23 | 24 | pointSizeLineEdit = new QLineEdit(this); 25 | pointSizeLineEdit->setValidator(validator); 26 | pointSizeLineEdit->setText("5.0"); 27 | form->addRow(QString("Display point size:"), pointSizeLineEdit); 28 | 29 | normScaleLineEdit = new QLineEdit(this); 30 | normScaleLineEdit->setValidator(validator); 31 | normScaleLineEdit->setText("0.01"); 32 | form->addRow(QString("Display normals size:"), normScaleLineEdit); 33 | 34 | buttonBox = new QDialogButtonBox( 35 | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, 36 | Qt::Horizontal, this); 37 | 38 | form->addRow(buttonBox); 39 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 40 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 41 | 42 | } 43 | 44 | 45 | bool OptionsDialog::getFields(float& pointSize, float& normScale) const 46 | { 47 | 48 | bool ok; 49 | 50 | ok = get_float_field(pointSizeLineEdit, validator, pointSize); 51 | if(!ok) return false; 52 | 53 | ok = get_float_field(normScaleLineEdit, validator, normScale); 54 | if(!ok) return false; 55 | 56 | return true; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /dialogs/OptionsDialog.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef OPTIONSDIALOG_H 5 | #define OPTIONSDIALOG_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | QT_BEGIN_NAMESPACE 12 | class QFormLayout; 13 | class QLineEdit; 14 | class QDialogButtonBox; 15 | class QDoubleValidator; 16 | QT_END_NAMESPACE 17 | 18 | class OptionsDialog : public QDialog 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | OptionsDialog(QWidget *parent = nullptr); 24 | bool getFields(float& pointSize, float& normScale) const; 25 | 26 | private: 27 | QFormLayout *form; 28 | QLineEdit *pointSizeLineEdit; 29 | QLineEdit *normScaleLineEdit; 30 | QDialogButtonBox *buttonBox; 31 | QDoubleValidator *validator; 32 | 33 | }; 34 | 35 | 36 | #endif // OPTIONSDIALOG_H 37 | -------------------------------------------------------------------------------- /dialogs/RandomSurfDialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #include "RandomSurfDialog.h" 5 | #include "get_field.h" 6 | #include "constants.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | RandomSurfDialog::RandomSurfDialog(QWidget *parent) : QDialog(parent) 18 | { 19 | validator = new QIntValidator(1, int_infinity, this); 20 | 21 | form = new QFormLayout(this); 22 | form->addRow(new QLabel( 23 | "Create a new point cloud sampled randomly from a random surface")); 24 | nPointsLineEdit = new QLineEdit(this); 25 | nPointsLineEdit->setValidator(validator); 26 | nPointsLineEdit->setText("15000"); 27 | form->addRow(QString("Number of points sampled:"), nPointsLineEdit); 28 | 29 | buttonBox = new QDialogButtonBox( 30 | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, 31 | Qt::Horizontal, this); 32 | 33 | form->addRow(buttonBox); 34 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 35 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 36 | 37 | } 38 | 39 | 40 | bool RandomSurfDialog::getFields(size_t& nPoints) const 41 | { 42 | 43 | bool ok = get_integer_field(nPointsLineEdit, validator, nPoints); 44 | return ok; 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /dialogs/RandomSurfDialog.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef SETRANDOMDIALOG_H 5 | #define SETRANDOMDIALOG_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | QT_BEGIN_NAMESPACE 12 | class QFormLayout; 13 | class QLineEdit; 14 | class QDialogButtonBox; 15 | class QIntValidator; 16 | QT_END_NAMESPACE 17 | 18 | class RandomSurfDialog : public QDialog 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | RandomSurfDialog(QWidget *parent = nullptr); 24 | bool getFields(size_t& nPoints) const; 25 | 26 | private: 27 | QFormLayout *form; 28 | QLineEdit *nPointsLineEdit; 29 | QDialogButtonBox *buttonBox; 30 | QIntValidator *validator; 31 | 32 | }; 33 | 34 | #endif // SETRANDOMDIALOG_H 35 | -------------------------------------------------------------------------------- /dialogs/ReconstructDialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #include "ReconstructDialog.h" 5 | #include "get_field.h" 6 | #include "constants.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | ReconstructDialog::ReconstructDialog(QWidget *parent) : QDialog(parent) 20 | { 21 | intValidator = new QIntValidator(1, int_infinity, this); 22 | doubleValidator = new QDoubleValidator(0.0, double_infinity, 2, this); 23 | 24 | form = new QFormLayout(this); 25 | form->addRow(new QLabel("Fill in surface gaps in cloud within bounding box")); 26 | 27 | nItersLineEdit = new QLineEdit(this); 28 | nItersLineEdit->setValidator(intValidator); 29 | nItersLineEdit->setText("15"); 30 | nItersLineEdit->setToolTip( 31 | QString("This sets the number of training iterations within which\n") + 32 | QString("the various local surface patterns in the point cloud\n") + 33 | QString("are learned, and used in the cloud's reconstruction.")); 34 | form->addRow(QString("Number of dictionary learning iterations:"), nItersLineEdit); 35 | 36 | kNNLineEdit = new QLineEdit(this); 37 | kNNLineEdit->setValidator(intValidator); 38 | kNNLineEdit->setText("50"); 39 | kNNLineEdit->setToolTip( 40 | QString("The cloud surface is reconstructed patch-by-patch.\n") + 41 | QString("This sets the maximum number of points in a patch.\n\n") + 42 | QString("The larger gaps in the cloud are relative to density\n") + 43 | QString("of point sampling, the larger this field should be.\n") + 44 | QString("Expect crazy results otherwise!")); 45 | form->addRow(QString("Local patch size:"), kNNLineEdit); 46 | 47 | nFreqLineEdit = new QLineEdit(this); 48 | nFreqLineEdit->setValidator(intValidator); 49 | nFreqLineEdit->setText("4"); 50 | nFreqLineEdit->setToolTip( 51 | QString("Each local patch has a measure of complexity given by the\n") + 52 | QString("surface bumpiness along an axis. This sets the maximum\n") + 53 | QString("number of bumps along an axis that can be expected for\n") + 54 | QString("the given patch size.\n\n") + 55 | QString("Note training time and memory footprint will degrade\n") + 56 | QString("quadratically as this value increases.")); 57 | form->addRow(QString("Maximum frequency in a patch:"), nFreqLineEdit); 58 | 59 | densifyLineEdit = new QLineEdit(this); 60 | densifyLineEdit->setValidator(doubleValidator); 61 | densifyLineEdit->setText("1.0"); 62 | densifyLineEdit->setToolTip( 63 | QString("This controls the density of the reconstruction in\n") + 64 | QString("each region compared to the density of the nearby\n") + 65 | QString("points in the original cloud.")); 66 | form->addRow(QString("Densification factor:"), densifyLineEdit); 67 | 68 | nAtmLineEdit = new QLineEdit(this); 69 | nAtmLineEdit->setValidator(intValidator); 70 | nAtmLineEdit->setText("10"); 71 | nAtmLineEdit->setToolTip( 72 | QString("Total number of dictionary atoms available.\n\n") + 73 | QString("A too large value leads to overfitting, and too small\n") + 74 | QString("leads to underfitting, depending on max. frequency.")); 75 | form->addRow(QString("Number of dictionary atoms:"), nAtmLineEdit); 76 | 77 | lAtmLineEdit = new QLineEdit(this); 78 | lAtmLineEdit->setValidator(intValidator); 79 | lAtmLineEdit->setText("4"); 80 | lAtmLineEdit->setToolTip( 81 | QString("Maximum dictionary atoms used in patch reconstruction.\n\n") + 82 | QString("A too large value leads to overfitting, and too small\n") + 83 | QString("leads to underfitting, depending on max. frequency.")); 84 | form->addRow(QString("Atom sparsity constraint:"), lAtmLineEdit); 85 | 86 | maxNewLineEdit = new QLineEdit(this); 87 | maxNewLineEdit->setValidator(intValidator); 88 | maxNewLineEdit->setText("45000"); 89 | maxNewLineEdit->setToolTip( 90 | QString("Maximum number of new points to add to the cloud.")); 91 | form->addRow(QString("Maximum number of new points to add:"), maxNewLineEdit); 92 | 93 | bBoxComboBox = new QComboBox; 94 | bBoxComboBox->addItem(tr("True")); 95 | bBoxComboBox->addItem(tr("False")); 96 | bBoxComboBox->setToolTip( 97 | QString("Whether positions of points outside the bounding box\n") + 98 | QString("are to be considered when reconstructing within the\n") + 99 | QString("bounding box.\n\n") + 100 | QString("This helps the reconstruction to agree with points just\n") + 101 | QString("outside the bounding box.\n\n") + 102 | QString("Note that this may have the undesirable effect of\n") + 103 | QString("including points from two areas parallel surfaces where\n") + 104 | QString("they nearly meet, depending on the patch size. In this\n") + 105 | QString("case the bounding box for reconstruction might have to\n") + 106 | QString("be adjusted manually to include only desired points,\n") + 107 | QString("leaving this field false.")); 108 | form->addRow(QString("Use points outside bounding box:"), bBoxComboBox); 109 | 110 | connect(bBoxComboBox, QOverload::of(&QComboBox::activated), 111 | this, &ReconstructDialog::bBoxComboChanged); 112 | 113 | methodComboBox = new QComboBox; 114 | methodComboBox->addItem(tr("Orthogonal Pursuit")); 115 | methodComboBox->addItem(tr("Matching Pursuit")); 116 | methodComboBox->setToolTip( 117 | QString("Sparse approximation method to use during training\n") + 118 | QString("and patch reconstruction.")); 119 | form->addRow(QString("Sparse approximation method:"), methodComboBox); 120 | 121 | connect(methodComboBox, QOverload::of(&QComboBox::activated), 122 | this, &ReconstructDialog::methodComboChanged); 123 | 124 | buttonBox = new QDialogButtonBox( 125 | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, 126 | Qt::Horizontal, this); 127 | 128 | form->addRow(buttonBox); 129 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 130 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 131 | 132 | 133 | 134 | } 135 | 136 | //--------------------------------------------------------- 137 | 138 | int ReconstructDialog::getFields( 139 | int& kSVDIters, size_t& kNN, size_t& nfreq, float& densify, 140 | size_t& natm, size_t& latm, size_t& maxNewPoints, bool& looseBBox, 141 | SparseApprox& method) const 142 | { 143 | bool ok; 144 | size_t temp; 145 | 146 | ok = get_integer_field(nItersLineEdit, intValidator, temp); 147 | if(!ok) return -1; 148 | kSVDIters = int(temp); 149 | 150 | ok = get_integer_field(kNNLineEdit, intValidator, kNN); 151 | if(!ok) return -2; 152 | 153 | ok = get_integer_field(nFreqLineEdit, intValidator, nfreq); 154 | if(!ok) return -3; 155 | 156 | ok = get_float_field(densifyLineEdit, doubleValidator, densify); 157 | if(!ok) return -4; 158 | 159 | ok = get_integer_field(nAtmLineEdit, intValidator, natm); 160 | if(!ok) return -5; 161 | 162 | ok = get_integer_field(lAtmLineEdit, intValidator, latm); 163 | if(ok) if(latm > natm) ok = false; 164 | if(!ok) return -6; 165 | 166 | ok = get_integer_field(maxNewLineEdit, intValidator, maxNewPoints); 167 | if(!ok) return -7; 168 | 169 | switch(m_bBoxComboIdx){ 170 | case 0: 171 | looseBBox = true; 172 | break; 173 | case 1: 174 | looseBBox = false; 175 | break; 176 | } 177 | 178 | switch(m_methodComboIdx){ 179 | case 0: 180 | method = SparseApprox::OrthogonalPursuit; 181 | break; 182 | case 1: 183 | method = SparseApprox::MatchingPursuit; 184 | break; 185 | } 186 | 187 | return 0; 188 | } 189 | -------------------------------------------------------------------------------- /dialogs/ReconstructDialog.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef RECONSTRUCTDIALOG_H 5 | #define RECONSTRUCTDIALOG_H 6 | 7 | #include "constants.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | QT_BEGIN_NAMESPACE 14 | class QFormLayout; 15 | class QLineEdit; 16 | class QDialogButtonBox; 17 | class QIntValidator; 18 | class QDoubleValidator; 19 | class QComboBox; 20 | QT_END_NAMESPACE 21 | 22 | class ReconstructDialog : public QDialog 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | ReconstructDialog(QWidget *parent = nullptr); 28 | int getFields( 29 | int& kSVDIters, size_t& kNN, size_t& nfreq, float& densify, 30 | size_t& natm, size_t& latm, size_t& maxNewPoints, bool& looseBBox, 31 | SparseApprox& method) const; 32 | 33 | public slots: 34 | void bBoxComboChanged(int idx) { m_bBoxComboIdx = idx; } 35 | void methodComboChanged(int idx) { m_methodComboIdx = idx; } 36 | 37 | private: 38 | QFormLayout *form; 39 | QLineEdit *nItersLineEdit; 40 | QLineEdit *kNNLineEdit; 41 | QLineEdit *nFreqLineEdit; 42 | QLineEdit *densifyLineEdit; 43 | QLineEdit *nAtmLineEdit; 44 | QLineEdit *lAtmLineEdit; 45 | QLineEdit *maxNewLineEdit; 46 | QComboBox *bBoxComboBox; 47 | int m_bBoxComboIdx = 0; 48 | QComboBox *methodComboBox; 49 | int m_methodComboIdx = 0; 50 | 51 | QDialogButtonBox *buttonBox; 52 | QIntValidator *intValidator; 53 | QDoubleValidator *doubleValidator; 54 | 55 | }; 56 | 57 | #endif // RECONSTRUCTDIALOG_H 58 | -------------------------------------------------------------------------------- /dialogs/SparsifyDialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #include "SparsifyDialog.h" 5 | #include "get_field.h" 6 | #include "constants.h" 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | SparsifyDialog::SparsifyDialog(QWidget *parent) : QDialog(parent) 20 | { 21 | validator = new QDoubleValidator(0.0, 100.0, 5, this); 22 | 23 | form = new QFormLayout(this); 24 | form->addRow(new QLabel( 25 | "Take a random subset of point cloud within bounding box")); 26 | percentLineEdit = new QLineEdit(this); 27 | percentLineEdit->setValidator(validator); 28 | percentLineEdit->setText("7.0"); 29 | form->addRow(QString("Percentage of points to keep:"), percentLineEdit); 30 | 31 | buttonBox = new QDialogButtonBox( 32 | QDialogButtonBox::Ok | QDialogButtonBox::Cancel, 33 | Qt::Horizontal, this); 34 | 35 | form->addRow(buttonBox); 36 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 37 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 38 | 39 | } 40 | 41 | 42 | bool SparsifyDialog::getFields(float& percent) const 43 | { 44 | bool ok = get_float_field(percentLineEdit, validator, percent); 45 | return ok; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /dialogs/SparsifyDialog.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef SPARSIFYDIALOG_H 5 | #define SPARSIFYDIALOG_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | QT_BEGIN_NAMESPACE 12 | class QFormLayout; 13 | class QLineEdit; 14 | class QDialogButtonBox; 15 | class QDoubleValidator; 16 | QT_END_NAMESPACE 17 | 18 | class SparsifyDialog : public QDialog 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | SparsifyDialog(QWidget *parent = nullptr); 24 | bool getFields(float& percent) const; 25 | 26 | private: 27 | QFormLayout *form; 28 | QLineEdit *percentLineEdit; 29 | QDialogButtonBox *buttonBox; 30 | QDoubleValidator *validator; 31 | 32 | }; 33 | 34 | #endif // SPARSIFYDIALOG_H 35 | -------------------------------------------------------------------------------- /dialogs/get_field.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #include "get_field.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | bool get_integer_field( 11 | QLineEdit *lineEdit, QIntValidator *validator, size_t& field) 12 | { 13 | QString fieldStr = lineEdit->text(); 14 | int pos = 0; 15 | if(validator->validate(fieldStr, pos) != QValidator::Acceptable){ 16 | lineEdit->clear(); 17 | return false; 18 | } 19 | else{ 20 | bool ok; 21 | field = fieldStr.toULongLong(&ok); 22 | if(!ok) { 23 | lineEdit->clear(); 24 | return false; 25 | } 26 | } 27 | 28 | return true; 29 | } 30 | 31 | bool get_float_field( 32 | QLineEdit *lineEdit, QDoubleValidator *validator, float& field) 33 | { 34 | QString fieldStr = lineEdit->text(); 35 | int pos = 0; 36 | if(validator->validate(fieldStr, pos) != QValidator::Acceptable){ 37 | lineEdit->clear(); 38 | return false; 39 | } 40 | else{ 41 | bool ok; 42 | field = fieldStr.toFloat(&ok); 43 | if(!ok) { 44 | lineEdit->clear(); 45 | return false; 46 | } 47 | } 48 | 49 | return true; 50 | } 51 | -------------------------------------------------------------------------------- /dialogs/get_field.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #ifndef GET_FIELD_H 5 | #define GET_FIELD_H 6 | 7 | #include 8 | 9 | QT_BEGIN_NAMESPACE 10 | class QLineEdit; 11 | class QIntValidator; 12 | class QDoubleValidator; 13 | QT_END_NAMESPACE 14 | 15 | bool get_integer_field( 16 | QLineEdit *lineEdit, QIntValidator *validator, size_t& field); 17 | 18 | bool get_float_field( 19 | QLineEdit *lineEdit, QDoubleValidator *validator, float& field); 20 | 21 | #endif // GET_FIELD_H 22 | -------------------------------------------------------------------------------- /dictionarylearning/MatchingPursuit.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #include "MatchingPursuit.h" 7 | #include "ensure_buffer_size.h" 8 | #include "constants.h" 9 | 10 | #include 11 | 12 | using Index = Eigen::Index; 13 | using MatrixXf = Eigen::MatrixXf; 14 | using VectorXf = Eigen::VectorXf; 15 | 16 | //------------------------------------------------------------------------- 17 | void MatchingPursuit::ensure( 18 | Index nd, Index na, Index lm) 19 | { 20 | if(nd != ndim || na != natm || lm != lmax){ 21 | ndim = nd; 22 | natm = na; 23 | lmax = (na < lm) ? na : lm; 24 | } 25 | } 26 | 27 | 28 | //------------------------------------------------------------------------- 29 | /** 30 | Given a size n signal vector Y, an n x m dictionary matrix D of 31 | m atoms (unit column vectors of size n), and an integer L, 32 | our task is to find a sparse size m code vector X that encodes Y 33 | as closely as possible using no more than L atoms in D. In detail, 34 | we try to solve to minimization problem 35 | 36 | min_X ||Y - DX||_F 37 | subject to 38 | ||X||_0 <= L 39 | 40 | where ||.||_F is the matrix Frobenius norm, and ||.||_0 is the 41 | vector L_0 norm (the number of non-zero entries in a vector). 42 | 43 | This is a greedy approach using the matching pursuit algorithm. 44 | 45 | @param[in] Y: size n vector. 46 | @param[in] D: n x m dictionary matrix. 47 | @param[in] latm: Sparsity constraint L. 48 | @param[out] X: size m code vector. 49 | @param[out] R: size n residual vector. 50 | 51 | */ 52 | 53 | 54 | 55 | void MatchingPursuit::operator()( 56 | const VectorXf& Y, const MatrixXf& D, Index latm, VectorXf& X, VectorXf& R) 57 | { 58 | assert(D.rows() == Y.rows()); 59 | assert(D.cols() == X.rows()); 60 | ensure(D.rows(), D.cols(), latm); 61 | 62 | X.setZero(); 63 | R = Y; 64 | 65 | for(Index j = 1; j <= latm; ++j){ 66 | float absprojmax = -float_infinity; 67 | float projmax = 0; 68 | Index imax = 0; 69 | for(Index i = 0; i < natm; ++i){ 70 | if( X(i) != 0.0f ) continue; 71 | float proj = R.dot(D.col(i)); 72 | float absproj = abs(proj); 73 | if( absproj > absprojmax ){ 74 | projmax = proj; 75 | absprojmax = absproj; 76 | imax = i; 77 | } 78 | } 79 | X(imax) = projmax; 80 | R = R - projmax*D.col(imax); 81 | } 82 | } 83 | //------------------------------------------------------------------------- 84 | -------------------------------------------------------------------------------- /dictionarylearning/MatchingPursuit.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef MATCHINGPURSUIT_H 7 | #define MATCHINGPURSUIT_H 8 | 9 | #include "alignment.h" 10 | 11 | #include 12 | 13 | 14 | class MatchingPursuit 15 | { 16 | using Index = Eigen::Index; 17 | using MatrixXf = Eigen::MatrixXf; 18 | using VectorXf = Eigen::VectorXf; 19 | 20 | public: 21 | MatchingPursuit() : 22 | ndim(0), natm(0), lmax(0) {} 23 | 24 | MatchingPursuit(const MatchingPursuit &mp) : MatchingPursuit() {} 25 | //{ ensure(sa.ndim, sa.natm, sa.lmax); } 26 | 27 | ~MatchingPursuit(){} 28 | 29 | void ensure(Index nd, Index na, Index lm); 30 | 31 | void operator() ( 32 | const VectorXf& Y, const MatrixXf& D, Index l, 33 | VectorXf& X, VectorXf& R); 34 | 35 | private: 36 | Index ndim; // Size of signal vector. 37 | Index natm; // No. of 'atoms' in dictionary. 38 | Index lmax; // maximum sparsity constraint <= natm. 39 | 40 | }; 41 | 42 | #endif // MATCHINGPURSUIT_H 43 | -------------------------------------------------------------------------------- /dictionarylearning/OrthogonalPursuit.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #include "OrthogonalPursuit.h" 6 | #include "ensure_buffer_size.h" 7 | #include "constants.h" 8 | 9 | #include 10 | #include 11 | 12 | using Eigen::Matrix; 13 | using Eigen::Dynamic; 14 | using Eigen::LDLT; 15 | using Eigen::Aligned16; 16 | using Eigen::Index; 17 | using Eigen::MatrixXf; 18 | using Eigen::VectorXf; 19 | using MapVectf = Eigen::Map; 20 | using MapMtrxf = Eigen::Map; 21 | template using Map = Eigen::Map; 22 | template using vector = std::vector>; 23 | 24 | 25 | //------------------------------------------------------------------------- 26 | void OrthogonalPursuit::ensure(Index nd, Index na, Index lm) 27 | { 28 | if(nd != ndim || na != natm || lm != lmax){ 29 | ndim = nd; 30 | natm = na; 31 | lmax = (na < lm) ? na : lm; 32 | ensureWorkspace(); 33 | } 34 | } 35 | 36 | //------------------------------------------------------------------------- 37 | 38 | void OrthogonalPursuit::ensureWorkspace() 39 | { 40 | // Allocate more workspace as necessary. 41 | //std::function< size_t(Index) > align_padded = 42 | // [=](Index n) ->size_t { return ALIGNEDX*(1+(n/ALIGNEDX)); }; 43 | size_t paddednd = align_padded(ndim); 44 | size_t paddedlm = align_padded(lmax); 45 | size_t paddedndlm = align_padded(ndim*lmax); 46 | size_t paddedlmsq = align_padded(lmax*lmax); 47 | 48 | size_t ndworkNeed = paddednd + 3*paddedlm + 2*paddedndlm + 2*paddedlmsq; 49 | ensure_buffer_size(ndworkNeed+ndworkNeed/2, dwork); 50 | 51 | // Map to pre-allocated workspace. 52 | size_t p = 0; 53 | new (&U) MapVectf(&dwork[0],ndim); 54 | p = p + paddednd; 55 | new (&V) MapVectf(&dwork[p],lmax); 56 | p = p + paddedlm; 57 | new (&W) MapVectf(&dwork[p],lmax); 58 | p = p + paddedlm; 59 | new (&XI) MapVectf(&dwork[p],lmax); 60 | p = p + paddedlm; 61 | new (&E) MapMtrxf(&dwork[p],ndim,lmax); 62 | p = p + paddedndlm; 63 | new (&F) MapMtrxf(&dwork[p],lmax,lmax); 64 | p = p + paddedlmsq; 65 | dworkOffset = p; 66 | 67 | size_t niworkNeed = lmax; 68 | ensure_buffer_size(niworkNeed+niworkNeed/2, iwork); 69 | 70 | // Map to pre-allocated workspace. 71 | new (&I) Map>(&iwork[0],lmax); 72 | 73 | if( lmax*lmax > ldltSize ){ 74 | ldltSize = lmax*lmax; 75 | delete ldlt; 76 | ldlt = new LDLT(ldltSize); 77 | } 78 | } 79 | 80 | //------------------------------------------------------------------------- 81 | /** 82 | Orthogonal Matching Pursuit: 83 | 84 | Similar to matching pursuit, but provides a better approximation 85 | at the expense of significantly more computation. Namely, updates 86 | all the coefficients of the current code vector X' at each iteration 87 | so that DX' is an orthogonal projection of the signal vector Y onto 88 | the subspace spanned by the dictionary atoms corresponding to the 89 | nonzero entries of X'. 90 | 91 | @param[in] Y: size n vector. 92 | @param[in] D: n x m dictionary matrix of unit column vectors. 93 | @param[in] latm: Sparsity constraint. 94 | @param[out] X: size m code vector. 95 | @param[out] R: size n residual vector. 96 | 97 | */ 98 | 99 | 100 | void OrthogonalPursuit::operator() ( 101 | const VectorXf& Y, const MatrixXf& D, Index latm, 102 | VectorXf& X, VectorXf& R) 103 | { 104 | assert(D.rows() == Y.rows()); 105 | assert(D.cols() == X.rows()); 106 | ensure(D.rows(),D.cols(),latm); 107 | 108 | X.setZero(); 109 | R = Y; 110 | 111 | for(Index j = 1; j <= latm; ++j){ 112 | // Find the next 'nearest' atom to current residual R. 113 | float absprojmax = -float_infinity; 114 | Index imax = 0; 115 | for(Index i = 0; i < natm; ++i){ 116 | if( X(i) != 0.0f ) continue; 117 | float proj = R.dot(D.col(i)); 118 | float absproj = abs(proj); 119 | if( absproj > absprojmax ){ 120 | absprojmax = absproj; 121 | imax = i; 122 | } 123 | } 124 | U = D.col(imax); // Dictionary atom U 'nearest' to R 125 | E.col(j-1) = U; // ...save it in j^th column of E 126 | I(j-1) = imax; // ...and save column index of U 127 | X(imax) = 1.0f; // Set temporarily 1.0 to mark traversed. 128 | 129 | // Map to pre-allocated workspace. 130 | Index p = dworkOffset; 131 | new (&ETblk) MapMtrxf(&dwork[p],j,ndim); 132 | p = p + align_padded(j*ndim); 133 | new (&Fblk) MapMtrxf(&dwork[p],j,j); 134 | 135 | // With U added to the current set E of j nearest atoms, 136 | // optimise the coefficients of XI w.r.t this E. This is 137 | // done by projecting Y onto the subspace spanned by E. 138 | if( j > 1 ) { 139 | // Compute the product E^T(:,1:j) * E(:,1:j), 140 | // This can be done quicker by reusing the product 141 | // E^T(:,1:j-1) * E(:,1:j-1) from the previous 142 | // iteration. 143 | 144 | V.segment(0,j-1).noalias() = U.transpose() * E.block(0,0,ndim,j-1); 145 | F.col(j-1).segment(0,j-1) = V.segment(0,j-1); 146 | F.row(j-1).segment(0,j-1) = V.segment(0,j-1); 147 | F(j-1,j-1) = 1.0f; 148 | 149 | Fblk = F.block(0,0,j,j); 150 | ETblk = E.block(0,0,ndim,j).transpose(); 151 | W.segment(0,j).noalias() = ETblk * Y; 152 | // Solve (E^T*E)*XI = (E^T)*Y 153 | ldlt->compute(Fblk); 154 | XI.segment(0,j) = ldlt->solve(W.segment(0,j)); 155 | 156 | //Update residual R 157 | R = Y; 158 | R.noalias() -= (E.block(0,0,ndim,j))*(XI.segment(0,j)); 159 | } 160 | else{ 161 | F(0,0) = 1.0f; 162 | XI(0) = Y.dot(U); 163 | //Update residual R 164 | R = Y; 165 | R.noalias() -= XI(0)*U; 166 | } 167 | } 168 | 169 | // Map back to code vector. 170 | for(Index i = 0; i < latm; ++i){ 171 | X(I(i)) = XI(i); 172 | } 173 | 174 | 175 | } 176 | //------------------------------------------------------------------------- 177 | -------------------------------------------------------------------------------- /dictionarylearning/OrthogonalPursuit.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef ORTHOGONALPURSUIT_H 7 | #define ORTHOGONALPURSUIT_H 8 | 9 | 10 | #include "alignment.h" 11 | 12 | #include 13 | #include 14 | 15 | 16 | class OrthogonalPursuit 17 | { 18 | using Index = Eigen::Index; 19 | using MatrixXf = Eigen::MatrixXf; 20 | using VectorXf = Eigen::VectorXf; 21 | using MapVectf = Eigen::Map; 22 | using MapMtrxf = Eigen::Map; 23 | template using Map = Eigen::Map; 24 | template using vector = std::vector>; 25 | 26 | public: 27 | OrthogonalPursuit() : 28 | ndim(0), natm(0), lmax(0), dworkOffset(0), 29 | U(nullptr,0), V(nullptr,0), W(nullptr,0), 30 | XI(nullptr,0), I(nullptr,0), 31 | E(nullptr,0,0), F(nullptr,0,0), 32 | ETblk(nullptr,0,0), Fblk(nullptr,0,0), 33 | ldlt(nullptr), ldltSize(0) {} 34 | 35 | OrthogonalPursuit(const OrthogonalPursuit &op) : OrthogonalPursuit(){} 36 | //{ ensure(sa.ndim, sa.natm, sa.lmax); } 37 | 38 | ~OrthogonalPursuit(){ 39 | delete ldlt; 40 | } 41 | 42 | void ensure(Index nd, Index na, Index lm); 43 | 44 | void operator() ( 45 | const VectorXf& Y, const MatrixXf& D, Index l, 46 | VectorXf& X, VectorXf& R); 47 | 48 | private: 49 | void ensureWorkspace(); 50 | 51 | Index ndim; // Size of signal vector. 52 | Index natm; // No. of 'atoms' in dictionary. 53 | Index lmax; // maximum sparsity constraint <= natm. 54 | 55 | vector dwork; 56 | size_t dworkOffset; 57 | vector iwork; 58 | // Work-space mapped onto work buffer. 59 | MapVectf U, V, W, XI; 60 | Map< Eigen::Matrix > I; 61 | MapMtrxf E, F, ETblk, Fblk; 62 | 63 | Eigen::LDLT *ldlt; 64 | Index ldltSize; 65 | 66 | 67 | }; 68 | 69 | #endif // ORTHOGONALPURSUIT_H 70 | 71 | -------------------------------------------------------------------------------- /dictionarylearning/cosine_transform.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #include "cosine_transform.h" 6 | #include "ensure_buffer_size.h" 7 | #include "constants.h" 8 | #include "alignment.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | 18 | using std::max; 19 | using std::vector; 20 | using Eigen::MatrixXf; 21 | using Eigen::Matrix; 22 | using Eigen::VectorXf; 23 | using Eigen::Index; 24 | using Eigen::Map; 25 | using Eigen::Dynamic; 26 | //using Eigen::Ref; 27 | template using Ref = Eigen::Ref; 28 | 29 | 30 | using alloc = Eigen::aligned_allocator; 31 | 32 | //----------------------------------------------------------- 33 | 34 | /** 35 | 2D non-uniform discrete cosine transform sampled at (u,v) 36 | coordinates in vectors U and V. 37 | 38 | @param[in] U: size n vector of u coordinates. 39 | @param[in] V: size n vector of v coordinates. 40 | @param[in] nfreq: max frequency in the u and v directions 41 | of transform. 42 | @param[in/out] dwork: work-array. Reallocated to correct size 43 | if too small. 44 | @param[out] T: 2D cosine transform, a dimension n x (nfreq*nfreq) 45 | matrix (must already be of this size on input). 46 | */ 47 | 48 | 49 | void cosine_transform( 50 | const VectorXf& U, 51 | const VectorXf& V, 52 | Index nfreq, 53 | vector& dwork, 54 | Ref T 55 | ) 56 | { 57 | Index nsmpl = U.size(); 58 | size_t nf = nsmpl*nfreq; 59 | 60 | assert(nsmpl == V.size()); 61 | assert(nsmpl == T.rows()); 62 | assert(nfreq*nfreq == T.cols()); 63 | 64 | Map CU(nullptr, nsmpl, nfreq); 65 | Map SU(nullptr, nsmpl, nfreq); 66 | Map CV(nullptr, nsmpl, nfreq); 67 | Map SV(nullptr, nsmpl, nfreq); 68 | 69 | size_t p = align_padded(nf); 70 | ensure_buffer_size(4*p, dwork); 71 | new (&SU) Map(&dwork[0], nsmpl, nfreq); 72 | new (&CU) Map(&dwork[p], nsmpl, nfreq); 73 | new (&SV) Map(&dwork[2*p], nsmpl, nfreq); 74 | new (&CV) Map(&dwork[3*p], nsmpl, nfreq); 75 | 76 | float maxu = 0.0f; 77 | float maxv = 0.0f; 78 | for(int k=0; k 0.0 ? M_PI/maxu : M_PI); 86 | float scalev = float(maxv > 0.0 ? M_PI/maxv : M_PI); 87 | 88 | // for(Index i=0; i approx_sin = [=](float t) { 105 | // if(t < 0){ return 1.27323954f*t + 0.405284735f*t*t; } 106 | // else{ return 1.27323954f*t - 0.405284735f*t*t; } 107 | // }; 108 | // std::function< float(float) > approx_cos = [=](float t) { 109 | // return approx_sin(t+1.57079632f); 110 | // }; 111 | 112 | for(Index k=0; k 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef COSINETRANSFORM_H 7 | #define COSINETRANSFORM_H 8 | 9 | #include "alignment.h" 10 | 11 | #include 12 | #include 13 | 14 | 15 | void cosine_transform( 16 | const Eigen::VectorXf& U, 17 | const Eigen::VectorXf& V, 18 | Eigen::Index nfreq, 19 | std::vector>& dwork, 20 | Eigen::Ref T 21 | ); 22 | 23 | #endif // COSINETRANSFORM_H 24 | -------------------------------------------------------------------------------- /dictionarylearning/ksvd.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | //#define DEBUG_KSVD 7 | 8 | #include "ksvd.h" 9 | #include "constants.h" 10 | #include "alignment.h" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | using std::cout; 22 | using std::endl; 23 | using std::vector; 24 | using Eigen::MatrixXf; 25 | using Eigen::MatrixXi; 26 | using Eigen::Matrix; 27 | using Eigen::VectorXf; 28 | using Eigen::VectorXi; 29 | using Eigen::Index; 30 | using Eigen::Map; 31 | using Eigen::Dynamic; 32 | using Eigen::Aligned16; 33 | 34 | 35 | //----------------------------------------------------------- 36 | 37 | /** 38 | The K-SVD dictionary learning algorithm. 39 | 40 | Provided an integer L and n x k matrix Y consisting of our 41 | k size n signal vectors, we look for an n x m dictionary 42 | matrix D of m atoms (unit column vectors of size n) and a 43 | sparse m x k matrix X of column code vectors that encode the 44 | signals in Y as closely as possible using no more than L 45 | atoms in D. In detail, we solve the minimization problem 46 | 47 | min_{X, D} ||Y - DX||_F 48 | subject to 49 | ||X||_0 <= L 50 | 51 | where ||.||_F is the matrix Frobenius norm, and ||.||_0 is the 52 | vector L_0 norm (the number of non-zero entries in a vector). 53 | 54 | 55 | @param[in] useOpenMP: Whether to parallelize using OpenMP. 56 | @param[in] Y: n x k signal matrix. 57 | @param[in] latm: Sparsity constraint L. 58 | @param[in] maxIters: Max. number of K-SVD iterations. 59 | @param[in] maxError: Max. error ||Y-D*X||^2 before an iteration 60 | can be aborted (< 0.0 for none). 61 | @param[in] svdPowIters: Number of power iterations to approximate 62 | first singular vectors. 63 | @param[in] Sparse approximation functor. 64 | @param[in/out] D in: first approximation n x m 'dictionary' matrix. 65 | D out: learnt dictionary adapted to the signals Y. 66 | @param[out] X: m x k 'code' matrix. 67 | 68 | */ 69 | 70 | 71 | void ksvd( 72 | bool useOpenMP, 73 | const MatrixXf& Y, 74 | Index latm, 75 | int maxIters, 76 | float maxError, 77 | int svPowIters, 78 | const std::function sparseFunct, 84 | MatrixXf& D, 85 | MatrixXf& X 86 | ) 87 | { 88 | Index ndim = D.rows(); 89 | Index natm = D.cols(); 90 | Index nsig = Y.cols(); 91 | assert(ndim == Y.rows()); 92 | assert(natm == X.rows()); 93 | assert(nsig == X.cols()); 94 | assert(maxIters >= 1); 95 | assert(svPowIters >= 1); 96 | assert(latm <= ndim && latm <= natm); 97 | 98 | bool stopAtMaxError = (maxError >= 0.0f); 99 | float maxErrorSq = 0.0f; 100 | bool smallError; 101 | float* errsig = new float[nsig]; 102 | for(Index i=0; i < nsig; ++i) { errsig[i] = float_infinity; } 103 | if( stopAtMaxError ) maxErrorSq = maxError*maxError; 104 | 105 | float *dwork = new float[nsig*ndim]; 106 | 107 | MatrixXf Z(ndim,nsig); 108 | Map Zblk(nullptr, 0, 0); 109 | VectorXf ZTA(nsig); 110 | VectorXf ZZTA(ndim); 111 | VectorXf A(ndim); 112 | VectorXf B(nsig); 113 | MatrixXi iatmUsed(latm,nsig); 114 | VectorXi natmUsed(nsig); 115 | 116 | #pragma omp parallel if(useOpenMP) default(shared) firstprivate(sparseFunct) 117 | { 118 | 119 | VectorXf Ysig(ndim); 120 | VectorXf Xsig(natm); 121 | VectorXf R(ndim); 122 | 123 | for(int iter = 1; iter <= maxIters; ++iter){ 124 | //*** 125 | #pragma omp single 126 | { 127 | #ifdef DEBUG_KSVD 128 | if(iter == 1) cout << "\nAverge error (coordinate difference):\n" ; 129 | cout << (Y-(D*X)).cwiseAbs().sum()/(ndim*nsig) << endl; 130 | #endif 131 | } 132 | 133 | // Fix dictionary D and optimize code matrix X. 134 | #pragma omp for schedule(dynamic) 135 | for(Index isig = 0; isig < nsig; ++isig){ 136 | Ysig = Y.col(isig); 137 | sparseFunct(Ysig, D, latm, Xsig, R); 138 | float error = R.dot(R); 139 | if( error <= errsig[isig] ) { 140 | X.col(isig) = Xsig; 141 | errsig[isig] = error; 142 | } 143 | } 144 | 145 | if( stopAtMaxError ){ 146 | // Stop if Y and D*X are similar within tolerance. 147 | #pragma omp single 148 | { 149 | smallError = true; 150 | for(Index isig = 0; isig < nsig; ++isig){ 151 | if( errsig[isig] > maxErrorSq ){ 152 | smallError = false; 153 | break; 154 | } 155 | } 156 | }//single 157 | if( smallError ) break; 158 | } 159 | // --- 160 | // Now optimize dictionary D for the current code vector X 161 | // one column (atom) at a time. 162 | //#pragma omp for schedule(dynamic) 163 | #pragma omp single 164 | { 165 | // queue up atoms used by each signal 166 | for(Index isig = 0; isig < nsig; ++isig){ 167 | int ic = 0; 168 | for(Index iatm = 0; iatm < natm; ++iatm){ 169 | if( X(iatm,isig) == 0.0f ) continue; 170 | iatmUsed(ic,isig) = int(iatm); 171 | ++ic; 172 | if(ic >= latm) break; 173 | } 174 | natmUsed(isig) = ic; 175 | } 176 | 177 | 178 | for(Index iatm = 0; iatm < natm; ++iatm){ 179 | A = D.col(iatm); // Original atom is our initial approx. 180 | // Compute the matrix Z of residuals for current atom. 181 | Index nsigUsing = 0; 182 | for(Index isig = 0; isig < nsig; ++isig){ 183 | if( X(iatm,isig) == 0.0f ) continue; 184 | Z.col(nsigUsing) = Y.col(isig); 185 | for(Index i = 0; i < natmUsed(isig); ++i){ 186 | Index jatm = iatmUsed(i,isig); 187 | if( jatm == iatm ) continue; 188 | Z.col(nsigUsing) -= X(jatm,isig)*D.col(jatm); 189 | } 190 | ++nsigUsing; 191 | } 192 | 193 | if( nsigUsing == 0 ) continue; 194 | 195 | // Map to workspace 196 | new (&Zblk) Map(dwork,ndim,nsigUsing); 197 | Zblk = Z.block(0,0,ndim,nsigUsing); 198 | 199 | // We only need the first singular vector, do a power 200 | // iteration to approximate it. This is our new improved atom. 201 | for(int i=1; i <= svPowIters; ++i){ 202 | ZTA.segment(0,nsigUsing).noalias() = Zblk.transpose()*A; 203 | ZZTA.noalias() = Zblk*ZTA.segment(0,nsigUsing); 204 | A = ZZTA.normalized(); // Optimized atom 205 | } 206 | 207 | // The projection coefficients describe the code vector 208 | // corresponding to the updated atom. 209 | B.segment(0,nsigUsing).noalias() = Zblk.transpose()*A; 210 | Index ic2 = 0; 211 | for(Index isig = 0; isig < nsig; ++isig){ 212 | if( X(iatm,isig) == 0.0f ) continue; 213 | X(iatm,isig) = B(ic2); 214 | ++ic2; 215 | } 216 | 217 | D.col(iatm) = A; 218 | } 219 | 220 | }//single 221 | } 222 | 223 | }//parallel 224 | 225 | delete[] dwork; 226 | if( stopAtMaxError ) delete[] errsig; 227 | 228 | } 229 | 230 | //----------------------------------------------------------- 231 | 232 | -------------------------------------------------------------------------------- /dictionarylearning/ksvd.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef KSVD_H 7 | #define KSVD_H 8 | 9 | #include 10 | #include 11 | 12 | 13 | 14 | void ksvd( 15 | bool useOpenMP, 16 | const Eigen::MatrixXf& Y, 17 | Eigen::Index latm, 18 | int maxIters, 19 | float maxError, 20 | int svPowIters, 21 | const std::function sparseFunct, 27 | Eigen::MatrixXf& D, 28 | Eigen::MatrixXf& X 29 | ); 30 | 31 | #endif // KSVD_H 32 | -------------------------------------------------------------------------------- /dictionarylearning/ksvd_dct2D.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #include "ksvd_dct2D.h" 7 | #include "cosine_transform.h" 8 | #include "ensure_buffer_size.h" 9 | #include "constants.h" 10 | #include "alignment.h" 11 | #include "MessageLogger.h" 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using std::cout; 22 | using std::endl; 23 | using std::vector; 24 | using Eigen::MatrixXf; 25 | using Eigen::MatrixXi; 26 | using Eigen::Matrix; 27 | using Eigen::VectorXf; 28 | using Eigen::VectorXi; 29 | using Eigen::Index; 30 | using Eigen::Map; 31 | using Eigen::Dynamic; 32 | using Eigen::Aligned16; 33 | using Eigen::LDLT; 34 | //using Eigen::Ref; 35 | 36 | template using aligned = Eigen::aligned_allocator; 37 | using vectorfa = vector>; 38 | using vectoria = vector>; 39 | 40 | template using Ref = Eigen::Ref; 41 | using MapMtrxf = Map; 42 | using MapVectf = Map; 43 | 44 | 45 | //----------------------------------------------------------- 46 | 47 | /** 48 | An extension of K-SVD to the domain of continuous signals and 49 | dictionary atoms, in this case realized by linear combinations 50 | of products of cosines of various frequencies coming from 2D 51 | discrete cosine transform sampled non-uniformly at given (u,v) 52 | coordinates associated to each signal's coordinates (samples). 53 | This allows signals of varying length as well estimating values 54 | of signals away from their sampled (u,v)-coordinates. 55 | 56 | C.f. "Cloud Dictionary: Coding and Modeling for Point Clouds", 57 | https://arxiv.org/abs/1612.04956 58 | 59 | @param[in] useOpenMP: Whether to parallelize using OpenMP. 60 | @param[in] Y: array of signal vectors (possibly of variable length). 61 | @param[in] U: array of vectors of 'u' coordinates for each coordinate 62 | of vector in Y. 63 | @param[in] V: array of vectors of 'v' coordinates for each coordinate 64 | of vector in Y. 65 | @param[in] nfreq: Largest cosine frequency. 66 | @param[in] latm: Sparsity constraint L. 67 | @param[in] maxIters: Max. number of K-SVD iterations. 68 | @param[in] maxError: Max. error ||Y-D*X||^2 before an iteration 69 | can be aborted (< 0.0 for none). 70 | @param[in] svdPowIters: Number of power iterations to approximate 71 | first singular vectors. 72 | @param[in] Sparse approx. functor. 73 | @param[in/out] D in: first approx. (nfreq*nfreq) x m 'dictionary' matrix. 74 | D out: learnt dictionary adapted to the signals Y. 75 | @param[out] X: m x nsig 'code' matrix. 76 | 77 | */ 78 | 79 | 80 | void ksvd_dct2D( 81 | bool useOpenMP, 82 | const vector& Y, 83 | const vector& U, 84 | const vector& V, 85 | Index nfreq, 86 | Index latm, 87 | int maxIters, 88 | float maxError, 89 | const std::function sparseFunct, 95 | MatrixXf& D, 96 | MatrixXf& X, 97 | MessageLogger* msgLogger 98 | ) 99 | { 100 | 101 | Index nsig = Y.size(); 102 | Index nfreqsq = nfreq*nfreq; 103 | Index natm = D.cols(); 104 | assert(size_t(nsig) == U.size()); 105 | assert(size_t(nsig) == V.size()); 106 | assert(nfreqsq == D.rows()); 107 | assert(natm == X.rows()); 108 | assert(nsig == X.cols()); 109 | assert(maxIters >= 1); 110 | assert(latm <= natm); 111 | Index maxThreads = omp_get_max_threads(); 112 | Index lastIter = 0; 113 | 114 | bool stopAtMaxError = (maxError >= 0.0f); 115 | float maxErrorSq = 0.0f; 116 | bool smallError; 117 | float* errsig = new float[nsig]; 118 | for(Index i=0; i < nsig; ++i) { errsig[i] = float_infinity; } 119 | if( stopAtMaxError ) maxErrorSq = maxError*maxError; 120 | 121 | Index maxSamples = 0; 122 | Index totalSamplesPadded = 0; 123 | for(Index isig = 0; isig < nsig; ++isig){ 124 | Index nsmpl = Y[isig].size(); 125 | maxSamples = std::max(maxSamples, nsmpl); 126 | totalSamplesPadded += align_padded(nsmpl); 127 | } 128 | 129 | vectorfa dworkZ; 130 | ensure_buffer_size(totalSamplesPadded, dworkZ); 131 | vector Zblk(nsig, MapVectf(nullptr,0)); 132 | 133 | Index p = 0; 134 | for(Index isig = 0; isig < nsig; ++isig){ 135 | Index nsmpl = Y[isig].size(); 136 | new (&Zblk[isig]) MapVectf(&dworkZ[p], nsmpl); 137 | p = p + align_padded(nsmpl); 138 | } 139 | 140 | bool aSigUsingAtm; 141 | MatrixXi iatmUsed(latm,nsig); 142 | VectorXi natmUsed(nsig); 143 | 144 | VectorXf TZS(nfreqsq); 145 | MatrixXf TTS(nfreqsq,nfreqsq); 146 | vector TZSs(maxThreads,VectorXf(nfreqsq)); 147 | vector TTSs(maxThreads,MatrixXf(nfreqsq,nfreqsq)); 148 | 149 | //Eigen::LDLT *ldlt = new LDLT(nfreqsq*nfreqsq); 150 | Eigen::LDLT *ldlt = new LDLT(); 151 | 152 | #pragma omp parallel if(useOpenMP) default(shared) firstprivate(sparseFunct) 153 | { 154 | Index numThreads = omp_get_num_threads(); 155 | Index iThread = omp_get_thread_num(); 156 | 157 | VectorXf TDatm(maxSamples); 158 | VectorXf TA(maxSamples); 159 | VectorXf TZ(nfreqsq); 160 | VectorXf Xsig(natm); 161 | VectorXf R(nfreqsq); 162 | 163 | size_t paddedA[2] = { 164 | align_padded(maxSamples*nfreqsq), 165 | align_padded(maxSamples*natm) 166 | }; 167 | vectorfa dworkDctA(paddedA[0] + paddedA[1]); 168 | vectorfa dworkDctB;//(4*align_padded(maxSamples*nfreq)); 169 | 170 | MapMtrxf T(nullptr, maxSamples, nfreqsq); 171 | MatrixXf TT(nfreqsq,nfreqsq); 172 | MapMtrxf TD(nullptr, maxSamples, natm); 173 | VectorXf NrmInv(natm); 174 | VectorXf TY(nfreqsq); 175 | 176 | for(int iter = 1; iter <= maxIters; ++iter){ 177 | // Fix dictionary D and optimize code matrix X. 178 | #pragma omp for schedule(dynamic) 179 | for(Index isig = 0; isig < nsig; ++isig){ 180 | Index nsmpl = Y[isig].size(); 181 | new (&T) MapMtrxf(&dworkDctA[0], nsmpl, nfreqsq); 182 | cosine_transform(U[isig], V[isig], nfreq, dworkDctB, T); 183 | 184 | new (&TD) MapMtrxf(&dworkDctA[paddedA[0]], nsmpl, natm); 185 | TD.noalias() = T*D; 186 | column_normalize(TD, NrmInv); 187 | sparseFunct(Y[isig], TD, latm, Xsig, R); 188 | 189 | float error = R.dot(R); 190 | if( error <= errsig[isig] ) { 191 | X.col(isig) = Xsig.cwiseProduct(NrmInv); 192 | errsig[isig] = error; 193 | } 194 | } 195 | 196 | 197 | if( stopAtMaxError ){ 198 | // Stop if Y and D*X are similar within tolerance. 199 | #pragma omp single 200 | { 201 | smallError = true; 202 | for(Index isig = 0; isig < nsig; ++isig){ 203 | if( errsig[isig] > maxErrorSq ){ 204 | smallError = false; 205 | break; 206 | } 207 | } 208 | }//single 209 | 210 | if( smallError ) break; 211 | } 212 | 213 | #pragma omp single 214 | { 215 | // queue up atoms used by each signal 216 | for(Index isig = 0; isig < nsig; ++isig){ 217 | int ic = 0; 218 | for(Index iatm = 0; iatm < natm; ++iatm){ 219 | if( X(iatm,isig) == 0.0f ) continue; 220 | iatmUsed(ic,isig) = int(iatm); 221 | ++ic; 222 | if(ic >= latm) break; 223 | } 224 | natmUsed(isig) = ic; 225 | } 226 | 227 | } //single 228 | 229 | 230 | // --- 231 | // Now optimize dictionary D for the current code vector X 232 | // one column (atom) at a time. 233 | 234 | for(Index iatm = 0; iatm < natm; ++iatm){ 235 | TTSs[iThread].setZero(); 236 | TZSs[iThread].setZero(); 237 | #pragma omp for schedule(static) 238 | for(Index isig = 0; isig < nsig; ++isig){ 239 | if(X(iatm,isig) == 0.0f) continue; 240 | Index nsmpl = Y[isig].size(); 241 | new (&T) MapMtrxf(&dworkDctA[0], nsmpl, nfreqsq); 242 | cosine_transform(U[isig], V[isig], nfreq, dworkDctB, T); 243 | 244 | // Compute the residual Z for current atom and signal. 245 | MapVectf& Z = Zblk[isig]; 246 | Z = Y[isig]; 247 | for(Index i = 0; i < natmUsed(isig); ++i){ 248 | Index jatm = iatmUsed(i,isig); 249 | if( jatm == iatm ) continue; 250 | TDatm.segment(0,nsmpl).noalias() = T*D.col(jatm); 251 | Z -= X(jatm,isig)*TDatm.segment(0,nsmpl); 252 | } 253 | 254 | TT.noalias() = T.transpose() * T; 255 | TA.segment(0,nsmpl).noalias() = T*D.col(iatm); 256 | float normsqTA = TA.segment(0,nsmpl).squaredNorm(); 257 | TZ.noalias() = T.transpose() * Z; 258 | float r = Z.dot(TA.segment(0,nsmpl)) / normsqTA; 259 | TTSs[iThread] += (r*r)*TT; 260 | TZSs[iThread] += r*TZ; 261 | aSigUsingAtm = true; 262 | } 263 | 264 | 265 | #pragma omp single 266 | { 267 | if( aSigUsingAtm ){ 268 | TTS.setZero(); 269 | TZS.setZero(); 270 | for(int i = 0; i < numThreads; ++i){ 271 | TTS += TTSs[i]; 272 | TZS += TZSs[i]; 273 | } 274 | // This is an analog of a single SVD power 275 | // iteration. Now the vector optimized to 276 | // best match each residual is constrained 277 | // to be a linear transformation of another 278 | // vector. 279 | // It can be worked out with some matrix 280 | // calculus by minimizing the sum of inner 281 | // products: 282 | // 283 | // Sum_i(Z_i-a_iT_iD').(Z_i-a_iT_iD') 284 | // 285 | // over D' and reals a_i, where D' is the 286 | // atom being optimized, Z_i the residual 287 | // for the ith signal that uses atom D', 288 | // and T_i the cosine transform for this 289 | // signal. The a_i's are the projections 290 | // Z_i.(T_iD')/(T_iD').(T_iD') . 291 | ldlt->compute(TTS); 292 | D.col(iatm) = ldlt->solve(TZS); // Optimized atom 293 | } 294 | 295 | } //single 296 | if( aSigUsingAtm ){ 297 | #pragma omp for schedule(static) 298 | for(Index isig = 0; isig < nsig; ++isig){ 299 | if(X(iatm,isig) == 0.0f) continue; 300 | Index nsmpl = Y[isig].size(); 301 | new (&T) MapMtrxf(&dworkDctA[0], nsmpl, nfreqsq); 302 | cosine_transform(U[isig], V[isig], nfreq, dworkDctB, T); 303 | TA.segment(0,nsmpl).noalias() = T*D.col(iatm); 304 | float normsqTA = TA.segment(0,nsmpl).squaredNorm(); 305 | // The projection coefficients describe the code vector 306 | // corresponding to the updated atom. 307 | X(iatm,isig) = Zblk[isig].dot(TA.segment(0,nsmpl)) / normsqTA; 308 | } 309 | } 310 | 311 | 312 | } 313 | 314 | 315 | if(msgLogger != nullptr) { 316 | #pragma omp single 317 | { 318 | lastIter = iter; 319 | if(iter == 1){ 320 | msgLogger->logMessage( 321 | "Avg. error (coord. diff., cos angle, vect. diff.)"); 322 | print_error_dct2D( 323 | useOpenMP, Y, U, V, D, X, nfreq, iter, msgLogger); 324 | } 325 | } 326 | } 327 | 328 | 329 | } // kSVD iterations 330 | 331 | 332 | 333 | if(msgLogger != nullptr) { 334 | #pragma omp single 335 | { 336 | print_error_dct2D( 337 | useOpenMP, Y, U, V, D, X, nfreq, lastIter, msgLogger); 338 | } 339 | } 340 | 341 | 342 | } //parallel 343 | 344 | 345 | 346 | if( stopAtMaxError ) delete[] errsig; 347 | 348 | delete ldlt; 349 | 350 | 351 | } 352 | 353 | 354 | //----------------------------------------------------------- 355 | 356 | void print_error_dct2D( 357 | bool useOpenMP, 358 | const vector& Y, 359 | const vector& U, 360 | const vector& V, 361 | const MatrixXf& D, 362 | const MatrixXf& X, 363 | Index nfreq, 364 | Index iter, 365 | MessageLogger* msgLogger 366 | ) 367 | { 368 | Index nsig = Y.size(); 369 | Index natm = D.cols(); 370 | Index nfreqsq = nfreq*nfreq; 371 | Index maxThreads = omp_get_max_threads(); 372 | Index numThreads; 373 | 374 | vector threadError1(maxThreads, 0.0f); 375 | vector threadError2(maxThreads, 0.0f); 376 | vector threadError3(maxThreads, 0.0f); 377 | vector threadTotalSmpl(maxThreads, 0); 378 | 379 | #pragma omp parallel if(useOpenMP) default(shared) 380 | { 381 | numThreads = omp_get_num_threads(); 382 | Index iThread = omp_get_thread_num(); 383 | 384 | MapMtrxf T(nullptr, 0, 0); 385 | MapMtrxf TD(nullptr, 0, 0); 386 | MapVectf TDX(nullptr, 0); 387 | 388 | vectorfa dworkA; 389 | vectorfa dworkB; 390 | size_t paddedA[3]; 391 | 392 | #pragma omp for schedule(static) 393 | for(Index isig = 0; isig < nsig; ++isig){ 394 | Index nsmpl = Y[isig].size(); 395 | paddedA[0] = align_padded(nsmpl*nfreqsq); 396 | paddedA[1] = align_padded(nsmpl*natm); 397 | paddedA[2] = align_padded(nsmpl); 398 | ensure_buffer_size(paddedA[0]+paddedA[1]+paddedA[2], dworkA); 399 | new (&T) MapMtrxf(&dworkA[0], nsmpl, nfreqsq); 400 | new (&TD) MapMtrxf(&dworkA[paddedA[0]], nsmpl, natm); 401 | new (&TDX) MapVectf(&dworkA[paddedA[1]], nsmpl); 402 | cosine_transform(U[isig], V[isig], nfreq, dworkB, T); 403 | TD.noalias() = T*D; 404 | TDX.noalias() = TD*(X.col(isig)); 405 | float normY = Y[isig].norm(); 406 | threadError1[iThread] += (Y[isig] - TDX).cwiseAbs().sum(); 407 | threadError2[iThread] += Y[isig].dot(TDX)/(normY*TDX.norm()); 408 | threadError3[iThread] += (Y[isig] - TDX).norm()/normY; 409 | threadTotalSmpl[iThread] += nsmpl; 410 | } 411 | 412 | } //parallel 413 | 414 | float error1 = 0.0f; 415 | float error2 = 0.0f; 416 | float error3 = 0.0f; 417 | Index totalsmpl = 0; 418 | for(Index iThread = 0; iThread < numThreads; ++iThread){ 419 | error1 += threadError1[iThread]; 420 | error2 += threadError2[iThread]; 421 | error3 += threadError3[iThread]; 422 | totalsmpl += threadTotalSmpl[iThread]; 423 | } 424 | 425 | QString errorStr = 426 | "Iteration " + QString::number(iter) + ": " + 427 | QString::number(error1/totalsmpl) + ", " + 428 | QString::number(error2/nsig) + ", " + 429 | QString::number(error3/nsig); 430 | msgLogger->logMessage(errorStr); 431 | 432 | 433 | 434 | } 435 | //----------------------------------------------------------- 436 | 437 | 438 | void column_normalize(Ref M, Ref NrmInv) 439 | { 440 | for(Index i=0; i < M.cols(); ++i) { 441 | NrmInv(i) = 1.0f/M.col(i).norm(); 442 | M.col(i) *= NrmInv(i); 443 | } 444 | } 445 | 446 | 447 | //----------------------------------------------------------- 448 | -------------------------------------------------------------------------------- /dictionarylearning/ksvd_dct2D.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef KSVD_DCT2D_H 7 | #define KSVD_DCT2D_H 8 | 9 | #include "alignment.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class MessageLogger; 16 | 17 | void ksvd_dct2D( 18 | bool useOpenMP, 19 | const std::vector& Y, 20 | const std::vector& U, 21 | const std::vector& V, 22 | Eigen::Index nfreq, 23 | Eigen::Index latm, 24 | int maxIters, 25 | float maxError, 26 | const std::function sparseFunct, 33 | Eigen::MatrixXf& D, 34 | Eigen::MatrixXf& X, 35 | MessageLogger* msgLogger = nullptr 36 | ); 37 | 38 | 39 | void print_error_dct2D( 40 | bool useOpenMP, 41 | const std::vector& Y, 42 | const std::vector& U, 43 | const std::vector& V, 44 | const Eigen::MatrixXf& D, 45 | const Eigen::MatrixXf& X, 46 | Eigen::Index nfreq, 47 | Eigen::Index iter, 48 | MessageLogger* msgLogger 49 | ); 50 | 51 | 52 | void column_normalize(Eigen::Ref M, 53 | Eigen::Ref NrmInv); 54 | 55 | #endif // KSVD_DCT2D_H 56 | -------------------------------------------------------------------------------- /images/Capture1a.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture1a.PNG -------------------------------------------------------------------------------- /images/Capture1b.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture1b.PNG -------------------------------------------------------------------------------- /images/Capture1c.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture1c.PNG -------------------------------------------------------------------------------- /images/Capture1d.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture1d.PNG -------------------------------------------------------------------------------- /images/Capture1e.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture1e.PNG -------------------------------------------------------------------------------- /images/Capture2a.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2a.PNG -------------------------------------------------------------------------------- /images/Capture2a2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2a2.PNG -------------------------------------------------------------------------------- /images/Capture2b.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2b.PNG -------------------------------------------------------------------------------- /images/Capture2b2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2b2.PNG -------------------------------------------------------------------------------- /images/Capture2c.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2c.PNG -------------------------------------------------------------------------------- /images/Capture2d.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2d.PNG -------------------------------------------------------------------------------- /images/Capture2d2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2d2.PNG -------------------------------------------------------------------------------- /images/Capture2e.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2e.PNG -------------------------------------------------------------------------------- /images/Capture2e2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2e2.PNG -------------------------------------------------------------------------------- /images/Capture2f.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2f.PNG -------------------------------------------------------------------------------- /images/Capture2f2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture2f2.PNG -------------------------------------------------------------------------------- /images/Capture5a.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture5a.PNG -------------------------------------------------------------------------------- /images/Capture5b.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture5b.PNG -------------------------------------------------------------------------------- /images/Capture5c.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture5c.PNG -------------------------------------------------------------------------------- /images/Capture5d.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture5d.PNG -------------------------------------------------------------------------------- /images/Capture8a.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture8a.PNG -------------------------------------------------------------------------------- /images/Capture8b.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture8b.PNG -------------------------------------------------------------------------------- /images/Capture8c.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture8c.PNG -------------------------------------------------------------------------------- /images/Capture8d.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/Capture8d.PNG -------------------------------------------------------------------------------- /images/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/copy.png -------------------------------------------------------------------------------- /images/cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/cut.png -------------------------------------------------------------------------------- /images/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/new.png -------------------------------------------------------------------------------- /images/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/open.png -------------------------------------------------------------------------------- /images/paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/paste.png -------------------------------------------------------------------------------- /images/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/save.png -------------------------------------------------------------------------------- /images/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/images/undo.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Piotr (Peter) Beben 2 | // See LICENSE included with this distribution. 3 | 4 | #include "GLWidget.h" 5 | #include "MainWindow.h" 6 | #include "constants.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | void out_of_mem_handler(); 20 | 21 | int main(int argc, char *argv[]) 22 | { 23 | srand(2); 24 | std::set_new_handler(out_of_mem_handler); 25 | Eigen::initParallel(); 26 | 27 | qRegisterMetaType("size_t"); 28 | qRegisterMetaType("QTextBlock"); 29 | qRegisterMetaType("QTextCursor"); 30 | qRegisterMetaType("SparseApprox"); 31 | 32 | QCoreApplication::setApplicationName("PCReconstruct"); 33 | QCoreApplication::setOrganizationName("Peter Beben"); 34 | QCoreApplication::setApplicationVersion(QT_VERSION_STR); 35 | QCoreApplication::addLibraryPath("./"); 36 | 37 | QApplication app(argc, argv); 38 | 39 | QCommandLineParser parser; 40 | parser.setApplicationDescription(QCoreApplication::applicationName()); 41 | parser.addHelpOption(); 42 | parser.addVersionOption(); 43 | QCommandLineOption multipleSampleOption("multisample", "Multisampling"); 44 | parser.addOption(multipleSampleOption); 45 | QCommandLineOption coreProfileOption("coreprofile", "Use core profile"); 46 | parser.addOption(coreProfileOption); 47 | QCommandLineOption transparentOption("transparent", "Transparent window"); 48 | parser.addOption(transparentOption); 49 | parser.process(app); 50 | 51 | QSurfaceFormat fmt; 52 | fmt.setDepthBufferSize(24); 53 | if (parser.isSet(multipleSampleOption)) 54 | fmt.setSamples(4); 55 | if (parser.isSet(coreProfileOption)) { 56 | fmt.setVersion(3, 2); 57 | fmt.setProfile(QSurfaceFormat::CoreProfile); 58 | } 59 | QSurfaceFormat::setDefaultFormat(fmt); 60 | 61 | MainWindow mainWindow; 62 | 63 | GLWidget::setTransparent(parser.isSet(transparentOption)); 64 | if (GLWidget::isTransparent()) { 65 | mainWindow.setAttribute(Qt::WA_TranslucentBackground); 66 | mainWindow.setAttribute(Qt::WA_NoSystemBackground, false); 67 | } 68 | mainWindow.resize(mainWindow.sizeHint()); 69 | // int desktopArea = QApplication::desktop()->width() * 70 | // QApplication::desktop()->height(); 71 | // int widgetArea = mainWindow.width() * mainWindow.height(); 72 | // if (((float)widgetArea / (float)desktopArea) < 0.75f) 73 | // mainWindow.show(); 74 | // else 75 | // mainWindow.showMaximized(); 76 | 77 | mainWindow.showMaximized(); 78 | 79 | return app.exec(); 80 | 81 | } 82 | 83 | 84 | void out_of_mem_handler() 85 | { 86 | std::cerr << "Unable to allocate memory.\n"; 87 | std::abort(); 88 | } 89 | -------------------------------------------------------------------------------- /release/OpenNI2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/release/OpenNI2.dll -------------------------------------------------------------------------------- /release/PCReconstruct.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/release/PCReconstruct.exe -------------------------------------------------------------------------------- /release/bunny.pcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/release/bunny.pcd -------------------------------------------------------------------------------- /release/pcl_common_release.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/release/pcl_common_release.dll -------------------------------------------------------------------------------- /release/pcl_io_ply_release.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/release/pcl_io_ply_release.dll -------------------------------------------------------------------------------- /release/pcl_io_release.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codearxiv/PCReconstruct/a247aea9fc4cf8530308ecb2ad130c501530b072/release/pcl_io_release.dll -------------------------------------------------------------------------------- /stable.h: -------------------------------------------------------------------------------- 1 | /* Add C includes here */ 2 | 3 | //#if defined __cplusplus 4 | /* Add C++ includes here */ 5 | //#include 6 | //#include 7 | //#include 8 | //#include 9 | //#include 10 | //#include 11 | //#include 12 | //#include 13 | //#include 14 | //#include 15 | //#include 16 | //#include 17 | //#include 18 | //#include 19 | //#include 20 | //#include 21 | //#include 22 | //#include 23 | //#include 24 | //#include 25 | //#include 26 | //#include 27 | //#include 28 | //#include 29 | //#include 30 | ////#include 31 | //#include 32 | //#include 33 | //#include 34 | //#include 35 | //#include 36 | //#include 37 | //#include 38 | //#include 39 | //#include 40 | //#include 41 | //#include 42 | //#include 43 | //#include 44 | //#include 45 | //#include 46 | //#include 47 | //#include 48 | //#include 49 | //#include 50 | //#include 51 | //#include 52 | //#include 53 | //#include 54 | //#include 55 | //#include 56 | //#include 57 | //#include 58 | //#include 59 | //#include 60 | //#include 61 | //#include 62 | //#include 63 | //#include 64 | //#include 65 | //#include 66 | //#include 67 | //#include 68 | //#include 69 | //#endif 70 | -------------------------------------------------------------------------------- /utils/Plane.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #include "Plane.h" 6 | 7 | #include 8 | 9 | using Eigen::Index; 10 | using Eigen::Matrix3f; 11 | using Eigen::Vector3f; 12 | using Eigen::Vector2f; 13 | 14 | //----------------------------------------------------------- 15 | 16 | void Plane::set(const Vector3f p0, const Vector3f norm) 17 | { 18 | 19 | const float RRT2 = 1.0f/sqrt(2.0f); 20 | m_p0 = p0; 21 | m_norm = norm; 22 | 23 | if( abs(norm(0)) < RRT2 ) { 24 | m_u(0) = 0.0f; 25 | m_u(1) = norm(2); 26 | m_u(2) = -norm(1); 27 | } 28 | else{ 29 | m_u(0) = -norm(2); 30 | m_u(1) = 0.0f; 31 | m_u(2) = norm(0); 32 | } 33 | m_v = m_u.cross(norm); 34 | 35 | m_u.normalize(); 36 | m_v.normalize(); 37 | 38 | } 39 | //----------------------------------------------------------- 40 | 41 | Eigen::Vector2f Plane::project_uv(const Vector3f q) 42 | { 43 | Vector3f w = q - m_p0; 44 | Vector2f puv; 45 | puv(0) = w.dot(m_u); 46 | puv(1) = w.dot(m_v); 47 | return puv; 48 | } 49 | 50 | //----------------------------------------------------------- 51 | 52 | 53 | Eigen::Vector3f Plane::project(const Vector3f q, Vector2f& puv) 54 | { 55 | puv = project_uv(q); 56 | Vector3f p = puv(0)*m_u + puv(1)*m_v; 57 | return p; 58 | } 59 | 60 | //----------------------------------------------------------- 61 | 62 | 63 | -------------------------------------------------------------------------------- /utils/Plane.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef PLANE_H 7 | #define PLANE_H 8 | 9 | #include 10 | 11 | class Plane 12 | { 13 | using Index = Eigen::Index; 14 | using Matrix3f = Eigen::Matrix3f; 15 | using Vector3f = Eigen::Vector3f; 16 | using Vector2f = Eigen::Vector2f; 17 | 18 | public: 19 | Plane(const Vector3f p0, const Vector3f norm) { set(p0,norm); } 20 | 21 | void set(const Vector3f p0, const Vector3f norm); 22 | Vector2f project_uv(const Vector3f q); 23 | Vector3f project(const Vector3f q, Vector2f& puv); 24 | void getUVAxes(Vector3f& u, Vector3f& v) {u = m_u; v = m_v;} 25 | 26 | private: 27 | Vector3f m_p0; 28 | Vector3f m_norm; 29 | Vector3f m_u; 30 | Vector3f m_v; 31 | 32 | }; 33 | 34 | #endif // PLANE_H 35 | -------------------------------------------------------------------------------- /utils/cloud_normal.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #include "cloud_normal.h" 6 | #include "ensure_buffer_size.h" 7 | #include "constants.h" 8 | 9 | #include 10 | #include 11 | 12 | using Eigen::Vector3f; 13 | //using Eigen::Matrix; 14 | using Eigen::Index; 15 | using Eigen::Ref; 16 | 17 | template using vector = std::vector; 18 | 19 | 20 | //------------------------------------------------------------------------- 21 | /** 22 | Finds a best fitting unit normal N at a point p0 w.r.t. a point cloud. 23 | We do this by minimizing over nonzero vectors N the sum of squares 24 | 25 | F(N) = Sum_i w_i (N.V_i)^2 / (N.N) 26 | 27 | where V_i = (c_i-p0)/|c_i-p0| for c_i the i^th cloud point, where w_i 28 | is a given weighting. We simply take each w_i = 1.0. 29 | 30 | The gradient of F is 31 | Grad F = Sum_i w_i X_i 32 | where 33 | X_i = 2(N.V_i)/(N.N) V_i - 2 (N.V_i)^2/(N.N)^2 N 34 | 35 | with which we do gradient descent. 36 | 37 | 38 | @param[in] p0: point to query normal. 39 | @param[in] cloud: point cloud of n points. 40 | @param[in] niters: number of iterations to optimize the normal. 41 | @param[inout] vwork: workspace for V_i's. Resized automatically. 42 | 43 | */ 44 | 45 | Vector3f cloud_normal(const Vector3f& p0, const vector& cloud, 46 | int niters, std::vector& vwork) 47 | { 48 | static const float ONE_OVER_SQRT3 = 1.0f/sqrt(3.0f); 49 | 50 | Vector3f N(ONE_OVER_SQRT3, ONE_OVER_SQRT3, ONE_OVER_SQRT3); 51 | 52 | size_t npoints = cloud.size(); 53 | 54 | vwork.resize(npoints); 55 | for(size_t i=0; i < npoints; ++i) { 56 | vwork[i] = (cloud[i] - p0).normalized(); 57 | } 58 | 59 | const double s = 0.5/niters; 60 | for(int iter=0; iter < niters; ++iter) { 61 | Vector3f G(0.0f, 0.0f, 0.0f); 62 | //float E = 0.0f; 63 | for(size_t i=0; i < npoints; ++i) { 64 | Vector3f V = vwork[i]; 65 | double t = (N.dot(V)); 66 | Vector3f X = t*(V - t*N); 67 | G += X; 68 | //E += t*t; 69 | } 70 | 71 | double rate = s*double(niters-iter); 72 | Vector3f N1 = N - rate*G.normalized(); 73 | float N1N1 = N1.dot(N1); 74 | if( N1N1 <= float_tiny ) break; 75 | N = N1/sqrt(N1N1); 76 | 77 | } 78 | 79 | // float minE = float_infinity; 80 | // for(int iter=0; iter < 100; ++iter) { 81 | // Vector3f N1; 82 | // N1.setRandom().normalized(); 83 | // float E = 0.0f; 84 | // for(size_t i=0; i < npoints; ++i) { 85 | // Vector3f V = (cloud[i] - p0).normalized(); 86 | // double t = (N1.dot(V)); 87 | // E += t*t; 88 | // } 89 | // if( E < minE ){ 90 | // minE = E; 91 | // N = N1; 92 | // } 93 | // } 94 | 95 | 96 | return N; 97 | } 98 | -------------------------------------------------------------------------------- /utils/cloud_normal.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef CLOUD_NORMAL_H 7 | #define CLOUD_NORMAL_H 8 | 9 | #include 10 | #include 11 | 12 | 13 | Eigen::Vector3f cloud_normal( 14 | const Eigen::Vector3f& p0, const std::vector& cloud, 15 | int niters, std::vector& vwork); 16 | 17 | 18 | #endif // CLOUD_NORMAL_H 19 | -------------------------------------------------------------------------------- /utils/ensure_buffer_size.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef ENSURE_BUFFER_SIZE_H 7 | #define ENSURE_BUFFER_SIZE_H 8 | 9 | #include 10 | 11 | //----------------------------------------------------------- 12 | 13 | 14 | template > 15 | void ensure_buffer_size(size_t sizeEnsure, std::vector& buffer) 16 | { 17 | if( buffer.capacity() < sizeEnsure ) buffer.reserve(2*sizeEnsure); 18 | if( buffer.size() < sizeEnsure ) buffer.resize(sizeEnsure); 19 | 20 | } 21 | 22 | //----------------------------------------------------------- 23 | 24 | 25 | #endif // ENSURE_BUFFER_SIZE_H 26 | -------------------------------------------------------------------------------- /utils/pt_to_pt_distsq.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #include "pt_to_pt_distsq.h" 6 | 7 | #include 8 | #include 9 | 10 | //----------------------------------------------------------- 11 | 12 | double pt_to_pt_distsq(const Eigen::VectorXf& v, const Eigen::VectorXf& w) 13 | { 14 | double distsq = 0.0; 15 | for(Eigen::Index i=0; i < v.size(); ++i) { 16 | distsq += (v[i]-w[i])*(v[i]-w[i]); 17 | } 18 | return distsq; 19 | } 20 | //----------------------------------------------------------- 21 | 22 | double pt_to_pt_distsq(const float v[3], const float w[3]) 23 | { 24 | double distsq = 0.0; 25 | for(size_t i=0; i < 3; ++i) { 26 | distsq += (v[i]-w[i])*(v[i]-w[i]); 27 | } 28 | return distsq; 29 | } 30 | //----------------------------------------------------------- 31 | -------------------------------------------------------------------------------- /utils/pt_to_pt_distsq.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | 6 | #ifndef PT_TO_PT_DISTSQ_H 7 | #define PT_TO_PT_DISTSQ_H 8 | 9 | #include 10 | 11 | double pt_to_pt_distsq(const Eigen::VectorXf& v, const Eigen::VectorXf& w); 12 | 13 | double pt_to_pt_distsq(const float v[3], const float w[3]); 14 | 15 | 16 | #endif // PT_TO_PT_DISTSQ_H 17 | -------------------------------------------------------------------------------- /utils/rotations.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #include "rotations.h" 6 | #include "constants.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using Eigen::Vector3f; 13 | using Eigen::Matrix3f; 14 | 15 | 16 | //----------------------------------------------------------- 17 | // Returns the rotation matrix M that rotates vector v to a 18 | // vector w (or to a line through w if lineThruW is true). 19 | 20 | void vector_to_vector_rotation_matrix( 21 | const Vector3f& v, const Vector3f& w, 22 | bool normalized, bool lineThruW, Matrix3f& M) 23 | { 24 | 25 | Vector3f vxw = v.cross(w); 26 | float vxwLen = vxw.norm(); 27 | float cosa = v.dot(w); 28 | float sina = vxwLen; 29 | 30 | if( lineThruW ){ 31 | if( cosa < 0.0f ){ 32 | cosa = -cosa; 33 | vxw = -vxw; 34 | } 35 | } 36 | 37 | if( !normalized ){ 38 | float vvww = sqrt(v.dot(v)*w.dot(w)); 39 | 40 | if( vvww > float_tiny ){ 41 | cosa = cosa/vvww; 42 | sina = sina/vvww; 43 | } 44 | else{ 45 | cosa = 1.0f; 46 | sina = 0.0f; 47 | } 48 | } 49 | 50 | if( vxwLen > float_tiny ){ 51 | Vector3f u = vxw/vxwLen; 52 | cos_sin_angle_vector_rotation_matrix(cosa, sina, u, M); 53 | } 54 | else{ 55 | M.setIdentity(); 56 | if( cosa < 0.0f ) M = -M; 57 | } 58 | 59 | } 60 | 61 | //----------------------------------------------------------- 62 | /* 63 | Returns the counter-clockwise rotation matrix M around 64 | a unit vector U by an angle. U must have unit length. 65 | */ 66 | void angle_vector_rotation_matrix( 67 | float angle, const Vector3f& u, Matrix3f& M) 68 | { 69 | cos_sin_angle_vector_rotation_matrix(cos(angle), sin(angle), u, M); 70 | } 71 | 72 | //----------------------------------------------------------- 73 | 74 | void cos_sin_angle_vector_rotation_matrix( 75 | float cosa, float sina, const Vector3f& u, 76 | Matrix3f& M) 77 | { 78 | 79 | Vector3f tu = (1.0f-cosa)*u; 80 | Vector3f su = sina*u; 81 | 82 | M(0,0) = tu(0)*u(0) + cosa; 83 | M(1,0) = tu(0)*u(1) + su(2); 84 | M(2,0) = tu(0)*u(2) - su(1); 85 | 86 | M(0,1) = tu(1)*u(0) - su(2); 87 | M(1,1) = tu(1)*u(1) + cosa; 88 | M(2,1) = tu(1)*u(2) + su(0); 89 | 90 | M(0,2) = tu(2)*u(0) + su(1); 91 | M(1,2) = tu(2)*u(1) - su(0); 92 | M(2,2) = tu(2)*u(2) + cosa; 93 | } 94 | //----------------------------------------------------------- 95 | -------------------------------------------------------------------------------- /utils/rotations.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------- 2 | // Copyright (C) 2019 Piotr (Peter) Beben 3 | // See LICENSE included with this distribution. 4 | 5 | #ifndef ROTATIONS_H 6 | #define ROTATIONS_H 7 | 8 | #include 9 | 10 | void vector_to_vector_rotation_matrix( 11 | const Eigen::Vector3f& v, const Eigen::Vector3f& w, 12 | bool normalized, bool lineThruW, Eigen::Matrix3f& M); 13 | 14 | void angle_vector_rotation_matrix( 15 | float angle, const Eigen::Vector3f& u, 16 | Eigen::Matrix3f& M); 17 | 18 | void cos_sin_angle_vector_rotation_matrix( 19 | float cosa, float sina, const Eigen::Vector3f& u, 20 | Eigen::Matrix3f& M); 21 | 22 | #endif // ROTATIONS_H 23 | --------------------------------------------------------------------------------