Windows CI: make vcpkg download compatible with draft release (#1727)

* Windows CI: make vcpkg download compatible with draft release

* forcing nvdia driver dll to be ignored in fixup_bundle

* disabling opencv cudec and python

* disabling rtabmap-console --version on cuda build

* Added assert when bad imu data is provided. Added checks when zed sdk is returning nan imu data.

* Updated zed sdk5 resolution and quality

* Updated zed sdk5 resolution and quality

* Fixing parameters reloaded when testing camera. Also fixed app freezing when closing Preferences dialog after using test camera dialog

* cleanup and debug logs

* Added dialogs when starting/stopping sensors/detection. Fixed nullptr bug on MainWindow::handleEvents

* renamed

* found a way to expose ZED model downloads

* Updated default config path on Windows

* Fixed extra endline in file logging (windows)

* Fixed camera local transform when used with lidar and odomSensor

* CI(macos): build g2o from source with OpenMP (libomp include/link fix)

* Mac: added ceres dep, fixed omp not found by cmake

* Mac: bundle orbbec extensions

* stripping rpath of orbbec's extensions

* cleanup

* Realsense2 error as error (not warning)

* Fixed config ownership changed to root when sarting in sudo. LidarVLP16: fixed crash on Mac by reimplementing our own socket threading

* LidarVLP16: own socket implementation only for Mac (linux and windows use original PCL's reader)
This commit is contained in:
matlabbe
2026-07-04 18:16:37 -07:00
committed by GitHub
parent cb34c4bd37
commit d069becc72
27 changed files with 982 additions and 141 deletions

View File

@@ -203,6 +203,47 @@ runs:
cmake --build build -j$NPROC cmake --build build -j$NPROC
DESTDIR="$STAGE" cmake --install build DESTDIR="$STAGE" cmake --install build
# ----------------------------- g2o ------------------------------
- name: Cache g2o
id: cache-g2o
uses: actions/cache@v4
with:
path: ${{ runner.temp }}/deps-stage/g2o
key: g2o-67cbe15c-${{ inputs.os }}-${{ steps.depver.outputs.hash }}
- name: Build g2o
if: steps.cache-g2o.outputs.cache-hit != 'true'
shell: bash
run: |
set -e
NPROC=$(sysctl -n hw.logicalcpu)
SRC="${{ runner.temp }}/src-deps/g2o"
STAGE="${{ runner.temp }}/deps-stage/g2o"
rm -rf "$SRC"; mkdir -p "$SRC" "$STAGE"
# g2o pins a commit (not a tag), so clone then checkout.
git clone https://github.com/RainerKuemmerle/g2o.git "$SRC/g2o"
cd "$SRC/g2o"
git checkout 67cbe15c998737ac6705d3cd18201a72be0d073d
# CMAKE_INSTALL_PREFIX=/usr/local so it stages under usr/local like the
# other deps. Homebrew's libomp is keg-only and g2o does not propagate
# OpenMP's include/lib dirs, so with Apple clang <omp.h> isn't found and
# libomp isn't linked; add its include (-I) and lib (-L -lomp) explicitly.
LIBOMP=$(brew --prefix libomp)
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ inputs.build_type }} \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_PREFIX_PATH="$LIBOMP" \
-DCMAKE_C_FLAGS="-I$LIBOMP/include" \
-DCMAKE_CXX_FLAGS="-I$LIBOMP/include" \
-DCMAKE_EXE_LINKER_FLAGS="-L$LIBOMP/lib -lomp" \
-DCMAKE_SHARED_LINKER_FLAGS="-L$LIBOMP/lib -lomp" \
-DG2O_BUILD_APPS=OFF \
-DG2O_BUILD_EXAMPLES=OFF \
-DG2O_USE_OPENMP=ON \
-DG2O_USE_OPENGL=OFF
cmake --build build -j$NPROC
DESTDIR="$STAGE" cmake --install build
# ------------------- install all units into /usr/local ---------- # ------------------- install all units into /usr/local ----------
- name: Install source dependencies into /usr/local - name: Install source dependencies into /usr/local
shell: bash shell: bash
@@ -213,7 +254,7 @@ runs:
set -e set -e
STAGE="${{ runner.temp }}/deps-stage" STAGE="${{ runner.temp }}/deps-stage"
found=0 found=0
for unit in gtsam pointmatcher orbbec depthai opengv; do for unit in gtsam pointmatcher orbbec depthai opengv g2o; do
if [ -d "$STAGE/$unit/usr/local" ]; then if [ -d "$STAGE/$unit/usr/local" ]; then
sudo cp -a "$STAGE/$unit/usr/local/." /usr/local/ sudo cp -a "$STAGE/$unit/usr/local/." /usr/local/
found=1 found=1

View File

@@ -26,7 +26,7 @@ runs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ runner.workspace }}/vcpkg_installed path: ${{ runner.workspace }}/vcpkg_installed
key: ${{ runner.os }}-vcpkg-export-66c0373d-x64-vs2022-cuda130_v1 key: ${{ runner.os }}-vcpkg-export-66c0373d-x64-vs2022-cuda130_v4
- name: Download and Install vcpkg - name: Download and Install vcpkg
if: steps.cache-vcpkg.outputs.cache-hit != 'true' if: steps.cache-vcpkg.outputs.cache-hit != 'true'
@@ -35,10 +35,25 @@ runs:
$install_dir = "${{ runner.workspace }}\vcpkg_installed" $install_dir = "${{ runner.workspace }}\vcpkg_installed"
$archivePath = "${{ runner.workspace }}\vcpkg-export.7z" $archivePath = "${{ runner.workspace }}\vcpkg-export.7z"
# The file has been built locally with bundle-windows-deps.bat # The 7z is built locally with bundle_windows_deps_cuda.bat and uploaded to a
$url = "https://github.com/introlab/rtabmap/releases/download/0.23.1/vcpkg-export-66c0373d-x64-vs2022-cuda130.7z" # GitHub release. Resolve it by filename via the API (authenticated with the
# built-in token) so it works even from a *draft* release and regardless of
Invoke-WebRequest -Uri $url -OutFile $archivePath # the (changing) asset id. Requires the workflow to run in-repo (github.token
# can't read introlab drafts from a fork PR).
$repo = "introlab/rtabmap"
$releaseTag = "0.23.8"
$assetName = "vcpkg-export-66c0373d-x64-vs2022-cuda130.7z"
$apiHeaders = @{ Authorization = "Bearer ${{ github.token }}"; "User-Agent" = "rtabmap-ci"; Accept = "application/vnd.github+json" }
# Select the release by tag_name (populated on drafts too, and unique), then
# take the asset from THAT release so concurrent releases can't be confused.
$rel = (Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/releases?per_page=100" -Headers $apiHeaders) |
Where-Object { $_.tag_name -eq $releaseTag } | Select-Object -First 1
if (-not $rel) { throw "Release tagged $releaseTag not found (including drafts)" }
$asset = $rel.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
if (-not $asset) { throw "Asset $assetName not found in release $releaseTag" }
Write-Host "Downloading $assetName (asset id $($asset.id)) ..."
$dlHeaders = @{ Authorization = "Bearer ${{ github.token }}"; "User-Agent" = "rtabmap-ci"; Accept = "application/octet-stream" }
Invoke-WebRequest -Uri $asset.url -Headers $dlHeaders -OutFile $archivePath
& 7z x $archivePath "-o$install_dir" -y & 7z x $archivePath "-o$install_dir" -y
- name: Add vcpkg to PATH and env variable - name: Add vcpkg to PATH and env variable

View File

@@ -13,7 +13,7 @@ runs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ runner.workspace }}/vcpkg_installed path: ${{ runner.workspace }}/vcpkg_installed
key: ${{ runner.os }}-vcpkg-export-66c0373d-x64-vs2022-v4 key: ${{ runner.os }}-vcpkg-export-66c0373d-x64-vs2022-v6
- name: Download and Install vcpkg - name: Download and Install vcpkg
if: steps.cache-vcpkg.outputs.cache-hit != 'true' if: steps.cache-vcpkg.outputs.cache-hit != 'true'
@@ -22,10 +22,25 @@ runs:
$install_dir = "${{ runner.workspace }}\vcpkg_installed" $install_dir = "${{ runner.workspace }}\vcpkg_installed"
$archivePath = "${{ runner.workspace }}\vcpkg-export.7z" $archivePath = "${{ runner.workspace }}\vcpkg-export.7z"
# The file has been built locally with bundle-windows-deps.bat # The 7z is built locally with bundle_windows_deps.bat and uploaded to a
$url = "https://github.com/introlab/rtabmap/releases/download/0.23.1/vcpkg-export-66c0373d-x64-vs2022.7z" # GitHub release. Resolve it by filename via the API (authenticated with the
# built-in token) so it works even from a *draft* release and regardless of
Invoke-WebRequest -Uri $url -OutFile $archivePath # the (changing) asset id. Requires the workflow to run in-repo (github.token
# can't read introlab drafts from a fork PR).
$repo = "introlab/rtabmap"
$releaseTag = "0.23.8"
$assetName = "vcpkg-export-66c0373d-x64-vs2022.7z"
$apiHeaders = @{ Authorization = "Bearer ${{ github.token }}"; "User-Agent" = "rtabmap-ci"; Accept = "application/vnd.github+json" }
# Select the release by tag_name (populated on drafts too, and unique), then
# take the asset from THAT release so concurrent releases can't be confused.
$rel = (Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/releases?per_page=100" -Headers $apiHeaders) |
Where-Object { $_.tag_name -eq $releaseTag } | Select-Object -First 1
if (-not $rel) { throw "Release tagged $releaseTag not found (including drafts)" }
$asset = $rel.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
if (-not $asset) { throw "Asset $assetName not found in release $releaseTag" }
Write-Host "Downloading $assetName (asset id $($asset.id)) ..."
$dlHeaders = @{ Authorization = "Bearer ${{ github.token }}"; "User-Agent" = "rtabmap-ci"; Accept = "application/octet-stream" }
Invoke-WebRequest -Uri $asset.url -Headers $dlHeaders -OutFile $archivePath
& 7z x $archivePath "-o$install_dir" -y & 7z x $archivePath "-o$install_dir" -y
- name: Add vcpkg to PATH and env variable - name: Add vcpkg to PATH and env variable

View File

@@ -26,24 +26,19 @@ jobs:
include: include:
- build_name: macos-sequoia-intel - build_name: macos-sequoia-intel
os: macos-15-intel os: macos-15-intel
extra_cmake_def: '-DBUILD_AS_BUNDLE=ON -DWITH_ORBBEC_SDK=ON -DWITH_DEPTHAI=ON'
- build_name: macos-sequoia-apple-silicon - build_name: macos-sequoia-apple-silicon
os: macos-15 os: macos-15
extra_cmake_def: '-DBUILD_AS_BUNDLE=ON -DWITH_ORBBEC_SDK=ON -DWITH_DEPTHAI=ON'
- build_name: macos-tahoe-intel - build_name: macos-tahoe-intel
os: macos-26-intel os: macos-26-intel
extra_cmake_def: '-DBUILD_AS_BUNDLE=ON -DWITH_ORBBEC_SDK=ON -DWITH_DEPTHAI=ON'
- build_name: macos-tahoe-apple-silicon - build_name: macos-tahoe-apple-silicon
os: macos-26 os: macos-26
extra_cmake_def: '-DBUILD_AS_BUNDLE=ON -DWITH_ORBBEC_SDK=ON -DWITH_DEPTHAI=ON'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install Brew Dependencies - name: Install Brew Dependencies
run: | run: |
# Update brew and install from Brewfile if present, or specific packages # Update brew and install from Brewfile if present, or specific packages
brew install pcl opencv octomap g2o pdal yaml-cpp librealsense libfreenect libusb zlib brew install pcl opencv octomap pdal yaml-cpp librealsense libfreenect libusb zlib libomp suite-sparse ceres-solver
- name: Install Source Dependencies - name: Install Source Dependencies
# Build (and per-dependency cache) the source-only deps not available from # Build (and per-dependency cache) the source-only deps not available from
@@ -55,7 +50,21 @@ jobs:
- name: Configure CMake - name: Configure CMake
run: | run: |
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${{ matrix.extra_cmake_def }} # Apple clang has no built-in OpenMP; point find_package(OpenMP) at
# Homebrew's keg-only libomp so PCL/g2o/rtabmap enable OpenMP instead of
# repeatedly logging "Could NOT find OpenMP".
LIBOMP=$(brew --prefix libomp)
cmake -B ${{github.workspace}}/build \
-DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
-DBUILD_AS_BUNDLE=ON \
-DWITH_ORBBEC_SDK=ON \
-DWITH_DEPTHAI=ON \
-DWITH_CERES=ON \
-DOpenMP_C_FLAGS="-Xclang -fopenmp -I$LIBOMP/include" \
-DOpenMP_C_LIB_NAMES=omp \
-DOpenMP_CXX_FLAGS="-Xclang -fopenmp -I$LIBOMP/include" \
-DOpenMP_CXX_LIB_NAMES=omp \
-DOpenMP_omp_LIBRARY="$LIBOMP/lib/libomp.dylib"
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}

View File

@@ -74,6 +74,14 @@ jobs:
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Info
# Skipped for CUDA: the binary links ZED (sl_zed64.dll -> nvcuvid/nvEncodeAPI64),
# which need the NVIDIA driver; the GPU-less runner can't load the exe.
if: matrix.build_name != 'windows-2022-cuda'
working-directory: ${{github.workspace}}/build/bin
run: |
./rtabmap-console --version
- name: Build Windows Package - name: Build Windows Package
shell: pwsh shell: pwsh
run: | run: |
@@ -88,11 +96,6 @@ jobs:
shell: pwsh shell: pwsh
run: Get-ChildItem -Path "build" -Filter "RTABMap-*" | Rename-Item -NewName { $_.BaseName + "_cuda" + $_.Extension } run: Get-ChildItem -Path "build" -Filter "RTABMap-*" | Rename-Item -NewName { $_.BaseName + "_cuda" + $_.Extension }
- name: Info
working-directory: ${{github.workspace}}/build/bin
run: |
./rtabmap-console --version
- name: Upload RTABMap Artifacts (ZIP) - name: Upload RTABMap Artifacts (ZIP)
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:

View File

@@ -151,14 +151,30 @@ IF(BUILD_AS_BUNDLE AND (APPLE OR WIN32))
IF(NOT OrbbecSDK_BIN_DIR) IF(NOT OrbbecSDK_BIN_DIR)
MESSAGE(FATAL_ERROR "OrbbecSDK.dll not found! Verify your PATH.") MESSAGE(FATAL_ERROR "OrbbecSDK.dll not found! Verify your PATH.")
ENDIF(NOT OrbbecSDK_BIN_DIR) ENDIF(NOT OrbbecSDK_BIN_DIR)
MESSAGE(FATAL "OrbbecSDK_BIN_DIR=${OrbbecSDK_BIN_DIR}") MESSAGE(STATUS "OrbbecSDK_BIN_DIR=${OrbbecSDK_BIN_DIR}")
INSTALL(DIRECTORY "${OrbbecSDK_BIN_DIR}/extensions" INSTALL(DIRECTORY "${OrbbecSDK_BIN_DIR}/extensions"
DESTINATION ${thirdparty_dest_dir} DESTINATION ${thirdparty_dest_dir}
COMPONENT runtime COMPONENT runtime
FILES_MATCHING FILES_MATCHING
PATTERN "*.lib" EXCLUDE PATTERN "*.lib" EXCLUDE
PATTERN "*") PATTERN "*")
ENDIF(WIN32) ELSEIF(APPLE)
# OrbbecSDK loads its extensions (frame processor, filters like
# FrameUnpacker) via dlopen relative to libOrbbecSDK. fixup_bundle puts
# libOrbbecSDK in Contents/Frameworks, so the extensions must sit next to
# it in Frameworks/extensions (they reference @rpath/libOrbbecSDK, which
# resolves via the app executable's @executable_path/../Frameworks rpath).
# Without this the camera is detected but frame processing fails.
get_filename_component(OrbbecSDK_LIB_DIR "${OrbbecSDK_DIR}/../.." ABSOLUTE)
INSTALL(DIRECTORY "${OrbbecSDK_LIB_DIR}/extensions"
DESTINATION Frameworks
COMPONENT runtime)
IF(EXISTS "${OrbbecSDK_LIB_DIR}/OrbbecSDKConfig.xml")
INSTALL(FILES "${OrbbecSDK_LIB_DIR}/OrbbecSDKConfig.xml"
DESTINATION Frameworks
COMPONENT runtime)
ENDIF()
ENDIF(WIN32)
ENDIF(OrbbecSDK_FOUND) ENDIF(OrbbecSDK_FOUND)
IF(Torch_FOUND) IF(Torch_FOUND)
@@ -405,7 +421,17 @@ IF(BUILD_AS_BUNDLE AND (APPLE OR WIN32))
install(CODE " install(CODE "
# Glob Qt Plugins # Glob Qt Plugins
file(GLOB_RECURSE ALL_LIBS \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*${CMAKE_SHARED_LIBRARY_SUFFIX}\") file(GLOB_RECURSE ALL_LIBS \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
# OrbbecSDK loads its filter/frame-processor extensions via dlopen, and they
# link @rpath/libOrbbecSDK. Feed them to fixup_bundle so their reference is
# rewritten to the embedded libOrbbecSDK
file(GLOB ORBBEC_EXT_LIBS
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/Frameworks/extensions/filters/*${CMAKE_SHARED_LIBRARY_SUFFIX}\"
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/Frameworks/extensions/frameprocessor/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
if(ORBBEC_EXT_LIBS)
list(APPEND ALL_LIBS \${ORBBEC_EXT_LIBS})
endif()
if(NOT \"${python_pyd_dir}\" STREQUAL \"\") if(NOT \"${python_pyd_dir}\" STREQUAL \"\")
file(GLOB_RECURSE PYD_FILES \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${python_pyd_dir}/*.pyd\") file(GLOB_RECURSE PYD_FILES \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${python_pyd_dir}/*.pyd\")
if(PYD_FILES) if(PYD_FILES)
@@ -421,6 +447,16 @@ IF(BUILD_AS_BUNDLE AND (APPLE OR WIN32))
set(BU_CHMOD_BUNDLE_ITEMS ON) set(BU_CHMOD_BUNDLE_ITEMS ON)
include(\"BundleUtilities\") include(\"BundleUtilities\")
# nvcuvid.dll (NVDEC) and nvEncodeAPI64.dll (NVENC), and the CUDA driver API
# nvcuda.dll, ship with the NVIDIA GPU driver (System32), not the CUDA toolkit,
# so they are absent on driver-less CI runners and must never be bundled (they
# are resolved at runtime from the end user's driver). Mark them 'system' so
# fixup_bundle skips them instead of failing to resolve them.
function(gp_resolved_file_type_override resolved_file type_var)
if(resolved_file MATCHES \"(nvcuvid|nvEncodeAPI64|nvcuda)\")
set(\${type_var} \"system\" PARENT_SCOPE)
endif()
endfunction()
fixup_bundle(\"${APPS}\" \"\${ALL_LIBS}\" \"${DIRS}\") fixup_bundle(\"${APPS}\" \"\${ALL_LIBS}\" \"${DIRS}\")
" COMPONENT runtime) " COMPONENT runtime)

View File

@@ -52,6 +52,10 @@ int main(int argc, char* argv[])
ULogger::setLevel(ULogger::kWarning); ULogger::setLevel(ULogger::kWarning);
#ifdef WIN32 #ifdef WIN32
// STA (single-threaded apartment) is required for the native file dialogs / File Explorer
// (see commit d75cc04, "Fixed File Explorer hanging (Qt 5.12)"). Do NOT switch to MTA
// (CoInitializeEx COINIT_MULTITHREADED): it deadlocks the
// native file dialogs.
CoInitialize(nullptr); CoInitialize(nullptr);
#endif #endif
@@ -60,6 +64,12 @@ int main(int argc, char* argv[])
QSurfaceFormat::setDefaultFormat(QVTKRenderWidget::defaultFormat()); QSurfaceFormat::setDefaultFormat(QVTKRenderWidget::defaultFormat());
#endif #endif
// Recommended by VTK when using QVTKOpenGLNativeWidget (a QOpenGLWidget): let all VTK
// render widgets share a single OpenGL context, which is needed for correct rendering
// when render widgets live in / move across multiple top-level windows. Must be set
// before QApplication is constructed.
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
/* Create tasks */ /* Create tasks */
QApplication * app = new QApplication(argc, argv); QApplication * app = new QApplication(argc, argv);
app->setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }"); // selectable message box app->setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }"); // selectable message box

View File

@@ -205,16 +205,15 @@ cmake -S . -B build -GNinja ^
-DBUILD_PERF_TESTS=OFF ^ -DBUILD_PERF_TESTS=OFF ^
-DOPENCV_ENABLE_NONFREE=ON ^ -DOPENCV_ENABLE_NONFREE=ON ^
-DBUILD_opencv_apps=OFF ^ -DBUILD_opencv_apps=OFF ^
-DBUILD_opencv_python3=ON ^ -DBUILD_opencv_cudacodec=OFF ^
-DPYTHON3_EXECUTABLE=%FINAL_EXPORT_PATH%/installed/%TRIPLET%/tools/python3/python.exe ^ -DBUILD_opencv_python3=OFF ^
-DPYTHON3_PACKAGES_PATH=bin/Lib/site-packages ^ -DBUILD_opencv_python_bindings_generator=OFF ^
-DBUILD_opencv_python_tests=OFF ^
-DBUILD_opencv_java_bindings_generator=OFF ^ -DBUILD_opencv_java_bindings_generator=OFF ^
-DWITH_CUDA=ON ^ -DWITH_CUDA=ON ^
-DWITH_VTK=OFF ^ -DWITH_VTK=OFF ^
-DWITH_TBB=ON || exit /b !errorlevel! -DWITH_TBB=ON || exit /b !errorlevel!
cmake --build build --config Release --target install || exit /b !errorlevel! cmake --build build --config Release --target install || exit /b !errorlevel!
:: move cv2 package under tools/python3
robocopy "%FINAL_EXPORT_PATH%\installed\%TRIPLET%\bin\Lib" "%FINAL_EXPORT_PATH%\installed\%TRIPLET%\tools\python3\Lib" /E /MOVE /NFL /NDL /NJH /NC /NS /NP
cd .. cd ..

View File

@@ -344,7 +344,7 @@ public:
void setGPS(const GPS & gps) {gps_ = gps;} void setGPS(const GPS & gps) {gps_ = gps;}
const GPS & gps() const {return gps_;} const GPS & gps() const {return gps_;}
void setIMU(const IMU & imu) {imu_ = imu; } void setIMU(const IMU & imu);
const IMU & imu() const {return imu_;} const IMU & imu() const {return imu_;}
void setEnvSensors(const EnvSensors & sensors) {_envSensors = sensors;} void setEnvSensors(const EnvSensors & sensors) {_envSensors = sensors;}

View File

@@ -49,8 +49,8 @@ public:
public: public:
CameraStereoZed( CameraStereoZed(
int deviceId, int deviceId,
int resolution = -1, // -1 = AUTO, 0=HD4K 1=HD2K 2=HD1080 3=HD1200 4=HD720 5=SVGA 6=VGA int resolution = -1, // -1 = AUTO, 0=HD4K 1=QHDPLUS 2=HD2K 3=HD1536 4=HD1080 5=HD1200 6=HD720 7=SVGA 8=VGA 9=XVGA 10=TXVGA
int quality = 1, // 0=NONE, 1=PERFORMANCE, 2=QUALITY int quality = 1, // 0=NONE, 1=PERFORMANCE, 2=QUALITY, 3=ULTRA, 4=NEURAL_LIGHT, 5=NEURAL, 6=NEURAL_ULTRA
int sensingMode = 0,// 0=STANDARD, 1=FILL int sensingMode = 0,// 0=STANDARD, 1=FILL
int confidenceThr = 100, int confidenceThr = 100,
bool computeOdometry = false, bool computeOdometry = false,
@@ -61,7 +61,7 @@ public:
int texturenessConfidenceThr = 90); // introduced with ZED SDK 3 int texturenessConfidenceThr = 90); // introduced with ZED SDK 3
CameraStereoZed( CameraStereoZed(
const std::string & svoFilePath, const std::string & svoFilePath,
int quality = 1, // 0=NONE, 1=PERFORMANCE, 2=QUALITY, 3=NEURAL int quality = 1, // 0=NONE, 1=PERFORMANCE, 2=QUALITY, 3=ULTRA, 4=NEURAL_LIGHT, 5=NEURAL, 6=NEURAL_ULTRA
int sensingMode = 0,// 0=STANDARD, 1=FILL int sensingMode = 0,// 0=STANDARD, 1=FILL
int confidenceThr = 100, int confidenceThr = 100,
bool computeOdometry = false, bool computeOdometry = false,
@@ -81,6 +81,15 @@ public:
void postInterIMUPublic(const IMU & imu, double stamp); void postInterIMUPublic(const IMU & imu, double stamp);
void setRightGrayScale(bool enabled = true); void setRightGrayScale(bool enabled = true);
/**
* If the given ZED depth mode (quality: same encoding as the constructor) is a NEURAL mode
* whose AI model isn't yet downloaded/optimized for the GPU, returns a human-readable warning
* that the first start will be slower (the ZED SDK downloads/optimizes the model during
* init()). Returns an empty string when the model is ready, the mode isn't neural, or the
* SDK/build doesn't support the check.
*/
static std::string getNeuralModelWarning(int quality);
protected: protected:
virtual SensorData captureImage(SensorCaptureInfo * info = 0); virtual SensorData captureImage(SensorCaptureInfo * info = 0);

View File

@@ -29,10 +29,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Should be first on windows to avoid "WinSock.h has already been included" error // Should be first on windows to avoid "WinSock.h has already been included" error
#include <pcl/io/vlp_grabber.h> #include <pcl/io/vlp_grabber.h>
#include <boost/version.hpp> #include <boost/version.hpp>
#include <boost/asio.hpp>
#include <rtabmap/core/Lidar.h> #include <rtabmap/core/Lidar.h>
#include <rtabmap/utilite/USemaphore.h> #include <rtabmap/utilite/USemaphore.h>
#include <thread>
#include <atomic>
// On Apple, closing pcl::HDLGrabber's UDP socket while its read thread is blocked
// in receive_from returns EBADF, which pcl::HDLGrabber::readPacketsFromSocket()
// rethrows uncaught in its own thread, aborting the process on shutdown. There we
// run our own single-threaded UDP receive loop and own the socket lifecycle.
// Other platforms use the base pcl::VLPGrabber path (which also avoids needing
// boost::asio::io_context, absent from the Boost 1.65 on e.g. Ubuntu Bionic).
// Comment this out to force the base path everywhere (e.g. A/B testing on macOS).
#if defined(__APPLE__)
#define RTABMAP_VLP16_USE_CUSTOM_SOCKET
#endif
namespace rtabmap { namespace rtabmap {
struct PointXYZIT { struct PointXYZIT {
@@ -68,9 +83,28 @@ public:
void setOrganized(bool enable); void setOrganized(bool enable);
#ifdef RTABMAP_VLP16_USE_CUSTOM_SOCKET
// Overridden only on Apple: in network mode we run our own UDP receive loop
// instead of pcl::HDLGrabber's, because on macOS/BSD closing the grabber's
// socket while its read thread is blocked in receive_from returns EBADF, a
// code that pcl::HDLGrabber::readPacketsFromSocket() rethrows uncaught (in
// its own thread), aborting the process on shutdown. Owning the socket lets
// us tear it down with the non-throwing receive_from overload. Like
// pcl::HDLGrabber, we bind to the given LOCAL interface address (to scope
// reception on a multi-homed host) and fall back to all interfaces (0.0.0.0)
// when it is unspecified or not a local address. In PCAP mode we delegate to
// the base grabber. On other platforms we inherit pcl::VLPGrabber directly.
virtual void start() override;
virtual void stop() override;
virtual bool isRunning() const override;
#endif
private: private:
void buildTimings(bool dualMode); void buildTimings(bool dualMode);
virtual void toPointClouds (HDLDataPacket *dataPacket) override; virtual void toPointClouds (HDLDataPacket *dataPacket) override;
#ifdef RTABMAP_VLP16_USE_CUSTOM_SOCKET
void readPackets();
#endif
protected: protected:
virtual SensorData captureData(SensorCaptureInfo * info = 0) override; virtual SensorData captureData(SensorCaptureInfo * info = 0) override;
@@ -88,6 +122,18 @@ private:
std::vector<std::vector<PointXYZIT> > accumulatedScans_; std::vector<std::vector<PointXYZIT> > accumulatedScans_;
USemaphore scanReady_; USemaphore scanReady_;
UMutex lastScanMutex_; UMutex lastScanMutex_;
// Own UDP receive path (network mode only).
bool networkMode_;
boost::asio::ip::address ipAddress_;
std::uint16_t port_;
bool receivedScan_; // set once the first scan is received
#ifdef RTABMAP_VLP16_USE_CUSTOM_SOCKET
boost::asio::io_context ioContext_;
boost::asio::ip::udp::socket * socket_;
std::thread * readThread_;
std::atomic<bool> terminate_;
#endif
}; };
} /* namespace rtabmap */ } /* namespace rtabmap */

View File

@@ -1247,7 +1247,12 @@ void readINIImpl(const CSimpleIniA & ini, const std::string & configFilePath, Pa
{ {
if(RTABMAP_VERSION_COMPARE(<, std::atoi(version[0].c_str()), std::atoi(version[1].c_str()), std::atoi(version[2].c_str()))) if(RTABMAP_VERSION_COMPARE(<, std::atoi(version[0].c_str()), std::atoi(version[1].c_str()), std::atoi(version[2].c_str())))
{ {
if(configFilePath.find(".rtabmap") != std::string::npos) // Detect the user's private config - matches both the legacy
// ~/.rtabmap/rtabmap.ini and the Windows %LOCALAPPDATA%/rtabmap/rtabmap.ini
// (".rtabmap/rtabmap.ini" also contains "rtabmap/rtabmap.ini"): downgrade-on-save is fine there.
// Accept both forward and backward slashes (native Windows paths).
if(configFilePath.find("rtabmap/rtabmap.ini") != std::string::npos ||
configFilePath.find("rtabmap\\rtabmap.ini") != std::string::npos)
{ {
UWARN("Version in the config file \"%s\" is more recent (\"%s\") than " UWARN("Version in the config file \"%s\" is more recent (\"%s\") than "
"current RTAB-Map version used (\"%s\"). The config file will be downgraded " "current RTAB-Map version used (\"%s\"). The config file will be downgraded "

View File

@@ -114,7 +114,6 @@ SensorCaptureThread::SensorCaptureThread(
_camera(camera), _camera(camera),
_odomSensor(odomSensor), _odomSensor(odomSensor),
_lidar(lidar), _lidar(lidar),
_extrinsicsOdomToCamera(extrinsics * CameraModel::opticalRotation()),
_odomAsGt(false), _odomAsGt(false),
_poseTimeOffset(poseTimeOffset), _poseTimeOffset(poseTimeOffset),
_poseScaleFactor(poseScaleFactor), _poseScaleFactor(poseScaleFactor),
@@ -153,10 +152,14 @@ SensorCaptureThread::SensorCaptureThread(
{ {
if(_camera) if(_camera)
{ {
if(_odomSensor == _camera && _extrinsicsOdomToCamera.isNull()) if(_odomSensor == _camera && extrinsics.isNull())
{ {
_extrinsicsOdomToCamera.setIdentity(); _extrinsicsOdomToCamera.setIdentity();
} }
else
{
_extrinsicsOdomToCamera = extrinsics * CameraModel::opticalRotation();
}
UASSERT(!_extrinsicsOdomToCamera.isNull()); UASSERT(!_extrinsicsOdomToCamera.isNull());
UDEBUG("_extrinsicsOdomToCamera=%s", _extrinsicsOdomToCamera.prettyPrint().c_str()); UDEBUG("_extrinsicsOdomToCamera=%s", _extrinsicsOdomToCamera.prettyPrint().c_str());
} }
@@ -492,20 +495,26 @@ void SensorCaptureThread::mainLoop()
} }
} }
// Adjust local transform of the camera based on the pose frame // Adjust local transform of the camera(s) based on the pose frame. The correction
// and odom->camera extrinsics are frame-level, so apply the same prefix to each
// camera while keeping its own local transform (multi-camera supported).
if(!data.cameraModels().empty()) if(!data.cameraModels().empty())
{ {
UASSERT(data.cameraModels().size()==1); std::vector<CameraModel> models = data.cameraModels();
CameraModel model = data.cameraModels()[0]; for(size_t i=0; i<models.size(); ++i)
model.setLocalTransform(cameraCorrection*_extrinsicsOdomToCamera); {
data.setCameraModel(model); models[i].setLocalTransform(cameraCorrection*_extrinsicsOdomToCamera*models[i].localTransform());
}
data.setCameraModels(models);
} }
else if(!data.stereoCameraModels().empty()) else if(!data.stereoCameraModels().empty())
{ {
UASSERT(data.stereoCameraModels().size()==1); std::vector<StereoCameraModel> models = data.stereoCameraModels();
StereoCameraModel model = data.stereoCameraModels()[0]; for(size_t i=0; i<models.size(); ++i)
model.setLocalTransform(cameraCorrection*_extrinsicsOdomToCamera); {
data.setStereoCameraModel(model); models[i].setLocalTransform(cameraCorrection*_extrinsicsOdomToCamera*models[i].localTransform());
}
data.setStereoCameraModels(models);
} }
} }
@@ -530,15 +539,21 @@ void SensorCaptureThread::mainLoop()
info.odomPose.setNull(); info.odomPose.setNull();
} }
if(!data.imageCompressed().empty() || !data.imageRaw().empty() || !data.laserScanRaw().empty() || (dynamic_cast<DBReader*>(_camera) != 0 && data.id()>0)) // intermediate nodes could not have image set if(this->isKilled())
{
// A kill was requested (e.g. while we were blocked capturing this frame): don't
// publish anything so we never deliver events to handlers that are being torn down.
}
else if(!data.imageCompressed().empty() || !data.imageRaw().empty() || !data.laserScanRaw().empty() || (dynamic_cast<DBReader*>(_camera) != 0 && data.id()>0)) // intermediate nodes could not have image set
{ {
postUpdate(&data, &info); postUpdate(&data, &info);
info.cameraName = _lidar?_lidar->getSerial():_camera->getSerial(); info.cameraName = _lidar?_lidar->getSerial():_camera->getSerial();
info.timeTotal = totalTime.ticks(); info.timeTotal = totalTime.ticks();
this->post(new SensorEvent(data, info)); this->post(new SensorEvent(data, info));
} }
else if(!this->isKilled()) else
{ {
// Not killed but no data: end of stream. Signal consumers once, then stop.
UWARN("no more data..."); UWARN("no more data...");
this->kill(); this->kill();
this->post(new SensorEvent()); this->post(new SensorEvent());

View File

@@ -952,6 +952,22 @@ void SensorData::setFeatures(const std::vector<cv::KeyPoint> & keypoints, const
_descriptors = descriptors; _descriptors = descriptors;
} }
void SensorData::setIMU(const IMU & imu)
{
UASSERT(
uIsFinite(imu.orientation()[0]) &&
uIsFinite(imu.orientation()[1]) &&
uIsFinite(imu.orientation()[2]) &&
uIsFinite(imu.orientation()[3]) &&
uIsFinite(imu.angularVelocity()[0]) &&
uIsFinite(imu.angularVelocity()[1]) &&
uIsFinite(imu.angularVelocity()[2]) &&
uIsFinite(imu.linearAcceleration()[0]) &&
uIsFinite(imu.linearAcceleration()[1]) &&
uIsFinite(imu.linearAcceleration()[2]));
imu_ = imu;
}
unsigned long SensorData::getMemoryUsed() const // Return memory usage in Bytes unsigned long SensorData::getMemoryUsed() const // Return memory usage in Bytes
{ {
return sizeof(SensorData) + return sizeof(SensorData) +

View File

@@ -107,10 +107,14 @@ void CameraRealSense2::close()
{ {
if(!_sensor.get_active_streams().empty()) if(!_sensor.get_active_streams().empty())
{ {
std::string sensorName = _sensor.supports(RS2_CAMERA_INFO_NAME) ? _sensor.get_info(RS2_CAMERA_INFO_NAME) : "?";
try try
{ {
UDEBUG("stop() sensor \"%s\"...", sensorName.c_str());
_sensor.stop(); _sensor.stop();
UDEBUG("stop() sensor \"%s\" done; close()...", sensorName.c_str());
_sensor.close(); _sensor.close();
UDEBUG("close() sensor \"%s\" done", sensorName.c_str());
} }
catch(const rs2::error & error) catch(const rs2::error & error)
{ {
@@ -119,12 +123,15 @@ void CameraRealSense2::close()
} }
} }
#ifdef WIN32 #ifdef WIN32
UDEBUG("Windows-only: Hardware reset (to avoid freezing when clearing devices)...");
dev_[i].hardware_reset(); // To avoid freezing on some Windows computers in the following destructor dev_[i].hardware_reset(); // To avoid freezing on some Windows computers in the following destructor
// Don't do this on linux (tested on Ubuntu 18.04, realsense v2.41.0): T265 cannot be restarted // Don't do this on linux (tested on Ubuntu 18.04, realsense v2.41.0): T265 cannot be restarted
UDEBUG("Windows-only: Hardware reset... done");
#endif #endif
} }
UDEBUG("Clearing devices..."); UDEBUG("Clearing devices...");
dev_.clear(); dev_.clear();
UDEBUG("Clearing devices... done!");
} }
catch(const rs2::error & error) catch(const rs2::error & error)
{ {
@@ -554,10 +561,10 @@ bool CameraRealSense2::init(const std::string & calibrationFolder, const std::st
catch(const rs2::error & error) catch(const rs2::error & error)
{ {
#ifdef __APPLE__ #ifdef __APPLE__
UWARN("%s. Is the camera already used with another app? On macOS, accessing a " UERROR("%s. Is the camera already used with another app? On macOS, accessing a "
"RealSense camera requires root privileges, so try running with sudo.", error.what()); "RealSense camera requires root privileges, so try running with sudo.", error.what());
#else #else
UWARN("%s. Is the camera already used with another app?", error.what()); UERROR("%s. Is the camera already used with another app?", error.what());
#endif #endif
} }

View File

@@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <rtabmap/utilite/UTimer.h> #include <rtabmap/utilite/UTimer.h>
#include <rtabmap/utilite/UThread.h> #include <rtabmap/utilite/UThread.h>
#include <rtabmap/utilite/UConversion.h> #include <rtabmap/utilite/UConversion.h>
#include <rtabmap/utilite/UMath.h>
#ifdef RTABMAP_ZED #ifdef RTABMAP_ZED
#include <sl/Camera.hpp> #include <sl/Camera.hpp>
@@ -77,7 +78,7 @@ static cv::Mat slMat2cvMat(sl::Mat& input) {
#endif #endif
} }
Transform zedPoseToTransform(const sl::Pose & pose) static Transform zedPoseToTransform(const sl::Pose & pose)
{ {
return Transform( return Transform(
pose.pose_data.m[0], pose.pose_data.m[1], pose.pose_data.m[2], pose.pose_data.m[3], pose.pose_data.m[0], pose.pose_data.m[1], pose.pose_data.m[2], pose.pose_data.m[3],
@@ -86,7 +87,7 @@ Transform zedPoseToTransform(const sl::Pose & pose)
} }
#if ZED_SDK_MAJOR_VERSION < 3 #if ZED_SDK_MAJOR_VERSION < 3
IMU zedIMUtoIMU(const sl::IMUData & imuData, const Transform & imuLocalTransform) static IMU zedIMUtoIMU(const sl::IMUData & imuData, const Transform & imuLocalTransform)
{ {
sl::Orientation orientation = imuData.pose_data.getOrientation(); sl::Orientation orientation = imuData.pose_data.getOrientation();
@@ -124,7 +125,7 @@ IMU zedIMUtoIMU(const sl::IMUData & imuData, const Transform & imuLocalTransform
imuLocalTransform); imuLocalTransform);
} }
#else #else
IMU zedIMUtoIMU(const sl::SensorsData & sensorData, const Transform & imuLocalTransform) static IMU zedIMUtoIMU(const sl::SensorsData & sensorData, const Transform & imuLocalTransform)
{ {
sl::Orientation orientation = sensorData.imu.pose.getOrientation(); sl::Orientation orientation = sensorData.imu.pose.getOrientation();
@@ -161,6 +162,23 @@ IMU zedIMUtoIMU(const sl::SensorsData & sensorData, const Transform & imuLocalTr
accCov, accCov,
imuLocalTransform); imuLocalTransform);
} }
// sl::SensorsData::imu.is_available only means the camera has an IMU; a returned
// sample can still contain NaN pose/accel/gyro (e.g. before the IMU fusion has
// initialized, or in SVO/STREAM mode). Validate the actual measurements.
static bool isImuValid(const sl::SensorsData & sensorData)
{
if(!sensorData.imu.is_available)
{
return false;
}
const sl::float3 & acc = sensorData.imu.linear_acceleration;
const sl::float3 & gyr = sensorData.imu.angular_velocity;
const sl::Orientation ori = sensorData.imu.pose.getOrientation();
return uIsFinite(acc.v[0]) && uIsFinite(acc.v[1]) && uIsFinite(acc.v[2]) &&
uIsFinite(gyr.v[0]) && uIsFinite(gyr.v[1]) && uIsFinite(gyr.v[2]) &&
uIsFinite(ori.ox) && uIsFinite(ori.oy) && uIsFinite(ori.oz) && uIsFinite(ori.ow);
}
#endif #endif
class ZedIMUThread: public UThread class ZedIMUThread: public UThread
@@ -217,7 +235,7 @@ private:
#else #else
sl::SensorsData sensordata; sl::SensorsData sensordata;
sl::ERROR_CODE res = zed_->getSensorsData(sensordata, sl::TIME_REFERENCE::CURRENT); sl::ERROR_CODE res = zed_->getSensorsData(sensordata, sl::TIME_REFERENCE::CURRENT);
if(res == sl::ERROR_CODE::SUCCESS && sensordata.imu.is_available) if(res == sl::ERROR_CODE::SUCCESS && isImuValid(sensordata))
{ {
camera_->postInterIMUPublic(zedIMUtoIMU(sensordata, imuLocalTransform_), double(sensordata.imu.timestamp.getNanoseconds())/10e8); camera_->postInterIMUPublic(zedIMUtoIMU(sensordata, imuLocalTransform_), double(sensordata.imu.timestamp.getNanoseconds())/10e8);
} }
@@ -251,6 +269,56 @@ int CameraStereoZed::sdkVersion()
#endif #endif
} }
#ifdef RTABMAP_ZED
static void backwardCompatibility(int & resolution, int & quality)
{
// -1 = AUTO, 0=HD4K 1=QHDPLUS 2=HD2K 3=HD1536 4=HD1080 5=HD1200 6=HD720 7=SVGA 8=VGA 9=XVGA 10=TXVGA
#if ZED_SDK_MAJOR_VERSION < 4
// Zed3 Supported: 2=HD2K 4=HD1080 6=HD720 8=VGA
if(resolution == 0 || resolution == 1) // 0=HD4K 1=QHDPLUS
{
UWARN("Zed SDK v3 doesn't support HD4K and QHDPLUS, setting HD2K.");
resolution = 2; // 2=HD2K
}
if(resolution == 3 || resolution == 5) // 3=HD1536 5=HD1200
{
UWARN("Zed SDK v3 doesn't support HD1536 and HD1200, setting HD1080.");
resolution = 4; // 4=HD1080
}
if(resolution == 7 || resolution >=9) // 7=SVGA 9=XVGA 10=TXVGA
{
UWARN("Zed SDK v3 doesn't support SVGA, XVGA and TXVGA, setting VGA.");
resolution = 8; // 8=VGA
}
if(quality == 4) { // 4=NEURAL_LIGHT 6=NEURAL_PLUS
UWARN("Zed SDK v3 doesn't support NEURAL_LIGHT and NEURAL PLUS, setting NEURAL.");
quality = 5; // NEURAL
}
#else
if(resolution == -1)
{
resolution = int(sl::RESOLUTION::AUTO); // AUTO
}
#if ZED_SDK_MAJOR_VERSION < 5
// Zed4 Supported: 0=HD4K 1=QHDPLUS 2=HD2K 4=HD1080 5=HD1200 6=HD720 7=SVGA 8=VGA
if(resolution == 3) // 3=HD1536
{
UWARN("Zed SDK v4 doesn't support HD1536, setting HD1200.");
resolution_ = 5; // 5=HD1200
}
if(resolution >=9) // 9=XVGA 10=TXVGA
{
UWARN("Zed SDK v4 doesn't support XVGA and TXVGA, setting VGA.");
resolution = 8; // 8=VGA
}
if(quality > 5) { // NEURAL_PLUS
UWARN("Zed SDK v4 doesn't support NEURAL_LIGHT and NEURAL PLUS, setting NEURAL.");
quality = 5; // NEURAL
}
#endif
#endif
}
#endif
CameraStereoZed::CameraStereoZed( CameraStereoZed::CameraStereoZed(
int deviceId, int deviceId,
@@ -285,29 +353,8 @@ CameraStereoZed::CameraStereoZed(
{ {
UDEBUG(""); UDEBUG("");
#ifdef RTABMAP_ZED #ifdef RTABMAP_ZED
#if ZED_SDK_MAJOR_VERSION < 4
if(resolution_ == 1 || resolution_ == 2) // HD2K, HD1080 backwardCompatibility(resolution_, quality_);
{
resolution_ -= 1; // HD2K=0, HD1080=1
}
if(resolution_ == 3) // HD1200
{
resolution_ = 1; // HD1080=1
}
if(resolution_ == 4 || resolution_ == -1)
{
resolution_ = 2; // HD720=2
}
else if(resolution_ == 5 || resolution_ == 6) // SVGA, VGA
{
resolution_ = 3; // VGA=3
}
#else // ZED=4
if(resolution_ == -1)
{
resolution_ = int(sl::RESOLUTION::AUTO); // AUTO
}
#endif
#if ZED_SDK_MAJOR_VERSION < 3 #if ZED_SDK_MAJOR_VERSION < 3
UASSERT(resolution_ >= sl::RESOLUTION_HD2K && resolution_ <sl::RESOLUTION_LAST); UASSERT(resolution_ >= sl::RESOLUTION_HD2K && resolution_ <sl::RESOLUTION_LAST);
@@ -371,6 +418,7 @@ CameraStereoZed::CameraStereoZed(
{ {
UDEBUG(""); UDEBUG("");
#ifdef RTABMAP_ZED #ifdef RTABMAP_ZED
backwardCompatibility(resolution_, quality_);
#if ZED_SDK_MAJOR_VERSION < 3 #if ZED_SDK_MAJOR_VERSION < 3
UASSERT(resolution_ >= sl::RESOLUTION_HD2K && resolution_ <sl::RESOLUTION_LAST); UASSERT(resolution_ >= sl::RESOLUTION_HD2K && resolution_ <sl::RESOLUTION_LAST);
UASSERT(quality_ >= sl::DEPTH_MODE_NONE && quality_ <sl::DEPTH_MODE_LAST); UASSERT(quality_ >= sl::DEPTH_MODE_NONE && quality_ <sl::DEPTH_MODE_LAST);
@@ -406,6 +454,33 @@ CameraStereoZed::~CameraStereoZed()
#endif #endif
} }
std::string CameraStereoZed::getNeuralModelWarning(int quality)
{
(void)quality;
#if defined(RTABMAP_ZED) && ZED_SDK_MAJOR_VERSION >= 5
sl::DEPTH_MODE depthMode = (sl::DEPTH_MODE)quality;
if(depthMode == sl::DEPTH_MODE::NEURAL_LIGHT ||
depthMode == sl::DEPTH_MODE::NEURAL ||
depthMode == sl::DEPTH_MODE::NEURAL_PLUS)
{
sl::AI_MODELS aiModel =
depthMode == sl::DEPTH_MODE::NEURAL_LIGHT ? sl::AI_MODELS::NEURAL_LIGHT_DEPTH :
depthMode == sl::DEPTH_MODE::NEURAL_PLUS ? sl::AI_MODELS::NEURAL_PLUS_DEPTH :
sl::AI_MODELS::NEURAL_DEPTH;
sl::AI_Model_status status = sl::checkAIModelStatus(aiModel);
if(!status.downloaded || !status.optimized)
{
return uFormat("The selected ZED NEURAL depth model is not ready yet (downloaded=%s, "
"optimized=%s): the first start may take significantly longer while the "
"ZED SDK downloads and/or optimizes the model for your GPU. Subsequent "
"starts will be faster.",
status.downloaded?"true":"false", status.optimized?"true":"false");
}
}
#endif
return std::string();
}
bool CameraStereoZed::init(const std::string & calibrationFolder, const std::string & cameraName) bool CameraStereoZed::init(const std::string & calibrationFolder, const std::string & cameraName)
{ {
UDEBUG(""); UDEBUG("");
@@ -463,9 +538,35 @@ bool CameraStereoZed::init(const std::string & calibrationFolder, const std::str
r = zed_->open(param); r = zed_->open(param);
} }
#if ZED_SDK_MAJOR_VERSION >= 3
// CORRUPTED_SDK_INSTALLATION on open() is typically a NEURAL depth mode whose optional
// neural/TensorRT runtime files aren't installed. Give a clear, actionable error.
if(r == sl::ERROR_CODE::CORRUPTED_SDK_INSTALLATION &&
#if ZED_SDK_MAJOR_VERSION >= 5
param.depth_mode >= sl::DEPTH_MODE::NEURAL_LIGHT)
#else
param.depth_mode >= sl::DEPTH_MODE::NEURAL)
#endif
{
UERROR("ZED open() returned \"%s\": the optional NEURAL/TensorRT runtime files are "
"likely missing. Install ZED SDK %d.%d, or select a non-NEURAL depth mode "
"(e.g. PERFORMANCE).", toString(r).c_str(), ZED_SDK_MAJOR_VERSION, ZED_SDK_MINOR_VERSION);
// Do NOT delete zed_ here: after a CORRUPTED_SDK_INSTALLATION open failure (NEURAL depth
// mode selected but the TensorRT/neural runtime isn't installed) the ZED SDK is left in a
// bad state and ~sl::Camera() crashes inside sl_zed64.dll. Leak the object (one-time,
// terminal error path) so we fail gracefully with the message above instead of crashing.
zed_ = 0;
return false;
}
#endif
if(r!=sl::ERROR_CODE::SUCCESS) if(r!=sl::ERROR_CODE::SUCCESS)
{ {
#if ZED_SDK_MAJOR_VERSION >= 4
UERROR("Camera initialization failed: \"%s\": %s", toString(r).c_str(), toVerbose(r).c_str());
#else
UERROR("Camera initialization failed: \"%s\"", toString(r).c_str()); UERROR("Camera initialization failed: \"%s\"", toString(r).c_str());
#endif
delete zed_; delete zed_;
zed_ = 0; zed_ = 0;
return false; return false;
@@ -502,7 +603,11 @@ bool CameraStereoZed::init(const std::string & calibrationFolder, const std::str
#endif #endif
if(r!=sl::ERROR_CODE::SUCCESS) if(r!=sl::ERROR_CODE::SUCCESS)
{ {
#if ZED_SDK_MAJOR_VERSION >= 4
UERROR("Camera tracking initialization failed: \"%s\": %s", toString(r).c_str(), toVerbose(r).c_str());
#else
UERROR("Camera tracking initialization failed: \"%s\"", toString(r).c_str()); UERROR("Camera tracking initialization failed: \"%s\"", toString(r).c_str());
#endif
} }
} }
@@ -731,16 +836,24 @@ SensorData CameraStereoZed::captureImage(SensorCaptureInfo * info)
res = zed_->grab(rparam); res = zed_->grab(rparam);
timestamp = zed_->getTimestamp(sl::TIME_REFERENCE::IMAGE); timestamp = zed_->getTimestamp(sl::TIME_REFERENCE::IMAGE);
// If the sensor supports IMU, wait IMU to be available before sending data. // If the sensor supports IMU, wait for IMU to be available before sending data.
if(imuPublishingThread_ == 0 && !imuLocalTransform_.isNull()) if(imuPublishingThread_ == 0 && !imuLocalTransform_.isNull())
{ {
sl::SensorsData imudatatmp; sl::SensorsData imudatatmp;
res = zed_->getSensorsData(imudatatmp, sl::TIME_REFERENCE::IMAGE); res = zed_->getSensorsData(imudatatmp, sl::TIME_REFERENCE::IMAGE);
imuReceived = res == sl::ERROR_CODE::SUCCESS && imudatatmp.imu.is_available && imudatatmp.imu.timestamp.getNanoseconds() != 0; imuReceived = res == sl::ERROR_CODE::SUCCESS && isImuValid(imudatatmp) && imudatatmp.imu.timestamp.getNanoseconds() != 0;
} }
} }
while(src_ == CameraVideo::kUsbDevice && (res!=sl::ERROR_CODE::SUCCESS || !imuReceived) && timer.elapsed() < 2.0); while(src_ == CameraVideo::kUsbDevice && (res!=sl::ERROR_CODE::SUCCESS || !imuReceived) && timer.elapsed() < 2.0);
// If no valid IMU arrived within the 2 sec startup window, null the IMU transform so
// we don't re-wait 2 sec on every subsequent frame (camera likely has no working IMU).
if(imuPublishingThread_ == 0 && !imuLocalTransform_.isNull() && !imuReceived)
{
UWARN("No valid IMU received within 2 sec; ignoring IMU for the rest of this session.");
imuLocalTransform_.setNull();
}
if(res==sl::ERROR_CODE::SUCCESS) if(res==sl::ERROR_CODE::SUCCESS)
#endif #endif
{ {
@@ -794,7 +907,7 @@ SensorData CameraStereoZed::captureImage(SensorCaptureInfo * info)
#endif #endif
} }
if(imuPublishingThread_ == 0) if(imuPublishingThread_ == 0 && !imuLocalTransform_.isNull())
{ {
#if ZED_SDK_MAJOR_VERSION < 3 #if ZED_SDK_MAJOR_VERSION < 3
sl::IMUData imudata; sl::IMUData imudata;
@@ -803,7 +916,7 @@ SensorData CameraStereoZed::captureImage(SensorCaptureInfo * info)
#else #else
sl::SensorsData imudata; sl::SensorsData imudata;
res = zed_->getSensorsData(imudata, sl::TIME_REFERENCE::IMAGE); res = zed_->getSensorsData(imudata, sl::TIME_REFERENCE::IMAGE);
if(res == sl::ERROR_CODE::SUCCESS && imudata.imu.is_available) if(res == sl::ERROR_CODE::SUCCESS && isImuValid(imudata))
#endif #endif
{ {
//ZED-Mini //ZED-Mini
@@ -876,7 +989,11 @@ SensorData CameraStereoZed::captureImage(SensorCaptureInfo * info)
} }
else if(src_ == CameraVideo::kUsbDevice) else if(src_ == CameraVideo::kUsbDevice)
{ {
#if ZED_SDK_MAJOR_VERSION >= 4
UERROR("CameraStereoZed: Failed to grab images after 2 seconds! (%s: %s)", toString(res).c_str(), toVerbose(res).c_str());
#else
UERROR("CameraStereoZed: Failed to grab images after 2 seconds!"); UERROR("CameraStereoZed: Failed to grab images after 2 seconds!");
#endif
} }
else else
{ {

View File

@@ -34,6 +34,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define VLP_DUAL_MODE 0x39 #define VLP_DUAL_MODE 0x39
#endif #endif
// RTABMAP_VLP16_USE_CUSTOM_SOCKET is defined (Apple only, and toggleable) in
// LidarVLP16.h. When set, network mode uses our own single-threaded UDP receive
// loop; otherwise start()/stop()/isRunning() delegate to pcl::VLPGrabber.
namespace rtabmap { namespace rtabmap {
/** @brief Function used to check that hour assigned to timestamp in conversion is /** @brief Function used to check that hour assigned to timestamp in conversion is
@@ -92,8 +96,17 @@ LidarVLP16::LidarVLP16(
startSweepTimeHost_(0), startSweepTimeHost_(0),
organized_(organized), organized_(organized),
useHostTime_(false), useHostTime_(false),
stampLast_(stampLast) stampLast_(stampLast),
networkMode_(false),
port_(0),
receivedScan_(false)
#ifdef RTABMAP_VLP16_USE_CUSTOM_SOCKET
,socket_(0)
,readThread_(0)
,terminate_(false)
#endif
{ {
// ipAddress_ unused in PCAP mode (default-constructed / unspecified).
UDEBUG("Using PCAP file \"%s\"", pcapFile.c_str()); UDEBUG("Using PCAP file \"%s\"", pcapFile.c_str());
} }
LidarVLP16::LidarVLP16( LidarVLP16::LidarVLP16(
@@ -111,7 +124,19 @@ LidarVLP16::LidarVLP16(
startSweepTimeHost_(0), startSweepTimeHost_(0),
organized_(organized), organized_(organized),
useHostTime_(useHostTime), useHostTime_(useHostTime),
stampLast_(stampLast) stampLast_(stampLast),
// networkMode_ = live network capture (vs PCAP playback), on all platforms;
// it drives the "no data received" warning below. The custom socket path it
// also selects in start()/stop() is Apple-only (RTABMAP_VLP16_USE_CUSTOM_SOCKET).
networkMode_(true),
ipAddress_(ipAddress),
port_(port),
receivedScan_(false)
#ifdef RTABMAP_VLP16_USE_CUSTOM_SOCKET
,socket_(0)
,readThread_(0)
,terminate_(false)
#endif
{ {
UDEBUG("Using network lidar with IP=%s port=%d", ipAddress.to_string().c_str(), port); UDEBUG("Using network lidar with IP=%s port=%d", ipAddress.to_string().c_str(), port);
} }
@@ -129,6 +154,132 @@ void LidarVLP16::setOrganized(bool enable)
organized_ = true; organized_ = true;
} }
#ifdef RTABMAP_VLP16_USE_CUSTOM_SOCKET
void LidarVLP16::start()
{
if(networkMode_)
{
if(readThread_ != 0)
{
// already running
return;
}
terminate_ = false;
// ipAddress_ is the LOCAL interface to listen on (as in pcl::HDLGrabber),
// not the sensor's address. Binding to a specific local IP scopes
// reception to that NIC on a multi-homed host. If it is unspecified
// (0.0.0.0) or not a local address (e.g. the sensor's IP was entered by
// mistake), fall back to listening on all interfaces, like pcl::HDLGrabber.
try
{
boost::asio::ip::udp::endpoint endpoint(
ipAddress_.is_unspecified() ? boost::asio::ip::address(boost::asio::ip::address_v4::any()) : ipAddress_,
port_);
// Unlike pcl::HDLGrabber, we read and process packets in a single
// thread instead of a producer/consumer queue. A large receive buffer
// lets the kernel hold a backlog (~800 VLP16 packets here) so a brief
// stall in toPointClouds() doesn't drop packets.
const int receiveBufferSize = 1024 * 1024; // 1 MB
try
{
socket_ = new boost::asio::ip::udp::socket(ioContext_);
socket_->open(boost::asio::ip::udp::v4());
socket_->set_option(boost::asio::socket_base::reuse_address(true));
socket_->set_option(boost::asio::socket_base::receive_buffer_size(receiveBufferSize));
socket_->bind(endpoint);
}
catch(const std::exception & e)
{
UWARN("Could not bind VLP16 socket to local address %s (%s); "
"falling back to listening on all interfaces (0.0.0.0).",
endpoint.address().to_string().c_str(), e.what());
delete socket_;
socket_ = new boost::asio::ip::udp::socket(ioContext_);
socket_->open(boost::asio::ip::udp::v4());
socket_->set_option(boost::asio::socket_base::reuse_address(true));
socket_->set_option(boost::asio::socket_base::receive_buffer_size(receiveBufferSize));
socket_->bind(boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), port_));
}
}
catch(const std::exception & e)
{
UERROR("Failed to bind to VLP16 UDP port %d: %s", (int)port_, e.what());
delete socket_;
socket_ = 0;
return;
}
receivedScan_ = false;
readThread_ = new std::thread(&LidarVLP16::readPackets, this);
return;
}
// PCAP mode: use the base grabber.
pcl::VLPGrabber::start();
}
void LidarVLP16::stop()
{
if(networkMode_)
{
terminate_ = true;
if(socket_ != 0)
{
// Closing the socket unblocks receive_from in the read thread. Because
// that call uses the non-throwing (error_code) overload, the EBADF that
// BSD/macOS returns here surfaces as an error code instead of an
// exception, so the thread exits cleanly rather than aborting.
boost::system::error_code ec;
socket_->close(ec);
}
if(readThread_ != 0)
{
readThread_->join();
delete readThread_;
readThread_ = 0;
}
delete socket_;
socket_ = 0;
return;
}
pcl::VLPGrabber::stop();
}
bool LidarVLP16::isRunning() const
{
if(networkMode_)
{
return readThread_ != 0;
}
return pcl::VLPGrabber::isRunning();
}
void LidarVLP16::readPackets()
{
std::uint8_t data[1500];
boost::asio::ip::udp::endpoint sender;
while(!terminate_)
{
boost::system::error_code ec;
std::size_t length = socket_->receive_from(boost::asio::buffer(data, sizeof(data)), sender, 0, ec);
if(ec)
{
// On stop() the socket is closed under us (bad_descriptor /
// operation_aborted); only warn if it wasn't an expected shutdown.
if(!terminate_)
{
UWARN("VLP16 socket receive error: %s", ec.message().c_str());
}
break;
}
// VLP-16 data packets are 1206 bytes; ignore anything shorter.
if(length >= 1206)
{
toPointClouds(reinterpret_cast<HDLDataPacket*>(data));
}
}
}
#endif // RTABMAP_VLP16_USE_CUSTOM_SOCKET
bool LidarVLP16::init(const std::string &, const std::string &) bool LidarVLP16::init(const std::string &, const std::string &)
{ {
UDEBUG("Init lidar"); UDEBUG("Init lidar");
@@ -365,8 +516,22 @@ SensorData LidarVLP16::captureData(SensorCaptureInfo * info)
{ {
data = lastScan_; data = lastScan_;
lastScan_ = SensorData(); lastScan_ = SensorData();
receivedScan_ = true;
} }
} }
else if(networkMode_ && !receivedScan_)
{
// No packet has ever arrived: most likely the sensor isn't sending data
// to this computer. Point the user at the configured listening endpoint.
UWARN("Did not receive any VLP16 data packets for the past 5 seconds. "
"This computer is listening on %s:%d (%s). Make sure the LiDAR is "
"powered on and configured to send its data to this computer's IP "
"address on UDP port %d (set the data destination host in the "
"LiDAR's own web/configuration interface).",
ipAddress_.to_string().c_str(), (int)port_,
ipAddress_.is_unspecified() ? "all local interfaces" : "this interface only",
(int)port_);
}
else else
{ {
UWARN("Did not receive any scans for the past 5 seconds."); UWARN("Did not receive any scans for the past 5 seconds.");

View File

@@ -130,6 +130,12 @@ public:
virtual QString getIniFilePath() const; virtual QString getIniFilePath() const;
virtual QString getTmpIniFilePath() const; virtual QString getTmpIniFilePath() const;
// When running under sudo, Qt's QSettings replaces the ini with a
// root-owned file (write-temp + rename), so later non-root runs can no
// longer save preferences. Restore the invoking user's ownership (from
// SUDO_UID/SUDO_GID) on the config file and its directory. No-op on Windows
// and when not running as root. Best-effort (ignores failures).
static void restoreConfigOwnership(const QString & filePath);
void init(const QString & iniFilePath = ""); void init(const QString & iniFilePath = "");
void setCurrentPanelToSource(); void setCurrentPanelToSource();
virtual QString getDefaultWorkingDirectory() const; virtual QString getDefaultWorkingDirectory() const;
@@ -263,6 +269,9 @@ public:
PreferencesDialog::Src getSourceDriver() const; PreferencesDialog::Src getSourceDriver() const;
QString getSourceDriverStr() const; QString getSourceDriverStr() const;
QString getSourceDevice() const; QString getSourceDevice() const;
// Non-empty warning to show before opening the selected source when init() is expected to be
// unusually slow; empty otherwise.
QString getSourceInitWarningMsg() const;
PreferencesDialog::Src getLidarSourceDriver() const; PreferencesDialog::Src getLidarSourceDriver() const;
PreferencesDialog::Src getOdomSourceDriver() const; PreferencesDialog::Src getOdomSourceDriver() const;

View File

@@ -39,6 +39,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QtCore/QTextStream> #include <QtCore/QTextStream>
#include <QtCore/QDateTime> #include <QtCore/QDateTime>
#include <QtCore/QSettings> #include <QtCore/QSettings>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QStandardPaths>
#include <QThread> #include <QThread>
#include <rtabmap/utilite/ULogger.h> #include <rtabmap/utilite/ULogger.h>
#include <rtabmap/utilite/UDirectory.h> #include <rtabmap/utilite/UDirectory.h>
@@ -583,12 +586,33 @@ QString DatabaseViewer::getIniFilePath() const
{ {
return iniFilePath_; return iniFilePath_;
} }
#ifdef WIN32
// Windows: store settings in the standard per-user config location (%LOCALAPPDATA%\rtabmap)
// instead of a Unix-style dotfile in the home root. A fixed "rtabmap" folder (not the app name)
// is used so the main GUI and DatabaseViewer share the same rtabmap.ini. Other platforms keep
// ~/.rtabmap for consistency.
QString privatePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/rtabmap";
#else
QString privatePath = QDir::homePath() + "/.rtabmap"; QString privatePath = QDir::homePath() + "/.rtabmap";
#endif
if(!QDir(privatePath).exists()) if(!QDir(privatePath).exists())
{ {
QDir::home().mkdir(".rtabmap"); QDir().mkpath(privatePath);
} }
return privatePath + "/rtabmap.ini"; QString iniPath = privatePath + "/rtabmap.ini";
#ifdef WIN32
// One-time migration: if there's no ini at the new location but the legacy ~/.rtabmap/rtabmap.ini
// exists, copy it over so users keep their existing settings.
if(!QFile::exists(iniPath))
{
QString legacyIni = QDir::homePath() + "/.rtabmap/rtabmap.ini";
if(QFile::exists(legacyIni))
{
QFile::copy(legacyIni, iniPath);
}
}
#endif
return iniPath;
} }
void DatabaseViewer::readSettings() void DatabaseViewer::readSettings()

View File

@@ -78,6 +78,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QtCore/QFileInfo> #include <QtCore/QFileInfo>
#include <QMessageBox> #include <QMessageBox>
#include <QFileDialog> #include <QFileDialog>
#include <QProgressDialog>
#include <QLabel>
#include <QGraphicsEllipseItem> #include <QGraphicsEllipseItem>
#include <QDockWidget> #include <QDockWidget>
#include <QtCore/QBuffer> #include <QtCore/QBuffer>
@@ -958,7 +960,7 @@ bool MainWindow::handleEvent(UEvent* anEvent)
else else
{ {
Q_EMIT cameraInfoReceived(sensorEvent->info()); Q_EMIT cameraInfoReceived(sensorEvent->info());
if (_odomThread == 0 && (_sensorCapture->odomProvided()) && _preferencesDialog->isRGBDMode()) if (_odomThread == 0 && _sensorCapture && _sensorCapture->odomProvided() && _preferencesDialog->isRGBDMode())
{ {
OdometryInfo odomInfo; OdometryInfo odomInfo;
odomInfo.reg.covariance = sensorEvent->info().odomCovariance; odomInfo.reg.covariance = sensorEvent->info().odomCovariance;
@@ -5528,6 +5530,10 @@ void MainWindow::saveConfigGUI()
_preferencesDialog->saveSettings(); _preferencesDialog->saveSettings();
this->saveFigures(); this->saveFigures();
this->setWindowModified(false); this->setWindowModified(false);
// If running under sudo, the above writes recreate rtabmap.ini as root;
// restore ownership so non-root runs can still save preferences.
PreferencesDialog::restoreConfigOwnership(_preferencesDialog->getIniFilePath());
} }
void MainWindow::newDatabase() void MainWindow::newDatabase()
@@ -5932,6 +5938,31 @@ void MainWindow::startDetection()
Camera * camera = 0; Camera * camera = 0;
Lidar * lidar = 0; Lidar * lidar = 0;
// Creating the sensors below opens the devices (createLidar/createCamera/createOdomSensor ->
// init(), a few seconds for ZED/RealSense) on the GUI thread; show a busy dialog (min==max==0
// => indeterminate) so the window isn't just frozen. Hidden once all sensors are created below.
QString startLabel = tr("Starting sensor...");
QString initWarn = _preferencesDialog->getSourceInitWarningMsg();
if(!initWarn.isEmpty())
{
startLabel += "\n\n" + initWarn;
}
QProgressDialog progress(startLabel, QString(), 0, 0, this);
if(!initWarn.isEmpty())
{
QLabel * wrapLabel = new QLabel(startLabel);
wrapLabel->setWordWrap(true);
progress.setLabel(wrapLabel); // QProgressDialog takes ownership
progress.setMinimumWidth(450);
}
progress.setWindowModality(Qt::ApplicationModal);
progress.setCancelButton(0);
progress.setMinimumDuration(0);
progress.setValue(0);
progress.show();
QApplication::processEvents();
QApplication::processEvents(); // make sure it is drawn
if(_preferencesDialog->getLidarSourceDriver() != PreferencesDialog::kSrcUndef) if(_preferencesDialog->getLidarSourceDriver() != PreferencesDialog::kSrcUndef)
{ {
lidar = _preferencesDialog->createLidar(); lidar = _preferencesDialog->createLidar();
@@ -5989,6 +6020,8 @@ void MainWindow::startDetection()
} }
} }
progress.hide(); // all sensors created/opened
_sensorCapture = new SensorCaptureThread(lidar, camera, odomSensor, extrinsics, poseTimeOffset, scaleFactor, waitTime, parameters); _sensorCapture = new SensorCaptureThread(lidar, camera, odomSensor, extrinsics, poseTimeOffset, scaleFactor, waitTime, parameters);
_sensorCapture->setOdomAsGroundTruth(_preferencesDialog->isOdomSensorAsGt()); _sensorCapture->setOdomAsGroundTruth(_preferencesDialog->isOdomSensorAsGt());
@@ -6229,6 +6262,24 @@ void MainWindow::stopDetection()
} }
ULOGGER_DEBUG(""); ULOGGER_DEBUG("");
// Closing the camera runs on the GUI thread in "delete _sensorCapture" below (via
// ~SensorCaptureThread -> ~Camera::close()) and can block for a while - e.g. the first 2-3
// RealSense closes per launch stall ~20s in the Motion Module stop() (librealsense warm-up).
// Show a busy dialog (min==max==0 => indeterminate) so the window isn't just frozen. It is
// declared here so it stays visible across the joins/deletes and closes on scope exit.
QProgressDialog progress(tr("Stopping sensor..."), QString(), 0, 0, this);
if(_sensorCapture)
{
progress.setWindowModality(Qt::ApplicationModal);
progress.setCancelButton(0);
progress.setMinimumDuration(0);
progress.setValue(0);
progress.show();
QApplication::processEvents();
QApplication::processEvents(); // make sure it is drawn
}
// kill the processes // kill the processes
if(_imuThread) if(_imuThread)
{ {

View File

@@ -39,9 +39,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QtCore/QSettings> #include <QtCore/QSettings>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QStandardPaths>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QUrl> #include <QUrl>
#ifndef _WIN32
#include <unistd.h> // geteuid, chown
#include <sys/types.h>
#include <cstdlib> // atoi, getenv
#include <cerrno>
#include <cstring> // strerror
#endif
#include <QButtonGroup> #include <QButtonGroup>
#include <QFileDialog> #include <QFileDialog>
#include <QInputDialog> #include <QInputDialog>
@@ -49,6 +60,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <QtGui/QStandardItemModel> #include <QtGui/QStandardItemModel>
#include <QMainWindow> #include <QMainWindow>
#include <QProgressDialog> #include <QProgressDialog>
#include <QApplication>
#include <QLabel>
#include <functional>
#include <QScrollBar> #include <QScrollBar>
#include <QStatusBar> #include <QStatusBar>
#include <QFormLayout> #include <QFormLayout>
@@ -437,12 +451,29 @@ PreferencesDialog::PreferencesDialog(QWidget * parent) :
_ui->comboBox_cameraStereo->setItemData(kSrcStereoZed - kSrcStereo, 0, Qt::UserRole - 1); _ui->comboBox_cameraStereo->setItemData(kSrcStereoZed - kSrcStereo, 0, Qt::UserRole - 1);
_ui->comboBox_odom_sensor->setItemData(2, 0, Qt::UserRole - 1); _ui->comboBox_odom_sensor->setItemData(2, 0, Qt::UserRole - 1);
} }
else if(CameraStereoZed::sdkVersion() < 4) else
{ {
_ui->comboBox_stereoZed_resolution->setItemData(1, 0, Qt::UserRole - 1); if(CameraStereoZed::sdkVersion() < 5)
_ui->comboBox_stereoZed_resolution->setItemData(4, 0, Qt::UserRole - 1); {
_ui->comboBox_stereoZed_resolution->setItemData(6, 0, Qt::UserRole - 1); // SDK 4.X
_ui->comboBox_stereoZed_quality->setItemData(3, 0, Qt::UserRole - 1); _ui->comboBox_stereoZed_quality->setItemData(5, 0, Qt::UserRole - 1); // NEURAL_ULTRA
_ui->comboBox_stereoZed_resolution->setItemData(4, 0, Qt::UserRole - 1); //HD1536
_ui->comboBox_stereoZed_resolution->setItemData(10, 0, Qt::UserRole - 1); //XVGA
_ui->comboBox_stereoZed_resolution->setItemData(11, 0, Qt::UserRole - 1); //TXGA
}
if(CameraStereoZed::sdkVersion() < 4)
{
// SDK 3.X
_ui->comboBox_stereoZed_quality->setItemData(3, 0, Qt::UserRole - 1); // NEURAL_LIGHT
_ui->comboBox_stereoZed_resolution->setItemData(1, 0, Qt::UserRole - 1); // HD4K
_ui->comboBox_stereoZed_resolution->setItemData(2, 0, Qt::UserRole - 1); // QHDPLUS
_ui->comboBox_stereoZed_resolution->setItemData(6, 0, Qt::UserRole - 1); // HD1200
_ui->comboBox_stereoZed_resolution->setItemData(8, 0, Qt::UserRole - 1); // SVGA
}
if(CameraStereoZed::sdkVersion() < 3)
{
_ui->comboBox_stereoZed_quality->setItemData(4, 0, Qt::UserRole - 1); // NEURAL
}
} }
if (!CameraStereoTara::available()) if (!CameraStereoTara::available())
{ {
@@ -985,7 +1016,7 @@ PreferencesDialog::PreferencesDialog(QWidget * parent) :
connect(_ui->lineEdit_vlp16_pcap_path, SIGNAL(textChanged(const QString &)), this, SLOT(makeObsoleteSourcePanel())); connect(_ui->lineEdit_vlp16_pcap_path, SIGNAL(textChanged(const QString &)), this, SLOT(makeObsoleteSourcePanel()));
connect(_ui->spinBox_vlp16_ip1, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); connect(_ui->spinBox_vlp16_ip1, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
connect(_ui->spinBox_vlp16_ip2, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); connect(_ui->spinBox_vlp16_ip2, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
connect(_ui->spinBox_vlp16_ip2, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); connect(_ui->spinBox_vlp16_ip3, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
connect(_ui->spinBox_vlp16_ip4, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); connect(_ui->spinBox_vlp16_ip4, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
connect(_ui->spinBox_vlp16_port, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel())); connect(_ui->spinBox_vlp16_port, SIGNAL(valueChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
connect(_ui->checkBox_vlp16_organized, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel())); connect(_ui->checkBox_vlp16_organized, SIGNAL(stateChanged(int)), this, SLOT(makeObsoleteSourcePanel()));
@@ -2440,10 +2471,10 @@ void PreferencesDialog::resetSettings(QGroupBox * groupBox)
_ui->lineEdit_lidar_local_transform->setText("0 0 0 0 0 0"); _ui->lineEdit_lidar_local_transform->setText("0 0 0 0 0 0");
_ui->lineEdit_vlp16_pcap_path->clear(); _ui->lineEdit_vlp16_pcap_path->clear();
_ui->spinBox_vlp16_ip1->setValue(192); _ui->spinBox_vlp16_ip1->setValue(0);
_ui->spinBox_vlp16_ip2->setValue(168); _ui->spinBox_vlp16_ip2->setValue(0);
_ui->spinBox_vlp16_ip3->setValue(1); _ui->spinBox_vlp16_ip3->setValue(0);
_ui->spinBox_vlp16_ip4->setValue(201); _ui->spinBox_vlp16_ip4->setValue(0);
_ui->spinBox_vlp16_port->setValue(2368); _ui->spinBox_vlp16_port->setValue(2368);
_ui->checkBox_vlp16_organized->setChecked(false); _ui->checkBox_vlp16_organized->setChecked(false);
_ui->checkBox_vlp16_hostTime->setChecked(true); _ui->checkBox_vlp16_hostTime->setChecked(true);
@@ -2559,12 +2590,33 @@ QString PreferencesDialog::getWorkingDirectory() const
QString PreferencesDialog::getIniFilePath() const QString PreferencesDialog::getIniFilePath() const
{ {
#ifdef WIN32
// Windows: store settings in the standard per-user config location (%LOCALAPPDATA%\rtabmap)
// instead of a Unix-style dotfile in the home root. A fixed "rtabmap" folder (not the app name)
// is used so the main GUI and DatabaseViewer share the same rtabmap.ini. Other platforms keep
// ~/.rtabmap for consistency.
QString privatePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/rtabmap";
#else
QString privatePath = QDir::homePath() + "/.rtabmap"; QString privatePath = QDir::homePath() + "/.rtabmap";
#endif
if(!QDir(privatePath).exists()) if(!QDir(privatePath).exists())
{ {
QDir::home().mkdir(".rtabmap"); QDir().mkpath(privatePath);
} }
return privatePath + "/rtabmap.ini"; QString iniPath = privatePath + "/rtabmap.ini";
#ifdef WIN32
// One-time migration: if there's no ini at the new location but the legacy ~/.rtabmap/rtabmap.ini
// exists, copy it over so users keep their existing settings.
if(!QFile::exists(iniPath))
{
QString legacyIni = QDir::homePath() + "/.rtabmap/rtabmap.ini";
if(QFile::exists(legacyIni))
{
QFile::copy(legacyIni, iniPath);
}
}
#endif
return iniPath;
} }
QString PreferencesDialog::getTmpIniFilePath() const QString PreferencesDialog::getTmpIniFilePath() const
@@ -2572,6 +2624,41 @@ QString PreferencesDialog::getTmpIniFilePath() const
return getIniFilePath()+".tmp"; return getIniFilePath()+".tmp";
} }
void PreferencesDialog::restoreConfigOwnership(const QString & filePath)
{
#ifndef _WIN32
// Only relevant when the process is effectively root (e.g. launched with
// sudo). getenv("SUDO_UID"/"SUDO_GID") give the invoking user.
if(geteuid() == 0)
{
const char * sudoUid = getenv("SUDO_UID");
const char * sudoGid = getenv("SUDO_GID");
if(sudoUid && sudoGid)
{
uid_t uid = (uid_t)atoi(sudoUid);
gid_t gid = (gid_t)atoi(sudoGid);
// Restore the config file and its containing directory so the user
// can still write preferences without sudo afterwards.
if(!filePath.isEmpty() && QFile::exists(filePath))
{
if(chown(filePath.toStdString().c_str(), uid, gid) != 0)
{
UWARN("Could not restore ownership of \"%s\" to uid=%d (%s).",
filePath.toStdString().c_str(), (int)uid, strerror(errno));
}
}
QString dir = QFileInfo(filePath).absolutePath();
if(!dir.isEmpty())
{
chown(dir.toStdString().c_str(), uid, gid);
}
}
}
#else
Q_UNUSED(filePath);
#endif
}
void PreferencesDialog::loadConfigFrom() void PreferencesDialog::loadConfigFrom()
{ {
QString path = QFileDialog::getOpenFileName(this, tr("Load settings..."), this->getWorkingDirectory(), "*.ini"); QString path = QFileDialog::getOpenFileName(this, tr("Load settings..."), this->getWorkingDirectory(), "*.ini");
@@ -3242,6 +3329,9 @@ void PreferencesDialog::writeSettings(const QString & filePath)
uInsert(_parameters, _modifiedParameters); // update cached parameters uInsert(_parameters, _modifiedParameters); // update cached parameters
_modifiedParameters.clear(); _modifiedParameters.clear();
_obsoletePanels = kPanelDummy; _obsoletePanels = kPanelDummy;
// Keep the ini writable by the invoking user even if we are running as root.
restoreConfigOwnership(filePath.isEmpty() ? getIniFilePath() : filePath);
} }
void PreferencesDialog::writeGuiSettings(const QString & filePath) const void PreferencesDialog::writeGuiSettings(const QString & filePath) const
@@ -6635,6 +6725,16 @@ QString PreferencesDialog::getSourceDriverStr() const
return ""; return "";
} }
QString PreferencesDialog::getSourceInitWarningMsg() const
{
if(getSourceDriver() == kSrcStereoZed)
{
return QString::fromStdString(
CameraStereoZed::getNeuralModelWarning(_ui->comboBox_stereoZed_quality->currentIndex()));
}
return QString();
}
QString PreferencesDialog::getSourceDevice() const QString PreferencesDialog::getSourceDevice() const
{ {
return _ui->lineEdit_sourceDevice->text(); return _ui->lineEdit_sourceDevice->text();
@@ -6820,11 +6920,11 @@ double PreferencesDialog::getSourceScanForceGroundNormalsUp() const
Camera * PreferencesDialog::createCamera(bool useRawImages, bool useColor) Camera * PreferencesDialog::createCamera(bool useRawImages, bool useColor)
{ {
return createCamera( return createCamera(
this->getSourceDriver(), this->getSourceDriver(),
_ui->lineEdit_sourceDevice->text(), _ui->lineEdit_sourceDevice->text(),
_ui->lineEdit_calibrationFile->text(), _ui->lineEdit_calibrationFile->text(),
useRawImages, useRawImages,
useColor, useColor,
false, false,
false); false);
} }
@@ -7728,7 +7828,30 @@ void PreferencesDialog::setSLAMMode(bool enabled)
void PreferencesDialog::testOdometry() void PreferencesDialog::testOdometry()
{ {
QString startLabel = tr("Starting camera...");
QString initWarn = getSourceInitWarningMsg();
if(!initWarn.isEmpty())
{
startLabel += "\n\n" + initWarn;
}
QProgressDialog progress(startLabel, QString(), 0, 0, this);
if(!initWarn.isEmpty())
{
QLabel * wrapLabel = new QLabel(startLabel);
wrapLabel->setWordWrap(true);
progress.setLabel(wrapLabel); // QProgressDialog takes ownership
progress.setMinimumWidth(450);
}
progress.setWindowModality(Qt::ApplicationModal);
progress.setCancelButton(0);
progress.setMinimumDuration(0);
progress.setValue(0);
progress.show();
QApplication::processEvents();
QApplication::processEvents(); // make sure it is drawn
Camera * camera = this->createCamera(); Camera * camera = this->createCamera();
progress.hide();
if(!camera) if(!camera)
{ {
return; return;
@@ -7776,12 +7899,13 @@ void PreferencesDialog::testOdometry()
_ui->odom_dataBufferSize->value()); _ui->odom_dataBufferSize->value());
odomThread.registerToEventsManager(); odomThread.registerToEventsManager();
// parent = 0 (not 'this'): see testCamera() - avoids the nested-modality crash.
OdometryViewer * odomViewer = new OdometryViewer(10, OdometryViewer * odomViewer = new OdometryViewer(10,
_ui->spinBox_decimation_odom->value(), _ui->spinBox_decimation_odom->value(),
0.0f, 0.0f,
_ui->doubleSpinBox_maxDepth_odom->value(), _ui->doubleSpinBox_maxDepth_odom->value(),
this->getOdomQualityWarnThr(), this->getOdomQualityWarnThr(),
this, 0,
this->getAllParameters()); this->getAllParameters());
odomViewer->setWindowTitle(tr("Odometry viewer")); odomViewer->setWindowTitle(tr("Odometry viewer"));
odomViewer->resize(1280, 480+QPushButton().minimumHeight()); odomViewer->resize(1280, 480+QPushButton().minimumHeight());
@@ -7843,25 +7967,79 @@ void PreferencesDialog::testOdometry()
} }
odomViewer->exec(); odomViewer->exec();
delete odomViewer; UDEBUG("Dialog closed, stopping sensor...");
// Tear down the pipes first so no more events are routed to the threads/viewer being
// destroyed, then stop the threads, then delete the viewer. This avoids delivering
// events to a handler that is being torn down.
UEventsManager::removePipe(&cameraThread, &odomThread, "SensorEvent");
if(imuThread)
{
UEventsManager::removePipe(imuThread, &odomThread, "IMUEvent");
}
UEventsManager::removePipe(&odomThread, odomViewer, "OdometryEvent");
UEventsManager::removePipe(odomViewer, &odomThread, "OdometryResetEvent");
if(imuThread) if(imuThread)
{ {
imuThread->join(true); imuThread->join(true);
delete imuThread;
} }
// Reuse the same dialog for the close. The device close() runs in cameraThread's destructor
// at function scope end (not in join()), so 'progress' stays visible across it. On Windows
// the first 2-3 RealSense closes per launch stall ~20s in the Motion Module stop().
progress.setLabelText(tr("Closing camera..."));
progress.show();
QApplication::processEvents();
QApplication::processEvents(); // make sure it is drawn
cameraThread.join(true); cameraThread.join(true);
odomThread.join(true); odomThread.join(true);
// deleteLater() (not delete): see testCamera() - avoids a dangling OpenGL platform
// window that crashes in QWindowsWindow::alertWindow when Preferences later closes.
odomViewer->deleteLater();
if(imuThread)
{
delete imuThread;
}
} }
void PreferencesDialog::testCamera() void PreferencesDialog::testCamera()
{ {
CameraViewer * window = new CameraViewer(this, this->getAllParameters()); // Not parented to 'this': the Preferences dialog is itself application-modal, and making
// the viewer a modal *child* of it (nested modality) with an OpenGL/VTK native window
// crashes Qt (QWindowsWindow::alertWindow, this==nullptr) when Preferences later closes.
// exec() below still makes the viewer application-modal, so interaction stays blocked.
CameraViewer * window = new CameraViewer(nullptr, this->getAllParameters());
window->setWindowTitle(tr("Camera viewer")); window->setWindowTitle(tr("Camera viewer"));
window->resize(1280, 480+QPushButton().minimumHeight()); window->resize(1280, 480+QPushButton().minimumHeight());
window->registerToEventsManager(); window->registerToEventsManager();
QString startLabel = tr("Starting camera...");
QString initWarn = getSourceInitWarningMsg();
if(!initWarn.isEmpty())
{
startLabel += "\n\n" + initWarn;
}
QProgressDialog progress(startLabel, QString(), 0, 0, this);
if(!initWarn.isEmpty())
{
QLabel * wrapLabel = new QLabel(startLabel);
wrapLabel->setWordWrap(true);
progress.setLabel(wrapLabel); // QProgressDialog takes ownership
progress.setMinimumWidth(450);
}
progress.setWindowModality(Qt::ApplicationModal);
progress.setCancelButton(0);
progress.setMinimumDuration(0);
progress.setValue(0);
progress.show();
QApplication::processEvents();
QApplication::processEvents(); // make sure it is drawn
// createCamera() init()s the device on the GUI thread (required by ZED) and takes a few seconds.
Camera * camera = this->createCamera(); Camera * camera = this->createCamera();
progress.hide();
if(camera) if(camera)
{ {
SensorCaptureThread cameraThread(camera, this->getAllParameters()); SensorCaptureThread cameraThread(camera, this->getAllParameters());
@@ -7906,12 +8084,26 @@ void PreferencesDialog::testCamera()
cameraThread.start(); cameraThread.start();
window->exec(); window->exec();
delete window; UDEBUG("Dialog closed, stopping sensor...");
cameraThread.join(true); UEventsManager::removePipe(&cameraThread, window, "SensorEvent");
// Reuse the same dialog for the close. The device close() runs in cameraThread's
// destructor at scope end (not in join()), so 'progress' - declared in the outer scope -
// stays visible across it. On Windows the first 2-3 RealSense closes per launch stall
// ~20s in the Motion Module stop() (librealsense warm-up); this keeps the user informed.
progress.setLabelText(tr("Closing camera..."));
progress.show();
QApplication::processEvents();
QApplication::processEvents(); // make sure it is drawn
cameraThread.join(true); // cameraThread's destructor (scope end) closes the device
// deleteLater() (not delete): defer destruction to the event loop so Qt finishes
// tearing down the OpenGL widget's context and window-proc subclass and drains
// pending activation messages first.
window->deleteLater();
} }
else else
{ {
delete window; window->deleteLater();
} }
} }
@@ -8377,14 +8569,25 @@ void PreferencesDialog::calibrateOdomSensorExtrinsics()
void PreferencesDialog::testLidar() void PreferencesDialog::testLidar()
{ {
CameraViewer * window = new CameraViewer(this, this->getAllParameters()); // Not parented to 'this': see testCamera() - avoids the nested-modality crash.
CameraViewer * window = new CameraViewer(nullptr, this->getAllParameters());
window->setWindowTitle(tr("Lidar viewer")); window->setWindowTitle(tr("Lidar viewer"));
window->setWindowFlags(Qt::Window); window->setWindowFlags(Qt::Window);
window->resize(1280, 480+QPushButton().minimumHeight()); window->resize(1280, 480+QPushButton().minimumHeight());
window->registerToEventsManager(); window->registerToEventsManager();
window->setDecimation(1); window->setDecimation(1);
QProgressDialog progress(tr("Starting lidar..."), QString(), 0, 0, this);
progress.setWindowModality(Qt::ApplicationModal);
progress.setCancelButton(0);
progress.setMinimumDuration(0);
progress.setValue(0);
progress.show();
QApplication::processEvents();
QApplication::processEvents(); // make sure it is drawn
Lidar * lidar = this->createLidar(); Lidar * lidar = this->createLidar();
progress.hide();
if(lidar) if(lidar)
{ {
SensorCaptureThread lidarThread(lidar, this->getAllParameters()); SensorCaptureThread lidarThread(lidar, this->getAllParameters());
@@ -8403,12 +8606,24 @@ void PreferencesDialog::testLidar()
lidarThread.start(); lidarThread.start();
window->exec(); window->exec();
delete window; UDEBUG("Dialog closed, stopping sensor...");
lidarThread.join(true); UEventsManager::removePipe(&lidarThread, window, "SensorEvent");
// Reuse the same dialog for the close. The device close() runs in lidarThread's
// destructor at scope end (not in join()), so 'progress' - declared in the outer
// scope - stays visible across it.
progress.setLabelText(tr("Closing sensor..."));
progress.show();
QApplication::processEvents();
QApplication::processEvents(); // make sure it is drawn
lidarThread.join(true); // lidarThread's destructor (scope end) closes the device
// deleteLater() (not delete): see testCamera() - avoids a dangling OpenGL platform
// window that crashes in QWindowsWindow::alertWindow when Preferences later closes.
window->deleteLater();
} }
else else
{ {
delete window; window->deleteLater();
} }
} }

View File

@@ -5740,11 +5740,21 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
<string>ULTRA</string> <string>ULTRA</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>NEURAL LIGHT</string>
</property>
</item>
<item> <item>
<property name="text"> <property name="text">
<string>NEURAL</string> <string>NEURAL</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>NEURAL PLUS</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="5" column="1">
@@ -5903,11 +5913,21 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
<string>HD4K</string> <string>HD4K</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>QHDPLUS</string>
</property>
</item>
<item> <item>
<property name="text"> <property name="text">
<string>HD2K</string> <string>HD2K</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>HD1536</string>
</property>
</item>
<item> <item>
<property name="text"> <property name="text">
<string>HD1080</string> <string>HD1080</string>
@@ -5933,6 +5953,16 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
<string>VGA</string> <string>VGA</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>XVGA</string>
</property>
</item>
<item>
<property name="text">
<string>TXVGA</string>
</property>
</item>
</widget> </widget>
</item> </item>
</layout> </layout>
@@ -9387,9 +9417,6 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
<layout class="QHBoxLayout" name="horizontalLayout_18"> <layout class="QHBoxLayout" name="horizontalLayout_18">
<item> <item>
<widget class="QSpinBox" name="spinBox_vlp16_ip1"> <widget class="QSpinBox" name="spinBox_vlp16_ip1">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;KITTI: 130 000 points&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::NoButtons</enum>
</property> </property>
@@ -9400,15 +9427,12 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
<number>255</number> <number>255</number>
</property> </property>
<property name="value"> <property name="value">
<number>192</number> <number>0</number>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="spinBox_vlp16_ip2"> <widget class="QSpinBox" name="spinBox_vlp16_ip2">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;KITTI: 130 000 points&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::NoButtons</enum>
</property> </property>
@@ -9419,15 +9443,12 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
<number>255</number> <number>255</number>
</property> </property>
<property name="value"> <property name="value">
<number>168</number> <number>0</number>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="spinBox_vlp16_ip3"> <widget class="QSpinBox" name="spinBox_vlp16_ip3">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;KITTI: 130 000 points&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::NoButtons</enum>
</property> </property>
@@ -9438,15 +9459,12 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
<number>250</number> <number>250</number>
</property> </property>
<property name="value"> <property name="value">
<number>1</number> <number>0</number>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QSpinBox" name="spinBox_vlp16_ip4"> <widget class="QSpinBox" name="spinBox_vlp16_ip4">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;KITTI: 130 000 points&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::NoButtons</enum>
</property> </property>
@@ -9457,7 +9475,7 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
<number>250</number> <number>250</number>
</property> </property>
<property name="value"> <property name="value">
<number>201</number> <number>0</number>
</property> </property>
</widget> </widget>
</item> </item>
@@ -9498,8 +9516,11 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QLabel" name="label_743"> <widget class="QLabel" name="label_743">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Local network interface (IP address of &lt;span style=&quot; font-weight:600;&quot;&gt;this&lt;/span&gt; computer) on which to listen for VLP16 packets. Use 0.0.0.0 to listen on all interfaces (recommended). This is NOT the sensor's IP address; the sensor decides where to send its data (configure its destination host in the sensor's own web interface).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>IP Address.</string> <string>Local IP to listen on (0.0.0.0 = all interfaces). See tooltip.</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@@ -9554,8 +9575,11 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
</item> </item>
<item row="1" column="2"> <item row="1" column="2">
<widget class="QLabel" name="label_744"> <widget class="QLabel" name="label_744">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Local UDP port on this computer to listen for VLP16 data packets (default 2368). The LiDAR must be configured to send its data to this port.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Port.</string> <string>Port to listen on (default 2368).</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@@ -9567,9 +9591,6 @@ when using the file type, logs are saved in LogRtabmap.txt (located in the worki
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="spinBox_vlp16_port"> <widget class="QSpinBox" name="spinBox_vlp16_port">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;KITTI: 130 000 points&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="buttonSymbols"> <property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum> <enum>QAbstractSpinBox::NoButtons</enum>
</property> </property>

View File

@@ -97,7 +97,7 @@ void sighandler(int sig)
int main(int argc, char * argv[]) int main(int argc, char * argv[])
{ {
ULogger::setType(ULogger::kTypeConsole); ULogger::setType(ULogger::kTypeConsole);
ULogger::setLevel(ULogger::kInfo); ULogger::setLevel(ULogger::kDebug);
//ULogger::setPrintTime(false); //ULogger::setPrintTime(false);
//ULogger::setPrintWhere(false); //ULogger::setPrintWhere(false);
@@ -272,7 +272,7 @@ int main(int argc, char * argv[])
UERROR("Not built with ZED sdk support..."); UERROR("Not built with ZED sdk support...");
exit(-1); exit(-1);
} }
camera = new rtabmap::CameraStereoZed(deviceId.empty()?0:uStr2Int(deviceId), -1, 1, 100, false, rate); camera = new rtabmap::CameraStereoZed(deviceId.empty()?0:uStr2Int(deviceId), -1, 1, 0, 100, false, rate);
} }
else if (driver == 9) else if (driver == 9)
{ {
@@ -523,5 +523,6 @@ int main(int argc, char * argv[])
cameraViewer.exec(); cameraViewer.exec();
cameraThread.join(true); cameraThread.join(true);
} }
printf("Exiting cleanly.\n");
return 0; return 0;
} }

View File

@@ -53,6 +53,12 @@ int main(int argc, char * argv[])
QSurfaceFormat::setDefaultFormat(QVTKRenderWidget::defaultFormat()); QSurfaceFormat::setDefaultFormat(QVTKRenderWidget::defaultFormat());
#endif #endif
// Recommended by VTK when using QVTKOpenGLNativeWidget (a QOpenGLWidget): let all VTK
// render widgets share a single OpenGL context, which is needed for correct rendering
// when render widgets live in / move across multiple top-level windows. Must be set
// before QApplication is constructed.
QApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
#ifdef RTABMAP_PYTHON #ifdef RTABMAP_PYTHON
rtabmap::PythonInterface pythonInterface; rtabmap::PythonInterface pythonInterface;
#endif #endif

View File

@@ -58,7 +58,7 @@ void showUsage()
{ {
printf("\nUsage:\n" printf("\nUsage:\n"
"rtabmap-lidar_viewer IP PORT driver\n" "rtabmap-lidar_viewer IP PORT driver\n"
" driver Driver number to use: 0=VLP16 (default IP and port are 192.168.1.201 2368)\n"); " driver Driver number to use: 0=VLP16 (default IP and port are 0.0.0.0 2368)\n");
exit(1); exit(1);
} }

View File

@@ -404,13 +404,16 @@ int main (int argc, char * argv[])
{ {
printf("The camera is not calibrated! You should calibrate the camera first.\n"); printf("The camera is not calibrated! You should calibrate the camera first.\n");
delete camera; delete camera;
return 1;
} }
} }
else else
{ {
printf("Failed to initialize the camera! Please select another driver (see \"--help\").\n"); printf("Failed to initialize the camera! Please select another driver (see \"--help\").\n");
delete camera; delete camera;
return 1;
} }
printf("Exiting cleanly.\n");
return 0; return 0;
} }

View File

@@ -139,10 +139,13 @@ protected:
fileToClear.close(); fileToClear.close();
} }
// Open in binary mode: the log formatter already emits "\r\n" line endings explicitly.
// On Windows, text mode ("a") would translate the "\n" of that "\r\n" into "\r\n" again,
// producing "\r\r\n" on disk - i.e. an extra blank line between entries.
#ifdef _MSC_VER #ifdef _MSC_VER
fopen_s(&fout_, fileName_.c_str(), "a"); fopen_s(&fout_, fileName_.c_str(), "ab");
#else #else
fout_ = fopen(fileName_.c_str(), "a"); fout_ = fopen(fileName_.c_str(), "ab");
#endif #endif
if(!fout_) { if(!fout_) {