pax_global_header00006660000000000000000000000064150657375370014534gustar00rootroot0000000000000052 comment=4057295952251a98c1cacba1cccef4ecffedc3dd QtAliceVision-2025.1.1/000077500000000000000000000000001506573753700144765ustar00rootroot00000000000000QtAliceVision-2025.1.1/.clang-format000066400000000000000000000040471506573753700170560ustar00rootroot00000000000000BasedOnStyle: Mozilla AlignTrailingComments: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: false BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true AfterClass: true AfterControlStatement: true AfterEnum: true AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: true AfterUnion: true AfterExternBlock: true BeforeCatch: true BeforeElse: true IndentBraces: false SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false BreakBeforeInheritanceComma: false BreakConstructorInitializers: BeforeColon BreakInheritanceList: BeforeColon ColumnLimit: 150 CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false Cpp11BracedListStyle: true DerivePointerAlignment: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true IncludeBlocks: Regroup IndentCaseLabels: true IndentPPDirectives: BeforeHash IndentWidth: 4 KeepEmptyLinesAtTheStartOfBlocks: false Language: Cpp PackConstructorInitializers: Never PointerAlignment: Left ReflowComments: true SpaceAfterLogicalNot: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpacesBeforeTrailingComments: 2 SpaceInEmptyParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: "c++17" IncludeCategories: - Regex: '^".*"' Priority: 1 - Regex: '^' Priority: 2 - Regex: '^' Priority: 3 - Regex: '^<.*\..*>' Priority: 4 SortIncludes: false QtAliceVision-2025.1.1/.git-blame-ignore-revs000066400000000000000000000017131506573753700206000ustar00rootroot00000000000000# [qtAliceVision] Apply clang-format on the module e2e5155ca87a5eeb52d27978e13977441cd07305 # [qmlSfmData] Reformatted with clang-format 271141c06f0f92b3ffc73e2283b4124b09c987d8 # [qtAliceVision] Reformatted with clang-format 83948e62bedeee2c755d9a038e988588163826c1 # [qtAliceVision] Reformat all sequence cache-related files d304d8655de26ed5702966b4d855b031f85268f1 # [qtAliceVision] Apply clang-format on modified files d0fd33d78af7d73f4c1c5ba0de7a1d49d263f5d0 # Reformat all plugins with latest clang-format rules a1969780d11ba0505b6ec99ddedf80e1917616ab # [qmlSfmData] Clean-up: Fix warnings and harmonize code acdea1b81afdfa7308d638f556d7ea4860a7f7f6 # Clean-up: Remove trailing spaces 74cfbfba88e2e26f3f1341f0aaa8a266196a1876 # [DepthMapEntity] Remove trailing white spaces 21a9bf2f258aeb15c6b1bba0d9bb7e431224fb35 # Clean-up: Improve readability edc2d3135e38b9ce25216335f1b90c6b2f39e907 # Clean-up: Remove trailing spaces f60668eb5c3d5b5684ab3c6168e45661bacf0ce4 QtAliceVision-2025.1.1/.gitignore000066400000000000000000000004441506573753700164700ustar00rootroot00000000000000# Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # IDE /.vscode /.cache/ compile_commands.json # Build directories build* dist*QtAliceVision-2025.1.1/CMakeLists.txt000066400000000000000000000025501506573753700172400ustar00rootroot00000000000000message("CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}") cmake_minimum_required(VERSION 3.11) project(QtAliceVisionPlugin LANGUAGES CXX VERSION 0.1.0) # Build options option(BUILD_IMAGEIO "Build qtAliceVisionImageIO plugin" ON) option(BUILD_DEPTHMAPENTITY "Build depthMapEntity plugin" ON) option(BUILD_SFM "Build qtAliceVision and qmlSfmData plugin" ON) message(STATUS "BUILD_IMAGEIO: ${BUILD_IMAGEIO}") message(STATUS "BUILD_DEPTHMAPENTITY: ${BUILD_DEPTHMAPENTITY}") message(STATUS "BUILD_SFM: ${BUILD_SFM}") # CMake Find modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # C++20 set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Qt find_package(Qt6Core REQUIRED) find_package(Qt6Quick REQUIRED) find_package(Qt6Qml REQUIRED) find_package(Qt6Charts REQUIRED) find_package(Qt6Gui REQUIRED) find_package(Qt6 COMPONENTS ShaderTools) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) add_definitions(-DQT_PLUGIN) add_definitions(-DQT_SHARED) add_definitions(-DQT_NO_DEBUG) add_definitions(-DQT_NO_KEYWORDS) # comment to get qDebug outputs add_definitions(-DQT_NO_DEBUG_OUTPUT) # for performance reasons let Qt handle jpeg, png and ico formats add_definitions(-DQT_ALICEVISIONIMAGEIO_USE_FORMATS_BLACKLIST) # AliceVision dependency find_package(AliceVision REQUIRED) # Builds add_subdirectory(src) QtAliceVision-2025.1.1/COPYING.md000066400000000000000000000001371506573753700161310ustar00rootroot00000000000000## QtAliceVision License QtAliceVision is licensed under the [MPL2 license](LICENSE-MPL2.md). QtAliceVision-2025.1.1/INSTALL.md000066400000000000000000000044121506573753700161270ustar00rootroot00000000000000# Development This guide will help you build and install QtAliceVision plugin. ## Requirements QtAliceVision requires: * [Qt6](https://www.qt.io/) (== 6.8.3, make sure to use the **same version** as the target application) * [AliceVision](https://github.com/aliceVision/AliceVision) * [OpenImageIO](https://github.com/AcademySoftwareFoundation/OpenImageIO) (>= 2.1, recommended >= 2.4.13) - with OpenEXR support for depth maps visualization * [Alembic](https://github.com/alembic/alembic) (>= 1.8.5) * [CMake](https://cmake.org/) (>= 3.11) * On Windows platform: Microsoft Visual Studio (>= 2022) > [!NOTE] For Windows, we recommend using [VCPKG](https://github.com/Microsoft/vcpkg) to get Alembic. Qt can either be installed with VCPKG or the official installer. ## Build instructions In the following steps, replace with the installation path of your choice. ### Windows > [!NOTE] > We will use "NMake Makefiles" generators here to have one-line build/installation, but Visual Studio solutions can be generated if need be. From a developer command-line, using Qt 6.8.3 (installed through the official installer) and VCPKG for OIIO: ``` set QT_DIR=/path/to/Qt/6.8.3/msvc2022_64 set AV_DIR=/path/to/aliceVision/cmake/folder cmake .. -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake -DAliceVision_DIR=%AV_DIR% -DCMAKE_PREFIX_PATH=%QT_DIR% -DVCPKG_TARGET_TRIPLET=x64-windows-release -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX= nmake install ``` ### Linux ```bash export QT_DIR=/path/to/Qt/6.8.3/gcc_64 export OPENIMAGEIO_DIR=/path/to/oiio/install export ALEMBIC_DIR=/path/to/alembic/config cmake .. -DAliceVision_DIR=$AV_DIR -DOPENIMAGEIO_LIBRARY_DIR_HINTS:PATH=$OPENIMAGEIO_DIR/lib/ -DOPENIMAGEIO_INCLUDE_DIR:PATH=$OPENIMAGEIO_DIR/include/ -DAlembic_DIR=$ALEMBIC_DIR -DCMAKE_PREFIX_PATH=$QT_DIR -DCMAKE_INSTALL_PREFIX= -DCMAKE_BUILD_TYPE=Release make install ``` ## Usage Once built, setup those environment variables before launching your application: ```bash # Windows: set QML2_IMPORT_PATH=/qml;%QML2_IMPORT_PATH% set QT_PLUGIN_PATH=;%QT_PLUGIN_PATH% # Linux: export QML2_IMPORT_PATH=/qml:$QML2_IMPORT_PATH export QT_PLUGIN_PATH=:$QT_PLUGIN_PATH ``` QtAliceVision-2025.1.1/LICENSE-MPL2.md000066400000000000000000000405251506573753700166200ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. QtAliceVision-2025.1.1/README.md000066400000000000000000000066311506573753700157630ustar00rootroot00000000000000# QtAliceVision - AliceVision/OIIO plugin for Qt QtAliceVision is a C++ plugin providing: - classes to load and visualize AliceVision and Alembic data through QML - an [OpenImageIO](http://github.com/OpenImageIO/oiio) backend for image IO in Qt. Currently available: - [X] Features Viewer - Position, scale and orientation - Status: extracted, matched or reconstructed - [ ] Matches Viewer - [X] Statistics - Per view statistics with reprojection error, observations histograms - Global SfM statistics with number of landmarks/matches, reprojection errors, observations per view - [X] Float Image Viewer - Visualize images with floating point precision - Dynamically adjust gain and gamma - [X] 3D Depth Maps - Visualize depth/sim maps generated by the [AliceVision](https://github.com/alicevision/AliceVision) framework in a 3D viewer - [X] Alembic 3D visualization - Point clouds - Cameras - [X] OIIO backend - Read RAW images from DSLRs - Read intermediate files of the [AliceVision](https://github.com/alicevision/AliceVision) framework stored in EXR format ## License The project is released under MPLv2, see [**COPYING.md**](COPYING.md). ## Get the project Get the source code: ```bash git clone --recursive git://github.com/alicevision/QtAliceVision cd QtAliceVision ``` See [**INSTALL.md**](INSTALL.md) to build and install the project. ## Usage ### AliceVision data visualization Once built and with the plugin installation folder in `QML2_IMPORT_PATH`: - Create an `MSfMData` object to get access to the SfM information: ```js import AliceVision 1.0 MSfMData { id: sfmData sfmDataPath: "/path/to/SfM/sfmData.abc” } ``` - Create an `MTracks` to load all matches and get access to tracks information: ```js import AliceVision 1.0 MTracks { id: tracks matchingFolder: "/path/to/FeatureMatching/UID/” } ``` - Create a `FeaturesViewer` to visualize features position, scale, orientation and optionally information about the feature status regarding tracks and SfmData. ```js FeaturesViewer { colorOffset: 0 describerType: "sift" featureFolder: "/path/to/features/folder" mTracks: tracks viewId: 101245654 color: “blue” landmarkColor: “red” displayMode: FeaturesViewer.Points mSfmData: sfmData } ``` - Create an `MSfMDataStats` to display global statistics about your SfMData: ```js MSfMDataStats { msfmData: msfmData mTracks: mTracks } ``` - Create an `MViewStats` to display statistics about a specific View of your SfMData: ```js MViewStats { msfmData: msfmData viewId: 451244710 } ``` - Create a `FloatImageViewer` to display an image with floating point precision, allowing to dynamically adjust the gain and gamma: ```js FloatImageViewer { source: "/path/to/image" gamma: 1.0 gain: 1.0 width: textureSize.width || 500 height: textureSize.height || 500 channelMode: "RGB" } ``` - Create a `DepthMapEntity` to display a depth map as a point cloud in a 3D viewer: ```js import DepthMapEntity 1.0 Scene3D { DepthMapEntity { source: "depthmap.exr" } } ``` - Create a `SfmDataEntity` to display point clouds (in .abc, .sfm or .json format) and cameras in a 3D viewer: ```js import SfmDataEntity 1.0 Scene3D { SfmDataEntity { source: "/path/to/pointcloud.sfm" } } ``` ### OpenImageIO backend When added to the `QT_PLUGIN_PATH`, all supported image files will be loaded through this plugin. QtAliceVision-2025.1.1/src/000077500000000000000000000000001506573753700152655ustar00rootroot00000000000000QtAliceVision-2025.1.1/src/CMakeLists.txt000066400000000000000000000003511506573753700200240ustar00rootroot00000000000000if(BUILD_IMAGEIO) add_subdirectory(qtAliceVisionImageIO) endif() if(BUILD_DEPTHMAPENTITY) add_subdirectory(depthMapEntity) endif() if(BUILD_SFM) add_subdirectory(qtAliceVision) add_subdirectory(qmlSfmData) endif() QtAliceVision-2025.1.1/src/depthMapEntity/000077500000000000000000000000001506573753700202245ustar00rootroot00000000000000QtAliceVision-2025.1.1/src/depthMapEntity/CMakeLists.txt000066400000000000000000000031311506573753700227620ustar00rootroot00000000000000# Target srcs set(PLUGIN_SOURCES DepthMapEntity.cpp ) set(PLUGIN_HEADERS plugin.hpp DepthMapEntity.hpp ) # Qt module dependency find_package(Qt6 COMPONENTS Gui REQUIRED) find_package(Qt6 COMPONENTS Qml REQUIRED) find_package(Qt6 COMPONENTS Quick REQUIRED) find_package(Qt6 COMPONENTS 3DCore REQUIRED) find_package(Qt6 COMPONENTS 3DRender REQUIRED) find_package(Qt6 COMPONENTS 3DExtras REQUIRED) # OpenImageIO dependency find_package(OpenImageIO REQUIRED) # Target properties add_library(depthMapEntityPlugin SHARED ${PLUGIN_SOURCES} ${PLUGIN_HEADERS}) if(MSVC) target_compile_options(depthMapEntityPlugin PUBLIC /W4) else() target_compile_options(depthMapEntityPlugin PUBLIC -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wpedantic) endif() target_include_directories(depthMapEntityPlugin PUBLIC ${OPENIMAGEIO_INCLUDE_DIRS} ) target_link_libraries(depthMapEntityPlugin PUBLIC ${OPENIMAGEIO_LIBRARIES} aliceVision_image aliceVision_mvsData aliceVision_numeric Qt6::Core Qt6::Gui Qt6::Qml Qt6::Quick Qt6::3DCore Qt6::3DRender Qt6::3DExtras ) set_target_properties(depthMapEntityPlugin PROPERTIES DEBUG_POSTFIX "" FOLDER "depthMapEntityPlugin" $SOVERSION ${PROJECT_VERSION_MAJOR} VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" ) # Install settings install(FILES "qmldir" DESTINATION ${CMAKE_INSTALL_PREFIX}/qml/DepthMapEntity) install(TARGETS depthMapEntityPlugin DESTINATION "${CMAKE_INSTALL_PREFIX}/qml/DepthMapEntity") QtAliceVision-2025.1.1/src/depthMapEntity/DepthMapEntity.cpp000066400000000000000000000445351506573753700236420ustar00rootroot00000000000000#include "DepthMapEntity.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace aliceVision; namespace depthMapEntity { DepthMapEntity::DepthMapEntity(Qt3DCore::QNode* parent) : Qt3DCore::QEntity(parent), _displayMode(DisplayMode::Unknown), _pointSizeParameter(new Qt3DRender::QParameter) { qDebug() << "[DepthMapEntity] DepthMapEntity"; createMaterials(); } void DepthMapEntity::setSource(const QUrl& value) { if (_source == value) return; if (!value.isValid()) { qDebug() << "[DepthMapEntity] Invalid source: " << value; _status = DepthMapEntity::Error; return; } _source = value; QFileInfo fileInfo = QFileInfo(_source.path()); QString filename = fileInfo.fileName(); if (filename.contains("depthMap")) { _depthMapSource = _source; _simMapSource = QUrl::fromLocalFile(QFileInfo(fileInfo.dir(), filename.replace("depthMap", "simMap")).filePath()); } else if (filename.contains("simMap")) { _simMapSource = _source; _depthMapSource = QUrl::fromLocalFile(QFileInfo(fileInfo.dir(), filename.replace("simMap", "depthMap")).filePath()); } else if (filename.contains("depth")) { _depthMapSource = _source; _simMapSource = ""; } else { qWarning() << "[DepthMapEntity] Source filename must contain depthMap or simMap: " << filename; _status = DepthMapEntity::Error; return; } loadDepthMap(); Q_EMIT sourceChanged(); } void DepthMapEntity::setDisplayMode(const DepthMapEntity::DisplayMode& value) { if (_displayMode == value) return; _displayMode = value; updateMaterial(); Q_EMIT displayModeChanged(); } void DepthMapEntity::setDisplayColor(bool value) { if (_displayColor == value) return; _displayColor = value; updateMaterial(); Q_EMIT displayColorChanged(); } void DepthMapEntity::updateMaterial() { if (_status != DepthMapEntity::Ready) return; Qt3DRender::QMaterial* newMaterial = nullptr; switch (_displayMode) { case DisplayMode::Points: newMaterial = _cloudMaterial; _meshRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Points); break; case DisplayMode::Triangles: _meshRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles); if (_displayColor) newMaterial = _colorMaterial; else newMaterial = _diffuseMaterial; break; default: newMaterial = _diffuseMaterial; } if (newMaterial == _currentMaterial) return; if (_currentMaterial) removeComponent(_currentMaterial); _currentMaterial = newMaterial; addComponent(_currentMaterial); } void DepthMapEntity::setPointSize(const float& value) { if (_pointSize == value) return; _pointSize = value; _pointSizeParameter->setValue(value); _cloudMaterial->setEnabled(_pointSize > 0.0f); Q_EMIT pointSizeChanged(); } // private void DepthMapEntity::createMaterials() { using namespace Qt3DCore; using namespace Qt3DRender; using namespace Qt3DExtras; { _cloudMaterial = new QMaterial(this); // configure cloud material QEffect* effect = new QEffect; QTechnique* technique = new QTechnique; QRenderPass* renderPass = new QRenderPass; QShaderProgram* shaderProgram = new QShaderProgram; // set vertex shader shaderProgram->setVertexShaderCode(R"(#version 130 in vec3 vertexPosition; in vec3 vertexColor; out vec3 color; uniform mat4 mvp; uniform mat4 projectionMatrix; uniform mat4 viewportMatrix; uniform float pointSize; void main() { color = vertexColor; gl_Position = mvp * vec4(vertexPosition, 1.0); gl_PointSize = max(viewportMatrix[1][1] * projectionMatrix[1][1] * pointSize / gl_Position.w, 1.0); } )"); // set fragment shader shaderProgram->setFragmentShaderCode(R"(#version 130 in vec3 color; out vec4 fragColor; void main(void) { fragColor = vec4(color, 1.0); } )"); // add a pointSize uniform _pointSizeParameter->setName("pointSize"); _pointSizeParameter->setValue(_pointSize); _cloudMaterial->addParameter(_pointSizeParameter); // build the material renderPass->setShaderProgram(shaderProgram); technique->addRenderPass(renderPass); effect->addTechnique(technique); _cloudMaterial->setEffect(effect); } { _colorMaterial = new QPerVertexColorMaterial(this); } { _diffuseMaterial = new QDiffuseSpecularMaterial(this); _diffuseMaterial->setAmbient(QColor(0, 0, 0)); _diffuseMaterial->setDiffuse(QColor(255, 255, 255)); _diffuseMaterial->setSpecular(QColor(0, 0, 0)); _diffuseMaterial->setShininess(0.0); } } bool validTriangleRatio(const Vec3f& a, const Vec3f& b, const Vec3f& c) { std::vector distances = {DistanceL2(a, b), DistanceL2(b, c), DistanceL2(c, a)}; double mi = std::min({distances[0], distances[1], distances[2]}); double ma = std::max({distances[0], distances[1], distances[2]}); if (ma == 0.0) return false; return (mi / ma) > 1.0 / 5.0; } std::optional getCenter(const oiio::ImageSpec & inSpec) { Point3d CArr; const oiio::ParamValue* cParam = inSpec.find_attribute("AliceVision:CArr"); if (!cParam) { return std::nullopt; } if (cParam->type().aggregate != oiio::TypeDesc::AGGREGATE::VEC3) { return std::nullopt; } if (cParam->type().basetype == oiio::TypeDesc::BASETYPE::DOUBLE) { std::copy_n(static_cast(cParam->data()), 3, CArr.m); return CArr; } if (cParam->type().basetype == oiio::TypeDesc::BASETYPE::FLOAT) { const float* d = static_cast(cParam->data()); for (int i = 0; i < 3; ++i) { CArr.m[i] = d[i]; } return CArr; } return std::nullopt; } std::optional getInverseProjection(const oiio::ImageSpec & inSpec) { Matrix3x3 iCamArr; const oiio::ParamValue* icParam = inSpec.find_attribute("AliceVision:iCamArr"); if (!icParam) { return std::nullopt; } if (icParam->type().aggregate != oiio::TypeDesc::AGGREGATE::MATRIX33) { return std::nullopt; } if (icParam->type().basetype == oiio::TypeDesc::BASETYPE::DOUBLE) { std::copy_n(static_cast(icParam->data()), 9, iCamArr.m); return iCamArr; } if (icParam->type().basetype == oiio::TypeDesc::BASETYPE::FLOAT) { const float* d = static_cast(icParam->data()); for (int i = 0; i < 9; ++i) { iCamArr.m[i] = d[i]; } return iCamArr; } return std::nullopt; } std::optional getInverseProjectionFromFov(const oiio::ImageSpec & inSpec) { double fovDegrees = -1.0; //Try to find fov metadata for (const auto & param : inSpec.extra_attribs) { std::string name = param.name().string(); size_t pos = name.find_last_of(":"); if (pos != std::string::npos) { name = name.erase(0, pos + 1); } std::transform(name.begin(), name.end(), name.begin(), ::tolower); if (name != "fov") { continue; } fovDegrees = double(param.get_float(90.0)); } if (fovDegrees < 0) { return std::nullopt; } double fovRadians = aliceVision::degreeToRadian(fovDegrees); //Simple trigonometry //tan(fov / 2) = (width / 2) / f //f = (width / 2) / (tan(fov / 2)) double w = double(inSpec.width); double f = (w * 0.5) / std::tan(fovRadians * 0.5); Matrix3x3 iCamArr; iCamArr.m11 = 1.0 / f; iCamArr.m22 = 1.0 / f; iCamArr.m33 = 1.0; return iCamArr; } // private void DepthMapEntity::loadDepthMap() { using namespace Qt3DCore; _status = DepthMapEntity::Loading; if (_meshRenderer) { removeComponent(_meshRenderer); _meshRenderer->deleteLater(); _meshRenderer = nullptr; } // Load depth map and metadata const std::string depthMapPath = _depthMapSource.toLocalFile().toStdString(); qDebug() << "[DepthMapEntity] Load depth map: " << _depthMapSource.toLocalFile(); image::Image depthMap; try { image::readImage(depthMapPath, depthMap, image::EImageColorSpace::LINEAR); } catch (const std::runtime_error& error) { qCritical() << "[DepthMapEntity] Could not load depth map:" << error.what(); _status = DepthMapEntity::Error; return; } oiio::ImageBuf inBuf; image::getBufferFromImage(depthMap, inBuf); qDebug() << "[DepthMapEntity] Image Size: " << depthMap.width() << "x" << depthMap.height(); oiio::ImageSpec inSpec = image::readImageSpec(depthMapPath); Point3d CArr; std::optional optCarr = getCenter(inSpec); if (optCarr.has_value()) { CArr = optCarr.value(); } Matrix3x3 iCamArr = diag3x3(1, 1, 1); std::optional optiCamArr = getInverseProjection(inSpec); if (optiCamArr.has_value()) { iCamArr = optiCamArr.value(); } else { optiCamArr = getInverseProjectionFromFov(inSpec); if (optiCamArr.has_value()) { iCamArr = optiCamArr.value(); } else { _status = DepthMapEntity::Error; return; } } // Load sim map image::Image simMap; if (_simMapSource.isValid()) { const std::string simMapPath = _simMapSource.toLocalFile().toStdString(); qDebug() << "[DepthMapEntity] Load sim map: " << _simMapSource.toLocalFile(); try { image::readImage(simMapPath, simMap, image::EImageColorSpace::LINEAR); } catch (const std::runtime_error& error) { qWarning() << "[DepthMapEntity] Sim map could not be loaded:" << error.what(); } } else { qWarning() << "[DepthMapEntity] Failed to find associated sim map"; } const bool validSimMap = (simMap.width() == depthMap.width()) && (simMap.height() == depthMap.height()); // 3D points position and color (using jetColorMap) qDebug() << "[DepthMapEntity] Computing positions and colors for point cloud"; std::vector indexPerPixel(static_cast(depthMap.width() * depthMap.height()), -1); std::vector positions; std::vector colors; oiio::ImageBufAlgo::PixelStats stats = oiio::ImageBufAlgo::computePixelStats(inBuf); for (int y = 0; y < depthMap.height(); ++y) { for (int x = 0; x < depthMap.width(); ++x) { float depthValue = depthMap(y, x); if (!std::isfinite(depthValue) || depthValue <= 0.f) continue; Point3d p = CArr + (iCamArr * Point2d(static_cast(x), static_cast(y))).normalize() * depthValue; Vec3f position(static_cast(p.x), static_cast(-p.y), static_cast(-p.z)); indexPerPixel[static_cast(y * depthMap.width() + x)] = static_cast(positions.size()); positions.push_back(position); if (validSimMap) { float simValue = simMap(y, x); image::RGBfColor color = getColorFromJetColorMap(simValue); colors.push_back(color); } else { const float range = stats.max[0] - stats.min[0]; float normalizedDepthValue = range != 0.0f ? (depthValue - stats.min[0]) / range : 1.0f; image::RGBfColor color = getColorFromJetColorMap(normalizedDepthValue); colors.push_back(color); } } } // Create geometry qDebug() << "[DepthMapEntity] Creating geometry"; QGeometry* customGeometry = new QGeometry; // vertices buffer std::vector trianglesIndexes; trianglesIndexes.reserve(2 * 3 * positions.size()); for (int y = 0; y < depthMap.height() - 1; ++y) { for (int x = 0; x < depthMap.width() - 1; ++x) { int pixelIndexA = indexPerPixel[static_cast(y * depthMap.width() + x)]; int pixelIndexB = indexPerPixel[static_cast((y + 1) * depthMap.width() + x)]; int pixelIndexC = indexPerPixel[static_cast((y + 1) * depthMap.width() + x + 1)]; int pixelIndexD = indexPerPixel[static_cast(y * depthMap.width() + x + 1)]; // Cast indices to std::size_t once for readability std::size_t sPixelIndexA = static_cast(pixelIndexA); std::size_t sPixelIndexB = static_cast(pixelIndexB); std::size_t sPixelIndexC = static_cast(pixelIndexC); std::size_t sPixelIndexD = static_cast(pixelIndexD); if (pixelIndexA != -1 && pixelIndexB != -1 && pixelIndexC != -1 && validTriangleRatio(positions[sPixelIndexA], positions[sPixelIndexB], positions[sPixelIndexC])) { trianglesIndexes.push_back(sPixelIndexA); trianglesIndexes.push_back(sPixelIndexB); trianglesIndexes.push_back(sPixelIndexC); } if (pixelIndexC != -1 && pixelIndexD != -1 && pixelIndexA != -1 && validTriangleRatio(positions[sPixelIndexC], positions[sPixelIndexD], positions[sPixelIndexA])) { trianglesIndexes.push_back(sPixelIndexC); trianglesIndexes.push_back(sPixelIndexD); trianglesIndexes.push_back(sPixelIndexA); } } } qDebug() << "[DepthMapEntity] Nb triangles: " << trianglesIndexes.size(); std::vector triangles; triangles.resize(trianglesIndexes.size()); for (std::size_t i = 0; i < trianglesIndexes.size(); ++i) { triangles[i] = positions[trianglesIndexes[i]]; } std::vector normals; normals.resize(triangles.size()); for (std::size_t i = 0; i < trianglesIndexes.size(); i += 3) { Vec3f normal = (triangles[i + 1] - triangles[i]).cross(triangles[i + 2] - triangles[i]); for (std::size_t t = 0; t < 3; ++t) normals[i + t] = normal; } QBuffer* vertexBuffer = new QBuffer; QByteArray trianglesData(reinterpret_cast(&triangles[0]), static_cast(triangles.size() * sizeof(Vec3f))); vertexBuffer->setData(trianglesData); QBuffer* normalBuffer = new QBuffer; QByteArray normalsData(reinterpret_cast(&normals[0]), static_cast(normals.size() * sizeof(Vec3f))); normalBuffer->setData(normalsData); QAttribute* positionAttribute = new QAttribute(this); positionAttribute->setName(QAttribute::defaultPositionAttributeName()); positionAttribute->setAttributeType(QAttribute::VertexAttribute); positionAttribute->setBuffer(vertexBuffer); positionAttribute->setVertexBaseType(QAttribute::Float); positionAttribute->setVertexSize(3); positionAttribute->setByteOffset(0); positionAttribute->setByteStride(sizeof(Vec3f)); positionAttribute->setCount(static_cast(triangles.size())); QAttribute* normalAttribute = new QAttribute(this); normalAttribute->setName(QAttribute::defaultNormalAttributeName()); normalAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute); normalAttribute->setBuffer(normalBuffer); normalAttribute->setVertexBaseType(QAttribute::Float); normalAttribute->setVertexSize(3); normalAttribute->setByteOffset(0); normalAttribute->setByteStride(sizeof(Vec3f)); normalAttribute->setCount(static_cast(normals.size())); customGeometry->addAttribute(positionAttribute); customGeometry->addAttribute(normalAttribute); // customGeometry->setBoundingVolumePositionAttribute(positionAttribute); // Duplicate colors as we cannot use indexes! std::vector colorsFlat; colorsFlat.reserve(trianglesIndexes.size()); for (std::size_t i = 0; i < trianglesIndexes.size(); ++i) { colorsFlat.push_back(colors[trianglesIndexes[i]]); } // read color data QBuffer* colorDataBuffer = new QBuffer; QByteArray colorData(reinterpret_cast(colorsFlat[0].data()), static_cast(colorsFlat.size() * 3 * sizeof(float))); colorDataBuffer->setData(colorData); QAttribute* colorAttribute = new QAttribute; qDebug() << "[DepthMapEntity] Qt3DRender::QAttribute::defaultColorAttributeName(): " << Qt3DCore::QAttribute::defaultColorAttributeName(); colorAttribute->setName(Qt3DCore::QAttribute::defaultColorAttributeName()); colorAttribute->setAttributeType(QAttribute::VertexAttribute); colorAttribute->setBuffer(colorDataBuffer); colorAttribute->setVertexBaseType(QAttribute::Float); colorAttribute->setVertexSize(3); colorAttribute->setByteOffset(0); colorAttribute->setByteStride(3 * sizeof(float)); colorAttribute->setCount(static_cast(colorsFlat.size())); customGeometry->addAttribute(colorAttribute); // create the geometry renderer _meshRenderer = new Qt3DRender::QGeometryRenderer; _meshRenderer->setGeometry(customGeometry); _status = DepthMapEntity::Ready; // add components addComponent(_meshRenderer); updateMaterial(); qDebug() << "[DepthMapEntity] Mesh Renderer added"; } } // namespace depthMapEntity QtAliceVision-2025.1.1/src/depthMapEntity/DepthMapEntity.hpp000066400000000000000000000052331506573753700236370ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include namespace depthMapEntity { class DepthMapEntity : public Qt3DCore::QEntity { Q_OBJECT Q_ENUMS(DisplayMode) Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(Status status READ status NOTIFY statusChanged) Q_PROPERTY(DisplayMode displayMode READ displayMode WRITE setDisplayMode NOTIFY displayModeChanged) Q_PROPERTY(bool displayColor READ displayColor WRITE setDisplayColor NOTIFY displayColorChanged) Q_PROPERTY(float pointSize READ pointSize WRITE setPointSize NOTIFY pointSizeChanged) public: // Identical to SceneLoader.Status enum Status { None = 0, Loading, Ready, Error }; Q_ENUM(Status) DepthMapEntity(Qt3DCore::QNode* = nullptr); ~DepthMapEntity() = default; enum class DisplayMode { Points, Triangles, Unknown }; public: Q_SLOT const QUrl& source() const { return _source; } Q_SLOT void setSource(const QUrl&); Status status() const { return _status; } void setStatus(Status status) { if (status == _status) return; _status = status; Q_EMIT statusChanged(_status); } Q_SLOT DisplayMode displayMode() const { return _displayMode; } Q_SLOT void setDisplayMode(const DisplayMode&); Q_SLOT bool displayColor() const { return _displayColor; } Q_SLOT void setDisplayColor(bool); Q_SLOT float pointSize() const { return _pointSize; } Q_SLOT void setPointSize(const float& value); private: void loadDepthMap(); void createMaterials(); void updateMaterial(); public: Q_SIGNAL void sourceChanged(); Q_SIGNAL void statusChanged(Status status); Q_SIGNAL void displayModeChanged(); Q_SIGNAL void displayColorChanged(); Q_SIGNAL void pointSizeChanged(); private: Status _status = DepthMapEntity::None; QUrl _source; QUrl _depthMapSource; QUrl _simMapSource; DisplayMode _displayMode = DisplayMode::Triangles; bool _displayColor = true; float _pointSize = 0.5f; Qt3DRender::QParameter* _pointSizeParameter; Qt3DRender::QMaterial* _cloudMaterial; Qt3DExtras::QDiffuseSpecularMaterial* _diffuseMaterial; Qt3DExtras::QPerVertexColorMaterial* _colorMaterial; Qt3DRender::QMaterial* _currentMaterial = nullptr; Qt3DRender::QGeometryRenderer* _meshRenderer = nullptr; }; } // namespace depthMapEntity QtAliceVision-2025.1.1/src/depthMapEntity/plugin.hpp000066400000000000000000000013251506573753700222340ustar00rootroot00000000000000#pragma once #include "DepthMapEntity.hpp" #include #include namespace depthMapEntity { class DepthMapEntityPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "depthMapEntity.qmlPlugin") public: void initializeEngine(QQmlEngine* engine, const char* uri) override { // Fix "unused parameter" warnings; should be replaced by [[maybe_unused]] when C++17 is supported (void)engine; (void)uri; } void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("DepthMapEntity")); qmlRegisterType(uri, 2, 1, "DepthMapEntity"); } }; } // namespace depthMapEntity QtAliceVision-2025.1.1/src/depthMapEntity/qmldir000066400000000000000000000000631506573753700214360ustar00rootroot00000000000000module DepthMapEntity plugin depthMapEntityPlugin QtAliceVision-2025.1.1/src/qmlSfmData/000077500000000000000000000000001506573753700173165ustar00rootroot00000000000000QtAliceVision-2025.1.1/src/qmlSfmData/CMakeLists.txt000066400000000000000000000027041506573753700220610ustar00rootroot00000000000000# Target srcs set(PLUGIN_SOURCES IOThread.cpp SfmDataEntity.cpp CameraLocatorEntity.cpp PointCloudEntity.cpp ) set(PLUGIN_HEADERS IOThread.hpp plugin.hpp SfmDataEntity.hpp CameraLocatorEntity.hpp PointCloudEntity.hpp ) # Qt module dependency find_package(Qt6 COMPONENTS Qml REQUIRED) find_package(Qt6 COMPONENTS 3DCore REQUIRED) find_package(Qt6 COMPONENTS 3DRender REQUIRED) find_package(Qt6 COMPONENTS 3DExtras REQUIRED) # Target properties add_library(sfmDataEntityQmlPlugin SHARED ${PLUGIN_SOURCES} ${PLUGIN_HEADERS}) if(MSVC) target_compile_options(sfmDataEntityQmlPlugin PUBLIC /W4) else() target_compile_options(sfmDataEntityQmlPlugin PUBLIC -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wpedantic ) endif() target_link_libraries(sfmDataEntityQmlPlugin PUBLIC Qt6::Core Qt6::Qml Qt6::3DCore Qt6::3DRender aliceVision_sfmDataIO aliceVision_sfm aliceVision_system PRIVATE Qt6::3DExtras ) set_target_properties(sfmDataEntityQmlPlugin PROPERTIES DEBUG_POSTFIX "" FOLDER "sfmDataEntityQmlPlugin" SOVERSION ${PROJECT_VERSION_MAJOR} VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" ) # Install settings install(FILES "qmldir" DESTINATION ${CMAKE_INSTALL_PREFIX}/qml/SfmDataEntity) install(TARGETS sfmDataEntityQmlPlugin DESTINATION "${CMAKE_INSTALL_PREFIX}/qml/SfmDataEntity") QtAliceVision-2025.1.1/src/qmlSfmData/CameraLocatorEntity.cpp000066400000000000000000000224051506573753700237360ustar00rootroot00000000000000#include "CameraLocatorEntity.hpp" #include #include #include #include #include namespace sfmdataentity { CameraLocatorEntity::CameraLocatorEntity(const aliceVision::IndexT& viewId, const aliceVision::IndexT& resectionId, float hfov, float vfov, Qt3DCore::QNode* parent) : Qt3DCore::QEntity(parent), _viewId(viewId), _resectionId(resectionId) { _transform = new Qt3DCore::QTransform; addComponent(_transform); using namespace Qt3DRender; using namespace Qt3DCore; // create a new geometry renderer auto customMeshRenderer = new QGeometryRenderer; auto customGeometry = new QGeometry; const float axisLength = 0.5f; const float halfImageWidth = 0.3f; const float yArrowHeight = 0.05f; const float radius = 0.3f; int subdiv = 1; if (hfov > boost::math::constants::pi() * 0.7 || vfov > boost::math::constants::pi() * 0.7) subdiv = 10; // clang-format off // vertices buffer QVector origin = { // Coord system 0.f, 0.f, 0.f, axisLength, 0.0f, 0.0f, // X 0.f, 0.f, 0.f, 0.0f, -axisLength, 0.0f, // Y 0.f, 0.f, 0.f, 0.0f, 0.0f, -axisLength, // Z }; QVector bodyCam; aliceVision::Vec3 tl, tr, bl, br; tl.x() = sin(-hfov / 2.0f); tl.y() = sin(vfov / 2.0f); tl.z() = cos(-hfov / 2.0f); tr.x() = sin(-hfov / 2.0f); tr.y() = sin(vfov / 2.0f); tr.z() = cos(vfov / 2.0f); const float vslice = vfov / static_cast(subdiv); const float hslice = hfov / static_cast(subdiv); Eigen::Vector3d vZ = - Eigen::Vector3d::UnitZ() * radius; for (int vid = 0; vid < subdiv; vid++) { float vangle1 = - vfov / 2.0f + (vid) * vslice; float vangle2 = - vfov / 2.0f + (vid + 1) * vslice; Eigen::AngleAxis Rv1(vangle1, Eigen::Vector3d::UnitX()); Eigen::AngleAxis Rv2(vangle2, Eigen::Vector3d::UnitX()); for (int hid = 0; hid < subdiv; hid++) { float hangle1 = - hfov / 2.0f + (hid) * hslice; float hangle2 = - hfov / 2.0f + (hid + 1) * hslice; Eigen::AngleAxis Rh1(hangle1, Eigen::Vector3d::UnitY()); Eigen::AngleAxis Rh2(hangle2, Eigen::Vector3d::UnitY()); aliceVision::Vec3f pt1 = (Rv1.toRotationMatrix() * Rh1.toRotationMatrix() * vZ).cast(); aliceVision::Vec3f pt2 = (Rv2.toRotationMatrix() * Rh1.toRotationMatrix() * vZ).cast(); aliceVision::Vec3f pt3 = (Rv2.toRotationMatrix() * Rh2.toRotationMatrix() * vZ).cast(); aliceVision::Vec3f pt4 = (Rv1.toRotationMatrix() * Rh2.toRotationMatrix() * vZ).cast(); QVector quad = { pt1.x(), pt1.y(), pt1.z(), pt2.x(), pt2.y(), pt2.z(), pt2.x(), pt2.y(), pt2.z(), pt3.x(), pt3.y(), pt3.z(), pt3.x(), pt3.y(), pt3.z(), pt4.x(), pt4.y(), pt4.z(), pt4.x(), pt4.y(), pt4.z(), pt1.x(), pt1.y(), pt1.z(), }; bodyCam.append(quad); } } float vangle1 = - vfov / 2.0f; float vangle2 = vfov / 2.0f; float hangle1 = - hfov / 2.0f; float hangle2 = hfov / 2.0f; Eigen::AngleAxis Rv1(vangle1, Eigen::Vector3d::UnitX()); Eigen::AngleAxis Rv2(vangle2, Eigen::Vector3d::UnitX()); Eigen::AngleAxis Rh1(hangle1, Eigen::Vector3d::UnitY()); Eigen::AngleAxis Rh2(hangle2, Eigen::Vector3d::UnitY()); aliceVision::Vec3f pt1 = (Rv1.toRotationMatrix() * Rh1.toRotationMatrix() * vZ).cast(); aliceVision::Vec3 pt2 = Rv2.toRotationMatrix() * Rh1.toRotationMatrix() * vZ; auto pt2f = pt2.cast(); aliceVision::Vec3 pt3 = Rv2.toRotationMatrix() * Rh2.toRotationMatrix() * vZ; auto pt3f = pt3.cast(); aliceVision::Vec3f pt4 = (Rv1.toRotationMatrix() * Rh2.toRotationMatrix() * vZ).cast(); QVector quad = { 0.0f, 0.0f, 0.0f, pt2f.x(), pt2f.y(), pt2f.z(), 0.0f, 0.0f, 0.0f, pt3f.x(), pt3f.y(), pt3f.z(), 0.0f, 0.0f, 0.0f, pt4.x(), pt4.y(), pt4.z(), 0.0f, 0.0f, 0.0f, pt1.x(), pt1.y(), pt1.z(), }; bodyCam.append(quad); aliceVision::Vec3f middle = (0.5 * (pt2 + pt3)).cast(); QVector upVector = { // Camera Up pt2f.x(), pt2f.y(), pt2f.z(), middle.x(), middle.y() + yArrowHeight, middle.z(), middle.x(), middle.y() + yArrowHeight, middle.z(), pt3f.x(), pt3f.y(), pt3f.z() }; QVector points; points.append(origin); points.append(bodyCam); points.append(upVector); // clang-format on QByteArray positionData(reinterpret_cast(points.data()), points.size() * static_cast(sizeof(float))); auto vertexDataBuffer = new QBuffer; vertexDataBuffer->setData(positionData); auto positionAttribute = new QAttribute; positionAttribute->setAttributeType(QAttribute::VertexAttribute); positionAttribute->setBuffer(vertexDataBuffer); positionAttribute->setVertexBaseType(QAttribute::Float); positionAttribute->setVertexSize(3); positionAttribute->setByteOffset(0); positionAttribute->setByteStride(3 * sizeof(float)); positionAttribute->setCount(static_cast(points.size() / 3)); positionAttribute->setName(QAttribute::defaultPositionAttributeName()); customGeometry->addAttribute(positionAttribute); // colors buffer _colors = initializeColors(points.size(), 1.0f); QByteArray colorData(reinterpret_cast(_colors.data()), _colors.size() * static_cast(sizeof(float))); auto colorDataBuffer = new QBuffer; colorDataBuffer->setData(colorData); _colorAttribute = new QAttribute; _colorAttribute->setAttributeType(QAttribute::VertexAttribute); _colorAttribute->setBuffer(colorDataBuffer); _colorAttribute->setVertexBaseType(QAttribute::Float); _colorAttribute->setVertexSize(3); _colorAttribute->setByteOffset(0); _colorAttribute->setByteStride(3 * sizeof(float)); _colorAttribute->setCount(static_cast(_colors.size() / 3)); _colorAttribute->setName(QAttribute::defaultColorAttributeName()); customGeometry->addAttribute(_colorAttribute); // geometry renderer settings customMeshRenderer->setInstanceCount(1); customMeshRenderer->setFirstVertex(0); customMeshRenderer->setFirstInstance(0); customMeshRenderer->setPrimitiveType(QGeometryRenderer::Lines); customMeshRenderer->setGeometry(customGeometry); customMeshRenderer->setVertexCount(points.size() / 3); // add components addComponent(customMeshRenderer); } void CameraLocatorEntity::setTransform(const Eigen::Matrix4d& T) { Eigen::Matrix4d M; M.setIdentity(); M(1, 1) = -1; M(2, 2) = -1; Eigen::Matrix4d mat = (M * T * M).inverse(); QMatrix4x4 qmat(static_cast(mat(0, 0)), static_cast(mat(0, 1)), static_cast(mat(0, 2)), static_cast(mat(0, 3)), static_cast(mat(1, 0)), static_cast(mat(1, 1)), static_cast(mat(1, 2)), static_cast(mat(1, 3)), static_cast(mat(2, 0)), static_cast(mat(2, 1)), static_cast(mat(2, 2)), static_cast(mat(2, 3)), static_cast(mat(3, 0)), static_cast(mat(3, 1)), static_cast(mat(3, 2)), static_cast(mat(3, 3))); _transform->setMatrix(qmat); } QVector CameraLocatorEntity::initializeColors(int size, float defaultValue) { QVector colors(size, defaultValue); float* color; // R color = &(colors[0 * 3]); color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f; // R color = &(colors[1 * 3]); color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f; // G color = &(colors[2 * 3]); color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f; // G color = &(colors[3 * 3]); color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f; // B color = &(colors[4 * 3]); color[0] = 0.0f; color[1] = 0.0f; color[2] = 1.0f; // B color = &(colors[5 * 3]); color[0] = 0.0f; color[1] = 0.0f; color[2] = 1.0f; return colors; } void CameraLocatorEntity::updateColors(float red, float green, float blue) { const int pyramidIndex = 6 * 3; // Only modify the colors of the pyramid, image plane and camera up direction for (int i = pyramidIndex; i < _colors.size(); i += 3) { _colors[i] = red; _colors[i + 1] = green; _colors[i + 2] = blue; } QByteArray colorData(reinterpret_cast(_colors.data()), _colors.size() * static_cast(sizeof(float))); auto colorDataBuffer = new Qt3DCore::QBuffer; colorDataBuffer->setData(colorData); _colorAttribute->setBuffer(colorDataBuffer); } } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/CameraLocatorEntity.hpp000066400000000000000000000025221506573753700237410ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace sfmdataentity { class CameraLocatorEntity : public Qt3DCore::QEntity { Q_OBJECT Q_PROPERTY(quint32 viewId MEMBER _viewId NOTIFY viewIdChanged) Q_PROPERTY(quint32 resectionId MEMBER _resectionId NOTIFY resectionIdChanged) public: explicit CameraLocatorEntity(const aliceVision::IndexT& viewId, const aliceVision::IndexT& resectionId, float hfov, float vfov, Qt3DCore::QNode* = nullptr); ~CameraLocatorEntity() override = default; void setTransform(const Eigen::Matrix4d&); QVector initializeColors(int size, float defaultValue = 1.0f); void updateColors(float red, float green, float blue); aliceVision::IndexT viewId() const { return _viewId; } aliceVision::IndexT resectionId() const { return _resectionId; } Q_SIGNAL void viewIdChanged(); Q_SIGNAL void resectionIdChanged(); private: Qt3DCore::QTransform* _transform; aliceVision::IndexT _viewId; aliceVision::IndexT _resectionId; Qt3DCore::QAttribute* _colorAttribute; QVector _colors; }; } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/IOThread.cpp000066400000000000000000000024541506573753700214660ustar00rootroot00000000000000#include "IOThread.hpp" #include #include namespace sfmdataentity { void IOThread::read(const QUrl& source) { _source = source; start(); } void IOThread::run() { // ensure file exists and is valid if (!_source.isValid() || !QFile::exists(_source.toLocalFile())) return; QMutexLocker lock(&_mutex); try { if (!aliceVision::sfmDataIO::load( _sfmData, _source.toLocalFile().toStdString(), aliceVision::sfmDataIO::ESfMData(aliceVision::sfmDataIO::ESfMData::VIEWS | aliceVision::sfmDataIO::ESfMData::INTRINSICS | aliceVision::sfmDataIO::ESfMData::EXTRINSICS | aliceVision::sfmDataIO::ESfMData::STRUCTURE))) { qWarning() << "[QmlSfmData] Failed to load SfMData: " << _source << "."; } } catch (const std::exception& e) { qCritical() << "[QmlSfmData] Error while loading the SfMData: " << e.what(); } } void IOThread::clear() { QMutexLocker lock(&_mutex); _sfmData = aliceVision::sfmData::SfMData(); } const aliceVision::sfmData::SfMData& IOThread::getSfmData() const { // mutex is mutable and can be locked in const methods QMutexLocker lock(&_mutex); return _sfmData; } } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/IOThread.hpp000066400000000000000000000012461506573753700214710ustar00rootroot00000000000000#pragma once #include #include #include #include namespace sfmdataentity { /** * @brief Handle Alembic IO in a separate thread. */ class IOThread : public QThread { Q_OBJECT public: /// Read the given source. Starts the thread main loop. void read(const QUrl& source); /// Thread main loop. void run() override; /// Reset internal members. void clear(); /// Get the sfmData const aliceVision::sfmData::SfMData& getSfmData() const; private: QUrl _source; mutable QMutex _mutex; aliceVision::sfmData::SfMData _sfmData; }; } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/PointCloudEntity.cpp000066400000000000000000000061621506573753700233040ustar00rootroot00000000000000#include "PointCloudEntity.hpp" #include #include #include #include namespace sfmdataentity { PointCloudEntity::PointCloudEntity(Qt3DCore::QNode* parent) : Qt3DCore::QEntity(parent) {} void PointCloudEntity::setData(const aliceVision::sfmData::Landmarks& landmarks) { using namespace Qt3DRender; using namespace Qt3DCore; // create a new geometry renderer auto customMeshRenderer = new QGeometryRenderer; auto customGeometry = new QGeometry; std::vector points; std::vector colors; for (const auto& l : landmarks) { points.push_back(static_cast(l.second.X(0))); points.push_back(static_cast(-l.second.X(1))); points.push_back(static_cast(-l.second.X(2))); colors.push_back(static_cast(l.second.rgb(0) / 255.0f)); colors.push_back(static_cast(l.second.rgb(1) / 255.0f)); colors.push_back(static_cast(l.second.rgb(2) / 255.0f)); } int npoints = static_cast(landmarks.size()); // vertices buffer QByteArray positionData(reinterpret_cast(points.data()), npoints * 3 * static_cast(sizeof(float))); auto vertexDataBuffer = new QBuffer; vertexDataBuffer->setData(positionData); auto positionAttribute = new QAttribute; positionAttribute->setAttributeType(QAttribute::VertexAttribute); positionAttribute->setBuffer(vertexDataBuffer); positionAttribute->setVertexBaseType(QAttribute::Float); positionAttribute->setVertexSize(3); positionAttribute->setByteOffset(0); positionAttribute->setByteStride(3 * sizeof(float)); positionAttribute->setCount(static_cast(npoints)); positionAttribute->setName(QAttribute::defaultPositionAttributeName()); customGeometry->addAttribute(positionAttribute); customGeometry->setBoundingVolumePositionAttribute(positionAttribute); // read color data auto colorDataBuffer = new QBuffer; QByteArray colorData(reinterpret_cast(colors.data()), npoints * 3 * static_cast(sizeof(float))); colorDataBuffer->setData(colorData); // colors buffer auto colorAttribute = new QAttribute; colorAttribute->setAttributeType(QAttribute::VertexAttribute); colorAttribute->setBuffer(colorDataBuffer); colorAttribute->setVertexBaseType(QAttribute::Float); colorAttribute->setVertexSize(3); colorAttribute->setByteOffset(0); colorAttribute->setByteStride(3 * sizeof(float)); colorAttribute->setCount(static_cast(npoints)); colorAttribute->setName(QAttribute::defaultColorAttributeName()); customGeometry->addAttribute(colorAttribute); // geometry renderer settings customMeshRenderer->setInstanceCount(1); customMeshRenderer->setFirstVertex(0); customMeshRenderer->setFirstInstance(0); customMeshRenderer->setPrimitiveType(QGeometryRenderer::Points); customMeshRenderer->setGeometry(customGeometry); customMeshRenderer->setVertexCount(npoints); // add components addComponent(customMeshRenderer); } } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/PointCloudEntity.hpp000066400000000000000000000006051506573753700233050ustar00rootroot00000000000000#pragma once #include #include namespace sfmdataentity { class PointCloudEntity : public Qt3DCore::QEntity { Q_OBJECT public: explicit PointCloudEntity(Qt3DCore::QNode* = nullptr); ~PointCloudEntity() override = default; void setData(const aliceVision::sfmData::Landmarks& landmarks); }; } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/SfmDataEntity.cpp000066400000000000000000000247231506573753700225460ustar00rootroot00000000000000#include "SfmDataEntity.hpp" #include "IOThread.hpp" #include "CameraLocatorEntity.hpp" #include "PointCloudEntity.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace sfmdataentity { SfmDataEntity::SfmDataEntity(Qt3DCore::QNode* parent) : Qt3DCore::QEntity(parent), _fixedPointSizeParameter(new Qt3DRender::QParameter), _pointSizeParameter(new Qt3DRender::QParameter), _ioThread(new IOThread()) { connect(_ioThread.get(), &IOThread::finished, this, &SfmDataEntity::onIOThreadFinished); createMaterials(); } void SfmDataEntity::setSource(const QUrl& value) { if (_source == value) return; _source = value; loadSfmData(); Q_EMIT sourceChanged(); } void SfmDataEntity::setFixedPointSize(const bool& value) { if (_fixedPointSize == value) { return; } _fixedPointSize = value; _fixedPointSizeParameter->setValue(value); Q_EMIT fixedPointSizeChanged(); } void SfmDataEntity::setPointSize(const float& value) { if (_pointSize == value) { return; } _pointSize = value; _pointSizeParameter->setValue(value); _cloudMaterial->setEnabled(_pointSize > 0.0f); Q_EMIT pointSizeChanged(); } void SfmDataEntity::setLocatorScale(const float& value) { if (_locatorScale == value) { return; } _locatorScale = value; scaleLocators(); Q_EMIT locatorScaleChanged(); } void SfmDataEntity::scaleLocators() const { for (auto* entity : _cameras) { for (auto* transform : entity->findChildren(QString(), Qt::FindDirectChildrenOnly)) { if (entity->viewId() == _selectedViewId) { entity->updateColors(0.f, 0.f, 1.f); transform->setScale(_locatorScale * 1.5f); } else { entity->updateColors(1.f, 1.f, 1.f); transform->setScale(_locatorScale); } } } } void SfmDataEntity::setSelectedViewId(const aliceVision::IndexT& viewId) { if (_selectedViewId == viewId) { return; } bool previousReset = _selectedViewId == 0 ? true : false; bool newUpdated = false; for (auto* entity : _cameras) { if (entity->viewId() == _selectedViewId) // Previously selected camera: the scale must be reset { entity->updateColors(1.f, 1.f, 1.f); for (auto* transform : entity->findChildren(QString(), Qt::FindDirectChildrenOnly)) { transform->setScale(_locatorScale); } previousReset = true; } else if (entity->viewId() == viewId) // Newly selected camera: the scale must be enlarged { entity->updateColors(0.f, 0.f, 1.f); for (auto* transform : entity->findChildren(QString(), Qt::FindDirectChildrenOnly)) { transform->setScale(_locatorScale * 1.5f); } newUpdated = true; } if (previousReset && newUpdated) { break; } } _selectedViewId = viewId; Q_EMIT selectedViewIdChanged(); } void SfmDataEntity::setResectionId(const aliceVision::IndexT& value) { if (_resectionId == value) { return; } _resectionId = value; for (auto* entity : _cameras) { if (entity->resectionId() > _resectionId && _displayResections) { entity->setEnabled(false); } else { entity->setEnabled(true); } } Q_EMIT resectionIdChanged(); } void SfmDataEntity::setDisplayResections(const bool value) { if (_displayResections == value) { return; } _displayResections = value; // Re-enable all cameras both when the display of the resections is enabled and when it is disabled for (auto* entity : _cameras) { entity->setEnabled(true); } } void SfmDataEntity::createMaterials() { using namespace Qt3DRender; using namespace Qt3DExtras; _cloudMaterial = new QMaterial(this); _cameraMaterial = new QPerVertexColorMaterial(this); // Configure cloud material auto effect = new QEffect(); auto technique = new QTechnique(); auto renderPass = new QRenderPass(); auto shaderProgram = new QShaderProgram(); technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::RHI); technique->graphicsApiFilter()->setMajorVersion(1); technique->graphicsApiFilter()->setMinorVersion(0); technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::CoreProfile); shaderProgram->setShaderCode(QShaderProgram::Vertex, R"(#version 450 layout(location = 0) in vec3 vertexPosition; layout(location = 1) in vec3 vertexColor; layout(location = 0) out vec3 color; layout(std140, binding = 0) uniform qt3d_render_view_uniforms { mat4 viewMatrix; mat4 projectionMatrix; mat4 uncorrectedProjectionMatrix; mat4 clipCorrectionMatrix; mat4 viewProjectionMatrix; mat4 inverseViewMatrix; mat4 inverseProjectionMatrix; mat4 inverseViewProjectionMatrix; mat4 viewportMatrix; mat4 inverseViewportMatrix; vec4 textureTransformMatrix; vec3 eyePosition; float aspectRatio; float gamma; float exposure; float time; }; layout(std140, binding = 1) uniform qt3d_command_uniforms { mat4 modelMatrix; mat4 inverseModelMatrix; mat4 modelViewMatrix; mat3 modelNormalMatrix; mat4 inverseModelViewMatrix; mat4 mvp; mat4 inverseModelViewProjectionMatrix; }; layout(std140, binding = 2) uniform custom_ubo { float pointSize; bool fixedPointSize; }; void main() { color = vertexColor; gl_Position = mvp * vec4(vertexPosition, 1.0); if (fixedPointSize) { gl_PointSize = pointSize; } else { gl_PointSize = max(viewportMatrix[1][1] * projectionMatrix[1][1] * pointSize * 0.01 / gl_Position.w, 1.0); } //If pointsize is not strictly positive, the point is assigned //A value outside of the OpenGL frustrum cube. //Therefore, it will be automatically discarded by the fixed pipeline. if (pointSize <= 0.0) { gl_Position = vec4(10.0, 10.0, 10.0, 1.0); } } )"); shaderProgram->setShaderCode(QShaderProgram::Fragment, R"(#version 450 layout(location = 0) out vec4 fragColor; layout(location = 0) in vec3 color; void main() { fragColor = vec4(color, 1.0); } )"); // Add a fixedPointSize uniform _fixedPointSizeParameter->setName("fixedPointSize"); _fixedPointSizeParameter->setValue(_fixedPointSize); _cloudMaterial->addParameter(_fixedPointSizeParameter); // Add a pointSize uniform _pointSizeParameter->setName("pointSize"); _pointSizeParameter->setValue(_pointSize); _cloudMaterial->addParameter(_pointSizeParameter); // Build the material renderPass->setShaderProgram(shaderProgram); technique->addRenderPass(renderPass); effect->addTechnique(technique); _cloudMaterial->setEffect(effect); } void SfmDataEntity::clear() { // Clear entity (remove direct children & all components) auto entities = findChildren(QString(), Qt::FindDirectChildrenOnly); for (auto entity : entities) { entity->setParent(static_cast(nullptr)); entity->deleteLater(); } for (auto& component : components()) { removeComponent(component); } _cameras.clear(); _pointClouds.clear(); } // Private void SfmDataEntity::loadSfmData() { clear(); if (_source.isEmpty()) { setStatus(SfmDataEntity::None); return; } setStatus(SfmDataEntity::Loading); _ioThread->read(_source); } void SfmDataEntity::onIOThreadFinished() { const auto& sfmData = _ioThread->getSfmData(); if (sfmData == aliceVision::sfmData::SfMData()) { qCritical() << "[QmlSfmData] The SfMData has not been correctly initialized, the file may not be valid."; setStatus(SfmDataEntity::Error); return; } else if (sfmData.getLandmarks().empty() && sfmData.getPoses().empty()) { qCritical() << "[QmlSfmData] The SfMData has been initialized but does not contain any 3D information."; setStatus(SfmDataEntity::Error); return; } else { Qt3DCore::QEntity* root = new Qt3DCore::QEntity(this); { PointCloudEntity* entity = new PointCloudEntity(root); entity->setData(sfmData.getLandmarks()); entity->addComponent(_cloudMaterial); } for (const auto& pv : sfmData.getViews()) { if (!sfmData.isPoseAndIntrinsicDefined(pv.second.get())) { continue; } aliceVision::IndexT intrinsicId = pv.second->getIntrinsicId(); auto intrinsic = sfmData.getIntrinsicSharedPtr(intrinsicId); float hfov = static_cast(intrinsic->getHorizontalFov()); float vfov = static_cast(intrinsic->getVerticalFov()); CameraLocatorEntity* entity = new CameraLocatorEntity(pv.first, pv.second->getResectionId(), hfov, vfov, root); entity->addComponent(_cameraMaterial); entity->setTransform(sfmData.getPose(*pv.second).getTransform().getHomogeneous()); entity->setObjectName(std::to_string(pv.first).c_str()); if (entity->resectionId() > _resectionId && _displayResections) { entity->setEnabled(false); } } _cameras = findChildren(); _pointClouds = findChildren(); scaleLocators(); setStatus(SfmDataEntity::Ready); } _ioThread->clear(); Q_EMIT pointCloudsChanged(); Q_EMIT camerasChanged(); } } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/SfmDataEntity.hpp000066400000000000000000000104531506573753700225460ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include namespace sfmdataentity { class PointCloudEntity; class CameraLocatorEntity; class IOThread; class SfmDataEntity : public Qt3DCore::QEntity { Q_OBJECT Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(bool skipHidden MEMBER _skipHidden NOTIFY skipHiddenChanged) Q_PROPERTY(bool fixedPointSize READ fixedPointSize WRITE setFixedPointSize NOTIFY fixedPointSizeChanged) Q_PROPERTY(float pointSize READ pointSize WRITE setPointSize NOTIFY pointSizeChanged) Q_PROPERTY(float locatorScale READ locatorScale WRITE setLocatorScale NOTIFY locatorScaleChanged) Q_PROPERTY(QQmlListProperty cameras READ cameras NOTIFY camerasChanged) Q_PROPERTY(QQmlListProperty pointClouds READ pointClouds NOTIFY pointCloudsChanged) Q_PROPERTY(quint32 selectedViewId READ selectedViewId WRITE setSelectedViewId NOTIFY selectedViewIdChanged) Q_PROPERTY(quint32 resectionId READ resectionId WRITE setResectionId NOTIFY resectionIdChanged) Q_PROPERTY(bool displayResections READ displayResections WRITE setDisplayResections NOTIFY displayResectionsChanged) Q_PROPERTY(Status status READ status NOTIFY statusChanged) public: // Identical to SceneLoader.Status enum Status { None = 0, Loading, Ready, Error }; Q_ENUM(Status) explicit SfmDataEntity(Qt3DCore::QNode* = nullptr); ~SfmDataEntity() override = default; Q_SLOT const QUrl& source() const { return _source; } Q_SLOT bool fixedPointSize() const { return _fixedPointSize; } Q_SLOT float pointSize() const { return _pointSize; } Q_SLOT float locatorScale() const { return _locatorScale; } Q_SLOT aliceVision::IndexT selectedViewId() const { return _selectedViewId; } Q_SLOT aliceVision::IndexT resectionId() const { return _resectionId; } Q_SLOT bool displayResections() const { return _displayResections; } Q_SLOT void setSource(const QUrl& source); Q_SLOT void setFixedPointSize(const bool& value); Q_SLOT void setPointSize(const float& value); Q_SLOT void setLocatorScale(const float& value); Q_SLOT void setSelectedViewId(const aliceVision::IndexT& viewId); Q_SLOT void setResectionId(const aliceVision::IndexT& value); Q_SLOT void setDisplayResections(const bool value); Status status() const { return _status; } void setStatus(Status status) { if (status == _status) return; _status = status; Q_EMIT statusChanged(_status); } Q_SIGNAL void sourceChanged(); Q_SIGNAL void camerasChanged(); Q_SIGNAL void fixedPointSizeChanged(); Q_SIGNAL void pointSizeChanged(); Q_SIGNAL void pointCloudsChanged(); Q_SIGNAL void locatorScaleChanged(); Q_SIGNAL void objectPicked(Qt3DCore::QTransform* transform); Q_SIGNAL void statusChanged(Status status); Q_SIGNAL void skipHiddenChanged(); Q_SIGNAL void selectedViewIdChanged(); Q_SIGNAL void resectionIdChanged(); Q_SIGNAL void displayResectionsChanged(); protected: /// Scale child locators void scaleLocators() const; void onIOThreadFinished(); private: /// Delete all child entities/components void clear(); void loadSfmData(); void createMaterials(); QQmlListProperty cameras() { return {this, &_cameras}; } QQmlListProperty pointClouds() { return {this, &_pointClouds}; } Status _status = SfmDataEntity::None; QUrl _source; bool _skipHidden = false; bool _fixedPointSize = false; float _pointSize = 0.5f; float _locatorScale = 1.0f; aliceVision::IndexT _selectedViewId = 0; aliceVision::IndexT _resectionId = 0; bool _displayResections = false; Qt3DRender::QParameter* _fixedPointSizeParameter; Qt3DRender::QParameter* _pointSizeParameter; Qt3DRender::QMaterial* _cloudMaterial; Qt3DRender::QMaterial* _cameraMaterial; QList _cameras; QList _pointClouds; std::unique_ptr _ioThread; }; } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/plugin.hpp000066400000000000000000000014371506573753700213320ustar00rootroot00000000000000#pragma once #include #include namespace sfmdataentity { class sfmDataEntityQmlPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "sfmDataEntity.qmlPlugin") public: void initializeEngine(QQmlEngine*, const char*) override {} void registerTypes(const char* uri) override { Q_ASSERT(uri == QLatin1String("SfmDataEntity")); qmlRegisterType(uri, 1, 0, "SfmDataEntity"); qmlRegisterUncreatableType(uri, 1, 0, "CameraLocatorEntity", "Cannot create CameraLocatorEntity instances from QML."); qmlRegisterUncreatableType(uri, 1, 0, "PointCloudEntity", "Cannot create PointCloudEntity instances from QML."); } }; } // namespace sfmdataentity QtAliceVision-2025.1.1/src/qmlSfmData/qmldir000066400000000000000000000000641506573753700205310ustar00rootroot00000000000000module SfmDataEntity plugin sfmDataEntityQmlPlugin QtAliceVision-2025.1.1/src/qtAliceVision/000077500000000000000000000000001506573753700200375ustar00rootroot00000000000000QtAliceVision-2025.1.1/src/qtAliceVision/AsyncFetcher.cpp000066400000000000000000000147531506573753700231330ustar00rootroot00000000000000#include "AsyncFetcher.hpp" #include #include #include #include using namespace aliceVision; namespace qtAliceVision { namespace imgserve { AsyncFetcher::AsyncFetcher() { _resizeRatio = 0.001; _isAsynchronous = false; _isPrefetching = false; _requestSynchronous = false; } AsyncFetcher::~AsyncFetcher() {} void AsyncFetcher::setSequence(const std::vector& paths) { // Sequence can't be changed while thread is running if (_isAsynchronous) { return; } _sequence = paths; _currentIndex = 0; for (unsigned idx = 0; idx < _sequence.size(); idx++) { _pathToSeqId[_sequence[idx]] = idx; } } void AsyncFetcher::setResizeRatio(double ratio) { QMutexLocker locker(&_mutexResizeRatio); _resizeRatio = ratio; } void AsyncFetcher::setPrefetching(bool prefetch) { _isPrefetching = prefetch; if (_isPrefetching) { // Make sure we're not waiting for new source _semLoop.release(1); } } bool AsyncFetcher::getPrefetching() { return _isPrefetching; } void AsyncFetcher::setCache(ImageCache::uptr&& cache) { // Cache can't be changed while thread is running if (_isAsynchronous) { return; } _cache = std::move(cache); } void AsyncFetcher::run() { using namespace std::chrono_literals; _isAsynchronous = true; _requestSynchronous = false; std::size_t previousCacheSize = getDiskLoads(); while (1) { if (_requestSynchronous) { _requestSynchronous = false; break; } // Lock the thread until someone ask something if (!_semLoop.tryAcquire(1, QDeadlineTimer(1s))) { continue; } if (_sequence.size() == 0) { continue; } const std::string& lpath = _sequence[static_cast(_currentIndex)]; // Load in cache if (_cache) { double ratio; { QMutexLocker locker(&_mutexResizeRatio); ratio = _resizeRatio; } _cache->get(lpath, static_cast(_currentIndex), ratio, false); } if (_isPrefetching) { _currentIndex++; int size = static_cast(_sequence.size()); if (_currentIndex >= size) { _currentIndex = 0; } _semLoop.release(1); } std::this_thread::sleep_for(1ms); std::size_t cacheSize = getDiskLoads(); if (cacheSize != previousCacheSize) { previousCacheSize = cacheSize; Q_EMIT onAsyncFetchProgressed(); } } _requestSynchronous = false; _isAsynchronous = false; } void AsyncFetcher::stopAsync() { _requestSynchronous = true; } void AsyncFetcher::updateCacheMemory(std::size_t maxMemory) { if (_cache) { _cache->updateMaxMemory(maxMemory); } } std::size_t AsyncFetcher::getCacheMemory() { return (_cache)?_cache->getMaxMemory():0; } std::size_t AsyncFetcher::getCacheSize() const { return (_cache) ? static_cast(_cache->info().getContentSize()) : 0; } std::size_t AsyncFetcher::getDiskLoads() const { return (_cache) ? static_cast(_cache->info().getLoadFromDisk()) : 0; } QVariantList AsyncFetcher::getCachedFrames() const { QVariantList intervals; if (!_cache) { return intervals; } // Accumulator variables auto region = std::make_pair(-1, -1); bool regionOpen = false; size_t size = _sequence.size(); { // Build cached frames intervals for (std::size_t i = 0; i < size; ++i) { const int frame = static_cast(i); // Check if current frame is in cache if (_cache->contains(_sequence[i], _resizeRatio)) { // Either grow currently open region or create a new region if (regionOpen) { region.second = frame; } else { region.first = frame; region.second = frame; regionOpen = true; } } else { // Close currently open region if (regionOpen) { intervals.append(QPoint(region.first, region.second)); regionOpen = false; } } } } // Last region may still be open if (regionOpen) { intervals.append(QPoint(region.first, region.second)); } return intervals; } bool AsyncFetcher::getFrame(const std::string& path, std::shared_ptr>& image, oiio::ParamValueList& metadatas, size_t& originalWidth, size_t& originalHeight, bool& missingFile, bool& loadingError) { // Need a cache if (!_cache) { return false; } // Do we only lookup in the cache or do we allow to load on disk immediately bool onlyCache = _isAsynchronous; // Upgrade the thread with the current Index for (std::size_t idx = 0; idx < _sequence.size(); ++idx) { if (_sequence[idx] == path) { _currentIndex = static_cast(idx); break; } } // Try to find in the cache std::optional ovalue = _cache->get(path, _currentIndex, _resizeRatio, onlyCache); if (ovalue.has_value()) { auto& value = ovalue.value(); image = value.get(); oiio::ParamValueList copy_metadatas = value.getMetadatas(); metadatas = copy_metadatas; originalWidth = value.getOriginalWidth(); originalHeight = value.getOriginalHeight(); missingFile = value.isFileMissing(); loadingError = value.hadErrorOnLoad(); if (image) { _cache->setReferenceFrameId(_currentIndex); } return true; } else { // If there is no cache, then poke the fetch thread _semLoop.release(1); } return false; } } // namespace imgserve } // namespace qtAliceVision #include "AsyncFetcher.moc" QtAliceVision-2025.1.1/src/qtAliceVision/AsyncFetcher.hpp000066400000000000000000000100211506573753700231200ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "ImageCache.hpp" namespace qtAliceVision { namespace imgserve { class AsyncFetcher : public QObject, public QRunnable { Q_OBJECT public: AsyncFetcher(); ~AsyncFetcher(); /** * @brief Cache object is created externally. * Pass it to the Fetcher for use (Fetcher get ownership) * @param cache the cache object to store */ void setCache(ImageCache::uptr&& cache); /** * @brief set the image sequence * The image sequence is a list of image paths which is ordered * The Fetcher must not be in asynchronous mode for this function to work * As such, the _sequence object is only used in read mode during async mode. */ void setSequence(const std::vector& paths); /** * @brief update the resize ratio of the image * @param ratio the coefficient of resize of the loaded images */ void setResizeRatio(double ratio); /** * @brief Do we enable prefetching ? Means that we are prefetching next frames * in the sequence before asked. * @param prefetch true if prefetching is activated */ void setPrefetching(bool prefetch); /** * @brief Do we enable prefetching ? Means that we are prefetching next frames * in the sequence before asked. * @return true if prefetching is activated */ bool getPrefetching(); /** * @brief retrieve a frame from the cache in both sync and async mode * @param path the image path which should be contained in _sequence. * @param image the result image pointer * @param metadatas the image metadatas found in the file * @param originalWidth the image width before the resize * @param originalHeight the image height before the resize * @param missingFile the image cache entry indicates a missing file * @param loadingError the image cache entry indicates a file loading error * @return true if the image was succesfully found in the cache */ bool getFrame(const std::string& path, std::shared_ptr>& image, oiio::ParamValueList& metadatas, std::size_t& originalWidth, std::size_t& originalHeight, bool& missingFile, bool& loadingError); /** * @brief Internal function for QT to start the asynchronous mode */ Q_SLOT void run() override; /** * @brief stop asynchronous mode * The caller have to wait on the thread pool to guarantee the effective end */ void stopAsync(); bool isAsync() const { return _isAsynchronous; } /** * @brief get the cache content size in bytes * @return the cache content size in bytes */ std::size_t getCacheSize() const; /** * @brief get the number of images loaded * @return the count of images loaded since the creation of the cache object */ std::size_t getDiskLoads() const; /** * @brief update maxMemory for the cache * @param maxMemory the number of bytes allowed in the cache */ void updateCacheMemory(std::size_t maxMemory); /** * @brief update maxMemory for the cache * @return the number of bytes allowed in the cache */ std::size_t getCacheMemory(); /** * @brief get a list of regions containing the image frames * @return a list of two values (begin, end) */ QVariantList getCachedFrames() const; public: Q_SIGNAL void onAsyncFetchProgressed(); private: ImageCache::uptr _cache; std::vector _sequence; std::unordered_map _pathToSeqId; QAtomicInt _currentIndex; QAtomicInt _isAsynchronous; QAtomicInt _isPrefetching; QAtomicInt _requestSynchronous; double _resizeRatio; QMutex _mutexResizeRatio; QSemaphore _semLoop; }; } // namespace imgserve } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/AttributeItemDelegate.frag000066400000000000000000000006131506573753700251150ustar00rootroot00000000000000#version 440 precision highp float; layout(location = 0) in vec2 qt_TexCoord0; layout(location = 0) out vec4 fragColor; vec3 hsv2rgb(vec3 c) { vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } void main() { fragColor = vec4(hsv2rgb(vec3(qt_TexCoord0.x, 1.0, 1.0)), 1.0); }QtAliceVision-2025.1.1/src/qtAliceVision/CMakeLists.txt000066400000000000000000000051011506573753700225740ustar00rootroot00000000000000# Target srcs set(PLUGIN_SOURCES FeaturesViewer.cpp MFeatures.cpp MSfMData.cpp MViewStats.cpp MTracks.cpp FloatImageViewer.cpp FloatTexture.cpp Surface.cpp MSfMDataStats.cpp PanoramaViewer.cpp PhongImageViewer.cpp Painter.cpp SequenceCache.cpp SingleImageLoader.cpp ImageCache.cpp AsyncFetcher.cpp ) set(PLUGIN_HEADERS plugin.hpp FeaturesViewer.hpp MFeatures.hpp MSfMData.hpp MTracks.hpp MViewStats.hpp FloatImageViewer.hpp FloatTexture.hpp MSfMDataStats.hpp PanoramaViewer.hpp PhongImageViewer.hpp Surface.hpp Painter.hpp ImageServer.hpp SequenceCache.hpp SingleImageLoader.hpp ImageCache.hpp AsyncFetcher.hpp ) set(PLUGIN_MOCS FeaturesViewer.hpp MFeatures.hpp MSfMData.hpp MViewStats.hpp MTracks.hpp MSfMDataStats.hpp SequenceCache.hpp SingleImageLoader.hpp AsyncFetcher.hpp ) # Qt module dependency find_package(Qt6 COMPONENTS Quick REQUIRED) find_package(Qt6 COMPONENTS Qml REQUIRED) find_package(Qt6 COMPONENTS Charts REQUIRED) find_package(Qt6 REQUIRED COMPONENTS Gui) find_package(Qt6 COMPONENTS ShaderTools) # Target properties add_library(qtAliceVisionPlugin SHARED ${PLUGIN_SOURCES} ${PLUGIN_HEADERS} ${PLUGIN_MOCS}) if(MSVC) target_compile_options(qtAliceVisionPlugin PUBLIC /W4) else() target_compile_options(qtAliceVisionPlugin PUBLIC -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wpedantic) endif() find_package(Qt6 COMPONENTS ShaderTools) qt6_add_shaders(qtAliceVisionPlugin "qtAliceVisionPlugin_shaders" BATCHABLE PRECOMPILE OPTIMIZED PREFIX "/shaders" FILES FloatImageViewer.vert FloatImageViewer.frag FeaturesViewer.vert FeaturesViewer.frag PhongImageViewer.vert PhongImageViewer.frag ImageOverlay.frag AttributeItemDelegate.frag ) target_link_libraries(qtAliceVisionPlugin PUBLIC aliceVision_feature aliceVision_sfmDataIO aliceVision_sfm aliceVision_system aliceVision_image Qt6::Core Qt6::Qml Qt6::Quick Qt6::Charts Qt6::GuiPrivate ) set_target_properties(qtAliceVisionPlugin PROPERTIES DEBUG_POSTFIX "" FOLDER "qtAliceVisionPlugin" SOVERSION ${PROJECT_VERSION_MAJOR} VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" ) # Install settings install(FILES "qmldir" DESTINATION ${CMAKE_INSTALL_PREFIX}/qml/AliceVision) install(TARGETS qtAliceVisionPlugin DESTINATION "${CMAKE_INSTALL_PREFIX}/qml/AliceVision") QtAliceVision-2025.1.1/src/qtAliceVision/FeaturesViewer.cpp000066400000000000000000000717551506573753700235220ustar00rootroot00000000000000#include "FeaturesViewer.hpp" #include #include #include #include #include #include #include #include namespace qtAliceVision { FeaturesViewer::FeaturesViewer(QQuickItem* parent) : QQuickItem(parent) { setFlag(QQuickItem::ItemHasContents, true); // trigger display repaint events connect(this, &FeaturesViewer::displayFeaturesChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::displayTracksChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::displayMatchesChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::displayLandmarksChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::featureDisplayModeChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::trackDisplayModeChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::featureMinScaleFilterChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::featureMaxScaleFilterChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::display3dTracksChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::trackContiguousFilterChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::trackInliersFilterChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::displayTrackEndpointsChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::featureColorChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::matchColorChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::landmarkColorChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::currentViewIdChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::enableTimeWindowChanged, this, &FeaturesViewer::update); connect(this, &FeaturesViewer::timeWindowChanged, this, &FeaturesViewer::update); // trigger data repaint events connect(this, &FeaturesViewer::describerTypeChanged, this, &FeaturesViewer::updateReconstruction); connect(this, &FeaturesViewer::featuresChanged, this, &FeaturesViewer::updateReconstruction); connect(this, &FeaturesViewer::tracksChanged, this, &FeaturesViewer::updateReconstruction); connect(this, &FeaturesViewer::sfmDataChanged, this, &FeaturesViewer::updateReconstruction); connect(this, &FeaturesViewer::reconstructionChanged, this, &FeaturesViewer::update); } FeaturesViewer::~FeaturesViewer() {} void FeaturesViewer::setMFeatures(MFeatures* features) { if (_mfeatures == features) return; if (_mfeatures != nullptr) { disconnect(_mfeatures, SIGNAL(featuresChanged()), this, SIGNAL(featuresChanged())); } _mfeatures = features; if (_mfeatures != nullptr) { connect(_mfeatures, SIGNAL(featuresChanged()), this, SIGNAL(featuresChanged())); } Q_EMIT featuresChanged(); } 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) { if (_msfmdata == sfmData) return; if (_msfmdata != nullptr) { disconnect(_msfmdata, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } _msfmdata = sfmData; if (_msfmdata != nullptr) { connect(_msfmdata, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } Q_EMIT sfmDataChanged(); } void FeaturesViewer::updatePaintFeatures(const PaintParams& params, QSGNode* node) { qDebug() << "[QtAliceVision] FeaturesViewer: Update paint " << _describerType << " features."; if (!_displayFeatures || !params.haveValidFeatures) { // nothing to draw or something is not ready painter.clearLayer(node, "features"); return; } switch (_featureDisplayMode) { case FeaturesViewer::Points: paintFeaturesAsPoints(params, node); break; case FeaturesViewer::Squares: paintFeaturesAsSquares(params, node); break; case FeaturesViewer::OrientedSquares: paintFeaturesAsOrientedSquares(params, node); break; } } void FeaturesViewer::paintFeaturesAsPoints(const PaintParams& params, QSGNode* node) { std::vector points; const auto currentViewFeaturesIt = _mreconstruction.featureDatasPerView.find(_currentViewId); if (currentViewFeaturesIt == _mreconstruction.featureDatasPerView.end()) { return; } const auto& currentViewFeatures = currentViewFeaturesIt->second; for (const auto& feature : currentViewFeatures) { // feature scale filter if (feature.scale > params.maxFeatureScale || feature.scale < params.minFeatureScale) { continue; } points.emplace_back(feature.x, feature.y); } painter.drawPoints(node, "features", points, _featureColor, 6.f); } void FeaturesViewer::paintFeaturesAsSquares(const PaintParams& params, QSGNode* node) { std::vector points; const auto currentViewFeaturesIt = _mreconstruction.featureDatasPerView.find(_currentViewId); if (currentViewFeaturesIt == _mreconstruction.featureDatasPerView.end()) { return; } const auto& currentViewFeatures = currentViewFeaturesIt->second; for (const auto& feature : currentViewFeatures) { // feature scale filter if (feature.scale > params.maxFeatureScale || feature.scale < params.minFeatureScale) { continue; } const float radius = feature.scale; const double diag = 2.0 * feature.scale; QRectF rect(feature.x, feature.y, diag, diag); rect.translate(-radius, -radius); QPointF tl = rect.topLeft(); QPointF tr = rect.topRight(); QPointF br = rect.bottomRight(); QPointF bl = rect.bottomLeft(); points.push_back(tl); points.push_back(tr); points.push_back(br); points.push_back(br); points.push_back(bl); points.push_back(tl); } painter.drawTriangles(node, "features", points, _featureColor); } void FeaturesViewer::paintFeaturesAsOrientedSquares(const PaintParams& params, QSGNode* node) { std::vector points; const auto currentViewFeaturesIt = _mreconstruction.featureDatasPerView.find(_currentViewId); if (currentViewFeaturesIt == _mreconstruction.featureDatasPerView.end()) { return; } const auto& currentViewFeatures = currentViewFeaturesIt->second; for (const auto& feature : currentViewFeatures) { // feature scale filter if (feature.scale > params.maxFeatureScale || feature.scale < params.minFeatureScale) { continue; } const float radius = feature.scale; const double diag = 2.0 * feature.scale; QRectF rect(feature.x, feature.y, diag, diag); rect.translate(-radius, -radius); QPointF tl = rect.topLeft(); QPointF tr = rect.topRight(); QPointF br = rect.bottomRight(); QPointF bl = rect.bottomLeft(); // compute angle: use feature angle and remove self rotation const auto radAngle = -feature.orientation - qDegreesToRadians(rotation()); // generate a QTransform that represent this rotation const auto t = QTransform().translate(feature.x, feature.y).rotateRadians(radAngle).translate(-feature.x, -feature.y); // create lines, each vertice has to be duplicated (A->B, B->C, C->D, D->A) since we use GL_LINES std::vector corners = {t.map(tl), t.map(tr), t.map(br), t.map(bl)}; for (unsigned int k = 0; k < corners.size(); ++k) { points.emplace_back(corners[k]); points.emplace_back(corners[(k + 1) % corners.size()]); } // orientation line: up vector (0, 1) auto o2 = t.map(rect.center() - QPointF(0.0f, radius)); // rotate end point points.emplace_back(rect.center()); points.emplace_back(o2); } painter.drawLines(node, "features", points, _featureColor, 2.f); } void FeaturesViewer::updatePaintTracks(const PaintParams& params, QSGNode* node) { qDebug() << "[QtAliceVision] FeaturesViewer: Update paint " << _describerType << " tracks."; bool valid = (_displayTracks && params.haveValidFeatures && params.haveValidTracks && params.haveValidLandmarks && _msfmdata && _currentViewId != aliceVision::UndefinedIndexT); aliceVision::IndexT currentFrameId = aliceVision::UndefinedIndexT; if (valid) { if (_msfmdata->rawData().getViews().count(_currentViewId)) { const auto& view = _msfmdata->rawData().getView(_currentViewId); currentFrameId = view.getFrameId(); } if (currentFrameId == aliceVision::UndefinedIndexT) { qInfo() << "[QtAliceVision] FeaturesViewer: Unable to update paint " << _describerType << " tracks, can't find current frame id."; valid = false; } } if (!valid) { painter.clearLayer(node, "trackEndpoints"); painter.clearLayer(node, "highlightPoints"); painter.clearLayer(node, "trackLines_gaps"); painter.clearLayer(node, "trackLines_reconstruction_none"); painter.clearLayer(node, "trackLines_reconstruction_partial_outliers"); painter.clearLayer(node, "trackLines_reconstruction_partial_inliers"); painter.clearLayer(node, "trackLines_reconstruction_full"); painter.clearLayer(node, "trackPoints_outliers"); painter.clearLayer(node, "trackPoints_inliers"); return; } std::vector trackEndpoints; std::vector highlightPoints; std::vector trackLines_reconstruction_none; std::vector trackLines_reconstruction_partial_outliers; std::vector trackLines_reconstruction_partial_inliers; std::vector trackLines_reconstruction_full; std::vector trackLines_gaps; std::vector trackPoints_outliers; std::vector trackPoints_inliers; for (const auto& track : _mreconstruction.trackDatas) { // check that track has at least 2 features if (track.elements.size() < 2) { continue; } // apply scale filter if (track.averageScale < params.minFeatureScale || track.averageScale > params.maxFeatureScale) { continue; } // retrieve track reconstruction state const bool trackHasInliers = (track.nbReconstructed > 0); const bool trackFullyReconstructed = (static_cast(track.nbReconstructed) == track.elements.size()); // apply inlier filter if (!trackHasInliers && _trackInliersFilter) { continue; } // check that track frame interval contains current frame const aliceVision::IndexT startFrameId = track.elements.front().frameId; const aliceVision::IndexT endFrameId = track.elements.back().frameId; if (currentFrameId < startFrameId || currentFrameId > endFrameId) { continue; } // utility variables to store data between iterations MReconstruction::FeatureData previousFeature; aliceVision::IndexT previousFrameId = aliceVision::UndefinedIndexT; bool previousFeatureInlier = false; for (const auto& elt : track.elements) { aliceVision::IndexT windowSize = static_cast(_timeWindow); aliceVision::IndexT checkId = std::max(currentFrameId, windowSize); // check that frameId is in timeWindow if enabled if (_enableTimeWindow && (_timeWindow >= 0) && (elt.frameId < checkId - windowSize || elt.frameId > currentFrameId + windowSize)) { continue; } // retrieve feature data const auto& viewFeatures = _mreconstruction.featureDatasPerView.at(elt.viewId); const auto& currentFeature = viewFeatures[elt.featureId]; // retrieve whether or not current feature has a corresponding observation const bool currentFeatureInlier = currentFeature.hasLandmark; if (previousFrameId != aliceVision::UndefinedIndexT) { // The 2 features of the track are contiguous const bool contiguous = (previousFrameId == (elt.frameId - 1)); // The 2 features of the track are resectioning inliers const bool inliers = previousFeatureInlier && currentFeatureInlier; // geometry const QPointF prevPoint = (_display3dTracks && previousFeatureInlier) ? QPointF(previousFeature.rx, previousFeature.ry) : QPointF(previousFeature.x, previousFeature.y); const QPointF curPoint = (_display3dTracks && currentFeatureInlier) ? QPointF(currentFeature.rx, currentFeature.ry) : QPointF(currentFeature.x, currentFeature.y); // track line if (!contiguous) { trackLines_gaps.push_back(prevPoint); trackLines_gaps.push_back(curPoint); } else if (!trackHasInliers && !_trackInliersFilter) { trackLines_reconstruction_none.push_back(prevPoint); trackLines_reconstruction_none.push_back(curPoint); } else if (!trackFullyReconstructed) { if (inliers) { trackLines_reconstruction_partial_inliers.push_back(prevPoint); trackLines_reconstruction_partial_inliers.push_back(curPoint); } else { trackLines_reconstruction_partial_outliers.push_back(prevPoint); trackLines_reconstruction_partial_outliers.push_back(curPoint); } } else if (trackFullyReconstructed) { trackLines_reconstruction_full.push_back(prevPoint); trackLines_reconstruction_full.push_back(curPoint); } // highlight point if (_trackDisplayMode == WithAllMatches || _trackDisplayMode == WithCurrentMatches) { if (previousFrameId == currentFrameId) { highlightPoints.push_back(prevPoint); } else if (elt.frameId == currentFrameId) { highlightPoints.push_back(curPoint); } } // track points // current point if (_trackDisplayMode == WithAllMatches || (elt.frameId == currentFrameId && _trackDisplayMode == WithCurrentMatches)) { if (currentFeatureInlier) { trackPoints_inliers.push_back(curPoint); } else { trackPoints_outliers.push_back(curPoint); } } // first point in time window if ((_trackDisplayMode == WithAllMatches && previousFrameId == startFrameId) || (_trackDisplayMode == WithCurrentMatches && previousFrameId == currentFrameId)) { if (previousFeatureInlier) { trackPoints_inliers.push_back(prevPoint); } else { trackPoints_outliers.push_back(prevPoint); } } if (_displayTrackEndpoints) { // draw global start point if (previousFrameId == startFrameId) { QPointF triangle[] = {QPointF(0, 0), QPointF(-2, 1), QPointF(-2, -1)}; double angle = QLineF(prevPoint, curPoint).angle() - rotation(); auto tr = QTransform().rotate(-angle).scale(10, 10); for (int i = 0; i < 3; ++i) trackEndpoints.push_back(prevPoint + tr.map(triangle[i])); } // draw global end point if (elt.frameId == endFrameId) { QPointF triangle[] = {QPointF(0, 0), QPointF(-2, 1), QPointF(-2, -1)}; double angle = QLineF(curPoint, prevPoint).angle() - rotation(); auto tr = QTransform().rotate(-angle).scale(10, 10); for (int i = 0; i < 3; ++i) trackEndpoints.push_back(curPoint + tr.map(triangle[i])); } } } // frame id is now previous frame id previousFrameId = elt.frameId; previousFeature = currentFeature; previousFeatureInlier = currentFeatureInlier; } } painter.drawTriangles(node, "trackEndpoints", trackEndpoints, _endpointColor); painter.drawPoints(node, "highlightPoints", highlightPoints, QColor(255, 255, 255), 6.f); painter.drawLines(node, "trackLines_reconstruction_none", trackLines_reconstruction_none, _featureColor, 2.f); painter.drawLines(node, "trackLines_reconstruction_partial_outliers", trackLines_reconstruction_partial_outliers, _matchColor, 2.f); painter.drawLines(node, "trackLines_reconstruction_partial_inliers", trackLines_reconstruction_partial_inliers, _landmarkColor, 2.f); painter.drawLines(node, "trackLines_reconstruction_full", trackLines_reconstruction_full, _landmarkColor, 5.f); painter.drawLines(node, "trackLines_gaps", trackLines_gaps, _trackContiguousFilter ? QColor(0, 0, 0, 0) : QColor(50, 50, 50), 2.f); painter.drawPoints(node, "trackPoints_outliers", trackPoints_outliers, _matchColor, 4.f); painter.drawPoints(node, "trackPoints_inliers", trackPoints_inliers, _landmarkColor, 4.f); } void FeaturesViewer::updatePaintMatches(const PaintParams& params, QSGNode* node) { qDebug() << "[QtAliceVision] FeaturesViewer: Update paint " << _describerType << " matches."; if (!_displayMatches || !params.haveValidFeatures || !params.haveValidTracks) { // nothing to draw or something is not ready painter.clearLayer(node, "matches"); return; } std::vector points; const auto currentViewFeaturesIt = _mreconstruction.featureDatasPerView.find(_currentViewId); if (currentViewFeaturesIt == _mreconstruction.featureDatasPerView.end()) { return; } const auto& currentViewFeatures = currentViewFeaturesIt->second; for (const auto& feature : currentViewFeatures) { if (feature.scale > params.maxFeatureScale || feature.scale < params.minFeatureScale) { continue; } if (!feature.hasTrack) { continue; } if (feature.hasLandmark && _displayLandmarks) { continue; } points.emplace_back(feature.x, feature.y); } painter.drawPoints(node, "matches", points, _matchColor, 6.f); } void FeaturesViewer::updatePaintLandmarks(const PaintParams& params, QSGNode* node) { qDebug() << "[QtAliceVision] FeaturesViewer: Update paint " << _describerType << " landmarks."; if (!_displayLandmarks || !params.haveValidFeatures || !params.haveValidLandmarks) { // nothing to draw or something is not ready painter.clearLayer(node, "reprojectionErrors"); painter.clearLayer(node, "landmarks"); return; } std::vector points; std::vector lines; const auto currentViewFeaturesIt = _mreconstruction.featureDatasPerView.find(_currentViewId); if (currentViewFeaturesIt == _mreconstruction.featureDatasPerView.end()) { return; } const auto& currentViewFeatures = currentViewFeaturesIt->second; for (const auto& feature : currentViewFeatures) { if (feature.scale > params.maxFeatureScale || feature.scale < params.minFeatureScale) { continue; } if (!feature.hasLandmark) { continue; } lines.emplace_back(feature.x, feature.y); lines.emplace_back(feature.rx, feature.ry); points.emplace_back(feature.rx, feature.ry); } const QColor reprojectionColor = _landmarkColor.darker(150); painter.drawLines(node, "reprojectionErrors", lines, reprojectionColor, 1.f); painter.drawPoints(node, "landmarks", points, _landmarkColor, 6.f); } void FeaturesViewer::initializePaintParams(PaintParams& params) { params.haveValidFeatures = _mfeatures ? _mfeatures->rawDataPtr() != nullptr && _mfeatures->status() == MFeatures::Ready : false; if (!params.haveValidFeatures) return; params.haveValidTracks = _mtracks ? _mtracks->tracksPtr() != nullptr && _mtracks->status() == MTracks::Ready : false; params.haveValidLandmarks = _msfmdata ? _msfmdata->rawDataPtr() != nullptr && _msfmdata->status() == MSfMData::Ready : false; const float minFeatureScale = _mreconstruction.minFeatureScale; const float difFeatureScale = _mreconstruction.maxFeatureScale - minFeatureScale; params.minFeatureScale = minFeatureScale + std::max(0.f, std::min(1.f, _featureMinScaleFilter)) * difFeatureScale; params.maxFeatureScale = minFeatureScale + std::max(0.f, std::min(1.f, _featureMaxScaleFilter)) * difFeatureScale; } QSGNode* FeaturesViewer::updatePaintNode(QSGNode* oldNode, [[maybe_unused]] QQuickItem::UpdatePaintNodeData* data) { PaintParams params; initializePaintParams(params); // Implementation remarks // Only one QSGGeometryNode containing all geometry needed to draw all features // is created. This is ATM the only solution that scales well and provides // good performance even for +100K feature points. // The number of created vertices varies depending on the selected display mode. QSGNode* node = oldNode; if (!node) node = new QSGNode; updatePaintFeatures(params, node); updatePaintTracks(params, node); updatePaintMatches(params, node); updatePaintLandmarks(params, node); return node; } void FeaturesViewer::updateReconstruction() { // clear previous data _mreconstruction.featureDatasPerView.clear(); _mreconstruction.trackDatas.clear(); _mreconstruction.minFeatureScale = 0.f; _mreconstruction.maxFeatureScale = std::numeric_limits::max(); // check validity of the different data sources bool validFeatures = _mfeatures ? _mfeatures->rawDataPtr() != nullptr && _mfeatures->status() == MFeatures::Ready : false; bool validTracks = _mtracks ? _mtracks->tracksPtr() != nullptr && _mtracks->status() == MTracks::Ready : false; bool validLandmarks = _msfmdata ? _msfmdata->rawDataPtr() != nullptr && _msfmdata->status() == MSfMData::Ready : false; // features if (validFeatures) { const auto& featuresPerViewPerDesc = _mfeatures->rawData(); const auto featuresPerViewIt = featuresPerViewPerDesc.find(_describerType.toStdString()); if (featuresPerViewIt == featuresPerViewPerDesc.end()) { return; } const auto& featuresPerView = featuresPerViewIt->second; float minScale = std::numeric_limits::max(); float maxScale = 0.f; for (const auto& [viewId, features] : featuresPerView) { std::vector featureDatas; for (const auto& feature : features) { MReconstruction::FeatureData data; data.x = feature.x(); data.y = feature.y(); data.rx = data.x; data.ry = data.y; data.scale = feature.scale(); data.orientation = feature.orientation(); data.hasTrack = false; data.hasLandmark = false; featureDatas.push_back(data); minScale = std::min(minScale, data.scale); maxScale = std::max(maxScale, data.scale); } _mreconstruction.featureDatasPerView[viewId] = featureDatas; } _mreconstruction.minFeatureScale = minScale; _mreconstruction.maxFeatureScale = maxScale; } else { return; } // sfmdata if (validLandmarks) { const auto& sfmData = _msfmdata->rawData(); const auto& landmarks = sfmData.getLandmarks(); for (const auto& [_, landmark] : landmarks) { for (const auto& [viewId, observation] : landmark.getObservations()) { const auto featureDatasIt = _mreconstruction.featureDatasPerView.find(viewId); if (featureDatasIt == _mreconstruction.featureDatasPerView.end()) { continue; } auto& featureDatas = featureDatasIt->second; if (observation.getFeatureId() >= featureDatas.size()) { continue; } auto& data = featureDatas[observation.getFeatureId()]; data.hasLandmark = true; if (!sfmData.getViews().count(viewId)) { continue; } const auto& view = sfmData.getView(static_cast(viewId)); if (!sfmData.isPoseAndIntrinsicDefined(&view)) { continue; } const auto& pose = sfmData.getPose(view); const auto& camTransform = pose.getTransform(); const auto& intrinsic = sfmData.getIntrinsicPtr(view.getIntrinsicId()); const aliceVision::Vec2 reprojection = intrinsic->transformProject(camTransform, landmark.X.homogeneous()); data.rx = static_cast(reprojection.x()); data.ry = static_cast(reprojection.y()); } } } // tracks if (validTracks) { const auto& tracks = _mtracks->tracks(); for (const auto& [_, track] : tracks) { if (track.featPerView.size() < 2) { continue; } MReconstruction::TrackData trackData; trackData.averageScale = 0.f; trackData.nbReconstructed = 0; for (const auto& [viewId, trackItem] : track.featPerView) { const auto featureDatasIt = _mreconstruction.featureDatasPerView.find(static_cast(viewId)); if (featureDatasIt == _mreconstruction.featureDatasPerView.end()) { continue; } size_t featureId = trackItem.featureId; auto& featureDatas = featureDatasIt->second; if (featureId >= featureDatas.size()) { continue; } auto& data = featureDatas[featureId]; data.hasTrack = true; trackData.averageScale += data.scale; if (validLandmarks) { MReconstruction::PointwiseTrackData elt; elt.viewId = static_cast(viewId); elt.featureId = static_cast(featureId); const auto& sfmData = _msfmdata->rawData(); if (sfmData.getViews().count(static_cast(viewId))) { const auto& view = sfmData.getView(static_cast(viewId)); elt.frameId = view.getFrameId(); } trackData.elements.push_back(elt); if (data.hasLandmark) { trackData.nbReconstructed++; } } } trackData.averageScale /= static_cast(track.featPerView.size()); std::sort( trackData.elements.begin(), trackData.elements.end(), [](const auto& elt1, const auto& elt2) { return elt1.frameId < elt2.frameId; }); _mreconstruction.trackDatas.push_back(trackData); } } // signal Q_EMIT reconstructionChanged(); } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/FeaturesViewer.frag000066400000000000000000000003021506573753700236330ustar00rootroot00000000000000#version 440 layout(location = 0) out vec4 fragColor; layout(std140, binding = 0) uniform buf { mat4 matrix; //64 vec4 color; float size; }; void main() { fragColor = color; }QtAliceVision-2025.1.1/src/qtAliceVision/FeaturesViewer.hpp000066400000000000000000000224751506573753700235220ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include namespace qtAliceVision { /** * @brief Cached reconstruction data used for drawing by the FeaturesViewer. * * Given a describer type, reconstruction data is organized in two parts: * - for each view, the features data with 3D reconstruction information if there is any * - the tracks, containing track elements ordered by frame number. */ class MReconstruction { public: struct FeatureData { float x, y; float rx, ry; float scale; float orientation; bool hasTrack; bool hasLandmark; }; struct PointwiseTrackData { aliceVision::IndexT frameId = aliceVision::UndefinedIndexT; aliceVision::IndexT viewId = aliceVision::UndefinedIndexT; aliceVision::IndexT featureId = aliceVision::UndefinedIndexT; }; struct TrackData { std::vector elements; float averageScale; int nbReconstructed; }; std::unordered_map> featureDatasPerView; std::vector trackDatas; float minFeatureScale; float maxFeatureScale; }; /** * @brief Display extracted features, matches, tracks and landmarks in 2D. * * The FeaturesViewer uses MFeatures, MTracks and MSfMData to load data from disk * and keeps an MReconstruction instance to cache the reconstruction data * in a way that makes sense for drawing. * * The various display options and filters can be manipulated from QML. * * The FeaturesViewer relies on the Painter class for paiting routines and layer organization. */ class FeaturesViewer : public QQuickItem { Q_OBJECT /// Display properties // Display all the 2D features extracted from the image Q_PROPERTY(bool displayFeatures MEMBER _displayFeatures NOTIFY displayFeaturesChanged) // Display the of the center of the tracks over time Q_PROPERTY(bool displayTracks MEMBER _displayTracks NOTIFY displayTracksChanged) // Display the center of the tracks unvalidated after resection Q_PROPERTY(bool displayMatches MEMBER _displayMatches NOTIFY displayMatchesChanged) // Display the 3D reprojection of the features associated to a landmark Q_PROPERTY(bool displayLandmarks MEMBER _displayLandmarks NOTIFY displayLandmarksChanged) // Feature display mode (see FeatureDisplayMode enum) Q_PROPERTY(FeatureDisplayMode featureDisplayMode MEMBER _featureDisplayMode NOTIFY featureDisplayModeChanged) // Track display mode (see TrackDisplayMode enum) Q_PROPERTY(TrackDisplayMode trackDisplayMode MEMBER _trackDisplayMode NOTIFY trackDisplayModeChanged) // Minimum feature scale to display Q_PROPERTY(float featureMinScaleFilter MEMBER _featureMinScaleFilter NOTIFY featureMinScaleFilterChanged) // Minimum feature scale to display Q_PROPERTY(float featureMaxScaleFilter MEMBER _featureMaxScaleFilter NOTIFY featureMaxScaleFilterChanged) // Display 3d tracks Q_PROPERTY(bool display3dTracks MEMBER _display3dTracks NOTIFY display3dTracksChanged) // Display only contiguous tracks Q_PROPERTY(bool trackContiguousFilter MEMBER _trackContiguousFilter NOTIFY trackContiguousFilterChanged) // Display only tracks with at least one inlier Q_PROPERTY(bool trackInliersFilter MEMBER _trackInliersFilter NOTIFY trackInliersFilterChanged) // Display track endpoints Q_PROPERTY(bool displayTrackEndpoints MEMBER _displayTrackEndpoints NOTIFY displayTrackEndpointsChanged) // Features color Q_PROPERTY(QColor featureColor MEMBER _featureColor NOTIFY featureColorChanged) // Matches color Q_PROPERTY(QColor matchColor MEMBER _matchColor NOTIFY matchColorChanged) // Landmarks color Q_PROPERTY(QColor landmarkColor MEMBER _landmarkColor NOTIFY landmarkColorChanged) // Currenty selected ViewId Q_PROPERTY(quint32 currentViewId MEMBER _currentViewId NOTIFY currentViewIdChanged) // Time window to consider Q_PROPERTY(bool enableTimeWindow MEMBER _enableTimeWindow NOTIFY enableTimeWindowChanged) Q_PROPERTY(int timeWindow MEMBER _timeWindow NOTIFY timeWindowChanged) /// Data properties // Describer type Q_PROPERTY(QString describerType MEMBER _describerType NOTIFY describerTypeChanged) // Pointer to Features Q_PROPERTY(qtAliceVision::MFeatures* mfeatures READ getMFeatures WRITE setMFeatures NOTIFY featuresChanged) // Pointer to Tracks Q_PROPERTY(qtAliceVision::MTracks* mtracks READ getMTracks WRITE setMTracks NOTIFY tracksChanged) // Pointer to SfmData Q_PROPERTY(qtAliceVision::MSfMData* msfmData READ getMSfMData WRITE setMSfMData NOTIFY sfmDataChanged) public: /// Helpers enum FeatureDisplayMode { Points = 0, // Simple points (GL_POINTS) Squares, // Scaled filled squares (GL_TRIANGLES) OrientedSquares // Scaled and oriented squares (GL_LINES) }; Q_ENUM(FeatureDisplayMode) enum TrackDisplayMode { LinesOnly = 0, WithCurrentMatches, WithAllMatches }; Q_ENUM(TrackDisplayMode) struct PaintParams { bool haveValidFeatures = false; bool haveValidTracks = false; bool haveValidLandmarks = false; float minFeatureScale = std::numeric_limits::min(); float maxFeatureScale = std::numeric_limits::max(); }; /// Slots Q_SLOT void updateReconstruction(); /// Signals Q_SIGNAL void displayFeaturesChanged(); Q_SIGNAL void displayTracksChanged(); Q_SIGNAL void displayMatchesChanged(); Q_SIGNAL void displayLandmarksChanged(); Q_SIGNAL void featureDisplayModeChanged(); Q_SIGNAL void trackDisplayModeChanged(); Q_SIGNAL void featureMinScaleFilterChanged(); Q_SIGNAL void featureMaxScaleFilterChanged(); Q_SIGNAL void display3dTracksChanged(); Q_SIGNAL void trackContiguousFilterChanged(); Q_SIGNAL void trackInliersFilterChanged(); Q_SIGNAL void displayTrackEndpointsChanged(); Q_SIGNAL void featureColorChanged(); Q_SIGNAL void matchColorChanged(); Q_SIGNAL void landmarkColorChanged(); Q_SIGNAL void currentViewIdChanged(); Q_SIGNAL void enableTimeWindowChanged(); Q_SIGNAL void timeWindowChanged(); Q_SIGNAL void describerTypeChanged(); Q_SIGNAL void featuresChanged(); Q_SIGNAL void tracksChanged(); Q_SIGNAL void sfmDataChanged(); Q_SIGNAL void reconstructionChanged(); /// Public methods explicit FeaturesViewer(QQuickItem* parent = nullptr); ~FeaturesViewer() override; MFeatures* getMFeatures() { return _mfeatures; } void setMFeatures(MFeatures* features); MTracks* getMTracks() { return _mtracks; } void setMTracks(MTracks* tracks); MSfMData* getMSfMData() { return _msfmdata; } void setMSfMData(MSfMData* sfmData); private: /// Custom QSGNode update QSGNode* updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* data) override; private: void initializePaintParams(PaintParams& params); void updatePaintFeatures(const PaintParams& params, QSGNode* node); void paintFeaturesAsPoints(const PaintParams& params, QSGNode* node); void paintFeaturesAsSquares(const PaintParams& params, QSGNode* node); void paintFeaturesAsOrientedSquares(const PaintParams& params, QSGNode* node); void updatePaintTracks(const PaintParams& params, QSGNode* node); void updatePaintMatches(const PaintParams& params, QSGNode* node); void updatePaintLandmarks(const PaintParams& params, QSGNode* node); bool _displayFeatures = true; bool _displayTracks = false; bool _displayMatches = true; bool _displayLandmarks = true; FeatureDisplayMode _featureDisplayMode = FeaturesViewer::Points; TrackDisplayMode _trackDisplayMode = FeaturesViewer::WithCurrentMatches; float _featureMinScaleFilter = 0.f; float _featureMaxScaleFilter = 1.f; bool _display3dTracks = false; bool _trackContiguousFilter = true; bool _trackInliersFilter = false; bool _displayTrackEndpoints = true; QColor _featureColor = QColor(20, 220, 80); QColor _matchColor = QColor(255, 127, 0); QColor _landmarkColor = QColor(255, 0, 0); QColor _endpointColor = QColor(80, 80, 80); aliceVision::IndexT _currentViewId; bool _enableTimeWindow = false; int _timeWindow = 1; QString _describerType = "sift"; MFeatures* _mfeatures = nullptr; MTracks* _mtracks = nullptr; MSfMData* _msfmdata = nullptr; MReconstruction _mreconstruction; Painter painter = Painter({"features", "trackEndpoints", "highlightPoints", "trackLines_reconstruction_none", "trackLines_reconstruction_partial_outliers", "trackLines_reconstruction_partial_inliers", "trackLines_reconstruction_full", "trackLines_gaps", "trackPoints_outliers", "trackPoints_inliers", "matches", "reprojectionErrors", "landmarks"}); }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/FeaturesViewer.vert000066400000000000000000000003551506573753700237040ustar00rootroot00000000000000#version 440 layout(location = 0) in vec4 vertexCoord; layout(std140, binding = 0) uniform buf { mat4 matrix; //64 vec4 color; float size; }; void main() { gl_Position = matrix * vertexCoord; gl_PointSize = size; }QtAliceVision-2025.1.1/src/qtAliceVision/FloatImageViewer.cpp000066400000000000000000000517301506573753700237430ustar00rootroot00000000000000#include "FloatImageViewer.hpp" #include "FloatTexture.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace qtAliceVision { namespace { class FloatImageViewerMaterialShader; class FloatImageViewerMaterial : public QSGMaterial { public: FloatImageViewerMaterial() { std::shared_ptr image = std::make_shared(1, 1, true); texture = std::make_unique(); texture->setImage(image); texture->setFiltering(QSGTexture::Nearest); texture->setHorizontalWrapMode(QSGTexture::Repeat); texture->setVerticalWrapMode(QSGTexture::Repeat); } QSGMaterialType* type() const override { static QSGMaterialType type; return &type; } int compare(const QSGMaterial* other) const override { Q_ASSERT(other && type() == other->type()); return other == this ? 0 : (other > this ? 1 : -1); } QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; struct { // warning: matches layout and padding of FloatImageViewer.vert/frag shaders QVector4D channelOrder = QVector4D(0, 1, 2, 3); QVector2D fisheyeCircleCoord = QVector2D(0, 0); float gamma = 1.f; float gain = 0.f; float fisheyeCircleRadius = 0.f; float aspectRatio = 0.f; } uniforms; bool dirtyUniforms; bool appliedHoveringGamma; std::unique_ptr texture = std::make_unique(); // should be initialize; }; class FloatImageViewerMaterialShader : public QSGMaterialShader { public: FloatImageViewerMaterialShader() { setShaderFileName(VertexStage, QLatin1String(":/shaders/FloatImageViewer.vert.qsb")); setShaderFileName(FragmentStage, QLatin1String(":/shaders/FloatImageViewer.frag.qsb")); } bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override { bool changed = false; QByteArray* buf = state.uniformData(); Q_ASSERT(buf->size() >= 84); if (state.isMatrixDirty()) { const QMatrix4x4 m = state.combinedMatrix(); memcpy(buf->data() + 0, m.constData(), 64); changed = true; } if (state.isOpacityDirty()) { const float opacity = state.opacity(); memcpy(buf->data() + 64, &opacity, 4); changed = true; } auto* customMaterial = static_cast(newMaterial); if (oldMaterial != newMaterial || customMaterial->dirtyUniforms) { memcpy(buf->data() + 80, &customMaterial->uniforms, 40); customMaterial->dirtyUniforms = false; changed = true; } return changed; } void updateSampledImage(RenderState& state, int binding, QSGTexture** texture, QSGMaterial* newMaterial, QSGMaterial*) override { FloatImageViewerMaterial* mat = static_cast(newMaterial); if (binding == 1) { if (mat->texture) { mat->texture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch()); } *texture = mat->texture.get(); } } }; QSGMaterialShader* FloatImageViewerMaterial::createShader(QSGRendererInterface::RenderMode) const { return new FloatImageViewerMaterialShader; } class FloatImageViewerNode : public QSGGeometryNode { public: FloatImageViewerNode(int vertexCount, int indexCount) { auto* m = new FloatImageViewerMaterial; setMaterial(m); setFlag(OwnsMaterial, true); QSGGeometry* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), vertexCount, indexCount); QSGGeometry::updateTexturedRectGeometry(geometry, QRect(), QRect()); geometry->setDrawingMode(GL_TRIANGLES); geometry->setIndexDataPattern(QSGGeometry::StaticPattern); geometry->setVertexDataPattern(QSGGeometry::StaticPattern); setGeometry(geometry); setFlag(OwnsGeometry, true); { /* Geometry and Material for the Grid */ _gridNode = new QSGGeometryNode; auto gridMaterial = new QSGFlatColorMaterial; { // Vertexcount of the grid is equal to indexCount of the image QSGGeometry* geometryLine = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), indexCount); geometryLine->setDrawingMode(GL_LINES); geometryLine->setLineWidth(2); _gridNode->setGeometry(geometryLine); _gridNode->setFlags(QSGNode::OwnsGeometry); _gridNode->setMaterial(gridMaterial); _gridNode->setFlags(QSGNode::OwnsMaterial); } appendChildNode(_gridNode); } } void setSubdivisions(int vertexCount, int indexCount) { geometry()->allocate(vertexCount, indexCount); markDirty(QSGNode::DirtyGeometry); // Vertexcount of the grid is equal to indexCount of the image _gridNode->geometry()->allocate(indexCount); _gridNode->markDirty(QSGNode::DirtyGeometry); } void updatePaintSurface(Surface& surface, QSize textureSize, int downscaleLevel, bool canBeHovered, bool wasHovered) { // Highlight if (canBeHovered) { if (surface.getMouseOver() && !wasHovered) { auto* m = static_cast(material()); if (!m->appliedHoveringGamma) { setGamma(m->uniforms.gamma + 1.f); m->appliedHoveringGamma = true; } } else if (!surface.getMouseOver() && wasHovered) { auto* m = static_cast(material()); if (m->appliedHoveringGamma) { setGamma(m->uniforms.gamma - 1.f); m->appliedHoveringGamma = false; } } markDirty(QSGNode::DirtyMaterial); } // If vertices has changed, Re-Compute the grid if (surface.hasVerticesChanged()) { // Retrieve Vertices and Index Data QSGGeometry::TexturedPoint2D* vertices = geometry()->vertexDataAsTexturedPoint2D(); quint16* indices = geometry()->indexDataAsUShort(); // Update surface surface.update(vertices, indices, textureSize, downscaleLevel); geometry()->markIndexDataDirty(); geometry()->markVertexDataDirty(); markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); // Fill the Surface vertices array surface.fillVertices(vertices); } // Draw the grid if Distortion Viewer is enabled and Grid Mode is enabled surface.getDisplayGrid() ? surface.computeGrid(_gridNode->geometry()) : surface.removeGrid(_gridNode->geometry()); _gridNode->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); } void setRect(const QRectF& bounds) { QSGGeometry::updateTexturedRectGeometry(geometry(), bounds, QRectF(0, 0, 1, 1)); markDirty(QSGNode::DirtyGeometry); } void setChannelOrder(QVector4D channelOrder) { auto* m = static_cast(material()); m->uniforms.channelOrder = channelOrder; m->dirtyUniforms = true; markDirty(DirtyMaterial); } void setBlending(bool value) { auto* m = static_cast(material()); m->setFlag(QSGMaterial::Blending, value); } void setGamma(float gamma) { auto* m = static_cast(material()); m->uniforms.gamma = gamma; m->dirtyUniforms = true; markDirty(DirtyMaterial); } void setGain(float gain) { auto* m = static_cast(material()); m->uniforms.gain = gain; m->dirtyUniforms = true; markDirty(DirtyMaterial); } void setTexture(std::unique_ptr texture) { auto* m = static_cast(material()); m->texture = std::move(texture); markDirty(DirtyMaterial); } void setGridColor(const QColor& gridColor) { auto* m = static_cast(_gridNode->material()); m->setColor(gridColor); } void setFisheye(float aspectRatio, float fisheyeCircleRadius, QVector2D fisheyeCircleCoord) { auto* m = static_cast(material()); m->uniforms.aspectRatio = aspectRatio; m->uniforms.fisheyeCircleRadius = fisheyeCircleRadius; m->uniforms.fisheyeCircleCoord = fisheyeCircleCoord; m->dirtyUniforms = true; markDirty(DirtyMaterial); } void resetFisheye() { auto* m = static_cast(material()); m->uniforms.fisheyeCircleRadius = 0.f; m->dirtyUniforms = true; markDirty(DirtyMaterial); } private: QSGGeometryNode* _gridNode; }; } // namespace FloatImageViewer::FloatImageViewer(QQuickItem* parent) : QQuickItem(parent) { setFlag(QQuickItem::ItemHasContents, true); // CONNECTS connect(this, &FloatImageViewer::gammaChanged, this, [this] { _gammaChanged = true; update(); }); connect(this, &FloatImageViewer::gainChanged, this, [this] { _gainChanged = true; update(); }); connect(this, &FloatImageViewer::textureSizeChanged, this, &FloatImageViewer::update); connect(this, &FloatImageViewer::sourceSizeChanged, this, &FloatImageViewer::update); connect(this, &FloatImageViewer::imageChanged, this, &FloatImageViewer::update); connect(this, &FloatImageViewer::sourceChanged, this, &FloatImageViewer::reload); connect(this, &FloatImageViewer::channelModeChanged, this, [this] { _channelModeChanged = true; update(); }); connect(this, &FloatImageViewer::downscaleLevelChanged, this, &FloatImageViewer::reload); connect(&_surface, &Surface::gridColorChanged, this, &FloatImageViewer::update); connect(&_surface, &Surface::gridOpacityChanged, this, &FloatImageViewer::update); connect(&_surface, &Surface::displayGridChanged, this, &FloatImageViewer::update); connect(&_surface, &Surface::mouseOverChanged, this, [this] { _mouseOverChanged = true; update(); }); connect(&_surface, &Surface::viewerTypeChanged, this, &FloatImageViewer::update); connect(&_surface, &Surface::subdivisionsChanged, this, &FloatImageViewer::update); connect(&_surface, &Surface::verticesChanged, this, &FloatImageViewer::update); connect(&_singleImageLoader, &imgserve::SingleImageLoader::requestHandled, this, &FloatImageViewer::reload); connect(&_sequenceCache, &imgserve::SequenceCache::requestHandled, this, &FloatImageViewer::reload); connect(this, &FloatImageViewer::useSequenceChanged, this, &FloatImageViewer::reload); connect(this, &FloatImageViewer::sequenceChanged, this, &FloatImageViewer::reload); } FloatImageViewer::~FloatImageViewer() {} void FloatImageViewer::setStatus(EStatus status) { if (_status == status) { return; } _status = status; Q_EMIT statusChanged(); } // LOADING FUNCTIONS void FloatImageViewer::setLoading(bool loading) { if (_loading == loading) { return; } _loading = loading; } void FloatImageViewer::setSequence(const QVariantList& paths) { _sequenceCache.setSequence(paths); Q_EMIT sequenceChanged(); } void FloatImageViewer::setFetchingSequence(bool fetching) { _sequenceCache.setPrefetching(fetching); Q_EMIT fetchingSequenceChanged(); } bool FloatImageViewer::getFetchingSequence() { return _sequenceCache.getPrefetching(); } void FloatImageViewer::setResizeRatio(double ratio) { ratio = std::clamp(ratio, 0.0, 1.0); ratio = std::ceil(ratio * 10.0) / 10.0; _sequenceCache.setResizeRatio(ratio); if (ratio != _clampedResizeRatio) { // If the clamped ratio has changed, then // We may need to reload the image with the correct resolution Q_EMIT sourceChanged(); } _clampedResizeRatio = ratio; Q_EMIT resizeRatioChanged(); } double FloatImageViewer::getResizeRatio() { return _clampedResizeRatio; } void FloatImageViewer::setMemoryLimit(int memoryLimit) { _sequenceCache.setMemoryLimit(memoryLimit); Q_EMIT memoryLimitChanged(); } int FloatImageViewer::getMemoryLimit() { return _sequenceCache.getMemoryLimit(); } QVariantList FloatImageViewer::getCachedFrames() const { return _sequenceCache.getCachedFrames(); } QPointF FloatImageViewer::getRamInfo() const { return _sequenceCache.getRamInfo(); } void FloatImageViewer::reload() { if (_clearBeforeLoad) { _image.reset(); _imageChanged = true; Q_EMIT imageChanged(); } _outdated = false; if (_loading) { _outdated = true; } if (!_source.isValid()) { _image.reset(); _imageChanged = true; _surface.clearVertices(); _surface.verticesChanged(); Q_EMIT imageChanged(); return; } // Send request imgserve::RequestData reqData; reqData.path = _source.toLocalFile().toUtf8().toStdString(); reqData.downscale = 1 << _downscaleLevel; imgserve::ResponseData response = _useSequence ? _sequenceCache.request(reqData) : _singleImageLoader.request(reqData); if (response.img) { setLoading(false); setStatus(EStatus::NONE); _surface.setVerticesChanged(true); _surface.setNeedToUseIntrinsic(true); _image = response.img; _imageChanged = true; Q_EMIT imageChanged(); _sourceSize = response.dim; Q_EMIT sourceSizeChanged(); _metadata = response.metadata; Q_EMIT metadataChanged(); } else if (response.error == imgserve::LoadingStatus::UNDEFINED) { setLoading(true); setStatus(EStatus::LOADING); } else if (response.error == imgserve::LoadingStatus::MISSING_FILE) { _image.reset(); setStatus(EStatus::MISSING_FILE); } else if (response.error == imgserve::LoadingStatus::LOADING_ERROR) { _image.reset(); setStatus(EStatus::LOADING_ERROR); } else if (_outdated) { qWarning() << "[QtAliceVision] FloatImageViewer: The loading status has not been updated since the last reload. Something wrong might have happened."; setStatus(EStatus::OUTDATED_LOADING); } Q_EMIT cachedFramesChanged(); } void FloatImageViewer::playback(bool active) {} void FloatImageViewer::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { QQuickItem::geometryChange(newGeometry, oldGeometry); _geometryChanged = true; } QVector4D FloatImageViewer::pixelValueAt(int x, int y) { if (_useSequence) { x = int(std::ceil(double(x) * _clampedResizeRatio)); y = int(std::ceil(double(y) * _clampedResizeRatio)); } if (!_image) { qDebug() << "[QtAliceVision] FloatImageViewer::pixelValueAt(" << x << ", " << y << ") => no valid image"; return QVector4D(0.0, 0.0, 0.0, 0.0); } else if (x < 0 || x >= _image->width() || y < 0 || y >= _image->height()) { qDebug() << "[QtAliceVision] FloatImageViewer::pixelValueAt(" << x << ", " << y << ") => out of range"; return QVector4D(0.0, 0.0, 0.0, 0.0); } aliceVision::image::RGBAfColor color = (*_image)(y, x); qDebug() << "[QtAliceVision] FloatImageViewer::pixelValueAt(" << x << ", " << y << ") => valid pixel: " << color(0) << ", " << color(1) << ", " << color(2) << ", " << color(3); return QVector4D(color(0), color(1), color(2), color(3)); } QSGNode* FloatImageViewer::updatePaintNode(QSGNode* oldNode, [[maybe_unused]] QQuickItem::UpdatePaintNodeData* data) { auto* node = static_cast(oldNode); bool isNewNode = false; if (!node) { node = new FloatImageViewerNode(_surface.vertexCount(), _surface.indexCount()); isNewNode = true; } else if (_surface.hasSubdivisionsChanged()) { node->setSubdivisions(_surface.vertexCount(), _surface.indexCount()); } node->setGridColor(_surface.getGridColor()); if (_imageChanged) { QSize newTextureSize; auto texture = std::make_unique(); if (_image) { texture->setImage(_image); texture->setFiltering(QSGTexture::Nearest); texture->setHorizontalWrapMode(QSGTexture::Repeat); texture->setVerticalWrapMode(QSGTexture::Repeat); newTextureSize = texture->textureSize(); // Crop the image to only display what is inside the fisheye circle const aliceVision::camera::Equidistant* intrinsicEquidistant = _surface.getIntrinsicEquidistant(); if (_cropFisheye && intrinsicEquidistant) { const aliceVision::Vec3 fisheyeCircleParams( intrinsicEquidistant->getCircleCenterX(), intrinsicEquidistant->getCircleCenterY(), intrinsicEquidistant->getCircleRadius()); const double width = _image->width() * pow(2.0, _downscaleLevel); const double height = _image->height() * pow(2.0, _downscaleLevel); const double aspectRatio = (width > height) ? width / height : height / width; const double radiusInPercentage = (fisheyeCircleParams.z() / ((width > height) ? height : width)) * 2.0; // Radius is converted in uv coordinates (0, 0.5) const double radius = 0.5 * (radiusInPercentage); node->setFisheye( static_cast(aspectRatio), static_cast(radius), QVector2D(static_cast(fisheyeCircleParams.x() / width), static_cast(fisheyeCircleParams.y() / height))); } else { node->resetFisheye(); } } node->setTexture(std::move(texture)); if (_textureSize != newTextureSize) { _textureSize = newTextureSize; _geometryChanged = true; Q_EMIT textureSizeChanged(); } } _imageChanged = false; const auto newBoundingRect = boundingRect(); if (_geometryChanged || _boundingRect != newBoundingRect) { _boundingRect = newBoundingRect; const float windowRatio = static_cast(_boundingRect.width()) / static_cast(_boundingRect.height()); const float textureRatio = static_cast(_textureSize.width()) / static_cast(_textureSize.height()); QRectF geometryRect = _boundingRect; if (windowRatio > textureRatio) { geometryRect.setWidth(geometryRect.height() * textureRatio); } else { geometryRect.setHeight(geometryRect.width() / textureRatio); } geometryRect.moveCenter(_boundingRect.center()); static const int MARGIN = 0; geometryRect = geometryRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN); QSGGeometry::updateTexturedRectGeometry(node->geometry(), geometryRect, QRectF(0, 0, 1, 1)); node->markDirty(QSGNode::DirtyGeometry); } _geometryChanged = false; if (isNewNode || _gammaChanged) { node->setGamma(_gamma); } _gammaChanged = false; if (isNewNode || _gainChanged) { node->setGain(_gain); } _gainChanged = false; if (isNewNode || _channelModeChanged) { QVector4D channelOrder(0.f, 1.f, 2.f, 3.f); switch (_channelMode) { case EChannelMode::R: channelOrder = QVector4D(0.f, 0.f, 0.f, -1.f); break; case EChannelMode::G: channelOrder = QVector4D(1.f, 1.f, 1.f, -1.f); break; case EChannelMode::B: channelOrder = QVector4D(2.f, 2.f, 2.f, -1.f); break; case EChannelMode::A: channelOrder = QVector4D(3.f, 3.f, 3.f, -1.f); break; default: break; } node->setChannelOrder(channelOrder); node->setBlending(_channelMode == EChannelMode::RGBA); } _channelModeChanged = false; if (!isNewNode && _image) { node->updatePaintSurface(_surface, _surface.isPanoramaViewerEnabled() ? _textureSize : _sourceSize, _downscaleLevel, _canBeHovered, !_surface.getMouseOver() && _mouseOverChanged); } _mouseOverChanged = false; return node; } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/FloatImageViewer.frag000066400000000000000000000023151506573753700240730ustar00rootroot00000000000000#version 440 layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 fragColor; layout(std140, binding = 0) uniform buf { // warning: matches layout and padding of FloatImageViewerMaterial::uniforms! mat4 qt_Matrix; // offset 0 float qt_Opacity; // offset 64 float _padding1; float _padding2; float _padding3; vec4 channelOrder; // offset 80 vec2 fisheyeCircleCoord; float gamma; float gain; float fisheyeCircleRadius; float aspectRatio; }; layout(binding = 1) uniform sampler2D tex; void main() { vec4 color = texture(tex, vTexCoord); color.rgb = pow(pow(max(color.rgb * vec3(gain), vec3(0.0, 0.0, 0.0)), vec3(1.0 / gamma)), vec3(1.0 / 2.2)); fragColor.r = color[int(channelOrder[0])]; fragColor.g = color[int(channelOrder[1])]; fragColor.b = color[int(channelOrder[2])]; fragColor.a = int(channelOrder[3]) == -1 ? 1.0 : color[int(channelOrder[3])]; fragColor.a *= qt_Opacity; if (distance(vec2(vTexCoord.x * aspectRatio, vTexCoord.y), vec2(fisheyeCircleCoord.x * aspectRatio, fisheyeCircleCoord.y)) > fisheyeCircleRadius && fisheyeCircleRadius > 0.0) { fragColor.a *= 0.001; } fragColor.rgb *= fragColor.a; }QtAliceVision-2025.1.1/src/qtAliceVision/FloatImageViewer.hpp000066400000000000000000000143761506573753700237550ustar00rootroot00000000000000#pragma once #include "FloatTexture.hpp" #include "Surface.hpp" #include "SequenceCache.hpp" #include "SingleImageLoader.hpp" #include #include #include #include #include #include #include #include #include #include namespace qtAliceVision { /** * @brief Load and display image. */ class FloatImageViewer : public QQuickItem { Q_OBJECT // Q_PROPERTIES Q_PROPERTY(QUrl source MEMBER _source NOTIFY sourceChanged) Q_PROPERTY(float gamma MEMBER _gamma NOTIFY gammaChanged) Q_PROPERTY(float gain MEMBER _gain NOTIFY gainChanged) Q_PROPERTY(bool canBeHovered MEMBER _canBeHovered NOTIFY canBeHoveredChanged) Q_PROPERTY(QSize textureSize MEMBER _textureSize NOTIFY textureSizeChanged) Q_PROPERTY(QSize sourceSize READ sourceSize NOTIFY sourceSizeChanged) Q_PROPERTY(EStatus status READ status NOTIFY statusChanged) Q_PROPERTY(bool clearBeforeLoad MEMBER _clearBeforeLoad NOTIFY clearBeforeLoadChanged) Q_PROPERTY(EChannelMode channelMode MEMBER _channelMode NOTIFY channelModeChanged) Q_PROPERTY(QVariantMap metadata READ metadata NOTIFY metadataChanged) Q_PROPERTY(int downscaleLevel READ getDownscaleLevel WRITE setDownscaleLevel NOTIFY downscaleLevelChanged) Q_PROPERTY(Surface* surface READ getSurfacePtr NOTIFY surfaceChanged) Q_PROPERTY(bool cropFisheye READ getCropFisheye WRITE setCropFisheye NOTIFY isCropFisheyeChanged) Q_PROPERTY(QVariantList sequence MEMBER _sequence WRITE setSequence NOTIFY sequenceChanged) Q_PROPERTY(double resizeRatio READ getResizeRatio WRITE setResizeRatio NOTIFY resizeRatioChanged) Q_PROPERTY(QVariantList cachedFrames READ getCachedFrames NOTIFY cachedFramesChanged) Q_PROPERTY(bool useSequence MEMBER _useSequence NOTIFY useSequenceChanged) Q_PROPERTY(bool fetchingSequence READ getFetchingSequence WRITE setFetchingSequence NOTIFY fetchingSequenceChanged) Q_PROPERTY(int memoryLimit READ getMemoryLimit WRITE setMemoryLimit NOTIFY memoryLimitChanged) Q_PROPERTY(QPointF ramInfo READ getRamInfo NOTIFY cachedFramesChanged) public: explicit FloatImageViewer(QQuickItem* parent = nullptr); ~FloatImageViewer() override; enum class EStatus : quint8 { NONE, // nothing is happening, no error has been detected LOADING, // an image is being loaded OUTDATED_LOADING, // an image was already loading during the previous reload() MISSING_FILE, // the file to load is missing LOADING_ERROR // generic error }; Q_ENUM(EStatus) EStatus status() const { return _status; } void setStatus(EStatus status); bool loading() const { return _loading; } void setLoading(bool loading); QSize sourceSize() const { return _sourceSize; } const QVariantMap& metadata() const { return _metadata; } int getDownscaleLevel() const { return _downscaleLevel; } void setDownscaleLevel(int level) { if (level == _downscaleLevel) return; _downscaleLevel = std::max(0, level); Q_EMIT downscaleLevelChanged(); } enum class EChannelMode : quint8 { RGBA, RGB, R, G, B, A }; Q_ENUM(EChannelMode) bool getCropFisheye() const { return _cropFisheye; } void setCropFisheye(bool cropFisheye) { _cropFisheye = cropFisheye; } Q_SIGNAL void isCropFisheyeChanged(); // Q_SIGNALS Q_SIGNAL void sourceChanged(); Q_SIGNAL void statusChanged(); Q_SIGNAL void clearBeforeLoadChanged(); Q_SIGNAL void gammaChanged(); Q_SIGNAL void gainChanged(); Q_SIGNAL void textureSizeChanged(); Q_SIGNAL void sourceSizeChanged(); Q_SIGNAL void channelModeChanged(); Q_SIGNAL void imageChanged(); Q_SIGNAL void metadataChanged(); Q_SIGNAL void downscaleLevelChanged(); Q_SIGNAL void surfaceChanged(); Q_SIGNAL void canBeHoveredChanged(); Q_SIGNAL void fisheyeCircleParametersChanged(); Q_SIGNAL void sequenceChanged(); Q_SIGNAL void targetSizeChanged(); Q_SIGNAL void resizeRatioChanged(); Q_SIGNAL void cachedFramesChanged(); Q_SIGNAL void useSequenceChanged(); Q_SIGNAL void fetchingSequenceChanged(); Q_SIGNAL void memoryLimitChanged(); // Q_INVOKABLE Q_INVOKABLE QVector4D pixelValueAt(int x, int y); Q_INVOKABLE void playback(bool active); Surface* getSurfacePtr() { return &_surface; } void setSequence(const QVariantList& paths); void setResizeRatio(double ratio); double getResizeRatio(); void setFetchingSequence(bool fetching); bool getFetchingSequence(); void setMemoryLimit(int memoryLimit); int getMemoryLimit(); QVariantList getCachedFrames() const; QPointF getRamInfo() const; protected: virtual void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; private: /// Reload image from source void reload(); /// Custom QSGNode update QSGNode* updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* data) override; QUrl _source; float _gamma = 1.f; bool _gammaChanged = false; float _gain = 1.f; bool _gainChanged = false; bool _mouseOverChanged = false; EStatus _status = EStatus::NONE; bool _loading = false; bool _outdated = false; bool _clearBeforeLoad = true; bool _geometryChanged = false; bool _imageChanged = false; EChannelMode _channelMode; bool _channelModeChanged = false; std::shared_ptr _image; QRectF _boundingRect; QSize _textureSize; QSize _sourceSize = QSize(0, 0); QVariantMap _metadata; Surface _surface; // Prevent to update surface without the root created bool _createRoot = true; // Level of downscale for images of a Panorama int _downscaleLevel = 0; bool _canBeHovered = false; bool _cropFisheye = false; imgserve::SequenceCache _sequenceCache; imgserve::SingleImageLoader _singleImageLoader; bool _useSequence = true; double _clampedResizeRatio = 1.0; QVariantList _sequence; }; } // namespace qtAliceVision Q_DECLARE_METATYPE(qtAliceVision::FloatImage) Q_DECLARE_METATYPE(std::shared_ptr) QtAliceVision-2025.1.1/src/qtAliceVision/FloatImageViewer.vert000066400000000000000000000012251506573753700241330ustar00rootroot00000000000000#version 440 layout(location = 0) in vec4 position; layout(location = 1) in vec2 texCoord; layout(location = 0) out vec2 vTexCoord; layout(std140, binding = 0) uniform buf { // warning: matches layout and padding of FloatImageViewerMaterial::uniforms! mat4 qt_Matrix; // offset 0 float qt_Opacity; // offset 64 float _padding1; float _padding2; float _padding3; vec4 channelOrder; // offset 80 vec2 fisheyeCircleCoord; float gamma; float gain; float fisheyeCircleRadius; float aspectRatio; }; out gl_PerVertex { vec4 gl_Position; }; void main() { vTexCoord = texCoord; gl_Position = qt_Matrix * position; }QtAliceVision-2025.1.1/src/qtAliceVision/FloatTexture.cpp000066400000000000000000000051311506573753700231710ustar00rootroot00000000000000#include "FloatTexture.hpp" #include #include #include #include #include namespace qtAliceVision { int FloatTexture::_maxTextureSize = -1; FloatTexture::FloatTexture() {} FloatTexture::~FloatTexture() { if (_rhiTexture) { _rhiTexture->destroy(); } } void FloatTexture::setImage(std::shared_ptr& image) { _srcImage = image; _textureSize = {_srcImage->width(), _srcImage->height()}; _dirty = true; _mipmapsGenerated = false; } bool FloatTexture::isValid() const { return _srcImage->width() != 0 && _srcImage->height() != 0; } qint64 FloatTexture::comparisonKey() const { return _rhiTexture ? _rhiTexture->nativeTexture().object : 0; } QRhiTexture* FloatTexture::rhiTexture() const { return _rhiTexture; } void FloatTexture::commitTextureOperations(QRhi* rhi, QRhiResourceUpdateBatch* resourceUpdates) { if (!_dirty) { return; } if (!isValid()) { if (_rhiTexture) { _rhiTexture->destroy(); } _rhiTexture = nullptr; return; } QRhiTexture::Format texFormat = QRhiTexture::RGBA32F; if (!rhi->isTextureFormatSupported(texFormat)) { qWarning() << "[QtAliceVision] Unsupported float images."; return; } // Init max texture size if (_maxTextureSize == -1) { _maxTextureSize = rhi->resourceLimit(QRhi::TextureSizeMax); } // Downscale the texture to fit inside the max texture limit if it is too big. while (_maxTextureSize != -1 && (_srcImage->width() > _maxTextureSize || _srcImage->height() > _maxTextureSize)) { FloatImage tmp; aliceVision::image::imageHalfSample(*_srcImage, tmp); *_srcImage = std::move(tmp); } _textureSize = {_srcImage->width(), _srcImage->height()}; const QRhiTexture::Flags texFlags(hasMipmaps() ? QRhiTexture::MipMapped : 0); _rhiTexture = rhi->newTexture(texFormat, _textureSize, 1, texFlags); if (!_rhiTexture || !_rhiTexture->create()) { qWarning() << "[QtAliceVision] Unable to create float texture."; return; } const QByteArray textureData(reinterpret_cast(_srcImage->data()), _srcImage->size() * sizeof(*_srcImage->data())); resourceUpdates->uploadTexture(_rhiTexture, QRhiTextureUploadEntry(0, 0, QRhiTextureSubresourceUploadDescription(textureData))); if (hasMipmaps()) { resourceUpdates->generateMips(_rhiTexture); _mipmapsGenerated = true; } _dirty = false; } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/FloatTexture.hpp000066400000000000000000000030411506573753700231740ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace qtAliceVision { using FloatImage = aliceVision::image::Image; /** * @brief A custom QSGTexture to display AliceVision images in QML */ class FloatTexture : public QSGTexture { public: FloatTexture(); ~FloatTexture() override; virtual qint64 comparisonKey() const override; virtual QRhiTexture* rhiTexture() const override; virtual void commitTextureOperations(QRhi* rhi, QRhiResourceUpdateBatch* resourceUpdates) override; QSize textureSize() const override { return _textureSize; } bool hasAlphaChannel() const override { return true; } bool hasMipmaps() const override { return mipmapFiltering() != QSGTexture::None; } void setImage(std::shared_ptr& image); const FloatImage& image() { return *_srcImage; } /** * @brief Get the maximum dimension of a texture. * * If the provided image is too large, it will be downscaled to fit the max dimension. * * @return -1 if unknown else the max size of a texture */ static int maxTextureSize() { return _maxTextureSize; } private: bool isValid() const; private: std::shared_ptr _srcImage; QRhiTexture* _rhiTexture = nullptr; QSize _textureSize; bool _dirty = false; bool _mipmapsGenerated = false; static int _maxTextureSize; }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/ImageCache.cpp000066400000000000000000000056751506573753700225260ustar00rootroot00000000000000#include "ImageCache.hpp" namespace qtAliceVision { ImageCache::ImageCache(unsigned long maxSize, const aliceVision::image::ImageReadOptions& options) : _info(maxSize), _options(options), _referenceFrameId(0) { //Use 4 threads max, but less if we don't have this int count = std::min(4, omp_get_max_threads()); //Setup openimageio oiio::attribute("threads", count); oiio::attribute("exr_threads", count); } ImageCache::~ImageCache() {} void ImageCache::cleanup(size_t requestedSize, const CacheKey& toAdd) { // At each step, we try to remove the LRU item which is not used while (1) { // Check if we did enough work? size_t available = _info.getAvailableSize(); if (available >= requestedSize) { return; } bool erased = false; // First, try to remove images with different ratios { std::scoped_lock lockKeys(_mutexAccessImages); for (const auto& [key, value] : _imagePtrs) { if (key.resizeRatio == toAdd.resizeRatio) { continue; } if (value.useCount() <= 1) { _imagePtrs.erase(key); _info.update(_imagePtrs); erased = true; break; } } } // If we get here, all the cache should contain only the same resize ratio if (!erased) { std::scoped_lock lockKeys(_mutexAccessImages); std::map orderedKeys; for (const auto& [key, value] : _imagePtrs) { int iOtherId = int(value.getFrameId()); int diff = iOtherId - _referenceFrameId; // Before the frameId, difference is negative. // The closest it is to the frameId before the frameId, the highest its priority to delete // After the frameId, the largest the difference, the highest its priority to delete if (diff < 0) { diff = std::numeric_limits::max() + diff; } orderedKeys[diff] = &key; } if (orderedKeys.size() > 0) { const CacheKey* pKey = orderedKeys.rbegin()->second; _imagePtrs.erase(*pKey); _info.update(_imagePtrs); } } // Nothing happened, nothing more will happen. if (!erased) { return; } } } void ImageCache::updateMaxMemory(unsigned long long int maxSize) { _info.setMaxMemory(maxSize); } unsigned long long int ImageCache::getMaxMemory() { return _info.getCapacity(); } void ImageCache::setReferenceFrameId(int referenceFrameId) { _referenceFrameId = referenceFrameId; } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/ImageCache.hpp000066400000000000000000000330341506573753700225210ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace qtAliceVision { /** * @brief A struct used to identify a cached image using its file description, color type info and downscale level. */ struct CacheKey { std::string filename; int nbChannels; oiio::TypeDesc::BASETYPE typeDesc; double resizeRatio; std::time_t lastWriteTime; CacheKey(const std::string& path, int nchannels, oiio::TypeDesc::BASETYPE baseType, double ratio, std::time_t time) : filename(path), nbChannels(nchannels), typeDesc(baseType), resizeRatio(ratio), lastWriteTime(time) {} bool operator==(const CacheKey& other) const { return (filename == other.filename && nbChannels == other.nbChannels && typeDesc == other.typeDesc && resizeRatio == other.resizeRatio && lastWriteTime == other.lastWriteTime); } }; struct CacheKeyHasher { std::size_t operator()(const CacheKey& key) const noexcept { std::size_t seed = 0; boost::hash_combine(seed, key.filename); boost::hash_combine(seed, key.nbChannels); boost::hash_combine(seed, key.typeDesc); boost::hash_combine(seed, key.resizeRatio); boost::hash_combine(seed, key.lastWriteTime); return seed; } }; /** * @brief A class to support shared pointers for all types of images. */ class CacheValue { public: template CacheValue(unsigned frameId, std::shared_ptr> img, bool missingFile, bool loadError) : _vimg(img), _frameId(frameId), _missingFile(missingFile), _loadError(loadError) {} public: /** * @brief Template method to get a shared pointer to the image with pixel type given as template argument. * @note At most one of the generated methods will provide a non-null pointer. * @return shared pointer to an image with the pixel type given as template argument */ template std::shared_ptr> get() const { return std::get>>(_vimg); } unsigned getOriginalWidth() const { return _originalWidth; } unsigned getOriginalHeight() const { return _originalHeight; } void setOriginalWidth(unsigned width) { _originalWidth = width; } void setOriginalHeight(unsigned height) { _originalHeight = height; } oiio::ParamValueList& getMetadatas() { return _metadatas; } unsigned getFrameId() const { return _frameId; } /** * @brief Count the number of usages of the wrapped shared pointer. * @return the use_count of the wrapped shared pointer if there is one, otherwise 0 */ long int useCount() const { return std::visit([](const auto& arg) -> long int { return arg.use_count(); }, _vimg); } /** * @brief Retrieve the memory size (in bytes) of the wrapped image. * @return the memory size of the wrapped image if there is one, otherwise 0 */ unsigned long long int memorySize() const { return std::visit([](const auto& arg) -> unsigned long long int { return arg->memorySize(); }, _vimg); } /** * @brief did the load failed ? * @return true if something bad happened during the loading */ bool hadErrorOnLoad() const { return _loadError; } /** * @brief is the file requested missing ? * @return true if the file doesn't exist */ bool isFileMissing() const { return _missingFile; } private: std::variant>, std::shared_ptr>, std::shared_ptr>, std::shared_ptr>, std::shared_ptr>, std::shared_ptr>> _vimg; unsigned _originalWidth; unsigned _originalHeight; oiio::ParamValueList _metadatas; unsigned _frameId; bool _loadError; bool _missingFile; }; /** * @brief A struct to store information about the cache current state and usage. */ class CacheInfo { public: CacheInfo(unsigned long int maxSize) : _maxSize(maxSize) {} void incrementCache() { const std::scoped_lock lockPeek(_mutex); _nbLoadFromCache++; } void incrementDisk() { const std::scoped_lock lock(_mutex); _nbLoadFromDisk++; } unsigned long long int getCapacity() const { const std::scoped_lock lock(_mutex); return _maxSize; } void update(const std::unordered_map& images) { std::scoped_lock lock(_mutex); _contentSize = 0; for (const auto& [key, value] : images) { _contentSize += value.memorySize(); _nbImages++; } } std::size_t getAvailableSize() const { const std::scoped_lock lock(_mutex); if (_maxSize <= _contentSize) { return 0; } return _maxSize - _contentSize; } bool isSmallEnough(std::size_t value) const { const std::scoped_lock lock(_mutex); return (_contentSize + value < _maxSize); } unsigned long long int getContentSize() const { const std::scoped_lock lock(_mutex); return _contentSize; } int getLoadFromDisk() const { const std::scoped_lock lock(_mutex); return _nbLoadFromDisk; } void setMaxMemory(unsigned long long int maxSize) { std::scoped_lock lock(_mutex); _maxSize = maxSize; } /// memory usage limits unsigned long long int _maxSize; /// current state of the cache int _nbImages = 0; unsigned long long int _contentSize = 0; /// usage statistics int _nbLoadFromDisk = 0; int _nbLoadFromCache = 0; int _nbRemoveUnused = 0; mutable std::mutex _mutex; }; class ImageCache { public: using uptr = std::unique_ptr; public: /** * @brief Create a new image cache by defining memory usage limits and image reading options. * @param[in] maxSize the cache maximal size (in bytes) * @param[in] options the reading options that will be used when loading images through this cache */ ImageCache(unsigned long maxSize, const aliceVision::image::ImageReadOptions& options); /** * @brief Destroy the cache and the unused images it contains. */ ~ImageCache(); /// make image cache class non-copyable ImageCache(const ImageCache&) = delete; ImageCache& operator=(const ImageCache&) = delete; /** * @brief Retrieve a cached image at a given downscale level. * @note This method is thread-safe. * @param[in] filename the image's filename on disk * @param[in] frameId additional data * @param[in] resizeRatio the resize ratio of the image * @param[in] cachedOnly if true, only return images that are already in the cache * @return a shared pointer to the cached image */ template std::optional get(const std::string& filename, unsigned frameId, double resizeRatio = 1.0, bool cachedOnly = false); /** * @brief Check if an image at a given downscale level is currently in the cache. * @note This method is thread-safe. * @param[in] filename the image's filename on disk * @param[in] resizeRatio the resize ratio of the image * @return whether or not the cache currently contains the image */ template bool contains(const std::string& filename, double resizeRatio = 1.0) const; /** * Ask for more room, by deleting the LRU items which are not used * @param requestedSize the required size for the new image * @param toAdd the key of the image to add after cleanup */ void cleanup(std::size_t requestedSize, const CacheKey& toAdd); /** * @return information on the current cache state and usage */ inline const CacheInfo& info() const { return _info; } /** * @return the image reading options of the cache */ inline const aliceVision::image::ImageReadOptions& readOptions() const { return _options; } /** * @brief update the cache max memory * @param maxSize the value to store */ void updateMaxMemory(unsigned long long int maxSize); /** * @brief get the cache max memory * @return the cache max memory */ unsigned long long int getMaxMemory(); /** * @brief set the reference frame ID * @param referenceFrameId the value to store */ void setReferenceFrameId(int referenceFrameId); private: /** * @brief Load a new image corresponding to the given key and add it as a new entry in the cache. * @param[in] key the key used to identify the entry in the cache * @param[in] frameId additional data */ template std::optional load(const CacheKey& key, unsigned frameId); CacheInfo _info; aliceVision::image::ImageReadOptions _options; // Set of images stored and indexed by CacheKey std::unordered_map _imagePtrs; mutable std::mutex _mutexAccessImages; // Reference frame Id used to compute the next image to remove // This should be equal to the currently displayed image std::atomic _referenceFrameId; }; // Since some methods in the ImageCache class are templated // their definition must be given in this header file template std::optional ImageCache::get(const std::string& filename, unsigned frameId, double resizeRatio, bool cachedOnly) { if (resizeRatio < 1e-12) { return std::nullopt; } // Build lookup key using TInfo = aliceVision::image::ColorTypeInfo; auto lastWriteTime = aliceVision::utils::getLastWriteTime(filename); CacheKey keyReq(filename, TInfo::size, TInfo::typeDesc, resizeRatio, lastWriteTime); // find the requested image in the cached images { std::scoped_lock lockImages(_mutexAccessImages); auto it = _imagePtrs.find(keyReq); if (it != _imagePtrs.end()) { return it->second; } } if (cachedOnly) { return std::nullopt; } // Load image and add to cache if possible return load(keyReq, frameId); } template std::optional ImageCache::load(const CacheKey& key, unsigned frameId) { // Increment disk access stats // This is incremented whatever happens next _info.incrementDisk(); aliceVision::image::Image img; auto resized = std::make_shared>(); int width = 0; int height = 0; oiio::ParamValueList metadatas; bool loadError = false; bool missingFile = false; // First check if the files exists on disk // LastWriteTime equals 0 if the file doesn't exist if (key.lastWriteTime == 0) { missingFile = true; loadError = true; } else { // If the file exist, then try to load it. try { // Retrieve metadatas metadatas = aliceVision::image::readImageMetadata(key.filename, width, height); // load image from disk readImage(key.filename, img, _options); } catch (...) { loadError = true; } } if (!loadError) { // Compute new size, make sure the size is at least 1 double dw = key.resizeRatio * double(img.width()); double dh = key.resizeRatio * double(img.height()); int tw = static_cast(std::max(1, int(std::ceil(dw)))); int th = static_cast(std::max(1, int(std::ceil(dh)))); using TInfo = aliceVision::image::ColorTypeInfo; cleanup(tw * th * std::size_t(TInfo::size), key); // apply downscale aliceVision::imageAlgo::resizeImage(tw, th, img, *resized); } // create wrapper around shared pointer // Note that we store cache item even on problem to not repeat trials CacheValue value(frameId, resized, missingFile, loadError); // Add additional information about the image value.setOriginalHeight(static_cast(height)); value.setOriginalWidth(static_cast(width)); value.getMetadatas() = metadatas; // Store image in map { std::scoped_lock lockImages(_mutexAccessImages); _imagePtrs.insert({key, value}); _info.update(_imagePtrs); } return value; } template bool ImageCache::contains(const std::string& filename, double resizeRatio) const { std::scoped_lock lockKeys(_mutexAccessImages); using TInfo = aliceVision::image::ColorTypeInfo; auto lastWriteTime = aliceVision::utils::getLastWriteTime(filename); CacheKey keyReq(filename, TInfo::size, TInfo::typeDesc, resizeRatio, lastWriteTime); auto it = _imagePtrs.find(keyReq); bool found = (it != _imagePtrs.end()); return found; } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/ImageOverlay.frag000066400000000000000000000010471506573753700232660ustar00rootroot00000000000000#version 450 precision highp float; layout(location = 0) in vec2 qt_TexCoord0; layout(location = 0) out vec4 fragColor; layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; float qt_Opacity; lowp vec2 uvCenterOffset; }; layout(binding = 1) uniform sampler2D src; void main() { vec2 xy = qt_TexCoord0 + uvCenterOffset; fragColor = texture(src, xy); fragColor.rgb *= qt_Opacity; fragColor.a = qt_Opacity; // remove undistortion black pixels fragColor.a *= step(0.001, fragColor.r + fragColor.g + fragColor.b); }QtAliceVision-2025.1.1/src/qtAliceVision/ImageServer.hpp000066400000000000000000000026321506573753700227640ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace qtAliceVision { namespace imgserve { enum LoadingStatus { UNDEFINED, SUCCESSFUL, MISSING_FILE, LOADING_ERROR }; /** * @brief Utility structure to encapsulate an image loading request. */ struct RequestData { std::string path; int downscale = 1; }; /** * @brief Utility structure to encapsulate the response to an image loading request. */ struct ResponseData { std::shared_ptr> img; QSize dim; QVariantMap metadata; LoadingStatus error = UNDEFINED; }; /** * @brief Interface for loading images from disk. */ class ImageServer { public: /** * @brief Request an image stored on disk with its metadata. * @note this is a pure virtual method * @param[in] path image's filepath on disk * @return a response to the request containing a pointer to the image and the image's metadata */ virtual ResponseData request(const RequestData& reqData) = 0; }; } // namespace imgserve } // namespace qtAliceVision // Make RequestData and ResponseData struct known to QMetaType // for usage in signals and slots Q_DECLARE_METATYPE(qtAliceVision::imgserve::RequestData) Q_DECLARE_METATYPE(qtAliceVision::imgserve::ResponseData) QtAliceVision-2025.1.1/src/qtAliceVision/MFeatures.cpp000066400000000000000000000124521506573753700224420ustar00rootroot00000000000000#include "MFeatures.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace qtAliceVision { void FeaturesIORunnable::run() { FeaturesPerViewPerDesc* featuresPerViewPerDesc = new FeaturesPerViewPerDesc; std::vector imageDescriberTypes; for (const auto& descType : _describerTypes) { imageDescriberTypes.emplace_back(aliceVision::feature::EImageDescriberType_stringToEnum(descType)); } std::vector>> regionsPerViewPerDesc; bool loaded = aliceVision::sfm::loadFeaturesPerDescPerView(regionsPerViewPerDesc, _viewIds, _folders, imageDescriberTypes); if (!loaded) { qWarning() << "[QtAliceVision] Features: Failed to load features"; delete featuresPerViewPerDesc; Q_EMIT resultReady(nullptr); return; } for (std::size_t dIdx = 0; dIdx < _describerTypes.size(); ++dIdx) { const auto& descTypeStr = _describerTypes.at(dIdx); const std::vector>& regionsPerView = regionsPerViewPerDesc.at(static_cast(dIdx)); for (std::size_t vIdx = 0; vIdx < _viewIds.size(); ++vIdx) { const auto& viewId = _viewIds.at(vIdx); qDebug() << "[QtAliceVision] Features: Load " << descTypeStr << " from viewId: " << viewId << "."; (*featuresPerViewPerDesc)[descTypeStr][viewId] = regionsPerView.at(vIdx)->Features(); } } Q_EMIT resultReady(featuresPerViewPerDesc); } MFeatures::MFeatures() { // trigger features reload events connect(this, &MFeatures::featureFoldersChanged, this, &MFeatures::load); connect(this, &MFeatures::describerTypesChanged, this, &MFeatures::load); connect(this, &MFeatures::viewIdsChanged, this, &MFeatures::load); } MFeatures::~MFeatures() { if (_featuresPerViewPerDesc) delete _featuresPerViewPerDesc; setStatus(None); } void MFeatures::load() { _needReload = false; if (_status == Loading) { qDebug("[QtAliceVision] Features: Unable to load, a load event is already running."); _needReload = true; return; } if (_describerTypes.empty()) { qDebug("[QtAliceVision] Features: Unable to load, no describer types given."); setStatus(None); return; } if (_featureFolders.empty()) { qDebug("[QtAliceVision] Features: Unable to load, no feature folder given."); setStatus(None); return; } if (_viewIds.empty()) { qDebug("[QtAliceVision] Features: Unable to load, no viewId given."); setStatus(None); return; } setStatus(Loading); // load features from file in a seperate thread qDebug("[QtAliceVision] Features: Load features from file in a seperate thread."); std::vector folders; for (const auto& var : _featureFolders) { folders.push_back(var.toString().toStdString()); } std::vector viewIds; for (const auto& var : _viewIds) { viewIds.push_back(static_cast(var.toUInt())); } std::vector describerTypes; for (const auto& var : _describerTypes) { describerTypes.push_back(var.toString().toStdString()); } FeaturesIORunnable* ioRunnable = new FeaturesIORunnable(folders, viewIds, describerTypes); connect(ioRunnable, &FeaturesIORunnable::resultReady, this, &MFeatures::onFeaturesReady); QThreadPool::globalInstance()->start(ioRunnable); } void MFeatures::onFeaturesReady(FeaturesPerViewPerDesc* featuresPerViewPerDesc) { if (_needReload) { if (featuresPerViewPerDesc) delete featuresPerViewPerDesc; setStatus(None); load(); return; } if (featuresPerViewPerDesc) { if (_featuresPerViewPerDesc) delete _featuresPerViewPerDesc; _featuresPerViewPerDesc = featuresPerViewPerDesc; } setStatus(Ready); } void MFeatures::setStatus(Status status) { if (status == _status) return; _status = status; Q_EMIT statusChanged(_status); if (status == Ready || status == Error) { Q_EMIT featuresChanged(); } } int MFeatures::nbFeatures(QString describerType, int viewId) const { if (_status != Ready) { return 0; } if (!_featuresPerViewPerDesc) { return 0; } const auto featuresPerViewIt = _featuresPerViewPerDesc->find(describerType.toStdString()); if (featuresPerViewIt == _featuresPerViewPerDesc->end()) { return 0; } if (viewId < 0) { return 0; } const auto& featuresPerView = featuresPerViewIt->second; const auto featuresIt = featuresPerView.find(static_cast(viewId)); if (featuresIt == featuresPerView.end()) { return 0; } const auto& features = featuresIt->second; return static_cast(features.size()); } } // namespace qtAliceVision #include "MFeatures.moc" QtAliceVision-2025.1.1/src/qtAliceVision/MFeatures.hpp000066400000000000000000000074331506573753700224520ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include namespace qtAliceVision { using FeaturesPerViewPerDesc = std::map>>; /** * @brief QObject wrapper around extracted features. * * Given a folder containing extracted features, * the role of an MFeatures instance is to load the features from disk. * Describer types and view IDs to load must also be specified. * This task is done asynchronously to avoid freezing the UI. * * MFeatures objects are accessible from QML * and can be manipulated through their properties. * * Note: * for a given describer type and view ID, * features are stored in an array-like structure * and the ID a a feature corresponds to its index in this array. */ class MFeatures : public QObject { Q_OBJECT /// Data properties // Path to folder containing the features Q_PROPERTY(QVariantList featureFolders MEMBER _featureFolders NOTIFY featureFoldersChanged) // View IDs to load Q_PROPERTY(QVariantList viewIds MEMBER _viewIds NOTIFY viewIdsChanged) // Describer types to load Q_PROPERTY(QVariantList describerTypes MEMBER _describerTypes NOTIFY describerTypesChanged) /// Status Q_PROPERTY(Status status READ status NOTIFY statusChanged) public: /// Status Enum enum Status { None = 0, Loading, Ready, Error }; Q_ENUM(Status) /// Slots Q_SLOT void load(); Q_SLOT void onFeaturesReady(FeaturesPerViewPerDesc* featuresPerViewPerDesc); /// Signals Q_SIGNAL void featureFoldersChanged(); Q_SIGNAL void describerTypesChanged(); Q_SIGNAL void viewIdsChanged(); Q_SIGNAL void featuresChanged(); Q_SIGNAL void statusChanged(Status status); /// Invokables Q_INVOKABLE int nbFeatures(QString describerType, int viewId) const; /// Public methods MFeatures(); MFeatures(const MFeatures& other) = default; ~MFeatures() override; FeaturesPerViewPerDesc& rawData() { return *_featuresPerViewPerDesc; } const FeaturesPerViewPerDesc& rawData() const { return *_featuresPerViewPerDesc; } const FeaturesPerViewPerDesc* rawDataPtr() const { return _featuresPerViewPerDesc; } Status status() const { return _status; } void setStatus(Status status); private: /// Private members QVariantList _featureFolders; QVariantList _viewIds; QVariantList _describerTypes; FeaturesPerViewPerDesc* _featuresPerViewPerDesc = nullptr; bool _needReload = false; Status _status = MFeatures::None; }; /** * @brief QRunnable object dedicated to loading features using AliceVision. */ class FeaturesIORunnable : public QObject, public QRunnable { Q_OBJECT public: FeaturesIORunnable(const std::vector& folders, const std::vector& viewIds, const std::vector& describerTypes) : _folders(folders), _viewIds(viewIds), _describerTypes(describerTypes) {} /// Load features based on input parameters Q_SLOT void run() override; /** * @brief Emitted when features have been loaded and Features objects created. * @warning Features objects are not parented - their deletion must be handled manually. * * @param features the loaded Features list */ Q_SIGNAL void resultReady(FeaturesPerViewPerDesc* featuresPerViewPerDesc); private: std::vector _folders; std::vector _viewIds; std::vector _describerTypes; }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/MSfMData.cpp000066400000000000000000000066351506573753700221510ustar00rootroot00000000000000#include "MSfMData.hpp" #include #include #include #include namespace qtAliceVision { void SfmDataIORunnable::run() { aliceVision::sfmData::SfMData* sfmData = new aliceVision::sfmData::SfMData; try { if (!aliceVision::sfmDataIO::load(*sfmData, _sfmDataPath.toLocalFile().toStdString(), aliceVision::sfmDataIO::ESfMData::ALL)) { qDebug() << "[QtAliceVision] Failed to load sfmData: " << _sfmDataPath << "."; } } catch (std::exception& e) { qDebug() << "[QtAliceVision] Failed to load sfmData: " << _sfmDataPath << "." << "\n" << e.what(); } Q_EMIT resultReady(sfmData); } MSfMData::MSfMData() { connect(this, &MSfMData::sfmDataPathChanged, this, &MSfMData::load); } MSfMData::~MSfMData() { if (_sfmData) delete _sfmData; setStatus(None); } void MSfMData::load() { _needReload = false; if (_status == Loading) { qDebug("[QtAliceVision] SfMData: Unable to load, a load event is already running."); _needReload = true; return; } if (_sfmDataPath.isEmpty()) { setStatus(None); return; } if (!QFileInfo::exists(_sfmDataPath.toLocalFile())) { setStatus(Error); return; } setStatus(Loading); // load features from file in a seperate thread SfmDataIORunnable* ioRunnable = new SfmDataIORunnable(_sfmDataPath); connect(ioRunnable, &SfmDataIORunnable::resultReady, this, &MSfMData::onSfmDataReady); QThreadPool::globalInstance()->start(ioRunnable); } QString MSfMData::getUrlFromViewId(int viewId) { return QString::fromUtf8(_sfmData->getView(aliceVision::IndexT(viewId)).getImage().getImagePath().c_str()); } void MSfMData::onSfmDataReady(aliceVision::sfmData::SfMData* sfmData) { if (_needReload) { if (sfmData) delete sfmData; setStatus(None); load(); return; } if (sfmData) { if (_sfmData) delete _sfmData; _sfmData = sfmData; } setStatus(Ready); } void MSfMData::setStatus(Status status) { if (status == _status) return; _status = status; Q_EMIT statusChanged(_status); if (status == Ready || status == Error) { Q_EMIT sfmDataChanged(); } } size_t MSfMData::nbCameras() const { if (!_sfmData || _status != Ready) return 0; return _sfmData->getValidViews().size(); } QVariantList MSfMData::getViewsIds() const { QVariantList viewsIds; for (const auto& id : _sfmData->getValidViews()) { viewsIds.append(id); } return viewsIds; } int MSfMData::nbLandmarks(QString describerType, int viewId) const { if (_status != Ready) { return 0; } if (!_sfmData) { return 0; } const auto& landmarks = _sfmData->getLandmarks(); int count = 0; auto descType = aliceVision::feature::EImageDescriberType_stringToEnum(describerType.toStdString()); for (const auto& [_, landmark] : landmarks) { if (landmark.descType != descType) continue; const auto observationIt = landmark.getObservations().find(viewId); if (observationIt == landmark.getObservations().end()) continue; ++count; } return count; } } // namespace qtAliceVision #include "MSfMData.moc" QtAliceVision-2025.1.1/src/qtAliceVision/MSfMData.hpp000066400000000000000000000062461506573753700221540ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace qtAliceVision { /** * @brief QObject wrapper around a SfMData. * * Given a path to a SfMData file, * the role of an MSfMData instance is to load the SfMData from disk. * This task is done asynchronously to avoid freezing the UI. * * MSfMData objects are accesible from QML * and can be manipulated through their properties. * * Note: * a SfMData contains important information for linking together various reconstruction data, such as: * - views (and their corresponding frame ID) * - poses (and their corresponding camera transform) * - intrinsics * - landmarks (and their corresponding observations, i.e. the 2D features used to build the landmark). */ class MSfMData : public QObject { Q_OBJECT /// Data properties // Path to SfMData file Q_PROPERTY(QUrl sfmDataPath MEMBER _sfmDataPath NOTIFY sfmDataPathChanged) // Total number of reconstructed cameras Q_PROPERTY(size_t nbCameras READ nbCameras NOTIFY statusChanged) // View IDs of views in the SfMData Q_PROPERTY(QVariantList viewsIds READ getViewsIds NOTIFY viewsIdsChanged) /// Status Q_PROPERTY(Status status READ status NOTIFY statusChanged) public: /// Status enum enum Status { None = 0, Loading, Ready, Error }; Q_ENUM(Status) MSfMData(); MSfMData& operator=(const MSfMData& other) = default; ~MSfMData() override; private: MSfMData(const MSfMData& other); public: /// Slots Q_SLOT void load(); Q_SLOT void onSfmDataReady(aliceVision::sfmData::SfMData* sfmData); /// Signals Q_SIGNAL void sfmDataPathChanged(); Q_SIGNAL void sfmDataChanged(); Q_SIGNAL void statusChanged(Status status); Q_SIGNAL void viewsIdsChanged(); /// Invokables Q_INVOKABLE QString getUrlFromViewId(int viewId); Q_INVOKABLE int nbLandmarks(QString describerType, int viewId) const; public: const aliceVision::sfmData::SfMData& rawData() const { return *_sfmData; } aliceVision::sfmData::SfMData& rawData() { return *_sfmData; } const aliceVision::sfmData::SfMData* rawDataPtr() const { return _sfmData; } Status status() const { return _status; } void setStatus(Status status); size_t nbCameras() const; QVariantList getViewsIds() const; private: /// Private members QUrl _sfmDataPath; aliceVision::sfmData::SfMData* _sfmData = nullptr; bool _needReload = false; Status _status = MSfMData::None; }; /** * @brief QRunnable object dedicated to loading a SfMData using AliceVision. */ class SfmDataIORunnable : public QObject, public QRunnable { Q_OBJECT public: explicit SfmDataIORunnable(const QUrl& sfmDataPath) : _sfmDataPath(sfmDataPath) {} /// Load SfM based on input parameters Q_SLOT void run() override; /** * @brief Emitted when sfmData have been loaded and sfmData objects created. */ Q_SIGNAL void resultReady(aliceVision::sfmData::SfMData* sfmData); private: const QUrl _sfmDataPath; }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/MSfMDataStats.cpp000066400000000000000000000423061506573753700231630ustar00rootroot00000000000000#include "MSfMDataStats.hpp" #include #include namespace qtAliceVision { void MSfMDataStats::fillLandmarksPerViewSerie(QXYSeries* landmarksPerView) { if (landmarksPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillLandmarksPerViewSerie: no landmarksPerView"; return; } landmarksPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillLandmarksPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbLandmarksPerView.size(); ++i) { landmarksPerView->append(double(i), double(_nbLandmarksPerView[i])); } } void MSfMDataStats::fillTracksPerViewSerie(QXYSeries* tracksPerView) { if (tracksPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillTracksPerViewSerie: no tracksPerView"; return; } tracksPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillTracksPerViewSerie: no SfMData loaded"; return; } if (_mTracks == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillTracksPerViewSerie: no Tracks loaded"; return; } for (std::size_t i = 0; i < _nbTracksPerView.size(); ++i) { tracksPerView->append(double(i), double(_nbTracksPerView[i])); } } void MSfMDataStats::fillResidualsMinPerViewSerie(QXYSeries* residualsPerView) { if (residualsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsPerViewSerie: no residualsPerView"; return; } residualsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbResidualsPerViewMin.size(); ++i) { residualsPerView->append(double(i), double(_nbResidualsPerViewMin[i])); } } void MSfMDataStats::fillResidualsMaxPerViewSerie(QXYSeries* residualsPerView) { if (residualsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsPerViewSerie: no residualsPerView"; return; } residualsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbResidualsPerViewMax.size(); ++i) { residualsPerView->append(double(i), double(_nbResidualsPerViewMax[i])); } } void MSfMDataStats::fillResidualsMeanPerViewSerie(QXYSeries* residualsPerView) { if (residualsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsPerViewSerie: no residualsPerView"; return; } residualsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbResidualsPerViewMean.size(); ++i) { residualsPerView->append(double(i), double(_nbResidualsPerViewMean[i])); } } void MSfMDataStats::fillResidualsMedianPerViewSerie(QXYSeries* residualsPerView) { if (residualsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsPerViewSerie: no residualsPerView"; return; } residualsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbResidualsPerViewMedian.size(); ++i) { residualsPerView->append(double(i), double(_nbResidualsPerViewMedian[i])); } } void MSfMDataStats::fillResidualsFirstQuartilePerViewSerie(QXYSeries* residualsPerView) { if (residualsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsFirstQuartilePerViewSerie: no residualsPerView"; return; } residualsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsFirstQuartilePerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbResidualsPerViewFirstQuartile.size(); ++i) { residualsPerView->append(double(i), double(_nbResidualsPerViewFirstQuartile[i])); } } void MSfMDataStats::fillResidualsThirdQuartilePerViewSerie(QXYSeries* residualsPerView) { if (residualsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsThirdQuartilePerViewSerie: no residualsPerView"; return; } residualsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillResidualsThirdQuartilePerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbResidualsPerViewThirdQuartile.size(); ++i) { residualsPerView->append(double(i), double(_nbResidualsPerViewThirdQuartile[i])); } } void MSfMDataStats::fillObservationsLengthsMinPerViewSerie(QXYSeries* observationsLengthsPerView) { if (observationsLengthsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsMinPerViewSerie: no observationsLengthsPerView"; return; } observationsLengthsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsMinPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbObservationsLengthsPerViewMin.size(); ++i) { observationsLengthsPerView->append(double(i), double(_nbObservationsLengthsPerViewMin[i])); } } void MSfMDataStats::fillObservationsLengthsMaxPerViewSerie(QXYSeries* observationsLengthsPerView) { if (observationsLengthsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsMaxPerViewSerie: no observationsLengthsPerView"; return; } observationsLengthsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsMaxPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbObservationsLengthsPerViewMax.size(); ++i) { observationsLengthsPerView->append(double(i), double(_nbObservationsLengthsPerViewMax[i])); } } void MSfMDataStats::fillObservationsLengthsMeanPerViewSerie(QXYSeries* observationsLengthsPerView) { if (observationsLengthsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsMeanPerViewSerie: no observationsLengthsPerView"; return; } observationsLengthsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsMeanPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbObservationsLengthsPerViewMean.size(); ++i) { observationsLengthsPerView->append(double(i), double(_nbObservationsLengthsPerViewMean[i])); } } void MSfMDataStats::fillObservationsLengthsMedianPerViewSerie(QXYSeries* observationsLengthsPerView) { if (observationsLengthsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsMedianPerViewSerie: no " "observationsLengthsPerView"; return; } observationsLengthsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsMedianPerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbObservationsLengthsPerViewMedian.size(); ++i) { observationsLengthsPerView->append(double(i), double(_nbObservationsLengthsPerViewMedian[i])); } } void MSfMDataStats::fillObservationsLengthsFirstQuartilePerViewSerie(QXYSeries* observationsLengthsPerView) { if (observationsLengthsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsFirstQuartilePerViewSerie: no " "observationsLengthsPerView"; return; } observationsLengthsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsFirstQuartilePerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbObservationsLengthsPerViewFirstQuartile.size(); ++i) { observationsLengthsPerView->append(double(i), double(_nbObservationsLengthsPerViewFirstQuartile[i])); } } void MSfMDataStats::fillObservationsLengthsThirdQuartilePerViewSerie(QXYSeries* observationsLengthsPerView) { if (observationsLengthsPerView == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsThirdQuartilePerViewSerie: no " "observationsLengthsPerView"; return; } observationsLengthsPerView->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::fillObservationsLengthsThirdQuartilePerViewSerie: no SfMData loaded"; return; } for (std::size_t i = 0; i < _nbObservationsLengthsPerViewThirdQuartile.size(); ++i) { observationsLengthsPerView->append(double(i), double(_nbObservationsLengthsPerViewThirdQuartile[i])); } } void MSfMDataStats::computeGlobalSfMStats() { using namespace aliceVision; _nbResidualsPerViewMin.clear(); _nbResidualsPerViewMax.clear(); _nbResidualsPerViewMean.clear(); _nbResidualsPerViewMedian.clear(); _nbResidualsPerViewFirstQuartile.clear(); _nbResidualsPerViewThirdQuartile.clear(); _nbObservationsLengthsPerViewMin.clear(); _nbObservationsLengthsPerViewMax.clear(); _nbObservationsLengthsPerViewMean.clear(); _nbObservationsLengthsPerViewMedian.clear(); _nbObservationsLengthsPerViewFirstQuartile.clear(); _nbObservationsLengthsPerViewThirdQuartile.clear(); _nbLandmarksPerView.clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::computeGlobalStats: no SfMData"; return; } if (_msfmData->status() != MSfMData::Ready) { qInfo() << "[QtAliceVision] MSfMDataStats::computeGlobalStats: SfMData is not ready: " << _msfmData->status(); return; } if (_msfmData->rawData().getViews().empty()) { qInfo() << "[QtAliceVision] MSfMDataStats::computeGlobalStats: SfMData is empty"; return; } // Landmarks per View { std::vector nbLandmarksPerView; sfm::computeLandmarksPerView(_msfmData->rawData(), nbLandmarksPerView); _nbLandmarksPerView.resize(nbLandmarksPerView.size()); std::copy(nbLandmarksPerView.begin(), nbLandmarksPerView.end(), _nbLandmarksPerView.begin()); _landmarksPerViewMaxAxisX = static_cast(_msfmData->rawData().getViews().size()); _landmarksPerViewMaxAxisY = 0.0; for (int v : nbLandmarksPerView) { _landmarksPerViewMaxAxisY = std::max(_landmarksPerViewMaxAxisY, double(v)); } } // Collect residuals histogram for each view // Residuals Per View graph { _residualsPerViewMaxAxisX = 0; sfm::computeResidualsPerView(_msfmData->rawData(), _residualsPerViewMaxAxisX, _nbResidualsPerViewMin, _nbResidualsPerViewMax, _nbResidualsPerViewMean, _nbResidualsPerViewMedian, _nbResidualsPerViewFirstQuartile, _nbResidualsPerViewThirdQuartile); _residualsPerViewMaxAxisY = 0.0; for (std::size_t i = 0; i < _nbResidualsPerViewMin.size(); ++i) { _residualsPerViewMaxAxisY = round(std::max(_residualsPerViewMaxAxisY, double(_nbResidualsPerViewMin[i]))); _residualsPerViewMaxAxisY = round(std::max(_residualsPerViewMaxAxisY, double(_nbResidualsPerViewMax[i]))); _residualsPerViewMaxAxisY = round(std::max(_residualsPerViewMaxAxisY, double(_nbResidualsPerViewMean[i]))); _residualsPerViewMaxAxisY = round(std::max(_residualsPerViewMaxAxisY, double(_nbResidualsPerViewMedian[i]))); _residualsPerViewMaxAxisY = round(std::max(_residualsPerViewMaxAxisY, double(_nbResidualsPerViewFirstQuartile[i]))); _residualsPerViewMaxAxisY = round(std::max(_residualsPerViewMaxAxisY, double(_nbResidualsPerViewThirdQuartile[i]))); } } // Collect observations lengths histogram for each view // Observations Lengths Per View graph { _observationsLengthsPerViewMaxAxisX = 0; sfm::computeObservationsLengthsPerView(_msfmData->rawData(), _observationsLengthsPerViewMaxAxisX, _nbObservationsLengthsPerViewMin, _nbObservationsLengthsPerViewMax, _nbObservationsLengthsPerViewMean, _nbObservationsLengthsPerViewMedian, _nbObservationsLengthsPerViewFirstQuartile, _nbObservationsLengthsPerViewThirdQuartile); _observationsLengthsPerViewMaxAxisY = 0.0; for (std::size_t i = 0; i < _nbObservationsLengthsPerViewMin.size(); ++i) { _observationsLengthsPerViewMaxAxisY = round(std::max(_observationsLengthsPerViewMaxAxisY, double(_nbObservationsLengthsPerViewMin[i]))); _observationsLengthsPerViewMaxAxisY = round(std::max(_observationsLengthsPerViewMaxAxisY, double(_nbObservationsLengthsPerViewMax[i]))); _observationsLengthsPerViewMaxAxisY = round(std::max(_observationsLengthsPerViewMaxAxisY, double(_nbObservationsLengthsPerViewMean[i]))); _observationsLengthsPerViewMaxAxisY = round(std::max(_observationsLengthsPerViewMaxAxisY, double(_nbObservationsLengthsPerViewMedian[i]))); _observationsLengthsPerViewMaxAxisY = round(std::max(_observationsLengthsPerViewMaxAxisY, double(_nbObservationsLengthsPerViewFirstQuartile[i]))); _observationsLengthsPerViewMaxAxisY = round(std::max(_observationsLengthsPerViewMaxAxisY, double(_nbObservationsLengthsPerViewThirdQuartile[i]))); } } Q_EMIT sfmStatsChanged(); } void MSfMDataStats::computeGlobalTracksStats() { _nbTracksPerView.clear(); // Tracks per View if (_mTracks == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::computeGlobalTracksStats: no Tracks loaded"; return; } if (_mTracks->status() != MTracks::Ready) { qInfo() << "[QtAliceVision] MSfMDataStats::computeGlobalTracksStats: Tracks is not ready: " << _mTracks->status(); return; } if (_mTracks->tracks().empty()) { qInfo() << "[QtAliceVision] MSfMDataStats::computeGlobalTracksStats: Tracks is empty"; return; } if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MSfMDataStats::computeGlobalTracksStats: no SfMData"; return; } if (_msfmData->status() != MSfMData::Ready) { qInfo() << "[QtAliceVision] MSfMDataStats::computeGlobalTracksStats: SfMData is not ready: " << _msfmData->status(); return; } _nbTracksPerView.reserve(_msfmData->rawData().getViews().size()); for (const auto& viewIt : _msfmData->rawData().getViews()) { const auto viewId = viewIt.first; if (_mTracks->tracksPerView().count(viewId)) _nbTracksPerView.push_back(double(_mTracks->tracksPerView().at(viewId).size())); else _nbTracksPerView.push_back(0.0); } for (double v : _nbTracksPerView) { _landmarksPerViewMaxAxisY = std::max(_landmarksPerViewMaxAxisY, v); } Q_EMIT tracksStatsChanged(); } MSfMDataStats::~MSfMDataStats() { setMSfmData(nullptr); setMTracks(nullptr); } void MSfMDataStats::setMSfmData(qtAliceVision::MSfMData* sfmData) { if (_msfmData == sfmData) { qInfo() << "[QtAliceVision] MSfMDataStats::setMSfMData: Reset the same pointer"; return; } if (_msfmData != nullptr) { disconnect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } _msfmData = sfmData; if (_msfmData != nullptr) { connect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } if (_msfmData == nullptr) { Q_EMIT sfmDataChanged(); } else if (_msfmData->status() == MSfMData::Ready) { Q_EMIT sfmDataChanged(); } } void MSfMDataStats::setMTracks(qtAliceVision::MTracks* tracks) { if (_mTracks == tracks) { qInfo() << "[QtAliceVision] MSfMDataStats::setMTracks: Reset the same pointer"; 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(); } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/MSfMDataStats.hpp000066400000000000000000000116011506573753700231620ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace qtAliceVision { class MSfMDataStats : public QObject { Q_OBJECT /// 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) /// max AxisX value for landmarks per view histogram Q_PROPERTY(int landmarksPerViewMaxAxisX MEMBER _landmarksPerViewMaxAxisX NOTIFY axisChanged) /// max AxisY value for landmarks per view histogram Q_PROPERTY(double landmarksPerViewMaxAxisY MEMBER _landmarksPerViewMaxAxisY NOTIFY axisChanged) /// max AxisX value for residuals per view histogram Q_PROPERTY(int residualsPerViewMaxAxisX MEMBER _residualsPerViewMaxAxisX NOTIFY axisChanged) /// max AxisY value for residuals per view histogram Q_PROPERTY(double residualsPerViewMaxAxisY MEMBER _residualsPerViewMaxAxisY NOTIFY axisChanged) /// max AxisX value for residuals per view histogram Q_PROPERTY(int observationsLengthsPerViewMaxAxisX MEMBER _observationsLengthsPerViewMaxAxisX NOTIFY axisChanged) /// max AxisY value for residuals per view histogram Q_PROPERTY(double observationsLengthsPerViewMaxAxisY MEMBER _observationsLengthsPerViewMaxAxisY NOTIFY axisChanged) public: MSfMDataStats() { connect(this, &MSfMDataStats::sfmDataChanged, this, &MSfMDataStats::computeGlobalSfMStats); connect(this, &MSfMDataStats::tracksChanged, this, &MSfMDataStats::computeGlobalTracksStats); connect(this, &MSfMDataStats::sfmDataChanged, this, &MSfMDataStats::computeGlobalTracksStats); connect(this, &MSfMDataStats::sfmStatsChanged, this, &MSfMDataStats::axisChanged); connect(this, &MSfMDataStats::tracksStatsChanged, this, &MSfMDataStats::axisChanged); } MSfMDataStats& operator=(const MSfMDataStats& other) = default; ~MSfMDataStats() override; Q_SIGNAL void sfmDataChanged(); Q_SIGNAL void tracksChanged(); Q_SIGNAL void sfmStatsChanged(); Q_SIGNAL void tracksStatsChanged(); Q_SIGNAL void axisChanged(); Q_SLOT void computeGlobalSfMStats(); Q_SLOT void computeGlobalTracksStats(); Q_INVOKABLE void fillLandmarksPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillTracksPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillResidualsMinPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillResidualsMaxPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillResidualsMeanPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillResidualsMedianPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillResidualsFirstQuartilePerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillResidualsThirdQuartilePerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsLengthsMinPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsLengthsMaxPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsLengthsMeanPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsLengthsMedianPerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsLengthsFirstQuartilePerViewSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsLengthsThirdQuartilePerViewSerie(QXYSeries* serie); MSfMData* getMSfmData() { return _msfmData; } void setMSfmData(qtAliceVision::MSfMData* sfmData); MTracks* getMTracks() { return _mTracks; } void setMTracks(qtAliceVision::MTracks* tracks); private: MSfMData* _msfmData = nullptr; MTracks* _mTracks = nullptr; int _residualsPerViewMaxAxisX = 0; int _landmarksPerViewMaxAxisX = 0; double _landmarksPerViewMaxAxisY = 0.0; double _residualsPerViewMaxAxisY = 0.0; int _observationsLengthsPerViewMaxAxisX = 0; double _observationsLengthsPerViewMaxAxisY = 0.0; std::vector _nbResidualsPerViewMin; std::vector _nbResidualsPerViewMax; std::vector _nbResidualsPerViewMean; std::vector _nbResidualsPerViewMedian; std::vector _nbResidualsPerViewFirstQuartile; std::vector _nbResidualsPerViewThirdQuartile; std::vector _nbObservationsLengthsPerViewMin; std::vector _nbObservationsLengthsPerViewMax; std::vector _nbObservationsLengthsPerViewMean; std::vector _nbObservationsLengthsPerViewMedian; std::vector _nbObservationsLengthsPerViewFirstQuartile; std::vector _nbObservationsLengthsPerViewThirdQuartile; std::vector _nbLandmarksPerView; std::vector _nbTracksPerView; }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/MTracks.cpp000066400000000000000000000127431506573753700221160ustar00rootroot00000000000000#include "MTracks.hpp" #include #include #include #include #include #include #include #include namespace qtAliceVision { void TracksIORunnable::run() { aliceVision::track::TracksMap* tracks = new aliceVision::track::TracksMap; aliceVision::track::TracksPerView* tracksPerView = new aliceVision::track::TracksPerView; try { aliceVision::matching::PairwiseMatches pairwiseMatches; if (!aliceVision::matching::Load(pairwiseMatches, /*viewsKeysFilter=*/{}, /*folders=*/_folders, /*descTypes=*/{}, /*maxNbMatches=*/0, /*minNbMatches=*/0)) { qDebug() << "[QtAliceVision] Failed to load matches"; } aliceVision::track::TracksBuilder tracksBuilder; tracksBuilder.build(pairwiseMatches); tracksBuilder.exportToSTL(*tracks); aliceVision::track::computeTracksPerView(*tracks, *tracksPerView); } catch (std::exception& e) { qDebug() << "[QtAliceVision] Error when loading matches: " << "\n" << e.what(); } Q_EMIT resultReady(tracks, tracksPerView); } void TracksDirectIORunnable::run() { aliceVision::track::TracksMap* tracks = new aliceVision::track::TracksMap; aliceVision::track::TracksPerView* tracksPerView = new aliceVision::track::TracksPerView; if (!aliceVision::track::loadTracks(*tracks, _filename)) { if (tracks) { delete tracks; } if (tracksPerView) { delete tracksPerView; } } aliceVision::track::computeTracksPerView(*tracks, *tracksPerView); Q_EMIT resultReady(tracks, tracksPerView); } MTracks::MTracks() { connect(this, &MTracks::matchingFoldersChanged, this, &MTracks::load); connect(this, &MTracks::tracksFileChanged, this, &MTracks::loadDirect); } MTracks::~MTracks() { if (_tracks) delete _tracks; if (_tracksPerView) delete _tracksPerView; setStatus(None); } void MTracks::load() { _needReload = false; if (_status == Loading) { qDebug("[QtAliceVision] Tracks: Unable to load, a load event is already running."); _needReload = true; return; } if (_matchingFolders.empty()) { setStatus(None); return; } setStatus(Loading); // load matches from file in a seperate thread qDebug("[QtAliceVision] Features: Load matches from file in a seperate thread."); std::vector folders; for (const auto& var : _matchingFolders) { folders.push_back(var.toString().toStdString()); } TracksIORunnable* ioRunnable = new TracksIORunnable(folders); connect(ioRunnable, &TracksIORunnable::resultReady, this, &MTracks::onReady); QThreadPool::globalInstance()->start(ioRunnable); } void MTracks::loadDirect() { _needReload = false; if (_status == Loading) { qDebug("[QtAliceVision] Tracks: Unable to load, a load event is already running."); _needReload = true; return; } if (_tracksFile.isEmpty()) { setStatus(None); return; } setStatus(Loading); std::string path = _tracksFile.toString().toStdString(); TracksDirectIORunnable* ioRunnable = new TracksDirectIORunnable(path); connect(ioRunnable, &TracksDirectIORunnable::resultReady, this, &MTracks::onReady); QThreadPool::globalInstance()->start(ioRunnable); } void MTracks::onReady(aliceVision::track::TracksMap* tracks, aliceVision::track::TracksPerView* tracksPerView) { if (_needReload) { if (tracks) delete tracks; if (tracksPerView) delete tracksPerView; setStatus(None); load(); return; } if (tracks && tracksPerView) { if (_tracks) delete _tracks; if (_tracksPerView) delete _tracksPerView; _tracks = tracks; _tracksPerView = tracksPerView; } else if (tracks) { delete tracks; } else if (tracksPerView) { delete tracksPerView; } setStatus(Ready); } void MTracks::setStatus(Status status) { if (status == _status) return; _status = status; Q_EMIT statusChanged(_status); if (status == Ready || status == Error) { Q_EMIT tracksChanged(); } } int MTracks::nbMatches(QString describerType, int viewId) const { if (_status != Ready) { return 0; } if (!_tracksPerView) { return 0; } const auto trackIdsIt = _tracksPerView->find(viewId); if (trackIdsIt == _tracksPerView->end()) { return 0; } const auto& trackIds = trackIdsIt->second; int count = 0; auto descType = aliceVision::feature::EImageDescriberType_stringToEnum(describerType.toStdString()); for (const auto& trackId : trackIds) { const auto trackIt = _tracks->find(trackId); if (trackIt == _tracks->end()) continue; const auto& track = trackIt->second; if (track.descType == descType) { ++count; } } return count; } } // namespace qtAliceVision #include "MTracks.moc" QtAliceVision-2025.1.1/src/qtAliceVision/MTracks.hpp000066400000000000000000000064171506573753700221240ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace qtAliceVision { /** * @brief QObject wrapper around Tracks. * * Given a folder containing feature matches, * the role of an MTracks instance is to load the matches from disk * and build the corresponding tracks. * These tasks are done asynchronously to avoid freezing the UI. * * MTracks objects are accessible from QML * and can be manipulated through their properties. */ class MTracks : public QObject { Q_OBJECT /// Data properties // Path to folder containing the matches Q_PROPERTY(QVariantList matchingFolders MEMBER _matchingFolders NOTIFY matchingFoldersChanged) /// Path to Q_PROPERTY(QUrl tracksFile MEMBER _tracksFile NOTIFY tracksFileChanged) /// Status Q_PROPERTY(Status status READ status NOTIFY statusChanged) public: /// Status Enum enum Status { None = 0, Loading, Ready, Error }; Q_ENUM(Status) MTracks(); MTracks& operator=(const MTracks& other) = default; ~MTracks() override; private: MTracks(const MTracks& other); public: /// Slots Q_SLOT void load(); Q_SLOT void loadDirect(); Q_SLOT void onReady(aliceVision::track::TracksMap* tracks, aliceVision::track::TracksPerView* tracksPerView); /// Signals Q_SIGNAL void matchingFoldersChanged(); Q_SIGNAL void tracksFileChanged(); Q_SIGNAL void tracksChanged(); Q_SIGNAL void statusChanged(Status status); /// Invokables Q_INVOKABLE int nbMatches(QString describerType, int viewId) const; public: const aliceVision::track::TracksMap* tracksPtr() const { return _tracks; } const aliceVision::track::TracksMap& tracks() const { return *_tracks; } const aliceVision::track::TracksPerView& tracksPerView() const { return *_tracksPerView; } Status status() const { return _status; } void setStatus(Status status); private: /// Private members QVariantList _matchingFolders; QUrl _tracksFile; aliceVision::track::TracksMap* _tracks = nullptr; aliceVision::track::TracksPerView* _tracksPerView = nullptr; bool _needReload = false; Status _status = MTracks::None; }; /** * @brief QRunnable object dedicated to loading matches and building tracks using AliceVision. */ class TracksIORunnable : public QObject, public QRunnable { Q_OBJECT public: explicit TracksIORunnable(const std::vector& folders) : _folders(folders) {} Q_SLOT void run() override; Q_SIGNAL void resultReady(aliceVision::track::TracksMap* tracks, aliceVision::track::TracksPerView* tracksPerView); private: std::vector _folders; }; /** * @brief QRunnable object dedicated to loading tracks using AliceVision. */ class TracksDirectIORunnable : public QObject, public QRunnable { Q_OBJECT public: explicit TracksDirectIORunnable(const std::string& filename) : _filename(filename) {} Q_SLOT void run() override; Q_SIGNAL void resultReady(aliceVision::track::TracksMap* tracks, aliceVision::track::TracksPerView* tracksPerView); private: std::string _filename; }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/MViewStats.cpp000066400000000000000000000367011506573753700226200ustar00rootroot00000000000000#include "MViewStats.hpp" #include #include #include #include #include namespace qtAliceVision { void MViewStats::fillResidualFullSerie(QXYSeries* residuals) { if (residuals == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillResidualFullSerie: no residuals"; return; } residuals->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillResidualFullSerie: no SfMData loaded"; return; } if (_viewId == aliceVision::UndefinedIndexT) { qInfo() << "[QtAliceVision] MViewStats::fillResidualFullSerie: no valid view: "; return; } std::vector residualHistX = _residualHistogramFull.GetXbinsValue(); std::vector residualHistY = _residualHistogramFull.GetHist(); if (residualHistX.size() != residualHistY.size()) { throw std::runtime_error("MViewStats::fillResidualFullSerie: residualHistX & residualHistY size mismatch."); } QPen pen(Qt::red, 1, Qt::DashLine, Qt::FlatCap, Qt::BevelJoin); for (std::size_t i = 0; i < residualHistX.size(); ++i) { residuals->append(double(residualHistX[i]), double(residualHistY[i])); residuals->setPen(pen); } } void MViewStats::fillResidualViewSerie(QXYSeries* residuals) { if (residuals == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillResidualViewSerie: no residuals"; return; } residuals->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillResidualViewSerie: no SfMData loaded"; return; } if (_viewId == aliceVision::UndefinedIndexT) { qInfo() << "[QtAliceVision] MViewStats::fillResidualViewSerie: no valid view: "; return; } std::vector residualHistX = _residualHistogramView.GetXbinsValue(); std::vector residualHistY = _residualHistogramView.GetHist(); if (residualHistX.size() != residualHistY.size()) { throw std::runtime_error("MViewStats::fillResidualViewSerie: residualHistX & residualHistY size mismatch."); } QPen pen(Qt::darkBlue, 3, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin); for (std::size_t i = 0; i < residualHistX.size(); ++i) { residuals->append(double(residualHistX[i]), double(residualHistY[i])); residuals->setPen(pen); } } void MViewStats::fillObservationsLengthsFullSerie(QXYSeries* observationsLengths) { if (observationsLengths == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsLengthsFullSerie: no observationsLengths"; return; } observationsLengths->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsLengthsFullSerie: no SfMData loaded"; return; } if (_viewId == aliceVision::UndefinedIndexT) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsLengthsFullSerie: no valid view: "; return; } std::vector observationsLengthsHistX = _observationsLengthsHistogramFull.GetXbinsValue(); std::vector observationsLengthsHistY = _observationsLengthsHistogramFull.GetHist(); if (observationsLengthsHistX.size() != observationsLengthsHistY.size()) { throw std::runtime_error("MViewStats::fillObservationsLengthsFullSerie: observationsLengthsHistX & observationsLengthsHistY size mismatch."); } QPen pen(Qt::red, 1, Qt::DashLine, Qt::FlatCap, Qt::BevelJoin); for (std::size_t i = 0; i < observationsLengthsHistX.size(); ++i) { observationsLengths->append(double(observationsLengthsHistX[i]), double(observationsLengthsHistY[i])); observationsLengths->setPen(pen); } } void MViewStats::fillObservationsLengthsViewSerie(QXYSeries* observationsLengths) { if (observationsLengths == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsLengthsViewSerie: no observationsLengths"; return; } observationsLengths->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsLengthsViewSerie: no SfMData loaded"; return; } if (_viewId == aliceVision::UndefinedIndexT) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsLengthsViewSerie: no valid view: "; return; } std::vector observationsLengthsHistX = _observationsLengthsHistogramView.GetXbinsValue(); std::vector observationsLengthsHistY = _observationsLengthsHistogramView.GetHist(); if (observationsLengthsHistX.size() != observationsLengthsHistY.size()) { throw std::runtime_error("MViewStats::fillObservationsLengthsViewSerie: observationsLengthsHistX & observationsLengthsHistY size mismatch."); } QPen pen(Qt::darkBlue, 3, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin); for (std::size_t i = 0; i < observationsLengthsHistX.size(); ++i) { observationsLengths->append(double(observationsLengthsHistX[i]), double(observationsLengthsHistY[i])); observationsLengths->setPen(pen); } } void MViewStats::fillObservationsScaleFullSerie(QXYSeries* observationsScale) { if (observationsScale == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsScaleFullSerie: no observationsScale"; return; } observationsScale->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsScaleFullSerie: no SfMData loaded"; return; } if (_viewId == aliceVision::UndefinedIndexT) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsScaleFullSerie: no valid view: "; return; } std::vector observationsScaleHistX = _observationsScaleHistogramFull.GetXbinsValue(); std::vector observationsScaleHistY = _observationsScaleHistogramFull.GetHist(); if (observationsScaleHistX.size() != observationsScaleHistY.size()) { throw std::runtime_error("MViewStats::fillObservationsScaleFullSerie: observationsScaleHistX & observationsScaleHistY size mismatch."); } QPen pen(Qt::red, 1, Qt::DashLine, Qt::FlatCap, Qt::BevelJoin); for (std::size_t i = 0; i < observationsScaleHistX.size(); ++i) { observationsScale->append(double(observationsScaleHistX[i]), double(observationsScaleHistY[i])); observationsScale->setPen(pen); } } void MViewStats::fillObservationsScaleViewSerie(QXYSeries* observationsScale) { if (observationsScale == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsScaleViewSerie: no observationsScale"; return; } observationsScale->clear(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsScaleViewSerie: no SfMData loaded"; return; } if (_viewId == aliceVision::UndefinedIndexT) { qInfo() << "[QtAliceVision] MViewStats::fillObservationsScaleViewSerie: no valid view: "; return; } std::vector observationsScaleHistX = _observationsScaleHistogramView.GetXbinsValue(); std::vector observationsScaleHistY = _observationsScaleHistogramView.GetHist(); if (observationsScaleHistX.size() != observationsScaleHistY.size()) { throw std::runtime_error("MViewStats::fillObservationsScaleViewSerie: observationsScaleHistX & observationsScaleHistY size mismatch."); } QPen pen(Qt::darkBlue, 3, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin); for (std::size_t i = 0; i < observationsScaleHistX.size(); ++i) { observationsScale->append(double(observationsScaleHistX[i]), double(observationsScaleHistY[i])); observationsScale->setPen(pen); } } void MViewStats::computeViewStats() { _residualHistogramFull = aliceVision::utils::Histogram(); _residualHistogramView = aliceVision::utils::Histogram(); _observationsLengthsHistogramFull = aliceVision::utils::Histogram(); _observationsLengthsHistogramView = aliceVision::utils::Histogram(); _observationsScaleHistogramFull = aliceVision::utils::Histogram(); _observationsScaleHistogramView = aliceVision::utils::Histogram(); if (_msfmData == nullptr) { qInfo() << "[QtAliceVision] MViewStats::computeViewStats: no SfMData loaded"; return; } if (_viewId == aliceVision::UndefinedIndexT) { qInfo() << "[QtAliceVision] MViewStats::computeViewStats: no valid view: " << _viewId; return; } using namespace aliceVision; // residual histogram { // Init max values per axis _residualMaxAxisX = 0.0; _residualMaxAxisY = 0.0; { // residual histogram of all views BoxStats residualFullStats; sfm::computeResidualsHistogram(_msfmData->rawData(), residualFullStats, &_residualHistogramFull); double nbCameras = double(_msfmData->nbCameras()); // normalize the histogram to get the average number of observations std::vector& residualFullHistY = _residualHistogramFull.GetHist(); for (std::size_t i = 0; i < residualFullHistY.size(); ++i) { residualFullHistY[i] = static_cast(round(static_cast(residualFullHistY[i]) / nbCameras)); } std::vector residualHistX = _residualHistogramFull.GetXbinsValue(); for (std::size_t i = 0; i < residualHistX.size(); ++i) { _residualMaxAxisX = round(std::max(_residualMaxAxisX, residualHistX[i])); } for (std::size_t i = 0; i < residualFullHistY.size(); ++i) { _residualMaxAxisY = round(std::max(_residualMaxAxisY, double(residualFullHistY[i]))); } } { // residual histogram of current view BoxStats residualViewStats; sfm::computeResidualsHistogram(_msfmData->rawData(), residualViewStats, &_residualHistogramView, {_viewId}); std::vector& residualViewHistY = _residualHistogramView.GetHist(); std::vector residualHistX = _residualHistogramView.GetXbinsValue(); for (std::size_t i = 0; i < residualHistX.size(); ++i) { _residualMaxAxisX = round(std::max(_residualMaxAxisX, residualHistX[i])); } for (std::size_t i = 0; i < residualViewHistY.size(); ++i) { _residualMaxAxisY = round(std::max(_residualMaxAxisY, double(residualViewHistY[i]))); } } } _nbObservations = 0; { _observationsLengthsMaxAxisX = 0.0; _observationsLengthsMaxAxisY = 0.0; // observationsLengths histogram { // observationsLengths histogram of all views BoxStats observationsLengthsFullStats; sfm::computeObservationsLengthsHistogram( _msfmData->rawData(), observationsLengthsFullStats, _nbObservations, &_observationsLengthsHistogramFull); const double nbCameras = double(_msfmData->nbCameras()); std::vector& observationsLengthsFullHistY = _observationsLengthsHistogramFull.GetHist(); // normalize the histogram to get the average number of observations for (std::size_t i = 0; i < observationsLengthsFullHistY.size(); ++i) { observationsLengthsFullHistY[i] = static_cast(round(static_cast(observationsLengthsFullHistY[i]) / nbCameras)); } std::vector observationsLengthsHistX = _observationsLengthsHistogramFull.GetXbinsValue(); for (std::size_t i = 0; i < observationsLengthsHistX.size(); ++i) { _observationsLengthsMaxAxisX = round(std::max(_observationsLengthsMaxAxisX, observationsLengthsHistX[i])); } for (std::size_t i = 0; i < observationsLengthsFullHistY.size(); ++i) { _observationsLengthsMaxAxisY = round(std::max(_observationsLengthsMaxAxisY, double(observationsLengthsFullHistY[i]))); } } { // observationsLengths histogram of current view BoxStats observationsLengthsViewStats; sfm::computeObservationsLengthsHistogram( _msfmData->rawData(), observationsLengthsViewStats, _nbObservations, &_observationsLengthsHistogramView, {_viewId}); const std::vector observationsLengthsViewHistY = _observationsLengthsHistogramView.GetHist(); const std::vector observationsLengthsHistX = _observationsLengthsHistogramView.GetXbinsValue(); for (std::size_t i = 0; i < observationsLengthsHistX.size(); ++i) { _observationsLengthsMaxAxisX = round(std::max(_observationsLengthsMaxAxisX, observationsLengthsHistX[i])); } for (std::size_t i = 0; i < observationsLengthsViewHistY.size(); ++i) { _observationsLengthsMaxAxisY = round(std::max(_observationsLengthsMaxAxisY, double(observationsLengthsViewHistY[i]))); } } } // scale histogram { // histogram of observations Scale of all views BoxStats observationsScaleFullStats; sfm::computeScaleHistogram(_msfmData->rawData(), observationsScaleFullStats, &_observationsScaleHistogramFull); const double nbCameras = double(_msfmData->nbCameras()); // normalize the histogram to get the average number of observations std::vector& observationsScaleFullHistY = _observationsScaleHistogramFull.GetHist(); for (std::size_t i = 0; i < observationsScaleFullHistY.size(); ++i) { observationsScaleFullHistY[i] = static_cast(std::round(static_cast(observationsScaleFullHistY[i]) / nbCameras)); } std::vector observationsScaleHistX = _observationsScaleHistogramFull.GetXbinsValue(); // histogram of observations Scale of current view BoxStats observationsScaleViewStats; sfm::computeScaleHistogram(_msfmData->rawData(), observationsScaleViewStats, &_observationsScaleHistogramView, {_viewId}); const std::vector observationsScaleViewHistY = _observationsScaleHistogramView.GetHist(); _observationsScaleMaxAxisX = 0.0; _observationsScaleMaxAxisY = 0.0; for (std::size_t i = 0; i < observationsScaleHistX.size(); ++i) { _observationsScaleMaxAxisX = round(std::max(_observationsScaleMaxAxisX, observationsScaleHistX[i])); } for (std::size_t i = 0; i < observationsScaleFullHistY.size(); ++i) { _observationsScaleMaxAxisY = round(std::max(_observationsScaleMaxAxisY, double(observationsScaleFullHistY[i]))); } for (std::size_t i = 0; i < observationsScaleViewHistY.size(); ++i) { _observationsScaleMaxAxisY = round(std::max(_observationsScaleMaxAxisY, double(observationsScaleViewHistY[i]))); } } Q_EMIT viewStatsChanged(); } void MViewStats::setMSfmData(qtAliceVision::MSfMData* sfmData) { if (_msfmData != nullptr) { disconnect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } _msfmData = sfmData; if (_msfmData != nullptr) { connect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } Q_EMIT sfmDataChanged(); } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/MViewStats.hpp000066400000000000000000000065611506573753700226260ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include namespace qtAliceVision { class MViewStats : public QObject { Q_OBJECT /// 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) /// max AxisX value for reprojection residual histogram Q_PROPERTY(double residualMaxAxisX MEMBER _residualMaxAxisX NOTIFY viewStatsChanged) /// max AxisY value for reprojection residual histogram Q_PROPERTY(double residualMaxAxisY MEMBER _residualMaxAxisY NOTIFY viewStatsChanged) /// max AxisX value for observations lengths histogram Q_PROPERTY(double observationsLengthsMaxAxisX MEMBER _observationsLengthsMaxAxisX NOTIFY viewStatsChanged) /// max AxisY value for observations lengths histogram Q_PROPERTY(double observationsLengthsMaxAxisY MEMBER _observationsLengthsMaxAxisY NOTIFY viewStatsChanged) /// max AxisX value for scale histogram Q_PROPERTY(double observationsScaleMaxAxisX MEMBER _observationsScaleMaxAxisX NOTIFY viewStatsChanged) /// max AxisY value for scale histogram Q_PROPERTY(double observationsScaleMaxAxisY MEMBER _observationsScaleMaxAxisY NOTIFY viewStatsChanged) public: MViewStats() { connect(this, &MViewStats::sfmDataChanged, this, &MViewStats::computeViewStats); connect(this, &MViewStats::viewIdChanged, this, &MViewStats::computeViewStats); } MViewStats& operator=(const MViewStats& other) = default; ~MViewStats() override = default; Q_SIGNAL void sfmDataChanged(); Q_SIGNAL void viewIdChanged(); Q_SIGNAL void viewStatsChanged(); Q_INVOKABLE void fillResidualFullSerie(QXYSeries* serie); Q_INVOKABLE void fillResidualViewSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsLengthsFullSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsLengthsViewSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsScaleFullSerie(QXYSeries* serie); Q_INVOKABLE void fillObservationsScaleViewSerie(QXYSeries* serie); Q_SLOT void computeViewStats(); MSfMData* getMSfmData() { return _msfmData; } void setMSfmData(qtAliceVision::MSfMData* sfmData); private: aliceVision::utils::Histogram _residualHistogramFull; aliceVision::utils::Histogram _residualHistogramView; aliceVision::utils::Histogram _observationsLengthsHistogramFull; aliceVision::utils::Histogram _observationsLengthsHistogramView; aliceVision::utils::Histogram _observationsScaleHistogramFull; aliceVision::utils::Histogram _observationsScaleHistogramView; double _residualMaxAxisX = 0.0; double _residualMaxAxisY = 0.0; double _observationsLengthsMaxAxisX = 0.0; double _observationsLengthsMaxAxisY = 0.0; double _observationsScaleMaxAxisX = 0.0; double _observationsScaleMaxAxisY = 0.0; int _nbObservations = 0; MSfMData* _msfmData = nullptr; aliceVision::IndexT _viewId = aliceVision::UndefinedIndexT; }; } // namespace qtAliceVision Q_DECLARE_METATYPE(QPointF) // for usage in signals/slots/properties QtAliceVision-2025.1.1/src/qtAliceVision/Painter.cpp000066400000000000000000000214141506573753700221470ustar00rootroot00000000000000#include #include #include #include #if QT_CONFIG(opengl) #include #else #error "opengl must be valid" #endif namespace qtAliceVision { class PointMaterialShader; class PointMaterial : public QSGMaterial { public: PointMaterial() : _color(1, 1, 1, 1), _size(1.0f) {} void setColor(const QColor& color) { _color = color; } QColor getColor() const { return _color; } void setSize(const float& size) { _size = size; } float getSize() const { return _size; } QSGMaterialType* type() const override { static QSGMaterialType type; return &type; } int compare(const QSGMaterial* other) const override { Q_ASSERT(other && type() == other->type()); return other == this ? 0 : (other > this ? 1 : -1); } QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; private: QColor _color; float _size; }; class PointMaterialShader : public QSGMaterialShader { public: PointMaterialShader() { setShaderFileName(VertexStage, QLatin1String(":/shaders/FeaturesViewer.vert.qsb")); setShaderFileName(FragmentStage, QLatin1String(":/shaders/FeaturesViewer.frag.qsb")); } bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override { bool changed = false; QByteArray* buf = state.uniformData(); if (state.isMatrixDirty()) { const QMatrix4x4 m = state.combinedMatrix(); memcpy(buf->data() + 0, m.constData(), 64); changed = true; } auto* currentMaterial = static_cast(newMaterial); if (currentMaterial != nullptr) { const QColor& color = currentMaterial->getColor(); const float& size = currentMaterial->getSize(); bool hasColorChanged = true; bool hasSizeChanged = true; if (oldMaterial != nullptr) { auto* previousMaterial = static_cast(oldMaterial); QColor pc = previousMaterial->getColor(); if (color == pc) { hasColorChanged = false; } if (previousMaterial->getSize() == size) { hasSizeChanged = false; } } if (state.isOpacityDirty() || hasColorChanged) { float opacity = state.opacity() * color.alphaF(); QVector4D v(color.redF() * opacity, color.greenF() * opacity, color.blueF() * opacity, opacity); memcpy(buf->data() + 64, &v, 4 * 4); changed = true; } if (hasSizeChanged) { memcpy(buf->data() + 80, &size, 4); changed = true; } } return changed; } }; QSGMaterialShader* PointMaterial::createShader(QSGRendererInterface::RenderMode) const { return new PointMaterialShader; } Painter::Painter(const std::vector& layers) : _layers(layers) {} int Painter::layerIndex(const std::string& layer) const { for (std::size_t idx = 0; idx < _layers.size(); idx++) { if (_layers[idx] == layer) { return static_cast(idx); } } return -1; } bool Painter::ensureGeometry(QSGNode* node) const { if (!node) { qDebug() << "[qtAliceVision] Painter::ensureGeometry: invalid node"; return false; } while (node->childCount() < static_cast(_layers.size())) { auto root = new QSGGeometryNode; auto geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0, 0, QSGGeometry::UnsignedIntType); geometry->setIndexDataPattern(QSGGeometry::StaticPattern); geometry->setVertexDataPattern(QSGGeometry::StaticPattern); auto material = new PointMaterial; root->setGeometry(geometry); root->setMaterial(material); root->setFlags(QSGNode::OwnsGeometry); root->setFlags(QSGNode::OwnsMaterial); node->appendChildNode(root); } return true; } QSGGeometryNode* Painter::getGeometryNode(QSGNode* node, const std::string& layer) const { if (!ensureGeometry(node)) { qDebug() << "[qtAliceVision] Painter::getGeometryNode: could not ensure node layers are correct"; return nullptr; } int index = layerIndex(layer); if (index < 0) { qDebug() << "[qtAliceVision] Painter::getGeometryNode: could not find corresponding index for layer " << layer; return nullptr; } auto* root = static_cast(node->childAtIndex(index)); return root; } void Painter::clearLayer(QSGNode* node, const std::string& layer) const { auto* root = getGeometryNode(node, layer); if (!root) { qDebug() << "[qtAliceVision] Painter::clearLayer: failed to retrieve geometry node for layer " << layer; return; } root->markDirty(QSGNode::DirtyGeometry); auto geometry = root->geometry(); geometry->allocate(0, 0); } void Painter::drawPoints(QSGNode* node, const std::string& layer, const std::vector& points, const QColor& color, float pointSize) const { auto* root = getGeometryNode(node, layer); if (!root) { qDebug() << "[qtAliceVision] Painter::drawPoints: failed to retrieve geometry node for layer " << layer; return; } root->markDirty(QSGNode::DirtyGeometry); auto geometry = root->geometry(); geometry->allocate(static_cast(points.size()), 0); geometry->setDrawingMode(QSGGeometry::DrawPoints); // geometry->setLineWidth(pointSize); auto* vertices = geometry->vertexDataAsPoint2D(); if (!vertices) { qDebug() << "[qtAliceVision] Painter::drawPoints: invalid vertex data for layer " << layer; return; } for (std::size_t i = 0; i < points.size(); i++) { const QPointF& p = points[i]; vertices[i].set(static_cast(p.x()), static_cast(p.y())); } auto* material = static_cast(root->material()); if (!material) { qDebug() << "[qtAliceVision] Painter::drawPoints: invalid material for layer " << layer; return; } material->setColor(color); material->setSize(pointSize); } void Painter::drawLines(QSGNode* node, const std::string& layer, const std::vector& points, const QColor& color, float lineWidth) const { auto* root = getGeometryNode(node, layer); if (!root) { qDebug() << "[qtAliceVision] Painter::drawLines: failed to retrieve geometry node for layer " << layer; return; } root->markDirty(QSGNode::DirtyGeometry); auto geometry = root->geometry(); geometry->allocate(static_cast(points.size()), 0); geometry->setDrawingMode(QSGGeometry::DrawLines); geometry->setLineWidth(lineWidth); auto* vertices = geometry->vertexDataAsPoint2D(); if (!vertices) { qDebug() << "[qtAliceVision] Painter::drawLines: invalid vertex data for layer " << layer; return; } for (std::size_t i = 0; i < points.size(); i++) { const QPointF& p = points[i]; vertices[i].set(static_cast(p.x()), static_cast(p.y())); } auto* material = static_cast(root->material()); if (!material) { qDebug() << "[qtAliceVision] Painter::drawLines: invalid material for layer " << layer; return; } material->setColor(color); } void Painter::drawTriangles(QSGNode* node, const std::string& layer, const std::vector& points, const QColor& color) const { auto* root = getGeometryNode(node, layer); if (!root) { qDebug() << "[qtAliceVision] Painter::drawTriangles: failed to retrieve geometry node for layer " << layer; return; } root->markDirty(QSGNode::DirtyGeometry); auto geometry = root->geometry(); geometry->allocate(static_cast(points.size()), 0); geometry->setDrawingMode(QSGGeometry::DrawTriangles); auto* vertices = geometry->vertexDataAsPoint2D(); if (!vertices) { qDebug() << "[qtAliceVision] Painter::drawTriangles: invalid vertex data for layer " << layer; return; } for (std::size_t i = 0; i < points.size(); i++) { const QPointF& p = points[i]; vertices[i].set(static_cast(p.x()), static_cast(p.y())); } auto* material = static_cast(root->material()); if (!material) { qDebug() << "[qtAliceVision] Painter::drawTriangles: invalid material for layer " << layer; return; } material->setColor(color); } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/Painter.hpp000066400000000000000000000063511506573753700221570ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace qtAliceVision { /** * @brief Utility class for abstracting the painting mechanisms in the Qt scene graph. * * The painting order is managed by a system of layers, identified by their names. * The first layer will be drawn behind all the others and the last layer will be drawn on top. * * Each layer is drawn using a flat color material, meaning all geometry will have the same color. */ class Painter { public: /** * @brief Construct a Painter object with the given layers. * @param layers Layer names ordered from first to last drawn. */ explicit Painter(const std::vector& layers); /** * @brief Clear the content of a layer. * @param node Scene graph node in which the drawing occurs. * @param layer Layer name. */ void clearLayer(QSGNode* node, const std::string& layer) const; /** * @brief Clear a layer and draw points on it. * @param node Scene graph node in which the drawing occurs. * @param layer Layer name. * @param points 2D points to draw. * @param color Points color (same for all points). * @param pointSize Points size (same for all points). */ void drawPoints(QSGNode* node, const std::string& layer, const std::vector& points, const QColor& color, float pointSize) const; /** * @brief Clear a layer and draw lines on it. * @param node Scene graph node in which the drawing occurs. * @param layer Layer name. * @param points Line corners as 2D points. * @param color Lines color (same for all lines). * @param lineWidth Lines width (same for all lines). */ void drawLines(QSGNode* node, const std::string& layer, const std::vector& points, const QColor& color, float lineWidth) const; /** * @brief Clear a layer and draw triangles on it. * @param node Scene graph node in which the drawing occurs. * @param layer Layer name. * @param points Triangle corners as 2D points. * @param color Triangles color (same for all triangles). */ void drawTriangles(QSGNode* node, const std::string& layer, const std::vector& points, const QColor& color) const; private: /// Layers stack, ordered from first to last drawn. std::vector _layers; /** * @brief Retrieve the index of a layer in the layers stack. * @param layer Layer name. * @return Layer index. */ int layerIndex(const std::string& layer) const; /** * @brief Add children nodes if necessary to the given scene graph node to match this object's layers. * @param node Scene graph node in which the drawing occurs. * @return Whether of not a problem occured. */ bool ensureGeometry(QSGNode* node) const; /** * @brief Retrieve the geometry node corresponding to a given layer. * @param node Scene graph node in which the drawing occurs. * @param layer Layer name. * @return Corresponding geometry node if it exists, otherwise nullptr. */ QSGGeometryNode* getGeometryNode(QSGNode* node, const std::string& layer) const; }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/PanoramaViewer.cpp000066400000000000000000000053341506573753700234700ustar00rootroot00000000000000#include "PanoramaViewer.hpp" #include "FloatImageViewer.hpp" #include "FloatTexture.hpp" #include #include #include #include #include #include #include #include #include namespace qtAliceVision { PanoramaViewer::PanoramaViewer(QQuickItem* parent) : QQuickItem(parent) { setFlag(QQuickItem::ItemHasContents, true); connect(this, &PanoramaViewer::sourceSizeChanged, this, &PanoramaViewer::update); connect(this, &PanoramaViewer::downscaleChanged, this, &PanoramaViewer::update); connect(this, &PanoramaViewer::sfmDataChanged, this, &PanoramaViewer::msfmDataUpdate); } PanoramaViewer::~PanoramaViewer() {} void PanoramaViewer::computeDownscale() { if (!_msfmData || _msfmData->status() != MSfMData::Status::Ready) return; int totalSizeImages = 0; for (const auto& view : _msfmData->rawData().getViews()) totalSizeImages += int(static_cast((view.second->getImage().getWidth() * view.second->getImage().getHeight()) * 4) / std::pow(10, 6)); // Downscale = 4 by default _downscale = 4; for (int i = 0; i < _downscale; i++) totalSizeImages = static_cast(totalSizeImages * 0.5); // ensure it fits in RAM memory aliceVision::system::MemoryInfo memInfo = aliceVision::system::getMemoryInfo(); const int freeRam = int(static_cast(memInfo.freeRam) / std::pow(2, 20)); while (totalSizeImages > freeRam * 0.5) { _downscale++; totalSizeImages = static_cast(totalSizeImages * 0.5); } Q_EMIT downscaleReady(); } QSGNode* PanoramaViewer::updatePaintNode(QSGNode* oldNode, [[maybe_unused]] QQuickItem::UpdatePaintNodeData* data) { QSGGeometryNode* root = static_cast(oldNode); return root; } void PanoramaViewer::setMSfmData(MSfMData* sfmData) { if (_msfmData == sfmData) return; if (_msfmData != nullptr) { disconnect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } _msfmData = sfmData; if (!_msfmData) return; if (_msfmData != nullptr) { connect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } if (_msfmData->status() != MSfMData::Ready) { qWarning() << "[QtAliceVision] PANORAMA setMSfmData: SfMData is not ready: " << _msfmData->status(); return; } if (_msfmData->rawData().getViews().empty()) { qWarning() << "[QtAliceVision] PANORAMA setMSfmData: SfMData is empty"; return; } } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/PanoramaViewer.hpp000066400000000000000000000030341506573753700234700ustar00rootroot00000000000000#pragma once #include "FloatImageViewer.hpp" #include "FloatTexture.hpp" #include "Surface.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace qtAliceVision { /** * @brief Displays a list of Float Images. */ class PanoramaViewer : public QQuickItem { Q_OBJECT Q_PROPERTY(QSize sourceSize READ sourceSize NOTIFY sourceSizeChanged) Q_PROPERTY(qtAliceVision::MSfMData* msfmData READ getMSfmData WRITE setMSfmData NOTIFY sfmDataChanged) Q_PROPERTY(int downscale MEMBER _downscale NOTIFY downscaleChanged) public: explicit PanoramaViewer(QQuickItem* parent = nullptr); ~PanoramaViewer() override; QSize sourceSize() const { return _sourceSize; } MSfMData* getMSfmData() { return _msfmData; } void setMSfmData(MSfMData* sfmData); Q_SLOT void msfmDataUpdate() { computeDownscale(); } public: Q_SIGNAL void sourceSizeChanged(); Q_SIGNAL void sfmDataChanged(); Q_SIGNAL void downscaleChanged(); Q_SIGNAL void downscaleReady(); private: /// Custom QSGNode update QSGNode* updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* data) override; void computeDownscale(); private: QSize _sourceSize = QSize(3000, 1500); MSfMData* _msfmData = nullptr; int _downscale = 4; }; } // namespace qtAliceVision Q_DECLARE_METATYPE(QList) QtAliceVision-2025.1.1/src/qtAliceVision/PhongImageViewer.cpp000066400000000000000000000343451506573753700237540ustar00rootroot00000000000000#include "PhongImageViewer.hpp" #include #include #include #include namespace qtAliceVision { namespace { class PhongImageViewerMaterialShader; class PhongImageViewerMaterial : public QSGMaterial { public: PhongImageViewerMaterial() {} QSGMaterialType* type() const override { static QSGMaterialType type; return &type; } int compare(const QSGMaterial* other) const override { Q_ASSERT(other && type() == other->type()); return other == this ? 0 : (other > this ? 1 : -1); } QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; struct { // warning: matches layout and padding of PhongImageViewer.vert/frag shaders QVector4D channelOrder = QVector4D(0, 1, 2, 3); QVector4D baseColor = QVector4D(.2f, .2f, .2f, 1.f); QVector3D lightDirection = QVector3D(0.f, 0.f, -1.f); float textureOpacity = 1.f; float gamma = 1.f; float gain = 0.f; float ka = 0.f; float kd = 1.f; float ks = 1.f; float shininess = 20.f; } uniforms; std::unique_ptr sourceTexture = std::make_unique(); // should be initialized std::unique_ptr normalTexture = std::make_unique(); // should be initialized bool dirtyUniforms; }; class PhongImageViewerMaterialShader : public QSGMaterialShader { public: PhongImageViewerMaterialShader() { setShaderFileName(VertexStage, QLatin1String(":/shaders/PhongImageViewer.vert.qsb")); setShaderFileName(FragmentStage, QLatin1String(":/shaders/PhongImageViewer.frag.qsb")); } bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override { bool changed = false; QByteArray* buf = state.uniformData(); Q_ASSERT(buf->size() >= 84); if (state.isMatrixDirty()) { const QMatrix4x4 m = state.combinedMatrix(); memcpy(buf->data() + 0, m.constData(), 64); changed = true; } if (state.isOpacityDirty()) { const float opacity = state.opacity(); memcpy(buf->data() + 64, &opacity, 4); changed = true; } auto* customMaterial = static_cast(newMaterial); if (oldMaterial != newMaterial || customMaterial->dirtyUniforms) { memcpy(buf->data() + 80, &customMaterial->uniforms, 72); // uniforms size is 72 customMaterial->dirtyUniforms = false; changed = true; } return changed; } void updateSampledImage(RenderState& state, int binding, QSGTexture** texture, QSGMaterial* newMaterial, QSGMaterial*) override { PhongImageViewerMaterial* mat = static_cast(newMaterial); if (binding == 1) { mat->sourceTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch()); *texture = mat->sourceTexture.get(); } if (binding == 2) { mat->normalTexture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch()); *texture = mat->normalTexture.get(); } } }; QSGMaterialShader* PhongImageViewerMaterial::createShader(QSGRendererInterface::RenderMode) const { return new PhongImageViewerMaterialShader; } class PhongImageViewerNode : public QSGGeometryNode { public: PhongImageViewerNode() { auto* m = new PhongImageViewerMaterial; setMaterial(m); setFlag(OwnsMaterial, true); QSGGeometry* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 0); geometry->setIndexDataPattern(QSGGeometry::StaticPattern); geometry->setVertexDataPattern(QSGGeometry::StaticPattern); setGeometry(geometry); setFlag(OwnsGeometry, true); } void setBlending(bool value) { auto* m = static_cast(material()); m->setFlag(QSGMaterial::Blending, value); } void setEmptyGeometry() { auto* g = geometry(); if (g->vertexCount() != 0) { g->allocate(0); markDirty(QSGNode::DirtyGeometry); } } void setRectGeometry(const QRectF& bounds) { auto* g = geometry(); if (g->vertexCount() != 4) { geometry()->allocate(4); } QSGGeometry::updateTexturedRectGeometry(g, bounds, QRectF(0, 0, 1, 1)); markDirty(QSGNode::DirtyGeometry); } void setSourceParameters(const QVector4D& channelOrder, float gamma, float gain) { auto* m = static_cast(material()); m->uniforms.gamma = gamma; m->uniforms.gain = gain; m->uniforms.channelOrder = channelOrder; m->dirtyUniforms = true; markDirty(DirtyMaterial); } void setShadingParameters(const QVector4D& baseColor, const QVector3D& lightDirection, float textureOpacity, float ka, float kd, float ks, float shininess) { auto* m = static_cast(material()); m->uniforms.baseColor = baseColor; m->uniforms.lightDirection = lightDirection; m->uniforms.textureOpacity = textureOpacity; m->uniforms.ka = ka; m->uniforms.kd = kd; m->uniforms.ks = ks; m->uniforms.shininess = shininess; m->dirtyUniforms = true; markDirty(DirtyMaterial); } void setTextures(std::unique_ptr sourceTexture, std::unique_ptr normalTexture) { auto* m = static_cast(material()); m->sourceTexture = std::move(sourceTexture); m->normalTexture = std::move(normalTexture); markDirty(DirtyMaterial); } }; } // namespace PhongImageViewer::PhongImageViewer(QQuickItem* parent) : QQuickItem(parent) { // flags setFlag(QQuickItem::ItemHasContents, true); // connects connect(this, &PhongImageViewer::sourcePathChanged, this, &PhongImageViewer::reload); connect(this, &PhongImageViewer::normalPathChanged, this, &PhongImageViewer::reload); connect(this, &PhongImageViewer::sourceParametersChanged, this, [this] { _sourceParametersChanged = true; update(); }); connect(this, &PhongImageViewer::shadingParametersChanged, this, [this] { _shadingParametersChanged = true; update(); }); connect(this, &PhongImageViewer::textureSizeChanged, this, &PhongImageViewer::update); connect(this, &PhongImageViewer::sourceSizeChanged, this, &PhongImageViewer::update); connect(this, &PhongImageViewer::imageChanged, this, &PhongImageViewer::update); connect(&_sourceImageLoader, &imgserve::SingleImageLoader::requestHandled, this, &PhongImageViewer::reload); connect(&_normalImageLoader, &imgserve::SingleImageLoader::requestHandled, this, &PhongImageViewer::reload); } PhongImageViewer::~PhongImageViewer() {} void PhongImageViewer::setStatus(EStatus status) { if (_status == status) { return; } _status = status; Q_EMIT statusChanged(); } void PhongImageViewer::reload() { // check source image path and normal image path if (!_sourcePath.isValid() || !_normalPath.isValid()) { clearImages(); setStatus(EStatus::MISSING_FILE); return; } // use image server to request input images imgserve::RequestData requestSourceImage = {_sourcePath.toLocalFile().toUtf8().toStdString()}; imgserve::RequestData requestNormalImage = {_normalPath.toLocalFile().toUtf8().toStdString()}; imgserve::ResponseData responseSourceImage = _sourceImageLoader.request(requestSourceImage); imgserve::ResponseData responseNormalImage = _normalImageLoader.request(requestNormalImage); // check for loading errors if ((responseSourceImage.img == nullptr) || (responseNormalImage.img == nullptr)) { if(responseSourceImage.error == imgserve::LoadingStatus::UNDEFINED || responseNormalImage.error == imgserve::LoadingStatus::UNDEFINED) { setStatus(EStatus::LOADING); } else if (responseSourceImage.error == imgserve::LoadingStatus::MISSING_FILE || responseNormalImage.error == imgserve::LoadingStatus::MISSING_FILE) { clearImages(); setStatus(EStatus::MISSING_FILE); } else // loading error { clearImages(); setStatus(EStatus::LOADING_ERROR); } return; } // check source and normal images dimensions if (responseSourceImage.dim != responseSourceImage.dim) { clearImages(); setStatus(EStatus::LOADING_ERROR); return; } // reset status setStatus(EStatus::NONE); // copy source image _sourceImage = responseSourceImage.img; // copy normal image _normalImage = responseNormalImage.img; // images have changed _imageChanged = true; Q_EMIT imageChanged(); // source image dimensions have changed _sourceSize = responseSourceImage.dim; Q_EMIT sourceSizeChanged(); // source image metadata have changed _metadata = responseSourceImage.metadata; Q_EMIT metadataChanged(); } void PhongImageViewer::clearImages() { _sourceImage.reset(); _normalImage.reset(); _imageChanged = true; Q_EMIT imageChanged(); } QSGNode* PhongImageViewer::updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* data) { auto* node = static_cast(oldNode); bool isNewNode = false; if (!node) { node = new PhongImageViewerNode(); isNewNode = true; } // update textures if (_imageChanged) { QSize newTextureSize; auto sourceTexture = std::make_unique(); auto normalTexture = std::make_unique(); if (_sourceImage) { sourceTexture->setImage(_sourceImage); sourceTexture->setFiltering(QSGTexture::Nearest); sourceTexture->setHorizontalWrapMode(QSGTexture::Repeat); sourceTexture->setVerticalWrapMode(QSGTexture::Repeat); newTextureSize = sourceTexture->textureSize(); } if (_normalImage) { normalTexture->setImage(_normalImage); normalTexture->setFiltering(QSGTexture::Nearest); normalTexture->setHorizontalWrapMode(QSGTexture::Repeat); normalTexture->setVerticalWrapMode(QSGTexture::Repeat); } node->setTextures(std::move(sourceTexture), std::move(normalTexture)); if (_textureSize != newTextureSize) { _textureSize = newTextureSize; _geometryChanged = true; Q_EMIT textureSizeChanged(); } _imageChanged = false; } const auto newBoundingRect = boundingRect(); // update geometry if (_geometryChanged || _boundingRect != newBoundingRect) { _boundingRect = newBoundingRect; if (_textureSize.width() <= 0 || _textureSize.height() <= 0) { node->setEmptyGeometry(); } else { const float windowRatio = static_cast(_boundingRect.width()) / static_cast(_boundingRect.height()); const float textureRatio = static_cast(_textureSize.width()) / static_cast(_textureSize.height()); QRectF geometryRect = _boundingRect; if (windowRatio > textureRatio) geometryRect.setWidth(geometryRect.height() * textureRatio); else geometryRect.setHeight(geometryRect.width() / textureRatio); geometryRect.moveCenter(_boundingRect.center()); static const int MARGIN = 0; geometryRect = geometryRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN); node->setRectGeometry(geometryRect); } _geometryChanged = false; } // update shader source parameters if (isNewNode || _sourceParametersChanged) { QVector4D channelOrder(0.f, 1.f, 2.f, 3.f); switch (_channelMode) { case EChannelMode::R: channelOrder = QVector4D(0.f, 0.f, 0.f, -1.f); break; case EChannelMode::G: channelOrder = QVector4D(1.f, 1.f, 1.f, -1.f); break; case EChannelMode::B: channelOrder = QVector4D(2.f, 2.f, 2.f, -1.f); break; case EChannelMode::A: channelOrder = QVector4D(3.f, 3.f, 3.f, -1.f); break; default: break; } node->setSourceParameters(channelOrder, _gamma, _gain); node->setBlending(_channelMode == EChannelMode::RGBA); _sourceParametersChanged = false; } // update shader Blinn-Phong parameters if (isNewNode || _shadingParametersChanged) { // light direction from Yaw and Pitch const Eigen::AngleAxis yawA(static_cast(aliceVision::degreeToRadian(_lightYaw)), Eigen::Vector3d::UnitY()); const Eigen::AngleAxis pitchA(static_cast(aliceVision::degreeToRadian(_lightPitch)), Eigen::Vector3d::UnitX()); const aliceVision::Vec3 direction = yawA.toRotationMatrix() * pitchA.toRotationMatrix() * aliceVision::Vec3(0.0, 0.0, -1.0); const QVector3D lightDirection(static_cast(direction.x()), static_cast(direction.y()), static_cast(direction.z())); // linear base color from QColor const QVector4D baseColor(std::pow(static_cast(_baseColor.redF()), 2.2f), std::pow(static_cast(_baseColor.greenF()), 2.2f), std::pow(static_cast(_baseColor.blueF()), 2.2f), _baseColor.alphaF()); node->setShadingParameters(baseColor, lightDirection, _textureOpacity, _ka, _kd, _ks, _shininess); _shadingParametersChanged = false; } return node; } } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/PhongImageViewer.frag000066400000000000000000000037341506573753700241070ustar00rootroot00000000000000#version 440 layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 fragColor; layout(std140, binding = 0) uniform buf { // warning: matches layout and padding of PhongImageViewerMaterial::uniforms mat4 qt_Matrix; // offset 0 float qt_Opacity; // offset 64 float _padding1; float _padding2; float _padding3; vec4 channelOrder; // offset 80 vec4 baseColor; vec3 lightDirection; float textureOpacity; float gamma; float gain; float ka; float kd; float ks; float shininess; }; layout(binding = 1) uniform sampler2D sourceSampler; layout(binding = 2) uniform sampler2D normalSampler; void main() { vec4 diffuseColor; vec4 tColor = texture(sourceSampler, vTexCoord); vec3 normal = texture(normalSampler, vTexCoord).rgb; // apply gamma and gain on linear source texture tColor.rgb = pow((tColor.rgb * vec3(gain)), vec3(1.0/gamma)); // apply channel mode diffuseColor.r = tColor[int(channelOrder[0])]; diffuseColor.g = tColor[int(channelOrder[1])]; diffuseColor.b = tColor[int(channelOrder[2])]; diffuseColor.a = int(channelOrder[3]) == -1 ? 1.0 : tColor[int(channelOrder[3])]; // apply texture opacity diffuseColor *= textureOpacity; diffuseColor += baseColor * (1.0 - textureOpacity); diffuseColor.a *= qt_Opacity; // Blinn-Phong shading float lambertian = max(dot(lightDirection, normal), 0.0); float specular = 0.0; if (lambertian > 0.0) { vec3 viewDir = vec3(0, 0, -1); vec3 halfDir = normalize(lightDirection + viewDir); float specularAngle = max(dot(halfDir, normal), 0.0); specular = pow(specularAngle, shininess); } vec3 outLinear = vec3(ka, ka, ka) + kd * diffuseColor.rgb * lambertian + vec3(ks, ks, ks) * specular; vec3 outGammaCorrected = pow(outLinear, vec3(1.0 / 2.2)); // assume the monitor is calibrated to the sRGB colorspace fragColor = vec4(outGammaCorrected, diffuseColor.a); }QtAliceVision-2025.1.1/src/qtAliceVision/PhongImageViewer.hpp000066400000000000000000000105671506573753700237610ustar00rootroot00000000000000#pragma once #include "FloatTexture.hpp" #include "SingleImageLoader.hpp" #include #include #include #include namespace qtAliceVision { /** * @brief Load and display image (albedo and normal) with a given light direction. * Shading is done using Blinn-Phong reflection model. */ class PhongImageViewer : public QQuickItem { Q_OBJECT // file paths Q_PROPERTY(QUrl sourcePath MEMBER _sourcePath NOTIFY sourcePathChanged) Q_PROPERTY(QUrl normalPath MEMBER _normalPath NOTIFY normalPathChanged) // source parameters (gamma, gain, channelMode) Q_PROPERTY(EChannelMode channelMode MEMBER _channelMode NOTIFY sourceParametersChanged) Q_PROPERTY(float gamma MEMBER _gamma NOTIFY sourceParametersChanged) Q_PROPERTY(float gain MEMBER _gain NOTIFY sourceParametersChanged) // shading parameters (material, light) Q_PROPERTY(QColor baseColor MEMBER _baseColor NOTIFY shadingParametersChanged) Q_PROPERTY(float textureOpacity MEMBER _textureOpacity NOTIFY shadingParametersChanged) Q_PROPERTY(float ka MEMBER _ka NOTIFY shadingParametersChanged) Q_PROPERTY(float kd MEMBER _kd NOTIFY shadingParametersChanged) Q_PROPERTY(float ks MEMBER _ks NOTIFY shadingParametersChanged) Q_PROPERTY(float shininess MEMBER _shininess NOTIFY shadingParametersChanged) Q_PROPERTY(float lightYaw MEMBER _lightYaw NOTIFY shadingParametersChanged) Q_PROPERTY(float lightPitch MEMBER _lightPitch NOTIFY shadingParametersChanged) // texture Q_PROPERTY(QSize textureSize MEMBER _textureSize NOTIFY textureSizeChanged) Q_PROPERTY(QSize sourceSize READ sourceSize NOTIFY sourceSizeChanged) // metadata Q_PROPERTY(QVariantMap metadata READ metadata NOTIFY metadataChanged) // viewer status Q_PROPERTY(EStatus status READ status NOTIFY statusChanged) public: enum class EStatus : quint8 { NONE, // nothing is happening, no error has been detected LOADING, // an image is being loaded MISSING_FILE, // the file to load is missing LOADING_ERROR, // generic error }; Q_ENUM(EStatus) enum class EChannelMode : quint8 { RGBA, RGB, R, G, B, A }; Q_ENUM(EChannelMode) // constructor explicit PhongImageViewer(QQuickItem* parent = nullptr); // destructor ~PhongImageViewer() override; // accessors EStatus status() const { return _status; } const QSize& sourceSize() const { return _sourceSize; } const QVariantMap& metadata() const { return _metadata; } // signals Q_SIGNAL void sourcePathChanged(); Q_SIGNAL void normalPathChanged(); Q_SIGNAL void sourceParametersChanged(); Q_SIGNAL void shadingParametersChanged(); Q_SIGNAL void textureSizeChanged(); Q_SIGNAL void sourceSizeChanged(); Q_SIGNAL void metadataChanged(); Q_SIGNAL void imageChanged(); Q_SIGNAL void statusChanged(); private: // set viewer status void setStatus(EStatus status); // reload image(s) void reload(); // clear images in memory void clearImages(); // custom QSGNode update QSGNode* updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* data) override; private: // file paths QUrl _sourcePath; QUrl _normalPath; // source parameters (gamma, gain, channelMode) EChannelMode _channelMode = EChannelMode::RGBA; float _gamma = 1.f; float _gain = 1.f; bool _sourceParametersChanged = false; // shading parameters (material, light) QColor _baseColor = {50, 50, 50}; float _textureOpacity = 1.f; float _ka = 0.f; float _kd = 1.f; float _ks = 1.f; float _shininess = 20.f; float _lightYaw = 0.f; float _lightPitch = 0.f; bool _shadingParametersChanged = false; // metadata QVariantMap _metadata; // screen and texture size QRectF _boundingRect = QRectF(); QSize _textureSize = QSize(0, 0); QSize _sourceSize = QSize(0, 0); // image std::shared_ptr _sourceImage; std::shared_ptr _normalImage; // image loaders imgserve::SingleImageLoader _sourceImageLoader; imgserve::SingleImageLoader _normalImageLoader; // viewer status EStatus _status = EStatus::NONE; bool _geometryChanged = false; bool _imageChanged = false; }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/PhongImageViewer.vert000066400000000000000000000013231506573753700241400ustar00rootroot00000000000000#version 440 layout(location = 0) in vec4 position; layout(location = 1) in vec2 texCoord; layout(location = 0) out vec2 vTexCoord; layout(std140, binding = 0) uniform buf { // warning: matches layout and padding of PhongImageViewerMaterial::uniforms mat4 qt_Matrix; // offset 0 float qt_Opacity; // offset 64 float _padding1; float _padding2; float _padding3; vec4 channelOrder; // offset 80 vec4 baseColor; vec3 lightDirection; float textureOpacity; float gamma; float gain; float ka; float kd; float ks; float shininess; }; out gl_PerVertex { vec4 gl_Position; }; void main() { vTexCoord = texCoord; gl_Position = qt_Matrix * position; }QtAliceVision-2025.1.1/src/qtAliceVision/SequenceCache.cpp000066400000000000000000000106651506573753700232470ustar00rootroot00000000000000#include "SequenceCache.hpp" #include using namespace aliceVision; namespace qtAliceVision { namespace imgserve { SequenceCache::SequenceCache(QObject* parent) : QObject(parent) { // Retrieve memory information from system const auto memInfo = system::getMemoryInfo(); // Compute proportion of RAM that can be dedicated to image caching // For now we use 30% of available RAM const double availableRam = static_cast(memInfo.availableRam); const double cacheRatio = 0.3; const double cacheRam = cacheRatio * availableRam; _maxMemory = static_cast(cacheRam); _fetcher.setAutoDelete(false); // Cache does not exist // Let's create a new one! { ImageCache::uptr cache = std::make_unique(_maxMemory, image::EImageColorSpace::LINEAR); _fetcher.setCache(std::move(cache)); } } SequenceCache::~SequenceCache() { _fetcher.stopAsync(); _threadPool.waitForDone(); } void SequenceCache::setSequence(const QVariantList& paths) { _fetcher.stopAsync(); _threadPool.waitForDone(); // Convert to string std::vector sequence; for (const auto& item : paths) { sequence.push_back(item.toString().toStdString()); } // Assign sequence to fetcher _fetcher.setSequence(sequence); // Restart if needed const bool isAsync = true; setAsyncFetching(isAsync); } void SequenceCache::setResizeRatio(double ratio) { _fetcher.setResizeRatio(ratio); } std::size_t SequenceCache::getMemoryLimit() { // Convert parameter in gigabytes to bytes const double gigaBytesToBytes = 1024. * 1024. * 1024.; const size_t memory = _fetcher.getCacheMemory(); return static_cast(static_cast(memory) / gigaBytesToBytes); } void SequenceCache::setMemoryLimit(std::size_t memory) { // Convert parameter in gigabytes to bytes const double gigaBytesToBytes = 1024. * 1024. * 1024.; _maxMemory = static_cast(static_cast(memory) * gigaBytesToBytes); _fetcher.updateCacheMemory(_maxMemory); } QVariantList SequenceCache::getCachedFrames() const { return _fetcher.getCachedFrames(); } void SequenceCache::setAsyncFetching(bool fetching) { // Always stop first _fetcher.stopAsync(); _threadPool.waitForDone(); if (fetching) { connect(&_fetcher, &AsyncFetcher::onAsyncFetchProgressed, this, &SequenceCache::onAsyncFetchProgressed); _threadPool.start(&_fetcher); } } void SequenceCache::setPrefetching(bool prefetching) { _fetcher.setPrefetching(prefetching); } bool SequenceCache::getPrefetching() { return _fetcher.getPrefetching(); } QPointF SequenceCache::getRamInfo() const { // Get available RAM in bytes and cache occupied memory const auto memInfo = aliceVision::system::getMemoryInfo(); double availableRam = static_cast(memInfo.availableRam) / (1024. * 1024. * 1024.); double contentSize = static_cast(_fetcher.getCacheSize()) / (1024. * 1024. * 1024. * 1024.); // Return in GB return QPointF(availableRam, contentSize); } ResponseData SequenceCache::request(const RequestData& reqData) { // Initialize empty response ResponseData response; std::shared_ptr> image; oiio::ParamValueList metadatas; size_t originalWidth = 0; size_t originalHeight = 0; bool hadErrorOnLoad = false; bool isFileMissing = false; if (!_fetcher.getFrame(reqData.path, image, metadatas, originalWidth, originalHeight, isFileMissing, hadErrorOnLoad)) { return response; } if (isFileMissing) { response.error = MISSING_FILE; return response; } if (hadErrorOnLoad) { response.error = LOADING_ERROR; return response; } // Build a new response with information fetched response.metadata.clear(); response.img = image; response.dim = QSize(static_cast(originalWidth), static_cast(originalHeight)); // Convert metadatas for (const auto& item : metadatas) { response.metadata[QString::fromStdString(item.name().string())] = QString::fromStdString(item.get_string()); } return response; } void SequenceCache::onAsyncFetchProgressed() { // Notify listeners that cache content has changed Q_EMIT requestHandled(); } } // namespace imgserve } // namespace qtAliceVision #include "SequenceCache.moc" QtAliceVision-2025.1.1/src/qtAliceVision/SequenceCache.hpp000066400000000000000000000053661506573753700232560ustar00rootroot00000000000000#pragma once #include "ImageServer.hpp" #include "ImageCache.hpp" #include "AsyncFetcher.hpp" #include #include #include namespace qtAliceVision { namespace imgserve { class SequenceCache : public QObject, public ImageServer { Q_OBJECT public: // Constructors and destructor explicit SequenceCache(QObject* parent = nullptr); ~SequenceCache(); public: // Data manipulation for the Qt property system /** * @brief Set the current image sequence. * @param[in] paths unordered list of image filepaths * @note the sequence order will not be changed */ void setSequence(const QVariantList& paths); /** * @brief Set the resize ratio for the images in the sequence. * @param[in] ratio target ratio for the image downscale */ void setResizeRatio(double ratio); /** * @brief Get the frames in the sequence that are currently cached. * @return a list of intervals, each one describing a range of cached frames * @note we use QPoints to represent the intervals (x: range start, y: range end) */ QVariantList getCachedFrames() const; /** * @brief Set the boolean flag indicating if the sequence is being fetched asynchronously. * @param[in] fetching new value for the fetching flag */ void setAsyncFetching(bool fetching); /** * @brief Set the boolean flag indicating if the sequence is performing prefetching (only if async). * @param[in] prefetching new value for the prefetching flag */ void setPrefetching(bool prefetching); /** * @brief Get the boolean flag indicating if the sequence is performing prefetching (only if async). * @return true if prefetching */ bool getPrefetching(); /** * @brief Get the maximum memory that can be filled by the cache. * @return memory maximum memory in gigabytes */ std::size_t getMemoryLimit(); /** * @brief Set the maximum memory that can be filled by the cache. * @param[in] memory maximum memory in gigabytes */ void setMemoryLimit(std::size_t memory); /** * @brief Get the maximum available RAM on the system. * @return maximum available RAM in bytes */ QPointF getRamInfo() const; public: // Request management /// If the image requested falls outside a certain region of cached images, /// this method will launch a worker thread to prefetch new images from disk. ResponseData request(const RequestData& reqData) override; public: Q_SLOT void onAsyncFetchProgressed(); Q_SIGNAL void requestHandled(); private: size_t _maxMemory; AsyncFetcher _fetcher; QThreadPool _threadPool; }; } // namespace imgserve } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/SingleImageLoader.cpp000066400000000000000000000067071506573753700240700ustar00rootroot00000000000000#include "SingleImageLoader.hpp" #include #include #include #include namespace qtAliceVision { namespace imgserve { SingleImageLoader::SingleImageLoader(QObject* parent) : QObject(parent) { // Initialize internal state _loading = false; } SingleImageLoader::~SingleImageLoader() {} ResponseData SingleImageLoader::request(const RequestData& reqData) { // Check if requested image matches currently loaded image if (reqData.path == _request.path && reqData.downscale == _request.downscale) { return _response; } // If there is not already a worker thread // start a new one using the currently requested image if (!_loading) { // Update internal state _loading = true; // Create new runnable and launch it in worker thread (managed by Qt thread pool) auto ioRunnable = new SingleImageLoadingIORunnable(reqData); connect(ioRunnable, &SingleImageLoadingIORunnable::done, this, &SingleImageLoader::onSingleImageLoadingDone); QThreadPool::globalInstance()->start(ioRunnable); } // Empty response return ResponseData(); } void SingleImageLoader::onSingleImageLoadingDone(RequestData reqData, ResponseData response) { // Update internal state _loading = false; _request = reqData; _response = response; // Notify listeners that an image has been loaded Q_EMIT requestHandled(); } SingleImageLoadingIORunnable::SingleImageLoadingIORunnable(const RequestData& reqData) : _reqData(reqData) {} SingleImageLoadingIORunnable::~SingleImageLoadingIORunnable() {} void SingleImageLoadingIORunnable::run() { ResponseData response; try { // Retrieve metadata from disk int width, height; auto metadata = aliceVision::image::readImageMetadata(_reqData.path, width, height); // Store original image dimensions response.dim = QSize(width, height); // Copy metadata into a QVariantMap for (const auto& item : metadata) { response.metadata[QString::fromStdString(item.name().string())] = QString::fromStdString(item.get_string()); } // Load image response.img = std::make_shared>(); aliceVision::image::readImage(_reqData.path, *(response.img), aliceVision::image::EImageColorSpace::LINEAR); // Apply downscale if (_reqData.downscale > 1) { aliceVision::imageAlgo::resizeImage(_reqData.downscale, *(response.img)); } // Set loading status response.error = SUCCESSFUL; } catch (const std::runtime_error& e) { // Retrieve and ignore the error code for the file exists test. // This avoids the throw of an exception (for any error like missing permission, etc) std::error_code ec; // std::runtime_error at this point is a "can't find/open image" error if (!std::filesystem::exists(_reqData.path, ec)) // "can't find image" case { response.error = MISSING_FILE; } else // "can't open image" case { response.error = LOADING_ERROR; } // Log error message std::cerr << e.what() << std::endl; } // Notify listeners that loading is finished Q_EMIT done(_reqData, response); } } // namespace imgserve } // namespace qtAliceVision #include "SingleImageLoader.moc" QtAliceVision-2025.1.1/src/qtAliceVision/SingleImageLoader.hpp000066400000000000000000000043731506573753700240720ustar00rootroot00000000000000#pragma once #include "ImageServer.hpp" #include #include #include #include namespace qtAliceVision { namespace imgserve { /** * @brief Image server that can load a single image at a time. */ class SingleImageLoader : public QObject, public ImageServer { Q_OBJECT public: // Constructors and destructor explicit SingleImageLoader(QObject* parent = nullptr); ~SingleImageLoader(); public: // Request management /// If the image requested cannot be retrieved immediatly, /// this method will launch a worker thread to load it from disk. ResponseData request(const RequestData& reqData) override; /** * @brief Slot called when the loading thread is done. * @param[in] reqData request data used to create the loading thread * @param[in] response a ResponseData instance containing the data loaded from disk */ Q_SLOT void onSingleImageLoadingDone(RequestData reqData, ResponseData response); /** * @brief Signal emitted when the loading thread is done and a previous request has been handled. */ Q_SIGNAL void requestHandled(); private: // Member variables /// Latest request data. RequestData _request; /// Latest response data. ResponseData _response; /// Keep track of whether or not there is an active worker thread. bool _loading; }; /** * @brief Utility class for loading a single image asynchronously. */ class SingleImageLoadingIORunnable : public QObject, public QRunnable { Q_OBJECT public: /** * @param[in] path filepath of the image to load */ explicit SingleImageLoadingIORunnable(const RequestData& reqData); ~SingleImageLoadingIORunnable(); /// Main method for loading a single image in a worker thread. Q_SLOT void run() override; /** * @brief Signal emitted when image loading is finished. * @param[in] reqData request data used to create the loading thread * @param[in] response a ResponseData instance containing the data loaded from disk */ Q_SIGNAL void done(RequestData reqData, ResponseData response); private: /// Request data of image to load. RequestData _reqData; }; } // namespace imgserve } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/Surface.cpp000066400000000000000000000501111506573753700221310ustar00rootroot00000000000000#include "Surface.hpp" #include #include #include #include #include // Import M_PI #include #include namespace qtAliceVision { aliceVision::Vec2 toEquirectangular(const aliceVision::Vec3& spherical, int width, int height) { const double vertical_angle = asin(spherical(1)); const double horizontal_angle = atan2(spherical(0), spherical(2)); const double latitude = ((vertical_angle + M_PI_2) / M_PI) * height; const double longitude = ((horizontal_angle + M_PI) / (2.0 * M_PI)) * width; return aliceVision::Vec2(longitude, latitude); } Surface::Surface(int subdivisions, QObject* parent) : QObject(parent) { updateSubdivisions(subdivisions); connect(this, &Surface::sfmDataChanged, this, &Surface::msfmDataUpdate); connect(this, &Surface::anglesChanged, this, &Surface::verticesChanged); } Surface::~Surface() {} void Surface::update(QSGGeometry::TexturedPoint2D* vertices, quint16* indices, QSize textureSize, int downscaleLevel) { // Compute Vertices coordinates and Indices order computeGrid(vertices, indices, textureSize, downscaleLevel); // If Panorama has been rotated, reset values and return true if (_isPanoramaRotating) _isPanoramaRotating = false; } // GRID METHODS void Surface::computeGrid(QSGGeometry::TexturedPoint2D* vertices, quint16* indices, QSize textureSize, int downscaleLevel) { aliceVision::camera::IntrinsicBase* intrinsic = nullptr; bool verticesComputed = false; if (_sfmLoaded && (_isPanoramaRotating || _needToUseIntrinsic)) { // Load Intrinsic with 2 ways whether we are in the Panorama or Distorsion Viewer if (_msfmData) { intrinsic = getIntrinsicFromViewId(_idView); } if (intrinsic) { computeVerticesGrid(vertices, textureSize, intrinsic, downscaleLevel); verticesComputed = true; setVerticesChanged(true); Q_EMIT verticesChanged(); _needToUseIntrinsic = false; } else { verticesComputed = false; } } // If there is no sfm data update or intrinsics are invalid, keep the same vertices if (!verticesComputed) { computeVerticesGrid(vertices, textureSize, nullptr); setVerticesChanged(false); } computeIndicesGrid(indices); } void Surface::computeGrid(QSGGeometry* geometryLine) { removeGrid(geometryLine); int countPoint = 0; int index = 0; for (int i = 0; i <= _subdivisions; i++) { for (int j = 0; j <= _subdivisions; j++) { if (i == _subdivisions && j == _subdivisions) continue; // Horizontal Lines if (i != _subdivisions) { geometryLine->vertexDataAsPoint2D()[countPoint++].set(static_cast(_vertices[index].x()), static_cast(_vertices[index].y())); index += _subdivisions + 1; geometryLine->vertexDataAsPoint2D()[countPoint++].set(static_cast(_vertices[index].x()), static_cast(_vertices[index].y())); index -= _subdivisions + 1; if (j == _subdivisions) { index++; continue; } } // Vertical Lines geometryLine->vertexDataAsPoint2D()[countPoint++].set(static_cast(_vertices[index].x()), static_cast(_vertices[index].y())); index++; geometryLine->vertexDataAsPoint2D()[countPoint++].set(static_cast(_vertices[index].x()), static_cast(_vertices[index].y())); } } } void Surface::computeVerticesGrid(QSGGeometry::TexturedPoint2D* vertices, QSize textureSize, aliceVision::camera::IntrinsicBase* intrinsic, int downscaleLevel) { // Retrieve pose if Panorama Viewer is enable if (isPanoramaViewerEnabled() && intrinsic) { // Downscale image according to downscale level textureSize *= pow(2.0, downscaleLevel); resetValuesVertexEnabled(); } // Retrieve pose aliceVision::sfmData::CameraPose pose; if (isPanoramaViewerEnabled() && intrinsic && _msfmData) { const auto viewIt = _msfmData->rawData().getViews().find(_idView); const aliceVision::sfmData::View& view = *viewIt->second; pose = _msfmData->rawData().getPose(view); } aliceVision::camera::Equidistant* eqcam = dynamic_cast(intrinsic); aliceVision::Vec2 center = {0, 0}; double radius = std::numeric_limits::max(); if (eqcam) { center = {eqcam->getCircleCenterX(), eqcam->getCircleCenterY()}; radius = eqcam->getCircleRadius(); } bool fillCoordsSphere = _defaultSphereCoordinates.empty(); int vertexIndex = 0; float fSubdivisions = static_cast(_subdivisions); for (size_t i = 0; i <= static_cast(_subdivisions); i++) { for (size_t j = 0; j <= static_cast(_subdivisions); j++) { float fI = static_cast(i); float fJ = static_cast(j); float x, y = 0.f; /* The panorama viewer needs to use the previously computed vertices to compute the current ones, * otherwise images will end up stacked together on the top-left corner. * For anything other than the panorama viewer, the surface vertices should be recomputed from scratch * so the distorsions are not accumulated. */ if (_vertices.empty() || !isPanoramaViewerEnabled()) { x = fI * static_cast(textureSize.width()) / fSubdivisions; y = fJ * static_cast(textureSize.height()) / fSubdivisions; } else { x = static_cast(_vertices[vertexIndex].x()); y = static_cast(_vertices[vertexIndex].y()); } const double cx = x - center(0); const double cy = y - center(1); const double dist = std::hypot(cx, cy); const double maxradius = 0.99 * radius; if (dist > maxradius) { x = static_cast(center(0) + maxradius * cx / dist); y = static_cast(center(1) + maxradius * cy / dist); } const float u = fI / fSubdivisions; const float v = fJ / fSubdivisions; // Remove Distortion only if sfmData has been updated if (isDistortionViewerEnabled() && intrinsic && intrinsic->hasDistortion()) { const aliceVision::Vec2 undisto_pix(x, y); const aliceVision::Vec2 disto_pix = intrinsic->getDistortedPixel(undisto_pix); vertices[vertexIndex].set(static_cast(disto_pix.x()), static_cast(disto_pix.y()), u, v); } // Equirectangular Convertion only if sfmData has been updated if (isPanoramaViewerEnabled() && intrinsic) { // Compute pixel coordinates on the Unit Sphere if (fillCoordsSphere) { // Image System Coordinates aliceVision::Vec2 uvCoord(x, y); const auto& transfromPose = pose.getTransform(); _defaultSphereCoordinates.push_back(aliceVision::camera::applyIntrinsicExtrinsic(transfromPose, intrinsic, uvCoord)); } // Rotate Panorama if some rotation values exist aliceVision::Vec3 sphereCoordinates(_defaultSphereCoordinates[static_cast(vertexIndex)]); rotatePanorama(sphereCoordinates); // Compute pixel coordinates in the panorama coordinate system aliceVision::Vec2 panoramaCoordinates = toEquirectangular(sphereCoordinates, _panoramaWidth, _panoramaHeight); // If image is on the seam if (vertexIndex > 0 && j > 0) { double deltaX = panoramaCoordinates.x() - vertices[vertexIndex - 1].x; if (abs(deltaX) > 0.7 * _panoramaWidth) { _vertexEnabled[i][j - 1] = false; } } if (vertexIndex >= (_subdivisions + 1)) { double deltaY = panoramaCoordinates.x() - vertices[vertexIndex - (_subdivisions + 1)].x; if (abs(deltaY) > 0.7 * _panoramaWidth && j > 0) { _vertexEnabled[i][j - 1] = false; } } vertices[vertexIndex].set(static_cast(panoramaCoordinates.x()), static_cast(panoramaCoordinates.y()), u, v); } // Default if (!intrinsic) { vertices[vertexIndex].set(x, y, u, v); } vertexIndex++; } } // End for loop Q_EMIT verticesChanged(); } bool Surface::isPointValid(size_t i, size_t j) const { // Check if the point and all its neighbours are enabled // If not, return false if (!_vertexEnabled[i][j]) return false; if (i > 0) { if (!_vertexEnabled[i - 1][j]) return false; } if (j > 0) { if (!_vertexEnabled[i][j - 1]) return false; } if (i > 0 && j > 0) { if (!_vertexEnabled[i - 1][j - 1]) return false; } if (static_cast(i) < _subdivisions + 1) { if (!_vertexEnabled[i + 1][j]) return false; if (j > 0 && !_vertexEnabled[i + 1][j - 1]) return false; } if (static_cast(j) < _subdivisions + 1) { if (!_vertexEnabled[i][j + 1]) return false; if (i > 0 && !_vertexEnabled[i - 1][j + 1]) return false; } if (static_cast(i) < _subdivisions + 1 && static_cast(j) < _subdivisions + 1) { if (!_vertexEnabled[i + 1][j + 1]) return false; } return true; } void Surface::resetValuesVertexEnabled() { for (size_t i = 0; i <= static_cast(_subdivisions); i++) { for (size_t j = 0; j <= static_cast(_subdivisions); j++) { _vertexEnabled[j][i] = true; } } } void Surface::computeIndicesGrid(quint16* indices) { int index = 0; for (size_t j = 0; j < static_cast(_subdivisions); j++) { for (size_t i = 0; i < static_cast(_subdivisions); i++) { if (!isPanoramaViewerEnabled() || (isPanoramaViewerEnabled() && isPointValid(i, j))) { int topLeft = (static_cast(i) * (_subdivisions + 1)) + static_cast(j); int topRight = topLeft + 1; int bottomLeft = topLeft + _subdivisions + 1; int bottomRight = bottomLeft + 1; indices[index++] = static_cast(topLeft); indices[index++] = static_cast(bottomLeft); indices[index++] = static_cast(topRight); indices[index++] = static_cast(topRight); indices[index++] = static_cast(bottomLeft); indices[index++] = static_cast(bottomRight); } else { indices[index++] = 0; indices[index++] = 0; indices[index++] = 0; indices[index++] = 0; indices[index++] = 0; indices[index++] = 0; } } } _indices.clear(); for (size_t i = 0; i < static_cast(_indexCount); i++) _indices.append(indices[i]); } void Surface::removeGrid(QSGGeometry* geometryLine) { for (size_t i = 0; i < static_cast(geometryLine->vertexCount()); i++) { geometryLine->vertexDataAsPoint2D()[i].set(0, 0); } } void Surface::setGridColor(const QColor& color) { if (_gridColor == color) return; _gridColor = color; _gridColor.setAlpha(_gridOpacity); Q_EMIT gridColorChanged(color); } void Surface::setGridOpacity(const int& opacity) { if (_gridOpacity == opacity) return; if (_gridOpacity == int((opacity / 100.0) * 255)) return; _gridOpacity = int((opacity / 100.0) * 255); _gridColor.setAlpha(_gridOpacity); Q_EMIT gridOpacityChanged(opacity); } void Surface::setDisplayGrid(bool display) { if (_displayGrid == display) return; _displayGrid = display; Q_EMIT displayGridChanged(); } QPointF Surface::getPrincipalPoint() { aliceVision::Vec2 ppCorrection(0.0, 0.0); aliceVision::camera::IntrinsicBase* intrinsic = nullptr; if (_msfmData) { intrinsic = getIntrinsicFromViewId(_idView); } if (intrinsic && aliceVision::camera::isPinhole(intrinsic->getType())) { ppCorrection = dynamic_cast(*intrinsic).getOffset(); } return {ppCorrection.x(), ppCorrection.y()}; } // VERTICES FUNCTION void Surface::fillVertices(QSGGeometry::TexturedPoint2D* vertices) { _vertices.clear(); for (int i = 0; i < _vertexCount; i++) { QPoint p(static_cast(vertices[i].x), static_cast(vertices[i].y)); _vertices.append(p); } } // SUBDIVISIONS FUNCTIONS void Surface::updateSubdivisions(int sub) { _subdivisions = sub; // Update vertexCount and indexCount according to new subdivision count _vertexCount = (_subdivisions + 1) * (_subdivisions + 1); _indexCount = _subdivisions * _subdivisions * 6; _vertexEnabled.resize(static_cast(_subdivisions) + 1); for (size_t i = 0; i < static_cast(_subdivisions) + 1; i++) _vertexEnabled[i].resize(static_cast(_subdivisions) + 1); } void Surface::setSubdivisions(int newSubdivisions) { if (newSubdivisions == _subdivisions) return; setHasSubdivisionsChanged(true); updateSubdivisions(newSubdivisions); clearVertices(); setVerticesChanged(true); _needToUseIntrinsic = true; Q_EMIT subdivisionsChanged(); } // PANORAMA void Surface::rotatePanorama(aliceVision::Vec3& coordSphere) { Eigen::AngleAxis Myaw(_yaw, Eigen::Vector3d::UnitY()); Eigen::AngleAxis Mpitch(_pitch, Eigen::Vector3d::UnitX()); Eigen::AngleAxis Mroll(_roll, Eigen::Vector3d::UnitZ()); Eigen::Matrix3d cRo = Myaw.toRotationMatrix() * Mpitch.toRotationMatrix() * Mroll.toRotationMatrix(); coordSphere = cRo * coordSphere; } double Surface::getPitch() { // Get pitch in degrees return getEulerAngleDegrees(_pitch); } void Surface::setPitch(double pitchInDegrees) { _pitch = aliceVision::degreeToRadian(pitchInDegrees); _isPanoramaRotating = true; setVerticesChanged(true); Q_EMIT anglesChanged(); } double Surface::getYaw() { // Get yaw in degrees return getEulerAngleDegrees(_yaw); } void Surface::setYaw(double yawInDegrees) { _yaw = aliceVision::degreeToRadian(yawInDegrees); _isPanoramaRotating = true; setVerticesChanged(true); Q_EMIT anglesChanged(); } double Surface::getRoll() { // Get roll in degrees return getEulerAngleDegrees(_roll); } void Surface::setRoll(double rollInDegrees) { _roll = aliceVision::degreeToRadian(rollInDegrees); _isPanoramaRotating = true; setVerticesChanged(true); Q_EMIT anglesChanged(); } double Surface::getEulerAngleDegrees(double angleRadians) { double angleDegrees = angleRadians; int power = static_cast(angleDegrees / M_PI); angleDegrees = fmod(angleDegrees, M_PI) * pow(-1, power); // Radians to Degrees angleDegrees *= (180.0f / M_PI); if (power % 2 != 0) angleDegrees = -180.0 - angleDegrees; return angleDegrees; } // ID VIEW FUNCTION void Surface::setIdView(int id) { // Handles cases where the sent view ID is unknown or unset (equals to -1) if (id >= 0) _idView = static_cast(id); else _idView = 0; } // MOUSE FUNCTIONS bool Surface::isMouseInside(float mx, float my) { QPointF P(mx, my); bool inside = false; for (int i = 0; i < indexCount(); i += 3) { if (i + 2 >= _indices.size()) break; QPointF A = _vertices[_indices[i]]; QPointF B = _vertices[_indices[i + 1]]; QPointF C = _vertices[_indices[i + 2]]; // Compute vectors QPointF v0 = C - A; QPointF v1 = B - A; QPointF v2 = P - A; // Compute dot products double dot00 = QPointF::dotProduct(v0, v0); double dot01 = QPointF::dotProduct(v0, v1); double dot02 = QPointF::dotProduct(v0, v2); double dot11 = QPointF::dotProduct(v1, v1); double dot12 = QPointF::dotProduct(v1, v2); // Compute barycentric coordinates const double dots = (dot00 * dot11 - dot01 * dot01); if (dots == 0) continue; double invDenom = 1 / dots; double u = (dot11 * dot02 - dot01 * dot12) * invDenom; double v = (dot00 * dot12 - dot01 * dot02) * invDenom; // Check if point is in triangle if ((u >= 0) && (v >= 0) && (u + v < 1)) { inside = true; break; } } return inside; } void Surface::setMouseOver(bool state) { if (state == _mouseOver) return; _mouseOver = state; Q_EMIT mouseOverChanged(); } // VIEWER TYPE FUNCTIONS void Surface::setViewerType(EViewerType type) { if (_viewerType == type) return; _viewerType = type; clearVertices(); setVerticesChanged(true); Q_EMIT viewerTypeChanged(); } bool Surface::isPanoramaViewerEnabled() const { return _viewerType == EViewerType::PANORAMA; } bool Surface::isDistortionViewerEnabled() const { return _viewerType == EViewerType::DISTORTION; } bool Surface::isHDRViewerEnabled() const { return _viewerType == EViewerType::HDR; } void Surface::setMSfmData(MSfMData* sfmData) { _sfmLoaded = false; if (_msfmData == sfmData) { _sfmLoaded = true; return; } if (_msfmData != nullptr) { disconnect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } _msfmData = sfmData; if (!_msfmData) return; if (_msfmData != nullptr) { connect(_msfmData, SIGNAL(sfmDataChanged()), this, SIGNAL(sfmDataChanged())); } if (_msfmData->status() != MSfMData::Ready) { qWarning() << "[QtAliceVision] SURFACE setMSfmData: SfMData is not ready: " << _msfmData->status(); return; } if (_msfmData->rawData().getViews().empty()) { qWarning() << "[QtAliceVision] SURFACE setMSfmData: SfMData is empty"; return; } Q_EMIT sfmDataChanged(); } aliceVision::camera::IntrinsicBase* Surface::getIntrinsicFromViewId(unsigned int viewId) const { if (!_msfmData) { return nullptr; } aliceVision::camera::IntrinsicBase* intrinsic = nullptr; const auto viewIt = _msfmData->rawData().getViews().find(viewId); if (viewIt == _msfmData->rawData().getViews().end()) { return nullptr; } const aliceVision::sfmData::View& view = *viewIt->second; intrinsic = _msfmData->rawData().getIntrinsicPtr(view.getIntrinsicId()); return intrinsic; } const aliceVision::camera::Equidistant* Surface::getIntrinsicEquidistant() const { const aliceVision::camera::IntrinsicBase* intrinsic = getIntrinsicFromViewId(_idView); if (!intrinsic) { return nullptr; } // Load equidistant intrinsic (the intrinsic for full circle fisheye cameras) const aliceVision::camera::Equidistant* intrinsicEquidistant = dynamic_cast(intrinsic); return intrinsicEquidistant; } } // namespace qtAliceVisionQtAliceVision-2025.1.1/src/qtAliceVision/Surface.hpp000066400000000000000000000151001506573753700221350ustar00rootroot00000000000000#pragma once #define _USE_MATH_DEFINES #include #include #include #include #include #include #include #include namespace qtAliceVision { /** * @brief Discretization of FloatImageViewer surface */ class Surface : public QObject { Q_OBJECT // Q_PROPERTIES Q_PROPERTY(bool displayGrid READ getDisplayGrid WRITE setDisplayGrid NOTIFY displayGridChanged) Q_PROPERTY(QColor gridColor READ getGridColor WRITE setGridColor NOTIFY gridColorChanged) Q_PROPERTY(int gridOpacity READ getGridOpacity WRITE setGridOpacity NOTIFY gridOpacityChanged) Q_PROPERTY(bool mouseOver READ getMouseOver WRITE setMouseOver NOTIFY mouseOverChanged) Q_PROPERTY(qtAliceVision::MSfMData* msfmData READ getMSfmData WRITE setMSfmData NOTIFY sfmDataChanged) Q_PROPERTY(EViewerType viewerType READ getViewerType WRITE setViewerType NOTIFY viewerTypeChanged) Q_PROPERTY(QList vertices READ vertices NOTIFY verticesChanged) Q_PROPERTY(int subdivisions READ getSubdivisions WRITE setSubdivisions NOTIFY subdivisionsChanged) Q_PROPERTY(double yaw READ getYaw WRITE setYaw NOTIFY anglesChanged) Q_PROPERTY(double pitch READ getPitch WRITE setPitch NOTIFY anglesChanged) Q_PROPERTY(double roll READ getRoll WRITE setRoll NOTIFY anglesChanged) public: Surface(int subdivisions = 12, QObject* parent = nullptr); Surface& operator=(const Surface& other) = default; ~Surface(); void update(QSGGeometry::TexturedPoint2D* vertices, quint16* indices, QSize textureSize, int downscaleLevel = 0); // Q_INVOKABLES Q_INVOKABLE QPointF getPrincipalPoint(); Q_INVOKABLE bool isMouseInside(float mx, float my); Q_INVOKABLE void setIdView(int id); // GRID void computeGrid(QSGGeometry* geometryLine); void removeGrid(QSGGeometry* geometryLine); // GRID COLOR QColor getGridColor() const { return _gridColor; } void setGridColor(const QColor& color); Q_SIGNAL void gridColorChanged(QColor); // GRID OPACITY int getGridOpacity() const { return _gridOpacity; } void setGridOpacity(const int& opacity); Q_SIGNAL void gridOpacityChanged(int); // DISPLAY GRID bool getDisplayGrid() const { return _displayGrid && isDistortionViewerEnabled(); } void setDisplayGrid(bool display); Q_SIGNAL void displayGridChanged(); // MOUSE OVER bool getMouseOver() const { return _mouseOver; } void setMouseOver(bool state); Q_SIGNAL void mouseOverChanged(); // SUBDIVISION int getSubdivisions() const { return _subdivisions; } void setSubdivisions(int newSubdivisions); bool hasSubdivisionsChanged() { return _subdivisionsChanged; } void setHasSubdivisionsChanged(bool state) { _subdivisionsChanged = state; } Q_SIGNAL void subdivisionsChanged(); // MSfmData MSfMData* getMSfmData() { return _msfmData; } void setMSfmData(MSfMData* sfmData); Q_SIGNAL void sfmDataChanged(); // VIEWER TYPE enum class EViewerType : quint8 { DEFAULT = 0, HDR, DISTORTION, PANORAMA }; Q_ENUM(EViewerType) EViewerType getViewerType() const { return _viewerType; } void setViewerType(EViewerType type); bool isPanoramaViewerEnabled() const; bool isDistortionViewerEnabled() const; bool isHDRViewerEnabled() const; Q_SIGNAL void viewerTypeChanged(); // VERTICES const QList& vertices() const { return _vertices; } inline bool hasVerticesChanged() const { return _verticesChanged; } void setVerticesChanged(bool change) { _verticesChanged = change; } void clearVertices() { _vertices.clear(); _defaultSphereCoordinates.clear(); } inline int indexCount() const { return _indexCount; } inline int vertexCount() const { return _vertexCount; } Q_SIGNAL void verticesChanged(); void setNeedToUseIntrinsic(bool state) { _needToUseIntrinsic = state; } void fillVertices(QSGGeometry::TexturedPoint2D* vertices); // Yaw double getYaw(); void setYaw(double yawInDegrees); // Pitch double getPitch(); void setPitch(double pitchInDegrees); // Roll double getRoll(); void setRoll(double rollInDegrees); Q_SIGNAL void anglesChanged(); void msfmDataUpdate() { _sfmLoaded = true; _needToUseIntrinsic = true; clearVertices(); setVerticesChanged(true); Q_EMIT verticesChanged(); } const aliceVision::camera::Equidistant* getIntrinsicEquidistant() const; private: aliceVision::camera::IntrinsicBase* getIntrinsicFromViewId(unsigned int viewId) const; void computeGrid(QSGGeometry::TexturedPoint2D* vertices, quint16* indices, QSize textureSize, int downscaleLevel = 0); void computeVerticesGrid(QSGGeometry::TexturedPoint2D* vertices, QSize textureSize, aliceVision::camera::IntrinsicBase* intrinsic, int downscaleLevel = 0); void computeIndicesGrid(quint16* indices); void rotatePanorama(aliceVision::Vec3& coordSphere); void updateSubdivisions(int sub); bool isPointValid(std::size_t i, std::size_t j) const; void resetValuesVertexEnabled(); // Get pitch / yaw / roll in radians and return degrees angle in the correct interval double getEulerAngleDegrees(double angleRadians); private: const int _panoramaWidth = 3000; const int _panoramaHeight = 1500; // Vertex Data QList _vertices; QList _indices; int _subdivisions; int _vertexCount; int _indexCount; std::vector> _vertexEnabled; // Vertices State bool _verticesChanged = true; // Grid State bool _displayGrid; QColor _gridColor = QColor(255, 0, 0, 255); int _gridOpacity = 255; bool _subdivisionsChanged = false; // SfmData MSfMData* _msfmData = nullptr; // sfmData is ready to use bool _sfmLoaded = false; // Compute vertices grid with intrinsic bool _needToUseIntrinsic = true; // Id View aliceVision::IndexT _idView; // Viewer EViewerType _viewerType = EViewerType::DEFAULT; double _pitch = 0.0; double _yaw = 0.0; double _roll = 0.0; bool _isFisheye = false; // Coordinates on Unit Sphere without any rotation std::vector _defaultSphereCoordinates; // Mouse Over bool _mouseOver = false; // If panorama is currently rotating bool _isPanoramaRotating = false; }; } // namespace qtAliceVisionQtAliceVision-2025.1.1/src/qtAliceVision/plugin.hpp000066400000000000000000000051151506573753700220500ustar00rootroot00000000000000#pragma once #include "ImageServer.hpp" #include "FeaturesViewer.hpp" #include "FloatImageViewer.hpp" #include "PhongImageViewer.hpp" #include "MFeatures.hpp" #include "MSfMDataStats.hpp" #include "MTracks.hpp" #include "MViewStats.hpp" #include "PanoramaViewer.hpp" #include "Surface.hpp" #include "MFeatures.hpp" #include #include #include #include namespace qtAliceVision { class QtAliceVisionPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "qtAliceVision.qmlPlugin") public: void initializeEngine(QQmlEngine* engine, const char* uri) override { // Fix "unused parameter" warnings; should be replaced by [[maybe_unused]] when C++17 is supported (void)engine; (void)uri; } void registerTypes(const char* uri) override { qInfo() << "[QtAliceVision] Plugin Initialized"; aliceVision::system::Logger::get()->setLogLevel("info"); Q_ASSERT(uri == QLatin1String("AliceVision")); qmlRegisterType(uri, 1, 0, "FeaturesViewer"); qmlRegisterType(uri, 1, 0, "MFeatures"); qmlRegisterType(uri, 1, 0, "MSfMData"); qmlRegisterType(uri, 1, 0, "MTracks"); qmlRegisterType(uri, 1, 0, "MViewStats"); qmlRegisterType(uri, 1, 0, "MSfMDataStats"); qRegisterMetaType>("QList"); qRegisterMetaType>("QQmlListProperty"); qRegisterMetaType("QSharedPtr"); // for usage in signals/slots qRegisterMetaType("size_t"); // for usage in signals/slots qmlRegisterType(uri, 1, 0, "FloatImageViewer"); qmlRegisterType(uri, 1, 0, "PhongImageViewer"); qmlRegisterType(uri, 1, 0, "Surface"); qmlRegisterType(uri, 1, 0, "PanoramaViewer"); qRegisterMetaType("QPointF"); qRegisterMetaType(); qRegisterMetaType>(); qRegisterMetaType("Surface*"); qRegisterMetaType("RequestData"); qRegisterMetaType("imgserve::RequestData"); qRegisterMetaType("ResponseData"); qRegisterMetaType("imgserve::ResponseData"); } }; } // namespace qtAliceVision QtAliceVision-2025.1.1/src/qtAliceVision/qmldir000066400000000000000000000000571506573753700212540ustar00rootroot00000000000000module AliceVision plugin qtAliceVisionPlugin QtAliceVision-2025.1.1/src/qtAliceVisionImageIO/000077500000000000000000000000001506573753700212325ustar00rootroot00000000000000QtAliceVision-2025.1.1/src/qtAliceVisionImageIO/CMakeLists.txt000066400000000000000000000017331506573753700237760ustar00rootroot00000000000000# Target srcs set(PLUGIN_SOURCES plugin.cpp QtAliceVisionImageIOHandler.cpp ) set(PLUGIN_HEADERS plugin.hpp QtAliceVisionImageIOHandler.hpp ) # Qt module dependencies find_package(Qt6 COMPONENTS Gui REQUIRED) # OpenImageIO dependency find_package(OpenImageIO REQUIRED) # Target properties add_library(qtAliceVisionImageIOPlugin SHARED ${PLUGIN_SOURCES} ${PLUGIN_HEADERS}) if(MSVC) target_compile_options(qtAliceVisionImageIOPlugin PUBLIC /W4) else() target_compile_options(qtAliceVisionImageIOPlugin PUBLIC -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wpedantic ) endif() target_include_directories(qtAliceVisionImageIOPlugin PUBLIC ${OPENIMAGEIO_INCLUDE_DIRS} ) target_link_libraries(qtAliceVisionImageIOPlugin PUBLIC ${OPENIMAGEIO_LIBRARIES} aliceVision_image Qt6::Core Qt6::Gui ) # Install settings install(TARGETS qtAliceVisionImageIOPlugin DESTINATION imageformats) QtAliceVision-2025.1.1/src/qtAliceVisionImageIO/QtAliceVisionImageIOHandler.cpp000066400000000000000000000146641506573753700271540ustar00rootroot00000000000000#include "QtAliceVisionImageIOHandler.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include inline const float& clamp(const float& v, const float& lo, const float& hi) { assert(!(hi < lo)); return (v < lo) ? lo : (hi < v) ? hi : v; } inline unsigned short floatToUShort(float v) { return static_cast(clamp(v, 0.0f, 1.0f) * 65535); } QtAliceVisionImageIOHandler::QtAliceVisionImageIOHandler() { qDebug() << "[QtAliceVisionImageIO] QtAliceVisionImageIOHandler"; } QtAliceVisionImageIOHandler::~QtAliceVisionImageIOHandler() {} bool QtAliceVisionImageIOHandler::canRead() const { if (canRead(device())) { setFormat(name()); return true; } return false; } bool QtAliceVisionImageIOHandler::canRead(QIODevice* device) { QFileDevice* d = dynamic_cast(device); if (!d) { qDebug() << "[QtAliceVisionImageIO] Cannot read: invalid device"; return false; } const std::string path = d->fileName().toStdString(); auto input = oiio::ImageInput::create(path); if (!input) { qDebug() << "[QtAliceVisionImageIO] Cannot read: failed to create image input"; return false; } if (!(input->valid_file(path))) { qDebug() << "[QtAliceVisionImageIO] Cannot read: invalid file"; return false; } qDebug() << "[QtAliceVisionImageIO] Can read file: " << d->fileName(); return true; } bool QtAliceVisionImageIOHandler::read(QImage* image) { QFileDevice* d = dynamic_cast(device()); if (!d) { qWarning() << "[QtAliceVisionImageIO] Read image failed (not a FileDevice)."; return false; } const std::string path = d->fileName().toStdString(); qDebug() << "[QtAliceVisionImageIO] Read image: " << path.c_str(); aliceVision::image::Image img; aliceVision::image::readImage(path, img, aliceVision::image::EImageColorSpace::SRGB); oiio::ImageBuf inBuf; aliceVision::image::getBufferFromImage(img, inBuf); oiio::ImageSpec inSpec = aliceVision::image::readImageSpec(path); float pixelAspectRatio = inSpec.get_float_attribute("PixelAspectRatio", 1.0f); qDebug() << "[QtAliceVisionImageIO] width:" << inSpec.width << ", height:" << inSpec.height << ", nchannels:" << inSpec.nchannels << ", pixelAspectRatio:" << pixelAspectRatio; qDebug() << "[QtAliceVisionImageIO] create output QImage"; QImage result(inSpec.width, inSpec.height, QImage::Format_RGB32); qDebug() << "[QtAliceVisionImageIO] shuffle channels"; const int nchannels = 4; const oiio::TypeDesc typeDesc = oiio::TypeDesc::UINT8; oiio::ImageSpec requestedSpec(inSpec.width, inSpec.height, nchannels, typeDesc); oiio::ImageBuf tmpBuf(requestedSpec); int channelOrder[] = {2, 1, 0, -1}; float channelValues[] = {1.f, 1.f, 1.f, 1.f}; oiio::ImageBufAlgo::channels(tmpBuf, inBuf, 4, channelOrder, channelValues, {}, false); inBuf.swap(tmpBuf); qDebug() << "[QtAliceVisionImageIO] fill output QImage"; oiio::ROI exportROI = inBuf.roi(); exportROI.chbegin = 0; exportROI.chend = nchannels; inBuf.get_pixels(exportROI, typeDesc, result.bits()); if (pixelAspectRatio != 1.0f) { QSize newSize(static_cast(static_cast(inSpec.width) * pixelAspectRatio), inSpec.height); result = result.scaled(newSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } if (_scaledSize.isValid()) { qDebug() << "[QtAliceVisionImageIO] _scaledSize: " << _scaledSize.width() << "x" << _scaledSize.height(); *image = result.scaled(_scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { *image = result; } return true; } bool QtAliceVisionImageIOHandler::write(const QImage&) { // TODO return false; } bool QtAliceVisionImageIOHandler::supportsOption(ImageOption option) const { if (option == Size) return true; if (option == ImageTransformation) return true; if (option == ScaledSize) return true; return false; } QVariant QtAliceVisionImageIOHandler::option(ImageOption option) const { QFileDevice* d = dynamic_cast(device()); if (!d) { qDebug("[QtAliceVisionImageIO] Read image failed (not a FileDevice)."); return QImageIOHandler::option(option); } std::string path = d->fileName().toStdString(); oiio::ImageSpec spec = aliceVision::image::readImageSpec(path); if (option == Size) { return QSize(spec.width, spec.height); } else if (option == ImageTransformation) { int orientation = 0; spec.getattribute("orientation", oiio::TypeInt, &orientation); switch (orientation) { case 1: return QImageIOHandler::TransformationNone; break; case 2: return QImageIOHandler::TransformationMirror; break; case 3: return QImageIOHandler::TransformationRotate180; break; case 4: return QImageIOHandler::TransformationFlip; break; case 5: return QImageIOHandler::TransformationFlipAndRotate90; break; case 6: return QImageIOHandler::TransformationRotate90; break; case 7: return QImageIOHandler::TransformationMirrorAndRotate90; break; case 8: return QImageIOHandler::TransformationRotate270; break; default: break; } } return QImageIOHandler::option(option); } void QtAliceVisionImageIOHandler::setOption(ImageOption option, const QVariant& value) { Q_UNUSED(option); Q_UNUSED(value); if (option == ScaledSize && value.isValid()) { _scaledSize = value.value(); qDebug() << "[QtAliceVisionImageIO] setOption scaledSize: " << _scaledSize.width() << "x" << _scaledSize.height(); } } QByteArray QtAliceVisionImageIOHandler::name() const { return "AliceVisionImageIO"; } QtAliceVision-2025.1.1/src/qtAliceVisionImageIO/QtAliceVisionImageIOHandler.hpp000066400000000000000000000011461506573753700271500ustar00rootroot00000000000000#pragma once #include #include class QtAliceVisionImageIOHandler : public QImageIOHandler { public: QtAliceVisionImageIOHandler(); ~QtAliceVisionImageIOHandler(); bool canRead() const override; bool read(QImage* image) override; bool write(const QImage& image) override; QByteArray name() const; static bool canRead(QIODevice* device); QVariant option(ImageOption option) const override; void setOption(ImageOption option, const QVariant& value) override; bool supportsOption(ImageOption option) const override; QSize _scaledSize; }; QtAliceVision-2025.1.1/src/qtAliceVisionImageIO/QtAliceVisionImageIOPlugin.json000066400000000000000000000017201506573753700272110ustar00rootroot00000000000000{ "Keys": [ "exr", "bay", "bmq", "cr2", "crw", "cs1", "dc2", "dcr", "dng", "erf", "fff", "hdr", "k25", "kdc", "mdc", "mos", "mrw", "nef", "orf", "pef", "pxn", "raf", "raw", "rdc", "sr2", "srf", "x3f", "arw", "3fr", "cine", "ia", "kc2", "mef", "nrw", "qtk", "rw2", "sti", "rwl", "srw", "drf", "dsc", "ptx", "cap", "iiq", "rwz" ], "MimeTypes": [ "image/exr", "image/bay", "image/bmq", "image/cr2", "image/crw", "image/cs1", "image/dc2", "image/dcr", "image/dng", "image/erf", "image/fff", "image/hdr", "image/k25", "image/kdc", "image/mdc", "image/mos", "image/mrw", "image/nef", "image/orf", "image/pef", "image/pxn", "image/raf", "image/raw", "image/rdc", "image/sr2", "image/srf", "image/x3f", "image/arw", "image/3fr", "image/cine", "image/ia", "image/kc2", "image/mef", "image/nrw", "image/qtk", "image/rw2", "image/sti", "image/rwl", "image/srw", "image/drf", "image/dsc", "image/ptx", "image/cap", "image/iiq", "image/rwz" ] } QtAliceVision-2025.1.1/src/qtAliceVisionImageIO/plugin.cpp000066400000000000000000000041671506573753700232440ustar00rootroot00000000000000#include "plugin.hpp" #include "QtAliceVisionImageIOHandler.hpp" #include #include #include #include using namespace aliceVision; QtAliceVisionImageIOPlugin::QtAliceVisionImageIOPlugin() { qDebug("[QtAliceVisionImageIO] init supported extensions."); std::vector extensions = image::getSupportedExtensions(); for (auto& ext : extensions) { QString format(ext.c_str()); format.remove('.'); qDebug() << "[QtAliceVisionImageIO] supported format: " << format; _supportedExtensions.append(format); } qInfo() << "[QtAliceVisionImageIO] Plugin Initialized"; } QtAliceVisionImageIOPlugin::~QtAliceVisionImageIOPlugin() {} QImageIOPlugin::Capabilities QtAliceVisionImageIOPlugin::capabilities(QIODevice* device, const QByteArray& format) const { QFileDevice* d = dynamic_cast(device); if (!d) return QImageIOPlugin::Capabilities(); const std::string path = d->fileName().toStdString(); if (path.empty() || path[0] == ':') return QImageIOPlugin::Capabilities(); #ifdef QT_ALICEVISIONIMAGEIO_USE_FORMATS_BLACKLIST // For performance sake, let Qt handle natively some formats. static const QStringList blacklist{"jpeg", "jpg", "png", "ico"}; if (blacklist.contains(format, Qt::CaseSensitivity::CaseInsensitive)) { return QImageIOPlugin::Capabilities(); } #endif if (_supportedExtensions.contains(format, Qt::CaseSensitivity::CaseInsensitive)) { qDebug() << "[QtAliceVisionImageIO] Capabilities: extension \"" << QString(format) << "\" supported."; Capabilities capabilities(CanRead); return capabilities; } qDebug() << "[QtAliceVisionImageIO] Capabilities: extension \"" << QString(format) << "\" not supported"; return QImageIOPlugin::Capabilities(); } QImageIOHandler* QtAliceVisionImageIOPlugin::create(QIODevice* device, const QByteArray& format) const { QtAliceVisionImageIOHandler* handler = new QtAliceVisionImageIOHandler; handler->setDevice(device); handler->setFormat(format); return handler; } QtAliceVision-2025.1.1/src/qtAliceVisionImageIO/plugin.hpp000066400000000000000000000011031506573753700232340ustar00rootroot00000000000000#pragma once #include #include class QtAliceVisionImageIOPlugin : public QImageIOPlugin { public: Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "QtAliceVisionImageIOPlugin.json") public: QStringList _supportedExtensions; public: QtAliceVisionImageIOPlugin(); ~QtAliceVisionImageIOPlugin(); Capabilities capabilities(QIODevice* device, const QByteArray& format) const; QImageIOHandler* create(QIODevice* device, const QByteArray& format = QByteArray()) const; };