diff --git a/README.md b/README.md index c4b18dbc..f3777f1a 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,18 @@ MTracks { id: tracks matchingFolder: "/path/to/FeatureMatching/UID/” } +``` + + - Create a `MFeatures` object to get access to all features data: + +```js +MFeatures { + viewId: 101245654 + describerTypes: ["sift", "akaze"] + featureFolder: "/path/to/features/folder" + mTracks: tracks + mSfmData: sfmData +} ``` - Create a `FeaturesViewer` to visualize features position, scale, orientation and optionally information about the feature status regarding tracks and SfmData. @@ -62,13 +74,10 @@ MTracks { FeaturesViewer { colorOffset: 0 describerType: "sift" - featureFolder: "/path/to/features/folder" - mTracks: tracks - viewId: 101245654 color: “blue” landmarkColor: “red” displayMode: FeaturesViewer.Points - mSfmData: sfmData + mdescFeatures: mfeatures.allFeatures(describerType) } ``` @@ -95,14 +104,14 @@ MViewStats { ```js FloatImageViewer { - source: "/path/to/image” - gamma: 1.0f + source: "/path/to/image" + gamma: 1.0f offset: 0.0f width: 500 height: 540 paintedWidth: 500 paintedHeight: 540 - channelMode: “rgb” + channelMode: “rgb” } ``` diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b90eb87..588f2d03 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,8 @@ set(PLUGIN_SOURCES MSfMData.cpp MViewStats.cpp MTracks.cpp FloatImageViewer.cpp FloatTexture.cpp - MSfMDataStats.cpp) + MSfMDataStats.cpp MFeatures.cpp + MDescFeatures.cpp) set(PLUGIN_HEADERS FeaturesViewer.hpp plugin.hpp @@ -12,12 +13,14 @@ set(PLUGIN_HEADERS MTracks.hpp MViewStats.hpp FloatImageViewer.hpp FloatTexture.hpp - MSfMDataStats.hpp) + MSfMDataStats.hpp MFeatures.hpp + MDescFeatures.hpp) set(PLUGIN_MOCS FeaturesViewer.hpp MFeature.hpp MSfMData.hpp MViewStats.hpp MTracks.hpp - MSfMDataStats.hpp) + MSfMDataStats.hpp MFeatures.hpp + MDescFeatures.hpp) # Target properties add_library(qtAliceVisionPlugin SHARED diff --git a/src/FeaturesViewer.cpp b/src/FeaturesViewer.cpp index eb0b895c..d668efa9 100644 --- a/src/FeaturesViewer.cpp +++ b/src/FeaturesViewer.cpp @@ -9,12 +9,6 @@ #include #include -#include -#include -#include - -#include - namespace qtAliceVision { @@ -28,284 +22,28 @@ FeaturesViewer::FeaturesViewer(QQuickItem* parent) connect(this, &FeaturesViewer::displayModeChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::colorChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::landmarkColorChanged, this, &FeaturesViewer::update); - connect(this, &FeaturesViewer::featuresChanged, this, &FeaturesViewer::update); + connect(this, &FeaturesViewer::descFeaturesChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::displayFeaturesChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::displayLandmarksChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::displayTracksChanged, this, &FeaturesViewer::update); - - // trigger features reload events - connect(this, &FeaturesViewer::featureFolderChanged, this, &FeaturesViewer::reloadFeatures); - connect(this, &FeaturesViewer::viewIdChanged, this, &FeaturesViewer::reloadFeatures); - connect(this, &FeaturesViewer::describerTypeChanged, this, &FeaturesViewer::reloadFeatures); - - connect(this, &FeaturesViewer::tracksChanged, this, &FeaturesViewer::updateFeatureFromTracksEmit); - connect(this, &FeaturesViewer::sfmDataChanged, this, &FeaturesViewer::updateFeatureFromSfMEmit); - -} - -FeaturesViewer::~FeaturesViewer() -{ - qDeleteAll(_features); -} - -void FeaturesViewer::reloadFeatures() -{ - _nbLandmarks = 0; - qDeleteAll(_features); - _features.clear(); - Q_EMIT featuresChanged(); - - _outdatedFeatures = false; - - if(!_featureFolder.isValid() || _viewId == aliceVision::UndefinedIndexT) - { - qWarning() << "[QtAliceVision] FeaturesViewer::reloadFeatures: No valid folder of view."; - Q_EMIT featuresChanged(); - return; - } - - if(!_loadingFeatures) - { - setLoadingFeatures(true); - - // load features from file in a seperate thread - auto* ioRunnable = new FeatureIORunnable(FeatureIORunnable::IOParams(_featureFolder, _viewId, _describerType)); - connect(ioRunnable, &FeatureIORunnable::resultReady, this, &FeaturesViewer::onFeaturesResultReady); - QThreadPool::globalInstance()->start(ioRunnable); - } - else - { - // mark current request as outdated - _outdatedFeatures = true; - } -} - -void FeaturesViewer::setMTracks(MTracks* tracks) -{ - if(_mtracks == tracks) - return; - if(_mtracks != nullptr) - { - disconnect(_mtracks, SIGNAL(tracksChanged()), this, SIGNAL(tracksChanged())); - } - _mtracks = tracks; - if(_mtracks != nullptr) - { - connect(_mtracks, SIGNAL(tracksChanged()), this, SIGNAL(tracksChanged())); - } - Q_EMIT tracksChanged(); } -void FeaturesViewer::setMSfmData(MSfMData* sfmData) +void FeaturesViewer::setMDescFeatures(MDescFeatures* descFeatures) { - if(_msfmData == sfmData) + if (_mdescFeatures == descFeatures) return; - if(_msfmData != nullptr) + if (_mdescFeatures != nullptr) { - disconnect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); + disconnect(_mdescFeatures, &MDescFeatures::featuresReadyToDisplayChanged, this, &FeaturesViewer::descFeaturesChanged); + disconnect(_mdescFeatures, &MDescFeatures::updateDisplayTracks, this, &FeaturesViewer::setDisplayTracks); } - _msfmData = sfmData; - if(_msfmData != nullptr) + _mdescFeatures = descFeatures; + if (_mdescFeatures != nullptr) { - connect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); + connect(_mdescFeatures, &MDescFeatures::featuresReadyToDisplayChanged, this, &FeaturesViewer::descFeaturesChanged); + connect(_mdescFeatures, &MDescFeatures::updateDisplayTracks, this, &FeaturesViewer::setDisplayTracks); } - Q_EMIT sfmDataChanged(); -} - -void FeaturesViewer::onFeaturesResultReady(QList features) -{ - // another request has been made while io thread was working - if(_outdatedFeatures) - { - // clear result and reload features from file with current parameters - qDeleteAll(features); - setLoadingFeatures(false); - reloadFeatures(); - return; - } - - // update features - _features = features; - setLoadingFeatures(false); - updateFeatureFromSfM(); - updateFeatureFromTracks(); - updateNbTracks(); - Q_EMIT featuresChanged(); -} - -void FeaturesViewer::clearTracksFromFeatures() -{ - for(const auto feature: _features) - { - feature->clearTrack(); - } -} - -void FeaturesViewer::updateFeatureFromTracks() -{ - if(_features.empty()) - return; - clearTracksFromFeatures(); - - if(_mtracks == nullptr) - { - qWarning() << "[QtAliceVision] updateFeatureFromTracks: no Track"; - return; - } - if(_mtracks->status() != MTracks::Ready) - { - qWarning() << "[QtAliceVision] updateFeatureFromTracks: Tracks is not ready: " << _mtracks->status(); - return; - } - if(_mtracks->tracks().empty()) - { - qWarning() << "[QtAliceVision] updateFeatureFromTracks: Tracks is empty"; - return; - } - - // Update newly loaded features with information from the sfmData - aliceVision::feature::EImageDescriberType descType = aliceVision::feature::EImageDescriberType_stringToEnum(_describerType.toStdString()); - - auto tracksPerViewIt = _mtracks->tracksPerView().find(_viewId); - if(tracksPerViewIt == _mtracks->tracksPerView().end()) - { - qWarning() << "[QtAliceVision] view does not exist in tracks."; - return; - } - const auto& tracksInCurrentView = tracksPerViewIt->second; - const auto& tracks = _mtracks->tracks(); - for(const auto& trackId: tracksInCurrentView) - { - const auto& trackIterator = tracks.find(trackId); - if(trackIterator == tracks.end()) - { - qWarning() << "[QtAliceVision] Track id: "<< trackId <<" in current view does not exist in Tracks."; - continue; - } - const aliceVision::track::Track& currentTrack = trackIterator->second; - - if(currentTrack.descType != descType) - continue; - - const auto& featIdPerViewIt = currentTrack.featPerView.find(_viewId); - if(featIdPerViewIt == currentTrack.featPerView.end()) - { - qWarning() << "[QtAliceVision] featIdPerViewIt invalid."; - continue; - } - const std::size_t featId = featIdPerViewIt->second; - if(featId >= _features.size()) - { - qWarning() << "[QtAliceVision] featId invalid regarding loaded features."; - continue; - } - MFeature* feature = _features.at(featId); - feature->setTrackId(trackId); - } -} - -void FeaturesViewer::updateFeatureFromTracksEmit() -{ - updateFeatureFromTracks(); - updateNbTracks(); - Q_EMIT featuresChanged(); -} - -void FeaturesViewer::updateNbTracks() -{ - _nbTracks = 0; - for(MFeature* feature: _features) - { - if(feature->trackId() < 0) - continue; - ++_nbTracks; - } -} - -void FeaturesViewer::clearSfMFromFeatures() -{ - _nbLandmarks = 0; - for(const auto feature: _features) - { - feature->clearLandmarkInfo(); - } -} - -void FeaturesViewer::updateFeatureFromSfM() -{ - clearSfMFromFeatures(); - - if(_features.empty()) - { - return; - } - if(_msfmData == nullptr) - { - qWarning() << "[QtAliceVision] updateFeatureFromSfM: no SfMData"; - return; - } - if(_msfmData->status() != MSfMData::Ready) - { - qWarning() << "[QtAliceVision] updateFeatureFromSfM: SfMData is not ready: " << _msfmData->status(); - return; - } - if(_msfmData->rawData().getViews().empty()) - { - qWarning() << "[QtAliceVision] updateFeatureFromSfM: SfMData is empty"; - return; - } - const auto viewIt = _msfmData->rawData().getViews().find(_viewId); - if(viewIt == _msfmData->rawData().getViews().end()) - { - qWarning() << "[QtAliceVision] updateFeatureFromSfM: View " << _viewId << " is not is the SfMData"; - return; - } - const aliceVision::sfmData::View& view = *viewIt->second; - if(!_msfmData->rawData().isPoseAndIntrinsicDefined(&view)) - { - qWarning() << "[QtAliceVision] updateFeatureFromSfM: SfMData has no valid pose and intrinsic for view " << _viewId; - return; - } - - // Update newly loaded features with information from the sfmData - aliceVision::feature::EImageDescriberType descType = aliceVision::feature::EImageDescriberType_stringToEnum(_describerType.toStdString()); - - const aliceVision::sfmData::CameraPose pose = _msfmData->rawData().getPose(view); - const aliceVision::geometry::Pose3 camTransform = pose.getTransform(); - const aliceVision::camera::IntrinsicBase* intrinsic = _msfmData->rawData().getIntrinsicPtr(view.getIntrinsicId()); - - int numLandmark = 0; - for(const auto& landmark: _msfmData->rawData().getLandmarks()) - { - if(landmark.second.descType != descType) - continue; - - auto itObs = landmark.second.observations.find(_viewId); - if(itObs != landmark.second.observations.end()) - { - // setup landmark id and landmark 2d reprojection in the current view - aliceVision::Vec2 r = intrinsic->project(camTransform, landmark.second.X); - - if (itObs->second.id_feat >= 0 && itObs->second.id_feat < _features.size()) - { - _features.at(itObs->second.id_feat)->setLandmarkInfo(landmark.first, r.cast()); - } - else if(!_features.empty()) - { - qWarning() << "[QtAliceVision] ---------- ERROR id_feat: " << itObs->second.id_feat << ", size: " << _features.size(); - } - - ++_nbLandmarks; - } - ++numLandmark; - } -} - -void FeaturesViewer::updateFeatureFromSfMEmit() -{ - updateFeatureFromSfM(); - updateNbTracks(); - Q_EMIT featuresChanged(); + Q_EMIT descFeaturesChanged(); } void FeaturesViewer::updatePaintFeatures(QSGNode* oldNode, QSGNode* node) @@ -313,7 +51,7 @@ void FeaturesViewer::updatePaintFeatures(QSGNode* oldNode, QSGNode* node) unsigned int kFeatVertices = 0; unsigned int kFeatIndices = 0; - switch(_displayMode) + switch (_displayMode) { case FeaturesViewer::Points: kFeatVertices = 1; @@ -326,11 +64,11 @@ void FeaturesViewer::updatePaintFeatures(QSGNode* oldNode, QSGNode* node) kFeatVertices = (4 * 2) + 2; // doubled rectangle points + orientation line break; } - std::size_t displayNbFeatures = _displayFeatures ? _features.size() : 0; + std::size_t displayNbFeatures = _displayFeatures ? _mdescFeatures->getFeaturesData().size() : 0; QSGGeometry* geometry = nullptr; - if(!oldNode) + if (!oldNode) { - if(_displayFeatures) + if (_displayFeatures) { auto root = new QSGGeometryNode; { @@ -339,7 +77,7 @@ void FeaturesViewer::updatePaintFeatures(QSGNode* oldNode, QSGNode* node) geometry = new QSGGeometry( QSGGeometry::defaultAttributes_ColoredPoint2D(), static_cast(displayNbFeatures * kFeatVertices), - static_cast(displayNbFeatures* kFeatIndices), + static_cast(displayNbFeatures * kFeatIndices), QSGGeometry::UnsignedIntType); geometry->setIndexDataPattern(QSGGeometry::StaticPattern); @@ -364,12 +102,12 @@ void FeaturesViewer::updatePaintFeatures(QSGNode* oldNode, QSGNode* node) ); } - if(!_displayFeatures) + if (!_displayFeatures) { return; } - switch(_displayMode) + switch (_displayMode) { case FeaturesViewer::Points: geometry->setDrawingMode(QSGGeometry::DrawPoints); @@ -397,17 +135,17 @@ void FeaturesViewer::updatePaintFeatures(QSGNode* oldNode, QSGNode* node) ); }; - for(int i = 0; i < _features.size(); ++i) + for (int i = 0; i < _mdescFeatures->getFeaturesData().size(); ++i) { - const auto& f = _features.at(i); + const auto& f = _mdescFeatures->getFeaturesData().at(i); bool isReconstructed = f->landmarkId() > 0; auto& feat = f->pointFeature(); const auto radius = feat.scale(); const auto diag = 2.0 * feat.scale(); - unsigned int vidx = i*kFeatVertices; - unsigned int iidx = i*kFeatIndices; + unsigned int vidx = i * kFeatVertices; + unsigned int iidx = i * kFeatIndices; - if(_displayMode == FeaturesViewer::Points) + if (_displayMode == FeaturesViewer::Points) { setVertice(vidx, QPointF(feat.x(), feat.y()), isReconstructed); } @@ -420,40 +158,40 @@ void FeaturesViewer::updatePaintFeatures(QSGNode* oldNode, QSGNode* node) QPointF br = rect.bottomRight(); QPointF bl = rect.bottomLeft(); - if(_displayMode == FeaturesViewer::Squares) + if (_displayMode == FeaturesViewer::Squares) { // create 2 triangles setVertice(vidx, tl, isReconstructed); - setVertice(vidx+1, tr, isReconstructed); - setVertice(vidx+2, br, isReconstructed); - setVertice(vidx+3, bl, isReconstructed); + setVertice(vidx + 1, tr, isReconstructed); + setVertice(vidx + 2, br, isReconstructed); + setVertice(vidx + 3, bl, isReconstructed); indices[iidx] = vidx; - indices[iidx+1] = vidx+1; - indices[iidx+2] = vidx+2; - indices[iidx+3] = vidx+2; - indices[iidx+4] = vidx+3; - indices[iidx+5] = vidx; + indices[iidx + 1] = vidx + 1; + indices[iidx + 2] = vidx + 2; + indices[iidx + 3] = vidx + 2; + indices[iidx + 4] = vidx + 3; + indices[iidx + 5] = vidx; } - else if(_displayMode == FeaturesViewer::OrientedSquares) + else if (_displayMode == FeaturesViewer::OrientedSquares) { // compute angle: use feature angle and remove self rotation - const auto radAngle = -feat.orientation()-qDegreesToRadians(rotation()); + const auto radAngle = -feat.orientation() - qDegreesToRadians(rotation()); // generate a QTransform that represent this rotation const auto t = QTransform().translate(feat.x(), feat.y()).rotateRadians(radAngle).translate(-feat.x(), -feat.y()); // create lines, each vertice has to be duplicated (A->B, B->C, C->D, D->A) since we use GL_LINES - std::vector points = {t.map(tl), t.map(tr), t.map(br), t.map(bl), t.map(tl)}; - for(unsigned int k = 0; k < points.size(); ++k) + std::vector points = { t.map(tl), t.map(tr), t.map(br), t.map(bl), t.map(tl) }; + for (unsigned int k = 0; k < points.size(); ++k) { - auto lidx = k*2; // local index - setVertice(vidx+lidx, points[k], isReconstructed); - setVertice(vidx+lidx+1, points[k+1], isReconstructed); + auto lidx = k * 2; // local index + setVertice(vidx + lidx, points[k], isReconstructed); + setVertice(vidx + lidx + 1, points[k + 1], isReconstructed); } // orientation line: up vector (0, 1) const auto nbPoints = static_cast(points.size()); - setVertice(vidx+nbPoints*2-2, rect.center(), isReconstructed); + setVertice(vidx + nbPoints * 2 - 2, rect.center(), isReconstructed); auto o2 = t.map(rect.center() - QPointF(0.0f, radius)); // rotate end point - setVertice(vidx+nbPoints*2-1, o2, isReconstructed); + setVertice(vidx + nbPoints * 2 - 1, o2, isReconstructed); } } } @@ -462,15 +200,14 @@ void FeaturesViewer::updatePaintFeatures(QSGNode* oldNode, QSGNode* node) void FeaturesViewer::updatePaintTracks(QSGNode* oldNode, QSGNode* node) { - unsigned int kTracksVertices = 1; - std::size_t displayNbTracks = _displayTracks ? _nbTracks : 0; + const std::size_t displayNbTracks = _displayTracks ? getNbTracks() - getNbLandmarks() : 0; QSGGeometry* geometryPoint = nullptr; - if(!oldNode) + if (!oldNode) { - if(_displayTracks) + if (_displayTracks) { auto root = new QSGGeometryNode; { @@ -505,7 +242,7 @@ void FeaturesViewer::updatePaintTracks(QSGNode* oldNode, QSGNode* node) ); } - if(!_displayTracks) + if (!_displayTracks) { return; } @@ -517,25 +254,25 @@ void FeaturesViewer::updatePaintTracks(QSGNode* oldNode, QSGNode* node) const auto setVerticePoint = [&](unsigned int index, const QPointF& point) { - QColor c = QColor(255,127,0); - verticesPoints[index].set( - point.x(), point.y(), - c.red(), c.green(), c.blue(), c.alpha() - ); + QColor c = QColor(255, 127, 0); + verticesPoints[index].set( + point.x(), point.y(), + c.red(), c.green(), c.blue(), c.alpha() + ); }; // Draw points in the center of non validated tracks int obsI = 0; - for(int i = 0; i < _features.size(); ++i) + for (int i = 0; i < _mdescFeatures->getFeaturesData().size(); ++i) { - const auto& f = _features.at(i); + const auto& f = _mdescFeatures->getFeaturesData().at(i); - if(f->trackId() < 0) + if (f->trackId() < 0) continue; - if(_nbLandmarks > 0 && f->landmarkId() >= 0) + if (getNbLandmarks() > 0 && f->landmarkId() >= 0) continue; - if(obsI >= displayNbTracks) + if (obsI >= displayNbTracks) { qWarning() << "[QtAliceVision] updatePaintTracks ERROR on number of tracks"; break; @@ -551,16 +288,15 @@ void FeaturesViewer::updatePaintTracks(QSGNode* oldNode, QSGNode* node) void FeaturesViewer::updatePaintLandmarks(QSGNode* oldNode, QSGNode* node) { - const unsigned int kReprojectionVertices = 2; // - int displayNbLandmarks = _displayLandmarks ? _nbLandmarks : 0; + const int displayNbLandmarks = _displayLandmarks ? getNbLandmarks() : 0; QSGGeometry* geometryLine = nullptr; QSGGeometry* geometryPoint = nullptr; - if(!oldNode) + if (!oldNode) { - if(_displayLandmarks) + if (_displayLandmarks) { { auto root = new QSGGeometryNode; @@ -626,7 +362,7 @@ void FeaturesViewer::updatePaintLandmarks(QSGNode* oldNode, QSGNode* node) ); } - if(!_displayLandmarks) + if (!_displayLandmarks) { return; } @@ -658,9 +394,9 @@ void FeaturesViewer::updatePaintLandmarks(QSGNode* oldNode, QSGNode* node) // Draw lines between reprojected points and features extracted int obsI = 0; - for(int i = 0; i < _features.size(); ++i) + for (int i = 0; i < _mdescFeatures->getFeaturesData().size(); ++i) { - const auto& f = _features.at(i); + const auto& f = _mdescFeatures->getFeaturesData().at(i); float x = f->x(); float y = f->y(); float rx = f->rx(); @@ -699,6 +435,9 @@ QSGNode* FeaturesViewer::updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePai node = oldNode; } + if (!_mdescFeatures) + return node; + updatePaintFeatures(oldNode, node); updatePaintTracks(oldNode, node); updatePaintLandmarks(oldNode, node); @@ -706,4 +445,4 @@ QSGNode* FeaturesViewer::updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePai return node; } -} +} // namespace diff --git a/src/FeaturesViewer.hpp b/src/FeaturesViewer.hpp index 8830ab52..62882002 100644 --- a/src/FeaturesViewer.hpp +++ b/src/FeaturesViewer.hpp @@ -1,62 +1,41 @@ #pragma once -#include -#include -#include - -#include -#include -#include -#include +#include "MDescFeatures.hpp" #include -#include -#include - - -namespace qtAliceVision { +#include +namespace qtAliceVision +{ /** * @brief Load and display extracted features of a View. + * @warning FeaturesViewer contains a pointer to an instance of MDescFeatures: it refers only to one type of decribers. */ class FeaturesViewer : public QQuickItem { Q_OBJECT - /// Path to folder containing the features - Q_PROPERTY(QUrl featureFolder MEMBER _featureFolder NOTIFY featureFolderChanged) - /// ViewID to consider - Q_PROPERTY(quint32 viewId MEMBER _viewId NOTIFY viewIdChanged) - /// Describer type to load - Q_PROPERTY(QString describerType MEMBER _describerType NOTIFY describerTypeChanged) - /// Pointer to SfmData - Q_PROPERTY(qtAliceVision::MSfMData* msfmData READ getMSfmData WRITE setMSfmData NOTIFY sfmDataChanged) - /// Pointer to Tracks - Q_PROPERTY(qtAliceVision::MTracks* mtracks READ getMTracks WRITE setMTracks NOTIFY tracksChanged) /// Display mode (see DisplayMode enum) Q_PROPERTY(DisplayMode displayMode MEMBER _displayMode NOTIFY displayModeChanged) + /// Describer type represented by this viewer + Q_PROPERTY(QString describerType MEMBER _describerType NOTIFY describerTypeChanged) /// Features color Q_PROPERTY(QColor color MEMBER _color NOTIFY colorChanged) /// Landmarks color Q_PROPERTY(QColor landmarkColor MEMBER _landmarkColor NOTIFY landmarkColorChanged) - /// Whether landmarks are reconstructed - Q_PROPERTY(bool haveValidLandmarks READ haveValidLandmarks NOTIFY sfmDataChanged) - /// Whether tracks are reconstructed - Q_PROPERTY(bool haveValidTracks READ haveValidTracks NOTIFY tracksChanged) - /// Whether features are currently being loaded from file - Q_PROPERTY(bool loadingFeatures READ loadingFeatures NOTIFY loadingFeaturesChanged) /// Display all the 2D features extracted from the image Q_PROPERTY(bool displayfeatures MEMBER _displayFeatures NOTIFY displayFeaturesChanged) /// Display the 3D reprojection of the features associated to a landmark Q_PROPERTY(bool displayLandmarks MEMBER _displayLandmarks NOTIFY displayLandmarksChanged) /// Display the center of the tracks unvalidated after resection - Q_PROPERTY(bool displayTracks MEMBER _displayTracks NOTIFY displayFeaturesChanged) + Q_PROPERTY(bool displayTracks MEMBER _displayTracks NOTIFY displayTracksChanged) /// Number of features reconstructed - Q_PROPERTY(int nbLandmarks MEMBER _nbLandmarks NOTIFY displayLandmarksChanged) + Q_PROPERTY(int nbLandmarks READ getNbLandmarks NOTIFY displayLandmarksChanged) /// Number of tracks - Q_PROPERTY(int nbTracks MEMBER _nbTracks NOTIFY displayTracksChanged) - /// The list of features - Q_PROPERTY(QQmlListProperty features READ features NOTIFY featuresChanged) + Q_PROPERTY(int nbTracks READ getNbTracks NOTIFY displayTracksChanged) + /// MDescFeatures pointer + Q_PROPERTY(qtAliceVision::MDescFeatures* mdescFeatures READ getMDescFeatures WRITE setMDescFeatures NOTIFY descFeaturesChanged) + public: enum DisplayMode { @@ -67,47 +46,32 @@ class FeaturesViewer : public QQuickItem Q_ENUM(DisplayMode) explicit FeaturesViewer(QQuickItem* parent = nullptr); - ~FeaturesViewer() override; + ~FeaturesViewer() override = default; - QQmlListProperty features() - { - return {this, _features}; - } - - inline bool loadingFeatures() const { return _loadingFeatures; } - inline void setLoadingFeatures(bool loadingFeatures) - { - if(_loadingFeatures == loadingFeatures) - return; - _loadingFeatures = loadingFeatures; - Q_EMIT loadingFeaturesChanged(); - } + /// Get the number of tracks corresponding to the describer type. + inline int getNbTracks() const { return _mdescFeatures->getNbTracks(); } + /// Get the number of landmarks corresponding to the describer type. + inline int getNbLandmarks() const { return _mdescFeatures->getNbLandmarks(); } - MSfMData* getMSfmData() { return _msfmData; } - void setMSfmData(MSfMData* sfmData); - bool haveValidLandmarks() const { - return _msfmData != nullptr && _msfmData->status() == MSfMData::Ready; - } + /// Return a pointer to the MDescFeatures instance. + MDescFeatures* getMDescFeatures() const { return _mdescFeatures; } + /// Set a MDescFeatures instance to the class member '_mdescFeatures'. + void setMDescFeatures(MDescFeatures* descFeatures); - MTracks* getMTracks() { return _mtracks; } - void setMTracks(MTracks* tracks); - bool haveValidTracks() const { - return _mtracks != nullptr && _mtracks->status() == MTracks::Ready; + /// Set the class member '_displayTracks' with a boolean. + void setDisplayTracks(bool displayTracks) { + if (_displayTracks == displayTracks) + return; + _displayTracks = displayTracks; + Q_EMIT displayTracksChanged(); } public: - Q_SIGNAL void sfmDataChanged(); - Q_SIGNAL void featureFolderChanged(); - Q_SIGNAL void viewIdChanged(); - Q_SIGNAL void describerTypeChanged(); - Q_SIGNAL void featuresChanged(); - Q_SIGNAL void loadingFeaturesChanged(); - Q_SIGNAL void tracksChanged(); - Q_SIGNAL void displayFeaturesChanged(); + Q_SIGNAL void describerTypeChanged(); Q_SIGNAL void displayLandmarksChanged(); Q_SIGNAL void displayTracksChanged(); - + Q_SIGNAL void descFeaturesChanged(); Q_SIGNAL void displayModeChanged(); Q_SIGNAL void colorChanged(); Q_SIGNAL void landmarkColorChanged(); @@ -115,40 +79,13 @@ class FeaturesViewer : public QQuickItem private: /// Custom QSGNode update QSGNode* updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* data) override; - -private: - /// Reload features from source - void reloadFeatures(); - /// Handle result from asynchronous file loading - Q_SLOT void onFeaturesResultReady(QList features); - - void reloadTracks(); - void clearTracksFromFeatures(); - void updateFeatureFromTracks(); - void updateFeatureFromTracksEmit(); - /** - * @brief Update the number of tracks not associated to a landmark. - * @warning depends on features, landmarks (sfmData), and tracks. - */ - void updateNbTracks(); - - void clearSfMFromFeatures(); - void updateFeatureFromSfM(); - void updateFeatureFromSfMEmit(); - + /// Update Features display using QSGNode. void updatePaintFeatures(QSGNode* oldNode, QSGNode* node); + /// Update Tracks display using QSGNode. void updatePaintTracks(QSGNode* oldNode, QSGNode* node); + /// Update Landmarks display using QSGNode. void updatePaintLandmarks(QSGNode* oldNode, QSGNode* node); - QUrl _featureFolder; - aliceVision::IndexT _viewId = aliceVision::UndefinedIndexT; - QString _describerType = "sift"; - QList _features; - MSfMData* _msfmData = nullptr; - MTracks* _mtracks = nullptr; - - int _nbLandmarks = 0; //< number of features associated to a 3D landmark - int _nbTracks = 0; // number of tracks unvalidated after resection DisplayMode _displayMode = FeaturesViewer::Points; QColor _color = QColor(20, 220, 80); QColor _landmarkColor = QColor(255, 0, 0); @@ -159,8 +96,9 @@ class FeaturesViewer : public QQuickItem bool _displayFeatures = true; bool _displayLandmarks = true; bool _displayTracks = true; + + MDescFeatures* _mdescFeatures = nullptr; + QString _describerType = "sift"; }; } // namespace - -Q_DECLARE_METATYPE(qtAliceVision::MFeature); // for usage in signals/slots diff --git a/src/MDescFeatures.cpp b/src/MDescFeatures.cpp new file mode 100644 index 00000000..d6fe5142 --- /dev/null +++ b/src/MDescFeatures.cpp @@ -0,0 +1,74 @@ +#include "MDescFeatures.hpp" + +#include +#include + +namespace qtAliceVision +{ + +MDescFeatures::MDescFeatures(const QString& desc, const QUrl& folder, aliceVision::IndexT viewId, QObject* parent) : + _describerType(desc), _featureFolder(folder), _viewId(viewId), QObject(parent) +{ +} + +MDescFeatures::~MDescFeatures() +{ + qDeleteAll(_features); +} + +void MDescFeatures::reloadFeatures() +{ + _nbLandmarks = 0; + setReady(false); + + // Make sure to free all the MFeature pointers and to clear the QList. + qDeleteAll(_features); + _features.clear(); + Q_EMIT featuresChanged(); + + _outdatedFeatures = false; + + if (!_featureFolder.isValid() || _viewId == aliceVision::UndefinedIndexT) + { + qWarning() << "[QtAliceVision] MDescFeatures::reloadFeatures: No valid folder or view. Describer : " << _describerType; + Q_EMIT featuresChanged(); + return; + } + + if (!_loadingFeatures) + { + setLoadingFeatures(true); + + // load features from file in a seperate thread + auto* ioRunnable = new FeatureIORunnable(FeatureIORunnable::IOParams(_featureFolder, _viewId, _describerType)); + connect(ioRunnable, &FeatureIORunnable::resultReady, this, &MDescFeatures::onFeaturesResultReady); + QThreadPool::globalInstance()->start(ioRunnable); + } + else + { + // mark current request as outdated + _outdatedFeatures = true; + } +} + +void MDescFeatures::onFeaturesResultReady(QList features) +{ + // another request has been made while io thread was working + if (_outdatedFeatures) + { + // clear result and reload features from file with current parameters + qDeleteAll(features); + setLoadingFeatures(false); + reloadFeatures(); + return; + } + + // update features + _features = features; + setLoadingFeatures(false); + + Q_EMIT featuresReadyChanged(_describerType); // Call the updateFeatures slot on the MFeatures instance + Q_EMIT featuresChanged(); +} + +} // namespace \ No newline at end of file diff --git a/src/MDescFeatures.hpp b/src/MDescFeatures.hpp new file mode 100644 index 00000000..8bb1ac83 --- /dev/null +++ b/src/MDescFeatures.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "MFeature.hpp" + +#include +#include +#include +#include + +namespace qtAliceVision +{ + +/** +* @brief Contains features data relative to one specific describer type. +*/ +class MDescFeatures : public QObject +{ + Q_OBJECT + /// String list of all the describerTypes used. Values comes from QML. + Q_PROPERTY(QString describerType MEMBER _describerType NOTIFY describerTypeChanged) + /// The list of features + Q_PROPERTY(QQmlListProperty features READ features NOTIFY featuresChanged) + /// Whether features are currently being loaded from file + Q_PROPERTY(bool loadingFeatures READ loadingFeatures NOTIFY loadingFeaturesChanged) + /// Number of Landmarks + Q_PROPERTY(int nbLandmarks MEMBER _nbLandmarks NOTIFY nbLandmarksChanged) + /// Number of Tracks + Q_PROPERTY(int nbTracks MEMBER _nbTracks NOTIFY nbTracksChanged) + +public: + explicit MDescFeatures(const QString& desc, const QUrl& folder, aliceVision::IndexT viewId, QObject* parent = nullptr); + ~MDescFeatures() override; + + /// Return a QQmlListProperty based on the QList of MFeature. + QQmlListProperty features() { return { this, _features }; } + + /// Return the number of landmarks corresponding to this describer. + inline int getNbLandmarks() const { return _nbLandmarks; } + /// Set the number of landmarks corresponding to this describer. + inline void setNbLandmarks(unsigned int nb) { _nbLandmarks = nb; } + + /// Return the number of tracks corresponding to this describer. + inline int getNbTracks() const { return _nbTracks; } + /// Set the number of tracks corresponding to this describer. + inline void setNbTracks(unsigned int nb) { _nbTracks = nb; } + + /// Return a pointer to the QList of MFeature. + inline const QList& getFeaturesData() const { return _features; } + /// Set the pointer to the QList of MFeature. + void setFeaturesData(const QList& features) + { + if (_features == features) return; + + qDeleteAll(_features); // Free memory of the current features + _features.clear(); // Clear the list + _features = features; + } + + /// Return if features are currently loading or not. + inline bool loadingFeatures() const { return _loadingFeatures; } + /// Set if features are currently loading or not. + void setLoadingFeatures(bool loadingFeatures) + { + if (_loadingFeatures == loadingFeatures) + return; + _loadingFeatures = loadingFeatures; + Q_EMIT loadingFeaturesChanged(); // Useful because it will make QML aware of the loading status + } + + /// Return if features are currently ready to be globally updated and displayed. + inline bool ready() const { return _ready; } + /// Set if features are currently ready to be globally updated and displayed. + void setReady(bool ready) { _ready = ready; } + + /// Set a new feature folder path. + void setFeatureFolder(const QUrl& folder) + { + if (_featureFolder == folder) + return; + _featureFolder = folder; + reloadFeatures(); + } + + /// Set a new view id. + void setViewId(aliceVision::IndexT id) + { + if (_viewId == id) + return; + _viewId = id; + reloadFeatures(); + } + +public: + Q_SIGNAL void describerTypeChanged(); + Q_SIGNAL void featuresReadyToDisplayChanged(); // Used to tell the FeaturesWiewer (if there is one) to call the update function + Q_SIGNAL void featuresChanged(); + Q_SIGNAL void loadingFeaturesChanged(); + Q_SIGNAL void nbLandmarksChanged(); + Q_SIGNAL void nbTracksChanged(); + Q_SIGNAL void updateDisplayTracks(bool boolean); + Q_SIGNAL void featuresReadyChanged(const QString& desc); // Used to call the updateFeatures slot on the MFeatures instance + +private: + /// Clean the current features and create a thread to calculate the new ones. + void reloadFeatures(); + /// Handle result from asynchronous file. + void onFeaturesResultReady(QList features); + + bool _loadingFeatures = false; + bool _outdatedFeatures = false; + bool _ready = false; + + QUrl _featureFolder; + aliceVision::IndexT _viewId = aliceVision::UndefinedIndexT; + QString _describerType = "sift"; + QList _features; + + int _nbLandmarks = 0; // number of features associated to a 3D landmark + int _nbTracks = 0; // number of tracks unvalidated after resection +}; + +} // namespace diff --git a/src/MFeature.hpp b/src/MFeature.hpp index 0a64f1e5..35f86fd3 100644 --- a/src/MFeature.hpp +++ b/src/MFeature.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace qtAliceVision { diff --git a/src/MFeatures.cpp b/src/MFeatures.cpp new file mode 100644 index 00000000..527bcde4 --- /dev/null +++ b/src/MFeatures.cpp @@ -0,0 +1,351 @@ +#include "MFeatures.hpp" + +#include + +namespace qtAliceVision +{ + +MFeatures::MFeatures(QObject* parent): + QObject(parent) +{ + setupBasicConnections(); +} + +MFeatures::~MFeatures() +{ + _allFeatures.clear(); // Make sure to clear the Map and free everything inside it +} + +void MFeatures::setupBasicConnections() +{ + // trigger features reload events + connect(this, &MFeatures::describerTypesChanged, this, &MFeatures::reloadQMap); + connect(this, &MFeatures::featureFolderChanged, this, &MFeatures::updateMapFeatureFolder); + connect(this, &MFeatures::viewIdChanged, this, &MFeatures::updateMapViewId); + + // trigger SFM and Tracks updates + connect(this, &MFeatures::tracksChanged, this, &MFeatures::updateFeatureFromTracksEmit); + connect(this, &MFeatures::sfmDataChanged, this, &MFeatures::updateFeatureFromSfMEmit); +} + +void MFeatures::reloadQMap() +{ + // Not sure it is really useful (because we clear the map later) but just in case + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + disconnect(this, &MFeatures::featuresChanged, i.value().get(), &MDescFeatures::featuresReadyToDisplayChanged); + + disconnect(i.value().get(), &MDescFeatures::featuresReadyChanged, this, &MFeatures::updateFeatures); + disconnect(i.value().get(), &MDescFeatures::loadingFeaturesChanged, this, &MFeatures::updateLoadingFeatures); + } + + _allFeatures.clear(); // Clear the map and delete the MDescFeatures instances + + // Populate the map and connect signals to slots + for (const QString& desc : _describerTypes) + { + QSharedPointer descFeatures(new MDescFeatures(desc, _featureFolder, _viewId)); + _allFeatures.insert(desc, descFeatures); + + connect(this, &MFeatures::featuresChanged, descFeatures.get(), &MDescFeatures::featuresReadyToDisplayChanged); + + connect(descFeatures.get(), &MDescFeatures::featuresReadyChanged, this, &MFeatures::updateFeatures); + connect(descFeatures.get(), &MDescFeatures::loadingFeaturesChanged, this, &MFeatures::updateLoadingFeatures); + } + + Q_EMIT mapChanged(); +} + +void MFeatures::updateMapFeatureFolder() +{ + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + i.value()->setFeatureFolder(_featureFolder); + } +} + +void MFeatures::updateMapViewId() +{ + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + i.value()->setViewId(_viewId); + } +} + +void MFeatures::setMTracks(MTracks* tracks) +{ + if (_mtracks == tracks) + return; + if (_mtracks != nullptr) + { + disconnect(_mtracks, &MTracks::tracksChanged, this, &MFeatures::tracksChanged); + } + _mtracks = tracks; + if (_mtracks != nullptr) + { + connect(_mtracks, &MTracks::tracksChanged, this, &MFeatures::tracksChanged); + } + + Q_EMIT tracksChanged(); +} + +void MFeatures::setMSfmData(MSfMData* sfmData) +{ + if (_msfmData == sfmData) + return; + if (_msfmData != nullptr) + { + disconnect(_msfmData, &MSfMData::sfmDataChanged, this, &MFeatures::sfmDataChanged); + } + _msfmData = sfmData; + if (_msfmData != nullptr) + { + connect(_msfmData, &MSfMData::sfmDataChanged, this, &MFeatures::sfmDataChanged); + } + + Q_EMIT sfmDataChanged(); +} + +void MFeatures::updateFeatures(const QString& desc) +{ + updateFeatureFromTracks(desc); + updateFeatureFromSfM(desc); + _allFeatures.value(desc)->setReady(true); + reloadFeatures(); +} + +void MFeatures::reloadFeatures() +{ + if (!checkAllFeaturesReady()) return; // Return if at least one of the MDescFeatures is not ready + + updateNbTracks(); + updateTotalNbLandmarks(); + + Q_EMIT featuresChanged(); +} + +void MFeatures::clearTracksFromFeatures(const QList& features) +{ + for (const auto feature : features) + { + feature->clearTrack(); + } +} + +void MFeatures::updateFeatureFromTracks(const QString& desc) +{ + QSharedPointer descFeatures = _allFeatures.value(desc); + const QList& features = descFeatures->getFeaturesData(); + + if (features.empty()) + return; + clearTracksFromFeatures(features); + + if (_mtracks == nullptr) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromTracks: no Track"; + Q_EMIT descFeatures->updateDisplayTracks(false); + return; + } + if (_mtracks->status() != MTracks::Ready) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromTracks: Tracks is not ready: " << _mtracks->status(); + Q_EMIT descFeatures->updateDisplayTracks(false); + return; + } + if (_mtracks->tracks().empty()) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromTracks: Tracks is empty"; + Q_EMIT descFeatures->updateDisplayTracks(false); + return; + } + + // Update newly loaded features with information from the sfmData + aliceVision::feature::EImageDescriberType descType = aliceVision::feature::EImageDescriberType_stringToEnum(desc.toStdString()); + + auto tracksPerViewIt = _mtracks->tracksPerView().find(_viewId); + if (tracksPerViewIt == _mtracks->tracksPerView().end()) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromTracks: view does not exist in tracks."; + return; + } + const auto& tracksInCurrentView = tracksPerViewIt->second; + const auto& tracks = _mtracks->tracks(); + for (const auto& trackId : tracksInCurrentView) + { + const auto& trackIterator = tracks.find(trackId); + if (trackIterator == tracks.end()) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromTracks: Track id: " << trackId << " in current view does not exist in Tracks."; + continue; + } + const aliceVision::track::Track& currentTrack = trackIterator->second; + + if (currentTrack.descType != descType) + continue; + + const auto& featIdPerViewIt = currentTrack.featPerView.find(_viewId); + if (featIdPerViewIt == currentTrack.featPerView.end()) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromTracks: featIdPerViewIt invalid."; + continue; + } + const std::size_t featId = featIdPerViewIt->second; + if (featId >= features.size()) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromTracks: featId invalid regarding loaded features."; + continue; + } + MFeature* feature = features.at(featId); + feature->setTrackId(trackId); + } + + //Q_EMIT descFeatures->updateDisplayTracks(true); +} + +void MFeatures::updateFeatureFromTracksEmit() +{ + // Iterate through every MDescFeatures + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + QString desc = i.key(); + updateFeatureFromTracks(desc); + } + + updateNbTracks(); + updateTotalNbLandmarks(); + + Q_EMIT featuresChanged(); +} + +void MFeatures::updateTotalNbLandmarks() +{ + _nbLandmarks = 0; + + // Iterate through every MDescFeatures + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + QSharedPointer descFeatures = i.value(); + _nbLandmarks += descFeatures->getNbLandmarks(); + } +} + +void MFeatures::updateNbTracks() +{ + _nbTracks = 0; // Total number of tracks + + // Iterate through every MDescFeatures + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + unsigned int nbTracksPerDesc = 0; // Local var to count + + QSharedPointer descFeatures = i.value(); + descFeatures->setNbTracks(0); + + for (MFeature* feature : descFeatures->getFeaturesData()) + { + if (feature->trackId() < 0) + continue; + ++nbTracksPerDesc; + } + descFeatures->setNbTracks(nbTracksPerDesc); // Set the number of tracks to each MDescFeatures + _nbTracks += nbTracksPerDesc; // Increment the total number of tracks + } +} + +void MFeatures::clearSfMFromFeatures(const QList& features) +{ + for (const auto feature : features) + { + feature->clearLandmarkInfo(); + } +} + +void MFeatures::updateFeatureFromSfM(const QString& desc) +{ + const QList& features = _allFeatures.value(desc)->getFeaturesData(); + + clearSfMFromFeatures(features); + + unsigned int nbLandmarks = 0; // Local variable to count the number of landmarks for those features + + if (features.empty()) + return; + if (_msfmData == nullptr) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromSfM: no SfMData"; + return; + } + if (_msfmData->status() != MSfMData::Ready) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromSfM: SfMData is not ready: " << _msfmData->status(); + return; + } + if (_msfmData->rawData().getViews().empty()) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromSfM: SfMData is empty"; + return; + } + const auto viewIt = _msfmData->rawData().getViews().find(_viewId); + if (viewIt == _msfmData->rawData().getViews().end()) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromSfM: View " << _viewId << " is not is the SfMData"; + return; + } + const aliceVision::sfmData::View& view = *viewIt->second; + if (!_msfmData->rawData().isPoseAndIntrinsicDefined(&view)) + { + qWarning() << "[QtAliceVision] MFeatures::updateFeatureFromSfM: SfMData has no valid pose and intrinsic for view " << _viewId; + return; + } + + // Update newly loaded features with information from the sfmData + aliceVision::feature::EImageDescriberType descType = aliceVision::feature::EImageDescriberType_stringToEnum(desc.toStdString()); + + const aliceVision::sfmData::CameraPose pose = _msfmData->rawData().getPose(view); + const aliceVision::geometry::Pose3 camTransform = pose.getTransform(); + const aliceVision::camera::IntrinsicBase* intrinsic = _msfmData->rawData().getIntrinsicPtr(view.getIntrinsicId()); + + int numLandmark = 0; + for (const auto& landmark : _msfmData->rawData().getLandmarks()) + { + if (landmark.second.descType != descType) + continue; + + auto itObs = landmark.second.observations.find(_viewId); + if (itObs != landmark.second.observations.end()) + { + // setup landmark id and landmark 2d reprojection in the current view + aliceVision::Vec2 r = intrinsic->project(camTransform, landmark.second.X); + + if (itObs->second.id_feat >= 0 && itObs->second.id_feat < features.size()) + { + features.at(itObs->second.id_feat)->setLandmarkInfo(landmark.first, r.cast()); + } + else if (!features.empty()) + { + qWarning() << "[QtAliceVision] ---------- ERROR id_feat: " << itObs->second.id_feat << ", size: " << features.size(); + } + + ++nbLandmarks; + } + ++numLandmark; + } + + _allFeatures.value(desc)->setNbLandmarks(nbLandmarks); // Set the number of landmarks to the good MDescFeatures +} + +void MFeatures::updateFeatureFromSfMEmit() +{ + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + QString desc = i.key(); + updateFeatureFromSfM(desc); + } + updateNbTracks(); + updateTotalNbLandmarks(); + + Q_EMIT featuresChanged(); +} + +} // namespace \ No newline at end of file diff --git a/src/MFeatures.hpp b/src/MFeatures.hpp new file mode 100644 index 00000000..133dc5cf --- /dev/null +++ b/src/MFeatures.hpp @@ -0,0 +1,176 @@ +#pragma once + +#include "MFeature.hpp" +#include "MSfMdata.hpp" +#include "MTracks.hpp" +#include "MDescFeatures.hpp" + +#include +#include +#include + +namespace qtAliceVision +{ + +/** +* @brief Contains all the data about the features. Contains a map of MDescFeatures. +*/ +class MFeatures : public QObject +{ + Q_OBJECT + /// String list of all the describerTypes used. Values comes from QML. + Q_PROPERTY(QStringList describerTypes MEMBER _describerTypes NOTIFY describerTypesChanged) + /// Path to folder containing the features + Q_PROPERTY(QUrl featureFolder MEMBER _featureFolder NOTIFY featureFolderChanged) + /// ViewID to consider + Q_PROPERTY(quint32 viewId MEMBER _viewId NOTIFY viewIdChanged) + /// Pointer to SfmData + Q_PROPERTY(qtAliceVision::MSfMData* msfmData READ getMSfmData WRITE setMSfmData NOTIFY sfmDataChanged) + /// Pointer to Tracks + Q_PROPERTY(qtAliceVision::MTracks* mtracks READ getMTracks WRITE setMTracks NOTIFY tracksChanged) + /// The list of features + Q_PROPERTY(QVariantMap allFeatures READ allFeatures NOTIFY mapChanged) + /// Whether features are currently being loaded from file + Q_PROPERTY(bool loadingFeatures READ loadingFeatures NOTIFY loadingFeaturesChanged) + /// Whether landmarks are reconstructed + Q_PROPERTY(bool haveValidLandmarks READ haveValidLandmarks NOTIFY sfmDataChanged) + /// Whether tracks are reconstructed + Q_PROPERTY(bool haveValidTracks READ haveValidTracks NOTIFY tracksChanged) + /// Number of all the landmarks + Q_PROPERTY(unsigned int nbLandmarks READ getNbLandmarks) + /// Number of all the tracks + Q_PROPERTY(unsigned int nbTracks READ getNbTracks) + +public: + explicit MFeatures(QObject* parent = nullptr); + ~MFeatures() override; + + /// Return a QVariantMap containing the MDescFeatures pointers. + QVariantMap allFeatures() const + { + QVariantMap map; + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + map.insert(i.key(), QVariant::fromValue(i.value().get())); + } + return map; + } + + /// Return the total number of landmarks. + inline unsigned int getNbLandmarks() const { return _nbLandmarks; } + /// Return the total number of tracks. + inline unsigned int getNbTracks() const { return _nbTracks; } + + /// Return a pointer to SFM Data. + inline MSfMData* getMSfmData() const { return _msfmData; } + /// Set a pointer to SFM Data. + void setMSfmData(MSfMData* sfmData); + + /// Return a pointer to Tracks Data. + inline MTracks* getMTracks() const { return _mtracks; } + /// Set a pointer to Tracks Data. + void setMTracks(MTracks* tracks); + + /// Return a reference to the QMap containing the MDecFeatures pointers. + inline const QMap>& getAllFeatures() const { return _allFeatures; } + + /// Return if global process is currently loading or not. + inline bool loadingFeatures() const { return _loadingFeatures; } + /// Return if global process is currently loading or not. + void setLoadingFeatures(bool loadingFeatures) + { + if (_loadingFeatures == loadingFeatures) return; + _loadingFeatures = loadingFeatures; + Q_EMIT loadingFeaturesChanged(); + } + + /// Update the global loading features (used in QML). Based on each MDescFeatures own loading status. + void updateLoadingFeatures() + { + _loadingFeatures = false; + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + if (i.value()->loadingFeatures()) + _loadingFeatures = true; + break; + } + Q_EMIT loadingFeaturesChanged(); + } + + /// Check if every MDecFeatures is ready to be globally updated and displayed + bool checkAllFeaturesReady() const + { + for (auto i = _allFeatures.constBegin(); i != _allFeatures.constEnd(); ++i) + { + if (!i.value()->ready()) return false; + } + return true; + } + + /// Return if landmarks (SFM Data) are valid or not. + bool haveValidLandmarks() const { return _msfmData != nullptr && _msfmData->status() == MSfMData::Ready; } + /// Return if tracks are valid or not. + bool haveValidTracks() const { return _mtracks != nullptr && _mtracks->status() == MTracks::Ready; } + +public: + Q_SIGNAL void sfmDataChanged(); + Q_SIGNAL void tracksChanged(); + Q_SIGNAL void featuresChanged(); + Q_SIGNAL void mapChanged(); + Q_SIGNAL void viewIdChanged(); + Q_SIGNAL void featureFolderChanged(); + Q_SIGNAL void describerTypesChanged(); + Q_SIGNAL void loadingFeaturesChanged(); + +private: + /// Setup the basic connections between signals and slots. + void setupBasicConnections(); + + /// Reload the QMap and setup specific connections between signals and slots. + void reloadQMap(); + /// Update feature folder to each MDescFeatures. + void updateMapFeatureFolder(); + /// Update view id to each MDescFeatures. + void updateMapViewId(); + + /// Update all features. + void updateFeatures(const QString& desc); + /// Reload all features. + void reloadFeatures(); + + /// Clear tracks from every feature passed in argument. + void clearTracksFromFeatures(const QList& features); + /// Update every feature described by the describer type passed in argument using tracks data. + void updateFeatureFromTracks(const QString& desc); + /// Update every feature using tracks data. Should be connected to a signal. + void updateFeatureFromTracksEmit(); + + /** + * @brief Update the number of tracks not associated to a landmark. Update number of tracks per describer + total number. + * @warning depends on features, landmarks (sfmData), and tracks. + */ + void updateNbTracks(); + /// Update the total number of landmarks. + void updateTotalNbLandmarks(); + + /// Clear SFM data from every feature passed in argument. + void clearSfMFromFeatures(const QList& features); + /// Update every feature described by the describer type passed in argument using SFM data. + void updateFeatureFromSfM(const QString& desc); + /// Update every feature using SFM data. Should be connected to a signal. + void updateFeatureFromSfMEmit(); + + + QStringList _describerTypes; + QUrl _featureFolder; + aliceVision::IndexT _viewId = aliceVision::UndefinedIndexT; + QMap> _allFeatures; + MSfMData* _msfmData = nullptr; + MTracks* _mtracks = nullptr; + bool _loadingFeatures = false; + + int _nbLandmarks = 0; // Total number of features associated to a 3D landmark + int _nbTracks = 0; // Total number of tracks unvalidated after resection +}; + +} // namespace diff --git a/src/plugin.hpp b/src/plugin.hpp index e6fa2f87..be635db6 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -31,8 +31,10 @@ class QtAliceVisionPlugin : public QQmlExtensionPlugin qmlRegisterType(uri, 1, 0, "FeaturesViewer"); qmlRegisterType(uri, 1, 0, "MSfMData"); qmlRegisterType(uri, 1, 0, "MTracks"); + qmlRegisterType(uri, 1, 0, "MFeatures"); qmlRegisterType(uri, 1, 0, "MViewStats"); qmlRegisterType(uri, 1, 0, "MSfMDataStats"); + qmlRegisterUncreatableType(uri, 1, 0, "MDescFeatures", "Cannot create MDescFeatures instances from QML."); qmlRegisterUncreatableType(uri, 1, 0, "MFeature", "Cannot create Feature instances from QML."); qRegisterMetaType>( "QList" ); // for usage in signals/slots qRegisterMetaType>("QList");