diff --git a/.github/workflows/compile-check.yml b/.github/workflows/compile-check.yml index 7e2ee7a..c6ddbce 100644 --- a/.github/workflows/compile-check.yml +++ b/.github/workflows/compile-check.yml @@ -18,7 +18,7 @@ jobs: compile: name: Compile runs-on: ubuntu-24.04 - timeout-minutes: 120 + timeout-minutes: 180 steps: - name: Check out ScopeOne @@ -39,15 +39,20 @@ jobs: autoconf-archive \ pkg-config \ libboost-all-dev \ - libopencv-dev \ - libtiff-dev \ - zlib1g-dev - - - name: Install Qt - uses: jurplel/install-qt-action@v4 - with: - version: '6.11.0' - cache: true + ninja-build curl zip unzip \ + libgl-dev libegl-dev libopengl-dev libx11-dev libx11-xcb-dev libxrender-dev \ + libxcb1-dev libxcb-cursor-dev libxcb-icccm4-dev libxcb-util-dev libxcb-image0-dev \ + libxcb-keysyms1-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-shape0-dev \ + libxcb-shm0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xkb-dev libxcb-xinput-dev \ + libxcb-glx0-dev libxkbcommon-dev libxkbcommon-x11-dev + + - name: Set up vcpkg + shell: bash + run: | + git clone https://github.com/microsoft/vcpkg.git "$RUNNER_TEMP/scopeone-vcpkg" + git -C "$RUNNER_TEMP/scopeone-vcpkg" checkout "$(jq -r '."builtin-baseline"' vcpkg.json)" + "$RUNNER_TEMP/scopeone-vcpkg/bootstrap-vcpkg.sh" -disableMetrics + echo "VCPKG_ROOT=$RUNNER_TEMP/scopeone-vcpkg" >> "$GITHUB_ENV" - name: Compile mmCoreAndDevices and ScopeOne shell: bash diff --git a/.gitignore b/.gitignore index c66dec1..98996c4 100644 --- a/.gitignore +++ b/.gitignore @@ -33,9 +33,11 @@ !/CMakeLists.txt !/README.md !/VERSION +!/vcpkg.json !/.gitignore /build/ +/vcpkg_installed/ /ref/ **/.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt index 611f16b..b76ed20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,9 +28,6 @@ set(ScopeOneCore_ROOT "${CMAKE_SOURCE_DIR}/ScopeOneCore/install" CACHE PATH "ScopeOneCore install prefix" ) -if (WIN32) - list(PREPEND CMAKE_PREFIX_PATH "C:/Qt/6.11.0/msvc2022_64") -endif () list(PREPEND CMAKE_PREFIX_PATH "${ScopeOneCore_ROOT}") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/include") diff --git a/README.md b/README.md index 0b6a665..e18ba0b 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,7 @@ There is an example dual-camera .cfg file in the config folder, just change the - [CMake](https://cmake.org/download/) 4.1.0 - [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) (MSVC v143 toolset) -- [Qt](https://www.qt.io/development/download-qt-installer-oss) 6.9.1 (msvc2022_64) -- OpenCV 4.12.0 +- [vcpkg](https://github.com/microsoft/vcpkg) for Qt 6.11.2, OpenCV 4.14.0, and ScopeWriter's libtiff, zlib, zstd and crc32c - mmCoreAndDevices Clone ScopeOne and initialize all submodules with: @@ -79,11 +78,10 @@ ScopeOne/ include/scopeone/ Core API and external plugin contracts external/ mmCoreAndDevices/ - opencv-4.12.0/ ScopeWriter/ ``` -ScopeWriter contains its filesystem Zarr V3 writer and carries libtiff, zlib, zstd and crc32c under its own `third_party` directory. It builds these dependencies from source without downloading packages during CMake configuration. +ScopeWriter contains its filesystem Zarr V3 writer. It uses libtiff, zlib, zstd and crc32c from vcpkg; ScopeOne lists them in its root `vcpkg.json`, and ScopeWriter declares the same dependencies in its own `vcpkg.json` for standalone builds. ### Plugin layout @@ -102,22 +100,37 @@ The Image Processing panel can process all live cameras or one selected camera. **Windows Build Steps:** -1. Build and install `ScopeOneCore`: +Set up vcpkg once outside the ScopeOne repository: ```powershell -cmake -S ScopeOneCore -B ScopeOneCore/build -cmake --build ScopeOneCore/build --config Release --parallel -cmake --install ScopeOneCore/build --config Release +git clone https://github.com/microsoft/vcpkg.git C:/dev/vcpkg +C:/dev/vcpkg/bootstrap-vcpkg.bat +$env:VCPKG_ROOT = "C:/dev/vcpkg" ``` -2. Build the GUI application: +For an existing vcpkg checkout, run `git -C $env:VCPKG_ROOT pull --ff-only` and rerun its `bootstrap-vcpkg.bat` before migrating. + +Build Core, plugins, and GUI with the repository script: + +```powershell +.\scripts\build.ps1 --configure +``` + +When migrating an existing build, use `--clean --configure` once to replace the old CMake caches. Later builds only need `.\scripts\build.ps1`. + +The root `vcpkg.json` pins Qt and OpenCV and provides ScopeWriter's libtiff, zlib, zstd and crc32c. Core, GUI, and plugins share `vcpkg_installed/`; the build scripts select `x64-windows`, `x64-linux-dynamic`, `arm64-osx-dynamic`, or another supported host triplet. Linux and macOS use the dynamic triplets so Qt is shared between the GUI, ScopeOneCore, and plugins. CMake installs dependencies on the first configure, and the Windows GUI uses vcpkg's `windeployqt` to deploy Qt plugins. Micro-Manager, CUDA, and ONNX Runtime retain their existing dependency setup. + +For a direct Core CMake configure: ```powershell -cmake -S . -B build -cmake --build build --config Release --parallel +cmake -S ScopeOneCore -B ScopeOneCore/build ` + "-DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ` + "-DVCPKG_MANIFEST_DIR=$PWD" ` + "-DVCPKG_INSTALLED_DIR=$PWD/vcpkg_installed" ` + -DVCPKG_TARGET_TRIPLET=x64-windows ``` -3. Run: +Run the built application: ```powershell .\build\Release\ScopeOne.exe @@ -125,106 +138,54 @@ cmake --build build --config Release --parallel **Linux and macOS Build Steps (experimental):** -Linux and macOS use the same native build flow. Use the top-level `micro-manager` repository to configure the native build, then compile `MMDevice`, `MMCore`, and the required device adapters. Create a symlink so ScopeOne can find the `mmCoreAndDevices` tree at the path expected by the current CMake files. - -Run `./scripts/build-unix.sh` from the ScopeOne repository root on Linux or macOS to automate the complete sequence below. +Linux and macOS use vcpkg for Qt, OpenCV, and ScopeWriter's dependencies. Micro-Manager is built from its own checkout because its native core and device adapters are not vcpkg dependencies. -1. Install common build dependencies. +1. Install the system build tools. Linux: ```bash sudo apt install \ git subversion build-essential cmake autoconf automake libtool autoconf-archive \ - pkg-config libboost-all-dev qt6-base-dev libopencv-dev libtiff-dev zlib1g-dev + pkg-config ninja-build curl zip unzip libboost-all-dev +sudo apt install --no-upgrade \ + libgl-dev libegl-dev libopengl-dev libx11-dev libx11-xcb-dev libxrender-dev \ + libxcb1-dev libxcb-cursor-dev libxcb-icccm4-dev libxcb-util-dev libxcb-image0-dev \ + libxcb-keysyms1-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-shape0-dev \ + libxcb-shm0-dev libxcb-sync-dev libxcb-xfixes0-dev libxcb-xkb-dev libxcb-xinput-dev \ + libxcb-glx0-dev libxkbcommon-dev libxkbcommon-x11-dev ``` +The second command installs the X11 and OpenGL development files that vcpkg's `qtbase` port expects from the system; it is not a system Qt installation. It uses the glvnd `libgl-dev`/`libegl-dev` packages instead of `libgl1-mesa-dev`/`libegl1-mesa-dev`, so `--no-upgrade` can install it without upgrading the installed Mesa drivers. + macOS: ```bash xcode-select --install brew install \ git subversion cmake autoconf automake libtool autoconf-archive \ - pkg-config boost qt opencv libtiff zlib -``` - -2. Clone Micro-Manager and create the `mmCoreAndDevices` symlink: - -```bash -cd /path/to/ScopeOne/ScopeOneCore -mkdir -p external -cd external - -git clone --recurse-submodules https://github.com/micro-manager/micro-manager.git micro-manager -ln -s micro-manager/mmCoreAndDevices mmCoreAndDevices - -cd micro-manager -git submodule update --init --recursive + pkg-config ninja boost ``` -3. Configure Micro-Manager without the Java application layer: +2. Install and bootstrap vcpkg: ```bash -./autogen.sh -./configure --without-java --enable-static +git clone https://github.com/microsoft/vcpkg.git "$HOME/vcpkg" +"$HOME/vcpkg/bootstrap-vcpkg.sh" +export VCPKG_ROOT="$HOME/vcpkg" ``` -4. Build the native core components: +3. Build from the ScopeOne repository root: ```bash -# Adjust `-j4` to match your CPU cores -make -C mmCoreAndDevices/MMDevice -j4 -make -C mmCoreAndDevices/MMCore -j4 +./scripts/build-unix.sh ``` -5. Build the adapters required by the demo configuration. You can build additional adapters as needed. +The script initializes the submodules, clones Micro-Manager into `ScopeOneCore/external/micro-manager` and links `mmCoreAndDevices`, builds MMDevice, MMCore and the DemoCamera and Utilities adapters, builds and installs ScopeOneCore, builds the GUI, and copies the demo adapters next to the executable. It selects the `x64-linux-dynamic`, `arm64-linux-dynamic`, `x64-osx-dynamic`, or `arm64-osx-dynamic` triplet from the host. Set `JOBS` to limit parallel jobs and `BUILD_TYPE` to change the CMake build type (default `Release`). -```bash -make -C mmCoreAndDevices/DeviceAdapters/DemoCamera -j4 -make -C mmCoreAndDevices/DeviceAdapters/Utilities -j4 -``` +Pass `--clean` once when migrating from a build that used system Qt or OpenCV, or from an earlier static `x64-linux` triplet build. -6. Build and install `ScopeOneCore`: - -```bash -cd /path/to/ScopeOne -cmake -S ScopeOneCore -B ScopeOneCore/build -DCMAKE_BUILD_TYPE=Release -cmake --build ScopeOneCore/build --parallel -cmake --install ScopeOneCore/build -``` - -7. Build the GUI application: - -```bash -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -cmake --build build --parallel -``` - -The Linux or macOS executable is expected at: - -```text -build/ScopeOne -``` - -To run `config/MMConfig_demo.cfg`, copy its runtime adapters next to the ScopeOne executable. - -Linux: - -```bash -cp -L \ - ScopeOneCore/external/mmCoreAndDevices/DeviceAdapters/DemoCamera/.libs/libmmgr_dal_DemoCamera.so.0 \ - ScopeOneCore/external/mmCoreAndDevices/DeviceAdapters/Utilities/.libs/libmmgr_dal_Utilities.so.0 \ - build/ -``` - -macOS uses an extensionless Mach-O bundle rather than the static `.a` file: - -```bash -cp \ - ScopeOneCore/external/mmCoreAndDevices/DeviceAdapters/DemoCamera/.libs/libmmgr_dal_DemoCamera \ - ScopeOneCore/external/mmCoreAndDevices/DeviceAdapters/Utilities/.libs/libmmgr_dal_Utilities \ - build/ -``` +The executable is `build/ScopeOne`, and `config/MMConfig_demo.cfg` runs with the copied demo adapters. It loads Qt and OpenCV from `vcpkg_installed//lib`, so run it from the build tree; there is no Linux or macOS deployment step yet. ## 🤖 Automation and AI Agents diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 956b349..969083b 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -11,15 +11,13 @@ include(CMakePackageConfigHelpers) if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/install" CACHE PATH "Install path" FORCE) endif () -if (WIN32) - list(PREPEND CMAKE_PREFIX_PATH "C:/Qt/6.11.0/msvc2022_64") -endif () set(MMCORE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/mmCoreAndDevices") set(MMCORE_INCLUDE_DIR "${MMCORE_DIR}/MMCore") set(MMDEVICE_INCLUDE_DIR "${MMCORE_DIR}/MMDevice") find_package(Qt6 COMPONENTS Core Gui Network Concurrent REQUIRED) +find_package(OpenCV CONFIG REQUIRED COMPONENTS core imgproc imgcodecs) set(SCOPEWRITER_BUILD_TESTS OFF) add_subdirectory(external/ScopeWriter) @@ -29,15 +27,11 @@ set(CMAKE_AUTOMOC ON) if (WIN32) set(MMCORE_BIN_DIR "${MMCORE_DIR}/build/Release/x64") - set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/opencv-4.12.0/build") - - find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs) - set(SCOPEONE_OPENCV_LIBRARIES opencv_world) - file(GLOB MMCORE_RUNTIME_DLLS "${MMCORE_BIN_DIR}/*.dll") + file(GLOB VCPKG_RUNTIME_DLLS "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin/*.dll") set(SCOPEONE_RUNTIME_DLLS - $ + ${VCPKG_RUNTIME_DLLS} ${MMCORE_RUNTIME_DLLS} ) @@ -48,11 +42,9 @@ if (WIN32) endif () endforeach () else () - # Linux/Unix: use system OpenCV and link the MMCore static libraries + # Linux/Unix: link the MMCore static libraries # built from the sibling micro-manager checkout (symlinked into # external/mmCoreAndDevices). No runtime DLLs to stage. - find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs) - set(SCOPEONE_OPENCV_LIBRARIES ${OpenCV_LIBS}) set(MMCORE_BIN_DIR "") add_library(MMCore STATIC IMPORTED GLOBAL) @@ -176,6 +168,7 @@ if (UNIX AND NOT APPLE) set_target_properties(ScopeWriter ScopeOneCore PROPERTIES BUILD_RPATH "$ORIGIN" INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH TRUE ) endif () set_target_properties(ScopeOnePluginSDK PROPERTIES EXPORT_NAME PluginSDK) @@ -192,7 +185,9 @@ target_link_libraries(ScopeOneCore MMCore Qt::Network Qt::Concurrent - ${SCOPEONE_OPENCV_LIBRARIES} + opencv_core + opencv_imgproc + opencv_imgcodecs ScopeWriter::ScopeWriter ) target_compile_definitions(ScopeOneCore PRIVATE @@ -253,7 +248,8 @@ add_executable(ScopeOne_DriverHost if (UNIX AND NOT APPLE) set_target_properties(ScopeOne_DriverHost PROPERTIES BUILD_RPATH "$ORIGIN" - INSTALL_RPATH "$ORIGIN/../lib" + INSTALL_RPATH "$ORIGIN/../lib;$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH TRUE ) endif () target_link_libraries(ScopeOne_DriverHost diff --git a/ScopeOneCore/external/ScopeWriter b/ScopeOneCore/external/ScopeWriter index 69f6bcb..bfcba79 160000 --- a/ScopeOneCore/external/ScopeWriter +++ b/ScopeOneCore/external/ScopeWriter @@ -1 +1 @@ -Subproject commit 69f6bcb952cecdcc01ffb7ebf76f3edcb17cbe87 +Subproject commit bfcba79c6c357f8798e89b3800197fe5edbee3cf diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 163050f..d98ad48 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -519,6 +519,7 @@ namespace scopeone::core static QString getVersion(); static QString getMMCoreVersion(); static QString getOpenCVVersion(); + static QString getScopeWriterVersion(); static QString getLibTiffVersion(); static QString getZlibVersion(); static QString rawLayerKey(const QString& cameraId); diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 6b5cd4f..08b4bf9 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -1145,6 +1145,11 @@ namespace scopeone::core } // Return the ScopeWriter libtiff version + QString ScopeOneCore::getScopeWriterVersion() + { + return QString::fromStdString(scopewriter::version()); + } + QString ScopeOneCore::getLibTiffVersion() { return QString::fromStdString(scopewriter::libTiffVersion()); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3d8de74..4d4909c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_AUTOMOC ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) find_package(ScopeOneCore CONFIG REQUIRED) -find_package(OpenCV REQUIRED COMPONENTS core imgcodecs imgproc) +find_package(OpenCV CONFIG REQUIRED COMPONENTS core imgcodecs imgproc) find_program(SCOPEONE_CUDA_NVCC_EXECUTABLE nvcc) function(scopeone_add_plugin target kind) diff --git a/scripts/build-unix.sh b/scripts/build-unix.sh index 1189430..db803a5 100755 --- a/scripts/build-unix.sh +++ b/scripts/build-unix.sh @@ -7,6 +7,19 @@ EXTERNAL_DIR="$CORE_DIR/external" MM_REPO_DIR="$EXTERNAL_DIR/micro-manager" MMCORE_LINK="$EXTERNAL_DIR/mmCoreAndDevices" BUILD_TYPE="${BUILD_TYPE:-Release}" +CLEAN=false + +for argument in "$@"; do + case "$argument" in + --clean) CLEAN=true ;; + *) echo "Unknown option: $argument" >&2; exit 1 ;; + esac +done + +if [[ -z "${VCPKG_ROOT:-}" || ! -f "$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ]]; then + echo "Set VCPKG_ROOT to a bootstrapped vcpkg checkout." >&2 + exit 1 +fi case "$(uname -s)" in Linux*) @@ -27,10 +40,35 @@ case "$(uname -s)" in ;; esac +case "$(uname -m)" in + x86_64|amd64) + VCPKG_TRIPLET="$([[ "$PLATFORM_NAME" == "Linux" ]] && echo x64-linux-dynamic || echo x64-osx-dynamic)" + ;; + aarch64|arm64) + VCPKG_TRIPLET="$([[ "$PLATFORM_NAME" == "Linux" ]] && echo arm64-linux-dynamic || echo arm64-osx-dynamic)" + ;; + *) + echo "Unsupported host architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + +VCPKG_ARGS=( + "-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" + "-DVCPKG_MANIFEST_DIR=$ROOT_DIR" + "-DVCPKG_INSTALLED_DIR=$ROOT_DIR/vcpkg_installed" + "-DVCPKG_TARGET_TRIPLET=$VCPKG_TRIPLET" + "-DVCPKG_HOST_TRIPLET=$VCPKG_TRIPLET" +) + JOBS="${JOBS:-$DEFAULT_JOBS}" DEMO_ADAPTER="$MMCORE_LINK/DeviceAdapters/DemoCamera/.libs/$DEMO_ADAPTER_NAME" UTILITIES_ADAPTER="$MMCORE_LINK/DeviceAdapters/Utilities/.libs/$UTILITIES_ADAPTER_NAME" +if [[ "$CLEAN" == true ]]; then + rm -rf "$CORE_DIR/build" "$ROOT_DIR/build" +fi + step() { printf '\n==> %s\n' "$1" } @@ -87,12 +125,12 @@ step "Building required Micro-Manager components" ) step "Building and installing ScopeOneCore" -run cmake -S "$CORE_DIR" -B "$CORE_DIR/build" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" +run cmake -S "$CORE_DIR" -B "$CORE_DIR/build" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" "${VCPKG_ARGS[@]}" run cmake --build "$CORE_DIR/build" --parallel "$JOBS" run cmake --install "$CORE_DIR/build" step "Building ScopeOne GUI" -run cmake -S "$ROOT_DIR" -B "$ROOT_DIR/build" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" +run cmake -S "$ROOT_DIR" -B "$ROOT_DIR/build" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" "${VCPKG_ARGS[@]}" run cmake --build "$ROOT_DIR/build" --parallel "$JOBS" step "Copying demo adapters" diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 18dabe9..b525dad 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -217,6 +217,7 @@ function Import-MsvcEnvironment { $env:CXX = "cl.exe" } +$scopeOneVcpkgRoot = $env:VCPKG_ROOT Import-MsvcEnvironment $cmake = (Get-Command cmake -ErrorAction Stop).Source @@ -244,6 +245,38 @@ $coreCachePath = Join-Path $coreBuildDir "CMakeCache.txt" $guiCachePath = Join-Path $guiBuildDir "CMakeCache.txt" $pluginCachePath = Join-Path $pluginBuildDir "CMakeCache.txt" +$vcpkgConfigureArgs = @() +$writerVcpkgArgs = @() +if ($env:OS -eq "Windows_NT") { + if (-not $scopeOneVcpkgRoot) { + throw "Set VCPKG_ROOT to a bootstrapped vcpkg checkout." + } + $vcpkgToolchain = Join-Path $scopeOneVcpkgRoot "scripts\buildsystems\vcpkg.cmake" + if (-not (Test-Path $vcpkgToolchain)) { + throw "Set VCPKG_ROOT to a bootstrapped vcpkg checkout." + } +} +if ($env:OS -eq "Windows_NT" -and $target -eq "scopewriter") { + # Standalone ScopeWriter installs its own vcpkg.json dependencies and ships their DLLs + $writerVcpkgArgs = @( + "-DCMAKE_TOOLCHAIN_FILE=$vcpkgToolchain", + "-DVCPKG_TARGET_TRIPLET=x64-windows", + "-DX_VCPKG_APPLOCAL_DEPS_INSTALL=ON" + ) +} +elseif ($env:OS -eq "Windows_NT") { + $vcpkgConfigureArgs = @( + "-DCMAKE_TOOLCHAIN_FILE=$vcpkgToolchain", + "-DVCPKG_MANIFEST_DIR=$repoRoot", + "-DVCPKG_INSTALLED_DIR=$(Join-Path $repoRoot 'vcpkg_installed')", + "-DVCPKG_TARGET_TRIPLET=x64-windows", + "-UTiff_DIR", + "-UOpenCV_DIR", + "-UQt6*_DIR", + "-UWINDEPLOYQT_EXECUTABLE" + ) +} + if ($clean) { if ($target -eq "scopewriter") { if (Test-Path $writerBuildRoot) { @@ -313,7 +346,7 @@ if ($target -eq "scopewriter") { "-B", $writerBuildDir, "-DCMAKE_INSTALL_PREFIX=$writerInstallDir", "-DSCOPEWRITER_BUILD_TESTS=ON" - ) + $writerGeneratorArgs) ` + ) + $writerVcpkgArgs + $writerGeneratorArgs) ` -WorkingDirectory $repoRoot Invoke-Step ` @@ -397,7 +430,7 @@ if ($target -in @("all", "core")) { $coreConfigureArgs += "-DCMAKE_INSTALL_PREFIX=$coreInstallDir" } - $coreConfigureArgs += $coreConfigureOption + $coreConfigureArgs += $vcpkgConfigureArgs + $coreConfigureOption Invoke-Step ` -Label "Configuring ScopeOneCore" ` @@ -444,17 +477,7 @@ if ($target -in @("all", "plugins")) { "-DCMAKE_PREFIX_PATH=$coreInstallDir", "-DCMAKE_INSTALL_PREFIX=$pluginInstallDir" ) - $coreQt6Dir = Get-CMakeCacheValue -CachePath $coreCachePath -Key "Qt6_DIR" - if ($coreQt6Dir) { - $pluginConfigureArgs += "-DQt6_DIR=$coreQt6Dir" - } - $coreOpenCvDir = Get-CMakeCacheValue -CachePath $coreCachePath -Key "OpenCV_DIR" - if (-not $coreOpenCvDir) { - $coreOpenCvDir = Join-Path $coreSourceDir "external\opencv-4.12.0\build" - } - if ($coreOpenCvDir) { - $pluginConfigureArgs += "-DOpenCV_DIR=$coreOpenCvDir" - } + $pluginConfigureArgs += $vcpkgConfigureArgs if ($env:CUDA_PATH) { $pluginConfigureArgs += @("-T", "cuda=$env:CUDA_PATH") } @@ -512,7 +535,7 @@ if ($target -in @("all", "gui")) { "-S", $guiSourceDir, "-B", $guiBuildDir, "-DScopeOneCore_ROOT=$coreInstallDir" - ) + $guiConfigureOption + ) + $vcpkgConfigureArgs + $guiConfigureOption Invoke-Step ` -Label "Configuring ScopeOne GUI" ` diff --git a/src/AboutDialog.cpp b/src/AboutDialog.cpp index c9012c2..a2b33db 100644 --- a/src/AboutDialog.cpp +++ b/src/AboutDialog.cpp @@ -61,8 +61,7 @@ namespace scopeone::ui const QString coreVersion = scopeone::core::ScopeOneCore::getVersion(); const QString mmCoreVersion = scopeone::core::ScopeOneCore::getMMCoreVersion(); const QString openCvVersion = scopeone::core::ScopeOneCore::getOpenCVVersion(); - const QString libTiffVersion = scopeone::core::ScopeOneCore::getLibTiffVersion(); - const QString zlibVersion = scopeone::core::ScopeOneCore::getZlibVersion(); + const QString scopeWriterVersion = scopeone::core::ScopeOneCore::getScopeWriterVersion(); const QString commit = QStringLiteral(SCOPEONE_GIT_COMMIT); const QString platformInfo = QString("%1, %2") .arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); @@ -79,9 +78,8 @@ namespace scopeone::ui MMCore%4 Qt%5 OpenCV%6 -libtiff%7 -zlib%8 -Platform%9 +ScopeWriter%7 +Platform%8

Links

@@ -103,8 +101,7 @@ Licensed under the BSD 3-Clause License. mmCoreVersion.toHtmlEscaped(), QString::fromLatin1(qVersion()).toHtmlEscaped(), openCvVersion.toHtmlEscaped(), - libTiffVersion.toHtmlEscaped(), - zlibVersion.toHtmlEscaped(), + scopeWriterVersion.toHtmlEscaped(), platformInfo.toHtmlEscaped())); } diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 7eea85f..adae816 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -683,8 +683,11 @@ namespace scopeone::ui QStringLiteral("Update display levels continuously as images arrive")); auto* layerHistogramLogCheckBox = new QCheckBox(QStringLiteral("Log scale"), layerHistogramGroup); - histogramLayout->addWidget(m_layerAutoStretchCheckBox); - histogramLayout->addWidget(layerHistogramLogCheckBox); + auto* histogramOptionsLayout = new QHBoxLayout(); + histogramOptionsLayout->addWidget(m_layerAutoStretchCheckBox); + histogramOptionsLayout->addWidget(layerHistogramLogCheckBox); + histogramOptionsLayout->addStretch(1); + histogramLayout->addLayout(histogramOptionsLayout); connect(layerHistogramLogCheckBox, &QCheckBox::toggled, m_layerHistogramWidget, &LayerHistogramWidget::setLogScale); @@ -1451,8 +1454,10 @@ namespace scopeone::ui { emit snapRequested(m_currentTarget); }); - layout->addWidget(m_previewToggleButton, row, 0); - layout->addWidget(m_snapButton, row, 1); + QHBoxLayout* previewLayout = new QHBoxLayout(); + previewLayout->addWidget(m_previewToggleButton, 1); + previewLayout->addWidget(m_snapButton, 1); + layout->addLayout(previewLayout, row, 0, 1, 2); QHBoxLayout* roiLayout = new QHBoxLayout(); m_drawROIButton = new QPushButton("Draw ROI", group); @@ -1932,6 +1937,10 @@ namespace scopeone::ui m_previewToggleButton->setText(m_previewRunning ? QStringLiteral("Stop Preview") : QStringLiteral("Start Preview")); + m_previewToggleButton->setStyleSheet(m_previewRunning && canControlHardware + ? QStringLiteral( + "QPushButton { background-color: #c0392b; color: white; font-weight: bold; }") + : QString()); const bool hasRoiTarget = !roiCameraTarget().isEmpty(); m_drawROIButton->setEnabled(canControlHardware && hasRoiTarget); m_halfROIButton->setEnabled(canControlHardware && hasRoiTarget); diff --git a/src/InspectWidget.cpp b/src/InspectWidget.cpp index 76a5caa..f136295 100644 --- a/src/InspectWidget.cpp +++ b/src/InspectWidget.cpp @@ -12,8 +12,6 @@ #include #include #include -#include -#include #include #include #include @@ -615,67 +613,10 @@ namespace scopeone::ui infoGroup.layerKey = normalizedLayerKey; infoGroup.groupBox = group; - auto* slidersLayout = new QHBoxLayout(); - - auto* minLabel = new QLabel(QStringLiteral("Min:"), group); - auto* minSlider = new QSlider(Qt::Horizontal, group); - minSlider->setRange(0, 255); - minSlider->setValue(0); - minSlider->setMinimumWidth(100); - auto* minSliderValueLabel = new QLabel(QStringLiteral("0"), group); - minSliderValueLabel->setMinimumWidth(50); - minSliderValueLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - - auto* maxLabel = new QLabel(QStringLiteral("Max:"), group); - auto* maxSlider = new QSlider(Qt::Horizontal, group); - maxSlider->setRange(0, 255); - maxSlider->setValue(255); - maxSlider->setMinimumWidth(100); - auto* maxSliderValueLabel = new QLabel(QStringLiteral("255"), group); - maxSliderValueLabel->setMinimumWidth(50); - maxSliderValueLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - - slidersLayout->addWidget(minLabel); - slidersLayout->addWidget(minSlider, 1); - slidersLayout->addWidget(minSliderValueLabel); - slidersLayout->addWidget(maxLabel); - slidersLayout->addWidget(maxSlider, 1); - slidersLayout->addWidget(maxSliderValueLabel); - layout->addLayout(slidersLayout); - - infoGroup.minSlider = minSlider; - infoGroup.maxSlider = maxSlider; - infoGroup.minSliderValueLabel = minSliderValueLabel; - infoGroup.maxSliderValueLabel = maxSliderValueLabel; layout->addWidget(createStatisticsGroup(infoGroup)); m_layerInfoGroups.insert(normalizedLayerKey, infoGroup); m_contentLayout->insertWidget(m_contentLayout->count() - 1, group); - connect(minSlider, &QSlider::valueChanged, this, - [this, normalizedLayerKey, minSlider, maxSlider, minSliderValueLabel](int value) - { - if (value >= maxSlider->value()) - { - QSignalBlocker blocker(minSlider); - minSlider->setValue(maxSlider->value() - 1); - value = maxSlider->value() - 1; - } - minSliderValueLabel->setText(QString::number(value)); - onLayerSliderChanged(normalizedLayerKey, value, maxSlider->value()); - }); - connect(maxSlider, &QSlider::valueChanged, this, - [this, normalizedLayerKey, minSlider, maxSlider, maxSliderValueLabel](int value) - { - if (value <= minSlider->value()) - { - QSignalBlocker blocker(maxSlider); - maxSlider->setValue(minSlider->value() + 1); - value = minSlider->value() + 1; - } - maxSliderValueLabel->setText(QString::number(value)); - onLayerSliderChanged(normalizedLayerKey, minSlider->value(), value); - }); - return group; } @@ -754,7 +695,6 @@ namespace scopeone::ui { return; } - LayerInfoGroup& infoGroup = it.value(); LayerInspectState& state = getOrCreateLayerState(normalizedLayerKey); state.stats = stats; @@ -764,25 +704,6 @@ namespace scopeone::ui return; } - scopeone::core::DocumentLayer layer; - if (!m_sceneModel || !m_sceneModel->findLayer(normalizedLayerKey, layer)) - { - return; - } - - const int maxValue = qMax(1, layer.display.levelDomainMax); - const int displayMin = qBound(0, layer.display.levelMin, maxValue - 1); - const int displayMax = qBound(displayMin + 1, layer.display.levelMax, maxValue); - infoGroup.minSlider->setRange(0, maxValue); - infoGroup.maxSlider->setRange(0, maxValue); - { - QSignalBlocker minBlocker(infoGroup.minSlider); - QSignalBlocker maxBlocker(infoGroup.maxSlider); - infoGroup.minSlider->setValue(displayMin); - infoGroup.maxSlider->setValue(displayMax); - } - infoGroup.minSliderValueLabel->setText(QString::number(displayMin)); - infoGroup.maxSliderValueLabel->setText(QString::number(displayMax)); updateStatisticsDisplay(normalizedLayerKey, stats); updateControlsState(); } @@ -840,16 +761,6 @@ namespace scopeone::ui && m_availableLayerKeys.contains(layerKey); m_drawMeasurementLineButton->setEnabled(annotationEnabled); m_clearMeasurementLinesButton->setEnabled(annotationEnabled); - - for (auto it = m_layerInfoGroups.begin(); it != m_layerInfoGroups.end(); ++it) - { - LayerInfoGroup& infoGroup = it.value(); - const auto stateIt = m_layerStates.constFind(infoGroup.layerKey); - const bool hasStats = stateIt != m_layerStates.constEnd() && stateIt.value().hasStats; - const bool isActiveLayer = infoGroup.layerKey == layerKey; - infoGroup.minSlider->setEnabled(hasStats && isActiveLayer); - infoGroup.maxSlider->setEnabled(hasStats && isActiveLayer); - } } // Shows inspect controls for the selected preview layer @@ -883,24 +794,6 @@ namespace scopeone::ui return it.value(); } - // Apply manual display range changes from layer sliders - void InspectWidget::onLayerSliderChanged(const QString& layerKey, int minValue, int maxValue) - { - auto stateIt = m_layerStates.find(layerKey); - if (stateIt == m_layerStates.end()) - { - return; - } - const LayerInspectState& state = stateIt.value(); - if (!state.hasStats) - { - return; - } - m_workspace->setLayerAutoStretchEnabled(layerKey, false); - m_sceneModel->setLayerDisplayLevels( - layerKey, minValue, maxValue, qMax(1, state.stats.maxValue)); - } - QString InspectWidget::currentLayerCameraId() const { return scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey()); diff --git a/src/InspectWidget.h b/src/InspectWidget.h index 03a39c0..ac1cc0d 100644 --- a/src/InspectWidget.h +++ b/src/InspectWidget.h @@ -12,7 +12,6 @@ class QGroupBox; class QLabel; class QPushButton; -class QSlider; class QVBoxLayout; namespace scopeone::ui @@ -63,10 +62,6 @@ namespace scopeone::ui { QString layerKey; QGroupBox* groupBox{nullptr}; - QSlider* minSlider{nullptr}; - QSlider* maxSlider{nullptr}; - QLabel* minSliderValueLabel{nullptr}; - QLabel* maxSliderValueLabel{nullptr}; QLabel* meanLabel{nullptr}; QLabel* minLabel{nullptr}; QLabel* maxLabel{nullptr}; @@ -98,7 +93,6 @@ namespace scopeone::ui void restoreViewerState(); QString currentLayerKey() const; LayerInspectState& getOrCreateLayerState(const QString& layerKey); - void onLayerSliderChanged(const QString& layerKey, int minValue, int maxValue); QString currentLayerCameraId() const; scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 7c1cb08..ae3b2f4 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -63,6 +63,8 @@ namespace scopeone::ui namespace { constexpr qsizetype kMaxRecentConfigurations = 8; + // Bump when dock widgets change so stale saved layouts are ignored + constexpr int kWindowLayoutVersion = 1; // Build raw layer keys for all camera ids QStringList rawLayerKeys(const QStringList& cameraIds) @@ -298,6 +300,7 @@ namespace scopeone::ui setWindowTitle("ScopeOne"); setMinimumSize(1366, 768); resize(1600, 900); + restoreWindowLayout(); } // Confirm what to do with unsaved gallery sessions @@ -309,6 +312,24 @@ namespace scopeone::ui return; } + saveWindowLayout(); + + // Core shutdown stops and finalizes the recording; confirm before cutting it short + if (m_scopeonecore->isRecording()) + { + const QMessageBox::StandardButton recordingReply = QMessageBox::warning( + this, + tr("Recording in Progress"), + tr("A recording is still running. Stop the recording and close ScopeOne?"), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel); + if (recordingReply != QMessageBox::Yes) + { + event->ignore(); + return; + } + } + const auto unsavedSessions = m_imageGalleryWidget->unsavedSessions(); if (unsavedSessions.isEmpty()) { @@ -963,6 +984,17 @@ namespace scopeone::ui { return m_imageWorkspace->pixelSizeUm(layerKey); }); + // Remember the scale bar choice across launches + { + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + m_previewWidget->setScaleBarVisible( + settings.value(QStringLiteral("View/scaleBarVisible"), true).toBool()); + } + connect(m_previewWidget, &PreviewWidget::scaleBarVisibilityChanged, this, [](bool visible) + { + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + settings.setValue(QStringLiteral("View/scaleBarVisible"), visible); + }); setCentralWidget(m_imageWorkspace->viewerHost()); setupTools(); @@ -1241,6 +1273,11 @@ namespace scopeone::ui viewMenu->addSeparator(); m_dockWidgetsMenu = viewMenu->addMenu(tr("&Dock Widgets")); + auto* resetLayoutAction = viewMenu->addAction(tr("&Reset Layout")); + connect(resetLayoutAction, &QAction::triggered, this, [this]() + { + restoreState(m_defaultWindowState, kWindowLayoutVersion); + }); auto* togglePreviewAction = new QAction(tr("Toggle Live Preview"), this); togglePreviewAction->setShortcut(QKeySequence(Qt::Key_Space)); @@ -1342,26 +1379,36 @@ namespace scopeone::ui m_imageProcessingWidget = new ImageProcessingWidget(m_scopeonecore, m_imageWorkspace, this); m_controlDockWidget = new QDockWidget(tr("Control"), this); + + m_controlDockWidget->setObjectName(QStringLiteral("ControlDock")); m_controlDockWidget->setWidget(m_deviceControlWidget->hardwareControlsWidget()); m_controlDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); addDockWidget(Qt::RightDockWidgetArea, m_controlDockWidget); m_viewDockWidget = new QDockWidget(tr("View"), this); + + m_viewDockWidget->setObjectName(QStringLiteral("ViewDock")); m_viewDockWidget->setWidget(m_deviceControlWidget->imageControlsWidget()); m_viewDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); tabifyDockWidget(m_controlDockWidget, m_viewDockWidget); m_analyzeDockWidget = new QDockWidget(tr("Analyze"), this); + + m_analyzeDockWidget->setObjectName(QStringLiteral("AnalyzeDock")); m_analyzeDockWidget->setWidget(m_inspectWidget); m_analyzeDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); tabifyDockWidget(m_controlDockWidget, m_analyzeDockWidget); m_processDockWidget = new QDockWidget(tr("Process"), this); + + m_processDockWidget->setObjectName(QStringLiteral("ProcessDock")); m_processDockWidget->setWidget(m_imageProcessingWidget); m_processDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); tabifyDockWidget(m_controlDockWidget, m_processDockWidget); m_consoleDockWidget = new QDockWidget(tr("Console"), this); + + m_consoleDockWidget->setObjectName(QStringLiteral("ConsoleDock")); m_consoleWidget = new ConsoleWidget(m_consoleDockWidget); m_consoleDockWidget->setWidget(m_consoleWidget); m_consoleDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); @@ -1379,6 +1426,7 @@ namespace scopeone::ui void MainWindow::setupPropertyBrowser() { m_propertyDockWidget = new QDockWidget(tr("Properties"), this); + m_propertyDockWidget->setObjectName(QStringLiteral("PropertiesDock")); m_propertyDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea); m_propertyBrowser = new DevicePropertyWidget(m_scopeonecore, this); @@ -1388,6 +1436,8 @@ namespace scopeone::ui addDockWidget(Qt::LeftDockWidgetArea, m_propertyDockWidget); m_configPresetDockWidget = new QDockWidget(tr("Configs"), this); + + m_configPresetDockWidget->setObjectName(QStringLiteral("ConfigsDock")); m_configPresetDockWidget->setWidget(m_configPresetWidget); m_configPresetDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea); } @@ -1396,6 +1446,7 @@ namespace scopeone::ui void MainWindow::setupRecording() { m_recordingDockWidget = new QDockWidget(tr("Recording"), this); + m_recordingDockWidget->setObjectName(QStringLiteral("RecordingDock")); m_recordingDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea); m_recordingWidget = new RecordingWidget(m_scopeonecore, this); @@ -1409,6 +1460,7 @@ namespace scopeone::ui void MainWindow::setupImageGallery() { m_imageGalleryDockWidget = new QDockWidget(tr("Image Gallery"), this); + m_imageGalleryDockWidget->setObjectName(QStringLiteral("ImageGalleryDock")); m_imageGalleryDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea); m_imageGalleryDockWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | @@ -1521,6 +1573,29 @@ namespace scopeone::ui addDock(m_consoleDockWidget, QStringLiteral("Console")); } + // Restore the saved window geometry and dock layout + void MainWindow::restoreWindowLayout() + { + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + restoreGeometry(settings.value(QStringLiteral("MainWindow/geometry")).toByteArray()); + const QByteArray savedState = settings.value(QStringLiteral("MainWindow/state")).toByteArray(); + + // Dock sizes are only valid once the window is shown, so capture the default layout then + QTimer::singleShot(0, this, [this, savedState]() + { + m_defaultWindowState = saveState(kWindowLayoutVersion); + restoreState(savedState, kWindowLayoutVersion); + }); + } + + // Save the window geometry and dock layout for the next launch + void MainWindow::saveWindowLayout() const + { + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + settings.setValue(QStringLiteral("MainWindow/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("MainWindow/state"), saveState(kWindowLayoutVersion)); + } + // Push loaded camera ids into every dependent panel void MainWindow::applyLoadedCameraState(const QStringList& cameraIds) { diff --git a/src/MainWindow.h b/src/MainWindow.h index 36252ac..b44a6c6 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -3,6 +3,7 @@ #include "scopeone/ScopeOneCore.h" #include "ScopeOneToolPlugin.h" +#include #include #include #include @@ -76,6 +77,8 @@ namespace scopeone::ui void showLivePreview(); void updateControlTarget(const QString& target); void updateDockWidgetMenu(); + void restoreWindowLayout(); + void saveWindowLayout() const; void syncCameraState(); void applyLoadedCameraState(const QStringList& cameraIds); void applyNoCameraState(); @@ -150,6 +153,7 @@ namespace scopeone::ui QMenu* m_recentConfigurationsMenu{nullptr}; QMenu* m_dockWidgetsMenu{nullptr}; + QByteArray m_defaultWindowState; QAction* m_toggleDimensionAction{nullptr}; QAction* m_toggle3dColorbarAction{nullptr}; diff --git a/src/RecordingWidget.cpp b/src/RecordingWidget.cpp index 5aa776c..e5f3369 100644 --- a/src/RecordingWidget.cpp +++ b/src/RecordingWidget.cpp @@ -308,6 +308,10 @@ namespace scopeone::ui { m_isRecording = recording; m_startStopButton->setText(recording ? "Stop" : "Start"); + m_startStopButton->setStyleSheet(recording + ? QStringLiteral( + "QPushButton { background-color: #c0392b; color: white; font-weight: bold; }") + : QString()); if (recording) { m_storageStatusTimer->start(); @@ -642,16 +646,23 @@ namespace scopeone::ui statusLayout->addWidget(m_burstCountLabel, 1, 1); statusLayout->addWidget(m_writerStatusLabel, 2, 0, 1, 2); statusLayout->addWidget(m_storageStatusLabel, 3, 0, 1, 2); - contentLayout->addWidget(statusGroup); m_startStopButton = new QPushButton("Start", this); m_startStopButton->setMinimumHeight(28); - contentLayout->addWidget(m_startStopButton); contentLayout->addStretch(1); scrollArea->setWidget(content); - mainLayout->addWidget(scrollArea); + mainLayout->addWidget(scrollArea, 1); + + // Keep status and Start/Stop visible below the scrolling settings + auto* footer = new QWidget(this); + auto* footerLayout = new QVBoxLayout(footer); + footerLayout->setContentsMargins(6, 6, 6, 6); + footerLayout->setSpacing(6); + footerLayout->addWidget(statusGroup); + footerLayout->addWidget(m_startStopButton); + mainLayout->addWidget(footer); } // Updates available detector choices diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..7f27d78 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,54 @@ +{ + "name": "scopeone", + "builtin-baseline": "434307da09bc05b2c86996dccc8b2351fc0d5d37", + "dependencies": [ + "crc32c", + { + "name": "opencv4", + "default-features": false, + "features": ["fs", "intrinsics", "jpeg", "png", "thread", "tiff", "webp"] + }, + { + "name": "qtbase", + "default-features": false, + "features": ["concurrent", "dnslookup", "gui", "harfbuzz", "jpeg", "network", "opengl", "png", "thread", "widgets"] + }, + { + "name": "qtsvg", + "default-features": false + }, + { + "name": "tiff", + "default-features": false, + "features": ["zip"] + }, + "zlib", + "zstd", + { + "name": "qtbase", + "default-features": false, + "features": ["windeployqt"], + "platform": "windows" + }, + { + "name": "qtbase", + "default-features": false, + "features": ["egl", "fontconfig", "xcb", "xcb-xlib", "xrender"], + "platform": "linux" + } + ], + "overrides": [ + { + "name": "opencv4", + "version": "4.14.0" + }, + { + "name": "qtbase", + "version": "6.11.2" + }, + { + "name": "qtsvg", + "version": "6.11.2" + } + ] +}