diff --git a/.gitignore b/.gitignore
index 6ebdb3c..0179a56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,10 @@
!/src/**
!/ScopeOneCore/
!/ScopeOneCore/**
+!/plugins/
+!/plugins/**
+!/ScopeOneCuda/
+!/ScopeOneCuda/**
/ScopeOneCore/install/
/ScopeOneCore/build/
/ScopeOneCore/external/*
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b1f968b..e2039b1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -71,16 +71,19 @@ set(SCOPEONE_UI_SOURCES
src/MainWindow.cpp
src/ImageProcessingWidget.cpp
src/ImageGalleryWidget.cpp
+ src/ImageWorkspace.cpp
src/ImageToolsDialog.cpp
src/PreviewWidget.cpp
src/ConsoleWidget.cpp
src/AboutDialog.cpp
src/SettingsDialog.cpp
+ src/PluginManagerDialog.cpp
src/DeviceControlWidget.cpp
src/RecordingWidget.cpp
src/DevicePropertyWidget.cpp
src/ConfigPresetWidget.cpp
src/ScopeOneLocalApiServer.cpp
+ src/ScopeOneToolPlugin.cpp
${CMAKE_SOURCE_DIR}/resources/resources.qrc
)
@@ -89,16 +92,19 @@ set(SCOPEONE_UI_HEADERS
src/MainWindow.h
src/ImageProcessingWidget.h
src/ImageGalleryWidget.h
+ src/ImageWorkspace.h
src/ImageToolsDialog.h
src/PreviewWidget.h
src/ConsoleWidget.h
src/AboutDialog.h
src/SettingsDialog.h
+ src/PluginManagerDialog.h
src/DeviceControlWidget.h
src/RecordingWidget.h
src/DevicePropertyWidget.h
src/ConfigPresetWidget.h
src/ScopeOneLocalApiServer.h
+ src/ScopeOneToolPlugin.h
)
if (WIN32)
@@ -176,11 +182,20 @@ install(FILES
DESTINATION .
)
-file(GLOB SCOPEONE_CORE_RUNTIME_FILES
- LIST_DIRECTORIES false
- "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/*.dll"
- "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/ScopeOne_Agent.exe"
-)
+if (WIN32)
+ file(GLOB SCOPEONE_CORE_RUNTIME_FILES
+ LIST_DIRECTORIES false
+ "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/*.dll"
+ "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/ScopeOne_DriverHost.exe"
+ )
+else ()
+ file(GLOB SCOPEONE_CORE_RUNTIME_FILES
+ LIST_DIRECTORIES false
+ "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/ScopeOne_DriverHost"
+ "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_LIBDIR}/libScopeOneCore.so*"
+ "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_LIBDIR}/libScopeWriter.so*"
+ )
+endif ()
if (SCOPEONE_CORE_RUNTIME_FILES)
add_custom_command(TARGET ScopeOne POST_BUILD
@@ -191,6 +206,18 @@ if (SCOPEONE_CORE_RUNTIME_FILES)
)
endif ()
+foreach (_plugin_kind IN ITEMS processing hardware tools)
+ set(_plugin_dir "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/plugins/${_plugin_kind}")
+ if (EXISTS "${_plugin_dir}")
+ add_custom_command(TARGET ScopeOne POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_directory
+ "${_plugin_dir}"
+ "$/plugins/${_plugin_kind}"
+ )
+ install(DIRECTORY "${_plugin_dir}/" DESTINATION "plugins/${_plugin_kind}")
+ endif ()
+endforeach ()
+
if (WIN32)
if (WINDEPLOYQT_EXECUTABLE)
add_custom_command(TARGET ScopeOne POST_BUILD
@@ -227,7 +254,7 @@ if (WIN32)
install(CODE "
file(GLOB _core_runtime_files
\"$/*.dll\"
- \"$/ScopeOne_Agent.exe\"
+ \"$/ScopeOne_DriverHost.exe\"
)
if (_core_runtime_files)
file(INSTALL
diff --git a/README.md b/README.md
index 2c5621c..e5cadfe 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,11 @@
-ScopeOne is an open-source microscopy control software for multi-camera imaging, originally developed for in-house lab use. Built with C++ and Qt, it uses a native [MMCore](https://github.com/micro-manager/mmCoreAndDevices) backend for single-camera operation and one isolated camera agent per device for simultaneous multi-camera preview and acquisition.
-It retains full compatibility with the [Micro-Manager](https://micro-manager.org/) device ecosystem and adds a modular real-time image processing pipeline with support for background calibration, temporal filtering, FFT analysis, and more.
+ScopeOne is open-source microscopy control software built with C++ and Qt. Hardware is accessed through provider-independent device contracts; [Micro-Manager](https://micro-manager.org/) is the built-in provider, and isolated devices run through ScopeOne DriverHost processes.
+It retains compatibility with the Micro-Manager device ecosystem while allowing native vendor providers and adds a modular real-time image processing pipeline with support for background calibration, temporal filtering, FFT analysis, and more.
- 
+ 
Graphical User Interface of ScopeOne
@@ -63,6 +63,7 @@ The expected layout is:
```text
ScopeOne/
ScopeOneCore/
+ include/scopeone/ Core API and external plugin contracts
external/
mmCoreAndDevices/
opencv-4.12.0/
@@ -71,6 +72,21 @@ ScopeOne/
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.
+### Plugin layout
+
+ScopeOne loads external plugins from these directories beside the application:
+
+- `plugins/processing` adds processing modules to the shared pipeline
+- `plugins/tools` adds optional workflow windows to the Tools menu
+- `plugins/hardware` adds isolated hardware providers hosted by `ScopeOne_DriverHost`
+- `plugins/hardware` also contains DAQ devices and signal source plugins
+
+All plugins use the installed `scopeone::PluginSDK` CMake target and the public contracts in `ScopeOneCore/include/scopeone`. The Core package owns the stable plugin-facing headers for image frames, hardware providers, DAQ devices, signal sources, processing modules, tool plugins, shared frames and manifests. A common manifest contains `id`, `name`, `version`, `scopeOneApi`, and `kind`. **Tools > Plugin Manager** installs plugins into the current user's application data directory. Hardware plugins can also be enabled and configured there; changes take effect after restart. Reference plugins are organized under `plugins/hardware`, `plugins/processing`, and `plugins/tools`.
+
+Micro-Manager remains the built-in camera provider and continues to load its Device Adapters from `.cfg` files. Native camera devices that do not belong in Micro-Manager use the `HardwareProvider` plugin contract. DAQ and signal acquisition are separate plugin contracts and are not linked into `ScopeOne.exe`.
+
+The Image Processing panel can process all live cameras or one selected camera. Recorded images and stacks open in independent windows, and the active image viewer becomes the target for Layers, Inspect, Image Processing, and Save As. Processing the current image or complete stack runs in the background and opens the result in a new window; stack results also remain available in Gallery. Temporal module state is preserved across each stack without changing live pipeline state.
+
**Windows Build Steps:**
1. Build and install `ScopeOneCore`:
@@ -230,7 +246,7 @@ To use it:
Use `scopeone` as the server name, `stdio` as the transport, the absolute path to `ScopeOneMcpServer.exe` as the command, and no command-line arguments. The exact configuration syntax depends on the agent host.
-The MCP tool set mirrors the Local API operation catalog, including system state, configuration, preview layers, automatic display levels, source alignment, markups, device properties, exposure, ROI, stages, stage mosaics, processing, experiments, recording sessions, frame transfer, and analysis. Agents can read the current frame of any image layer, monitor live acquisition and writer progress, and optionally export or display particle masks. ScopeOne remains the authority for parameter validation and hardware read-back, and MCP tool calls are visible in the desktop UI through the same application state used by manual controls.
+The MCP tool set mirrors the Local API operation catalog, including system state, configuration, preview layers, independent image windows, automatic display levels, source alignment, markups, device properties, exposure, ROI, stages, stage mosaics, processing, experiments, recording sessions, frame transfer, and analysis. Agents can list, open, activate, process, save, and close independent image windows, read the current frame of any image layer, monitor live acquisition and writer progress, and optionally export or display particle masks. ScopeOne remains the authority for parameter validation and hardware read-back, and MCP tool calls are visible in the desktop UI through the same application state used by manual controls.
Configuration loading and unloading report an explicit lifecycle state through the Local API and MCP. A configuration with non-camera initialization warnings is reported as `partially_loaded` with failed device labels; camera backend startup failures are cleaned up and reported as errors. During `loading` or `unloading`, hardware mutations are rejected until the operation finishes.
diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt
index 38568aa..c03f0da 100644
--- a/ScopeOneCore/CMakeLists.txt
+++ b/ScopeOneCore/CMakeLists.txt
@@ -31,7 +31,7 @@ if (WIN32)
set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/opencv-4.12.0/build")
- find_package(OpenCV REQUIRED COMPONENTS core imgproc)
+ find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs)
set(SCOPEONE_OPENCV_LIBRARIES opencv_world)
file(GLOB MMCORE_RUNTIME_DLLS "${MMCORE_BIN_DIR}/*.dll")
@@ -51,7 +51,7 @@ else ()
# Linux/Unix: use system OpenCV and 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)
+ find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs)
set(SCOPEONE_OPENCV_LIBRARIES ${OpenCV_LIBS})
set(MMCORE_BIN_DIR "")
@@ -77,15 +77,30 @@ endfunction()
set(CORE_SOURCES
src/ExperimentDocument.cpp
+ src/AcquisitionEngine.cpp
+ src/DriverHostProviderProxy.cpp
+ src/DaqDeviceManager.cpp
+ src/HardwareProvider.cpp
+ src/PluginManifest.cpp
+ src/HardwareRuntime.cpp
+ src/MicroManagerProvider.cpp
+ src/SimulatorProvider.cpp
src/ImageSceneModel.cpp
src/MMCoreManager.cpp
src/FrameBufferUtils.cpp
src/ParticleAnalysis.cpp
src/StageMosaicManager.cpp
src/ImageProcessingFramework.cpp
+ src/ProcessingPipeline.cpp
+ src/ToolTask.cpp
+ src/ToolFrameStream.cpp
+ src/ProcessingModuleRegistry.cpp
src/SpatiotemporalBinningModule.cpp
src/GaussianBlurModule.cpp
src/FFTModule.cpp
+ src/FrequencyDomainFilterModule.cpp
+ src/MaskModule.cpp
+ src/IFFTModule.cpp
src/BackgroundCalibrationModule.cpp
src/DifferentialRollingModule.cpp
src/MDAManager.cpp
@@ -93,37 +108,76 @@ set(CORE_SOURCES
src/CameraBackend.cpp
src/CameraManager.cpp
src/NativeCameraBackend.cpp
- src/AgentCameraBackend.cpp
+ src/DriverHostCameraBackend.cpp
src/ScopeOneCore.cpp
+ src/ScanImageAssembler.cpp
+ src/SignalSourceManager.cpp
)
set(CORE_HEADERS
include/scopeone/ExperimentDocument.h
+ include/scopeone/CameraProvider.h
+ include/scopeone/DaqDevice.h
+ include/scopeone/DriverHostProviderPlugin.h
+ include/scopeone/HardwareCapabilities.h
+ include/scopeone/HardwareProvider.h
+ include/scopeone/HardwareTypes.h
+ include/scopeone/ImageFrame.h
+ include/scopeone/PluginManifest.h
+ include/scopeone/PluginSDK.h
+ include/scopeone/ProcessingPlugin.h
+ include/scopeone/ScanImageAssembler.h
+ include/scopeone/scopeone_sdk_export.h
+ include/scopeone/SharedFrame.h
+ include/scopeone/SignalSource.h
+ include/scopeone/ToolFrameStream.h
+ include/scopeone/ToolPlugin.h
+ include/scopeone/ToolTask.h
+ internal/AcquisitionEngine.h
+ internal/CameraRuntimeControl.h
+ include/scopeone/SimulatorProvider.h
include/scopeone/ImageSceneModel.h
+ internal/HardwareRuntime.h
+ internal/MicroManagerProvider.h
internal/MMCoreManager.h
internal/FrameBufferUtils.h
internal/ParticleAnalysis.h
internal/StageMosaicManager.h
internal/ImageProcessingFramework.h
internal/ProcessingModule.h
- internal/AgentProtocol.h
+ internal/ProcessingModuleRegistry.h
+ include/scopeone/ProcessingPipeline.h
+ internal/DriverHostProtocol.h
+ internal/SharedFrameRing.h
+ internal/DriverHostProviderProxy.h
+ internal/DaqDeviceManager.h
+ internal/SignalSourceManager.h
internal/SpatiotemporalBinningModule.h
internal/GaussianBlurModule.h
internal/FFTModule.h
+ internal/FrequencyDomainFilterModule.h
+ internal/MaskModule.h
+ internal/IFFTModule.h
internal/BackgroundCalibrationModule.h
internal/DifferentialRollingModule.h
internal/MDAManager.h
internal/RecordingManager.h
internal/CameraBackend.h
internal/CameraManager.h
- include/scopeone/ImageFrame.h
- include/scopeone/SharedFrame.h
include/scopeone/scopeone_core_export.h
include/scopeone/ScopeOneCore.h
)
add_library(ScopeOneCore SHARED ${CORE_SOURCES} ${CORE_HEADERS})
add_library(scopeone::ScopeOneCore ALIAS ScopeOneCore)
+add_library(ScopeOnePluginSDK INTERFACE)
+add_library(scopeone::PluginSDK ALIAS ScopeOnePluginSDK)
+set_target_properties(ScopeOnePluginSDK PROPERTIES EXPORT_NAME PluginSDK)
+target_link_libraries(ScopeOnePluginSDK INTERFACE ScopeOneCore Qt::Core Qt::Gui)
+target_include_directories(ScopeOnePluginSDK INTERFACE
+ $
+ $
+)
target_link_libraries(ScopeOneCore
PUBLIC
Qt::Core
@@ -187,24 +241,25 @@ add_custom_command(TARGET ScopeOneCore POST_BUILD
$
)
-add_executable(ScopeOne_Agent
- src/AgentMain.cpp
+add_executable(ScopeOne_DriverHost
+ src/DriverHostMain.cpp
)
-target_link_libraries(ScopeOne_Agent
+target_link_libraries(ScopeOne_DriverHost
+ ScopeOneCore
MMCore
Qt::Core
Qt::Network
)
-target_compile_definitions(ScopeOne_Agent PRIVATE MMDEVICE_CLIENT_BUILD)
-target_include_directories(ScopeOne_Agent PRIVATE
+target_compile_definitions(ScopeOne_DriverHost PRIVATE MMDEVICE_CLIENT_BUILD)
+target_include_directories(ScopeOne_DriverHost PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}
${MMCORE_INCLUDE_DIR}
${MMDEVICE_INCLUDE_DIR}
)
-target_link_directories(ScopeOne_Agent PRIVATE ${MMCORE_BIN_DIR})
+target_link_directories(ScopeOne_DriverHost PRIVATE ${MMCORE_BIN_DIR})
-scopeone_add_runtime_copy_commands(ScopeOne_Agent $)
+scopeone_add_runtime_copy_commands(ScopeOne_DriverHost $)
# Fast Release build
# if (MSVC)
@@ -222,10 +277,10 @@ if (MSVC)
$<$:/LTCG>
)
- target_compile_options(ScopeOne_Agent PRIVATE
+ target_compile_options(ScopeOne_DriverHost PRIVATE
$<$:/GL /fp:fast /arch:AVX2>
)
- target_link_options(ScopeOne_Agent PRIVATE
+ target_link_options(ScopeOne_DriverHost PRIVATE
$<$:/LTCG>
)
endif ()
@@ -242,13 +297,13 @@ write_basic_package_version_file(
COMPATIBILITY SameMajorVersion
)
-install(TARGETS ScopeOneCore
+install(TARGETS ScopeOneCore ScopeOnePluginSDK
EXPORT ScopeOneCoreTargets
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
-install(TARGETS ScopeOne_Agent
+install(TARGETS ScopeOne_DriverHost
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/"
diff --git a/ScopeOneCore/README.md b/ScopeOneCore/README.md
index f504d66..8186681 100644
--- a/ScopeOneCore/README.md
+++ b/ScopeOneCore/README.md
@@ -2,13 +2,13 @@
`ScopeOneCore` is the reusable runtime library behind the desktop app.
-The desktop app currently assumes `ScopeOneCore` is checked out under the `ScopeOne` repository root.
+The desktop app consumes `ScopeOneCore` through its installed CMake package.
## Source Layout
```text
ScopeOneCore/
-|-- include/scopeone/ Public C++ headers installed for consumers
+|-- include/scopeone/ Core facade and all public plugin contracts
|-- internal/ Private headers used only while building ScopeOneCore
|-- src/ C++ implementations for both public and private types
|-- python/scopeone/ External Python client for a running ScopeOne app
@@ -18,20 +18,20 @@ ScopeOneCore/
`-- install/ Generated local installation consumed by the desktop app
```
-`include/scopeone` defines the installed C++ contract. A header belongs here only when the desktop app or another external consumer must compile against it. Consumers include these files with the installed prefix, for example:
+`include/scopeone` contains the Core facade, Core-owned public models and all plugin contracts:
```cpp
#include
#include
```
-`internal` contains implementation contracts between Core managers, processing modules and the camera agent. These headers are available to the `ScopeOneCore` target through a private include path, are not installed, and may change without preserving source compatibility. Code outside `ScopeOneCore` must not include them.
+`internal` contains implementation contracts between Core managers, processing modules and DriverHost. These headers are available to the `ScopeOneCore` target through a private include path, are not installed, and may change without preserving source compatibility. Code outside `ScopeOneCore` must not include them.
`src` contains implementations. A public class such as `ScopeOneCore`, `ImageSceneModel` or `ExperimentDocument` still has its `.cpp` file in `src`; being public is determined by its header location and exported API, not by the location of its implementation.
Use this placement rule:
-- Put a stable type or function required by consumers in `include/scopeone`.
+- Put every public Core model, plugin contract and shared plugin data type in `ScopeOneCore/include/scopeone`.
- Put a Core-only manager, algorithm or protocol detail in `internal`.
- Put executable implementation in `src`.
- Keep desktop widgets and Qt UI behavior in the top-level ScopeOne `src` directory, outside `ScopeOneCore`.
@@ -42,9 +42,10 @@ Use this placement rule:
| Namespace | Purpose | Examples |
|---|---|---|
-| `scopeone::core` | Stable Core-facing types and public facades | `ScopeOneCore`, `ImageFrame`, `ExperimentDocument`, `ImageSceneModel` |
+| `scopeone::core` | Stable Core-facing types and public facades | `ScopeOneCore`, `ExperimentDocument`, `ImageSceneModel` |
+| `scopeone::core` SDK contracts | Stable plugin-facing types and interfaces | `ImageFrame`, `CameraProvider`, `DaqDevice`, `SignalSource`, `ProcessingPlugin`, `ToolPlugin` |
| `scopeone::core::internal` | Core-only managers and processing implementations | `CameraManager`, `MMCoreManager`, `RecordingManager`, processing modules |
-| `scopeone::core::internal::agent` | Private camera-agent protocol details | Agent request, response and frame transport types |
+| `scopeone::core::internal::driverhost` | Shared DriverHost message framing | Versioned request, response and event envelopes |
| `scopeone::ui` | Desktop application widgets and UI coordination outside this library | `MainWindow`, `PreviewWidget`, `InspectWidget` |
Code in `src` that implements a public type remains in `scopeone::core`. Code that implements an `internal` header remains in `scopeone::core::internal`. The Python package named `scopeone` is an external client package and is not an embedded form of the C++ namespace.
@@ -87,29 +88,45 @@ Outputs:
- `build/Release/ScopeOneCore.dll`
- `build/Release/ScopeOneCore.lib`
-- `build/Release/ScopeOne_Agent.exe`
+- `build/Release/ScopeOne_DriverHost.exe`
+- External plugins are built under `plugins`.
- `build/ScopeOneCoreConfig.cmake`
- `install/bin/ScopeOneCore.dll`
-- `install/bin/ScopeOne_Agent.exe`
+- `install/bin/ScopeOne_DriverHost.exe`
+- Hardware, DAQ and signal source plugins are installed under `plugins/hardware`.
- `install/lib/cmake/ScopeOneCore/ScopeOneCoreConfig.cmake`
-## Public API
+## Core and SDK API
-The installed headers are the source of truth for the public API:
+The installed headers in `include/scopeone` are the source of truth for the public API:
- `ScopeOneCore.h` provides the main hardware, acquisition, processing, recording and frame-graph facade.
-- `ImageFrame.h` defines the image payload and metadata exchanged across Core features.
-- `ExperimentDocument.h` defines experiment plans, results, persistence and provenance.
-- `ImageSceneModel.h` defines shared image-layer, display-state and markup state.
-- `SharedFrame.h` defines the language-neutral shared-memory frame layout.
+- `SimulatorProvider.h`, `ProcessingPipeline.h`, `ExperimentDocument.h` and `ImageSceneModel.h` are Core-owned runtime models and services.
+- The SDK provides `HardwareProvider.h`, `HardwareCapabilities.h`, `CameraProvider.h`, `DriverHostProviderPlugin.h`, `DaqDevice.h`, `SignalSource.h`, `ScanImageAssembler.h`, `ProcessingPlugin.h`, `ToolPlugin.h`, `PluginManifest.h`, `HardwareTypes.h`, `ImageFrame.h` and `SharedFrame.h`.
- `scopeone_core_export.h` supplies DLL import and export declarations and is normally included indirectly.
External code should enter through these headers and `scopeone::core::ScopeOneCore`. Internal managers are implementation details and must not become alternate access paths.
+Providers use ScopeOne logical device IDs and publish `ImageFrame` objects through `CameraProvider::FrameSink`. Register in-process providers with `ScopeOneCore::registerHardwareProvider(...)`. Submit isolated module loading with `ScopeOneCore::registerDriverHostProvider(providerId, modulePath, options)` and observe `hardwareProviderRegistrationFinished` for the result. One DriverHost process owns the complete Provider and registers all of its cameras and control devices together. Micro-Manager remains the built-in provider, using the native camera path for one camera and isolated DriverHost camera processes for multiple cameras.
+
+## Plugin Boundaries
+
+- `plugins/hardware` contains native `HardwareProvider` modules. Each provider runs in an isolated DriverHost process. Micro-Manager Device Adapters remain under Micro-Manager and are not wrapped as ScopeOne plugins.
+- `plugins/hardware` contains DAQ and signal source plugins alongside native `HardwareProvider` modules. The Core selects each plugin by its interface without linking DAQ vendor libraries into the application.
+- `ScanImageAssembler` is implemented in Core as a provider-independent 1D-to-2D reconstruction algorithm, while its public contract is owned by the SDK. The Core publishes reconstructed frames through the shared frame graph and Gallery session path.
+- `plugins/processing` contains `ProcessingPlugin` modules loaded by ScopeOneCore. A plugin publishes stable module IDs, parameter descriptors and factories. Built-in processing methods use the same registry.
+- `plugins/tools` contains optional desktop `ScopeOneToolPlugin` modules. These receive a restricted UI context rather than direct access to `MainWindow` or `PreviewWidget`. Built-in Scale, Stage Mosaic and Particle Detection tools use the same registry.
+
+External projects consume the exported `scopeone::PluginSDK` CMake target. Every plugin manifest declares `id`, `name`, `version`, `scopeOneApi`, and `kind`; incompatible manifests are rejected before the plugin instance is created.
+
+Hardware, processing, DAQ, signal-source and tool contracts are installed SDK APIs. Desktop tool plugins target the ScopeOne application UI contract exposed by the SDK.
+
## Processing Data Flow
-`ImageFrame` is the frame model used by preview, processing, recording, gallery and the local API. Use `processFrameThrough(...)` to stop at one pipeline stage and `processFrameFrom(...)` to continue from a later module after an edited frame is written back. Saved OME-TIFF, OME-Zarr, TIFF and binary recording outputs are read back asynchronously through `ScopeOneCore::requestRecordingSessionFrame(...)`. Live preview processing and synchronous API processing use separate runtime pipeline state so offline frame edits do not change live module buffers.
+`ImageFrame` is the frame model used by preview, processing, recording, gallery and the local API. Processing recipes persist stable module IDs rather than registry positions. Available modules and their parameter descriptors come from the processing registry. Use `processFrameThrough(...)` to stop at one pipeline stage and `processFrameFrom(...)` to continue from a later module after an edited frame is written back. Saved OME-TIFF, OME-Zarr, TIFF and binary recording outputs are read back asynchronously through `ScopeOneCore::requestRecordingSessionFrame(...)`.
+
+Real-time processing can consume all camera streams or one camera selected with `setRealTimeProcessingSource(...)`. `requestImageProcessing(...)` applies an isolated pipeline to one current image. `requestRecordingSessionStackProcessing(...)` applies one stateful isolated runtime to a complete session camera stack, reports progress, supports cancellation and creates a new in-memory Gallery session. These offline paths do not change live module buffers. The Local API can list, open, activate, process, save and close independent image windows backed by retained sessions.
Raw live frames, processed live frames, static tool/gallery frames, external API frames and session frame sources are routed through the core frame graph. UI preview widgets keep only a render cache, and callers should use `ScopeOneCore` frame facade methods instead of reading camera managers, recording sessions or preview cache state directly.
diff --git a/ScopeOneCore/external/ScopeWriter b/ScopeOneCore/external/ScopeWriter
index 87809f6..69f6bcb 160000
--- a/ScopeOneCore/external/ScopeWriter
+++ b/ScopeOneCore/external/ScopeWriter
@@ -1 +1 @@
-Subproject commit 87809f65cf43b55c248a67b23b52c2057b14d976
+Subproject commit 69f6bcb952cecdcc01ffb7ebf76f3edcb17cbe87
diff --git a/ScopeOneCore/include/scopeone/CameraProvider.h b/ScopeOneCore/include/scopeone/CameraProvider.h
new file mode 100644
index 0000000..97a95f4
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/CameraProvider.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include
+#include
+
+#include
+
+#include "scopeone/HardwareCapabilities.h"
+#include "scopeone/ImageFrame.h"
+
+namespace scopeone::core
+{
+ class SCOPEONE_SDK_EXPORT CameraProvider : public DevicePropertyProvider
+ {
+ public:
+ using FrameSink = std::function;
+ using PreviewStateSink = std::function;
+
+ ~CameraProvider() override;
+
+ virtual void setFrameSink(FrameSink sink) = 0;
+ virtual void setPreviewStateSink(PreviewStateSink sink) = 0;
+ virtual bool startPreview() = 0;
+ virtual bool stopPreview() = 0;
+ virtual bool startPreviewFor(const QString& cameraId) = 0;
+ virtual bool stopPreviewFor(const QString& cameraId) = 0;
+ virtual bool isPreviewRunning(const QString& cameraId) const = 0;
+ virtual bool getExposure(const QString& cameraIdOrAll, double& exposureMs) const = 0;
+ virtual bool setExposure(const QString& cameraIdOrAll, double exposureMs) = 0;
+ virtual bool setROI(const QString& cameraId, int x, int y, int width, int height) = 0;
+ virtual bool clearROI(const QString& cameraId) = 0;
+ virtual bool getROI(const QString& cameraId,
+ int& x,
+ int& y,
+ int& width,
+ int& height) = 0;
+ virtual bool captureEventFrame(const QString& cameraId,
+ ImageFrame& frame,
+ int timeoutMs = 1500) = 0;
+ };
+}
diff --git a/ScopeOneCore/include/scopeone/DaqDevice.h b/ScopeOneCore/include/scopeone/DaqDevice.h
new file mode 100644
index 0000000..f669265
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/DaqDevice.h
@@ -0,0 +1,192 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "scopeone/scopeone_sdk_export.h"
+
+namespace scopeone::core
+{
+ enum class DaqChannelType
+ {
+ AnalogInput,
+ AnalogOutput,
+ DigitalInput,
+ DigitalOutput,
+ CounterInput,
+ CounterOutput
+ };
+
+ enum class DaqEdge
+ {
+ Rising,
+ Falling
+ };
+
+ enum class DaqState
+ {
+ Idle,
+ Armed,
+ Running,
+ Error
+ };
+
+ enum class DaqTaskDirection
+ {
+ Input,
+ Output
+ };
+
+ enum class DaqSampleMode
+ {
+ Finite,
+ Continuous
+ };
+
+ struct DaqChannelDescriptor
+ {
+ QString physicalName;
+ DaqChannelType type{DaqChannelType::DigitalInput};
+ };
+
+ struct DaqDeviceDescriptor
+ {
+ QString id;
+ QString name;
+ QString provider;
+ QString product;
+ QList channels;
+ QStringList terminals;
+ };
+
+ struct DaqPulseTaskConfig
+ {
+ QString name;
+ QString counter;
+ QString outputTerminal;
+ double frequencyHz{1000.0};
+ double dutyCycle{0.5};
+ double initialDelaySeconds{0.0};
+ QString startTrigger;
+ DaqEdge startEdge{DaqEdge::Rising};
+ QString timebaseSource;
+ quint32 initialDelayTicks{0};
+ quint32 lowTicks{0};
+ quint32 highTicks{0};
+ };
+
+ struct DaqTaskTiming
+ {
+ QString sampleClock;
+ double sampleRateHz{1000.0};
+ DaqEdge sampleEdge{DaqEdge::Rising};
+ DaqSampleMode sampleMode{DaqSampleMode::Finite};
+ quint64 samplesPerChannel{1};
+ QString startTrigger;
+ DaqEdge startEdge{DaqEdge::Rising};
+ };
+
+ struct DaqAnalogTaskConfig
+ {
+ QString name;
+ DaqTaskDirection direction{DaqTaskDirection::Input};
+ QStringList channels;
+ double minimumVolts{-10.0};
+ double maximumVolts{10.0};
+ DaqTaskTiming timing;
+ QVector outputSamplesByScan;
+ };
+
+ struct DaqDigitalTaskConfig
+ {
+ QString name;
+ DaqTaskDirection direction{DaqTaskDirection::Input};
+ QStringList lines;
+ DaqTaskTiming timing;
+ QVector outputSamplesByScan;
+ };
+
+ struct DaqInputChunk
+ {
+ QString deviceId;
+ QString taskName;
+ QStringList channels;
+ quint64 firstSample{0};
+ double nominalSampleRateHz{0.0};
+ QVector analogSamplesByScan;
+ QVector digitalSamplesByScan;
+ };
+
+ struct DaqTerminalRoute
+ {
+ QString source;
+ QString destination;
+ bool inverted{false};
+ };
+
+ struct DaqRasterScanConfig
+ {
+ QString name;
+ QString lineClock;
+ double nominalLineRateHz{1000.0};
+ quint32 activeLines{512};
+ quint32 flybackLines{16};
+ QString yChannel;
+ double yStartVolts{-1.0};
+ double yEndVolts{1.0};
+ QString frameCounter;
+ QString lineOutputTerminal;
+ QString frameOutputTerminal;
+ };
+
+ struct DaqSessionConfig
+ {
+ QString deviceId;
+ QList rasterScans;
+ QList pulseTasks;
+ QList analogTasks;
+ QList digitalTasks;
+ QList routes;
+ };
+
+ class SCOPEONE_SDK_EXPORT DaqController : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ explicit DaqController(QObject* parent = nullptr);
+ ~DaqController() override;
+
+ virtual bool start(const DaqSessionConfig& config,
+ QString* errorMessage = nullptr) = 0;
+ virtual void stop() = 0;
+ virtual DaqState state() const = 0;
+ virtual QString stateMessage() const = 0;
+
+ signals:
+ void stateChanged(scopeone::core::DaqState state,
+ const QString& message);
+ void controllerError(const QString& errorMessage);
+ void inputDataReady(const scopeone::core::DaqInputChunk& chunk);
+ };
+
+ class DaqDevicePlugin
+ {
+ public:
+ virtual ~DaqDevicePlugin() = default;
+ virtual QList devices() const = 0;
+ virtual DaqController* createController(const QString& deviceId,
+ QObject* parent = nullptr) = 0;
+ };
+}
+
+#define SCOPEONE_DAQ_DEVICE_PLUGIN_IID "org.scopeone.DaqDevicePlugin/1.0"
+Q_DECLARE_INTERFACE(scopeone::core::DaqDevicePlugin, SCOPEONE_DAQ_DEVICE_PLUGIN_IID)
+
+Q_DECLARE_METATYPE(scopeone::core::DaqState)
+Q_DECLARE_METATYPE(scopeone::core::DaqInputChunk)
diff --git a/ScopeOneCore/include/scopeone/DriverHostProviderPlugin.h b/ScopeOneCore/include/scopeone/DriverHostProviderPlugin.h
new file mode 100644
index 0000000..5e7caec
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/DriverHostProviderPlugin.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include
+#include
+
+#include "scopeone/HardwareProvider.h"
+
+namespace scopeone::core
+{
+ class DriverHostProviderPlugin
+ {
+ public:
+ virtual ~DriverHostProviderPlugin() = default;
+
+ virtual QString providerId() const = 0;
+ virtual HardwareProviderPtr createProvider(const QJsonObject& options,
+ QString* errorMessage = nullptr) = 0;
+ };
+}
+
+#define ScopeOneDriverHostProviderPlugin_iid "org.scopeone.DriverHostProviderPlugin/1.0"
+Q_DECLARE_INTERFACE(scopeone::core::DriverHostProviderPlugin,
+ ScopeOneDriverHostProviderPlugin_iid)
diff --git a/ScopeOneCore/include/scopeone/ExperimentDocument.h b/ScopeOneCore/include/scopeone/ExperimentDocument.h
index 6dc7e41..cc59837 100644
--- a/ScopeOneCore/include/scopeone/ExperimentDocument.h
+++ b/ScopeOneCore/include/scopeone/ExperimentDocument.h
@@ -35,16 +35,6 @@ namespace scopeone::core
XY = 2
};
- enum class ProcessingModuleKind
- {
- FFT = 0,
- BackgroundCalibration = 2,
- SpatiotemporalBinning = 3,
- GaussianBlur = 4,
- DifferentialRolling = 5,
- Unknown = 255
- };
-
enum class ProcessingBitDepth
{
Bit8 = 8,
@@ -64,6 +54,7 @@ namespace scopeone::core
{
Raw,
Processed,
+ Tool,
Static,
Gallery
};
@@ -84,7 +75,7 @@ namespace scopeone::core
struct ProcessingModuleRecipe
{
- ProcessingModuleKind kind{ProcessingModuleKind::Unknown};
+ QString moduleId;
int schemaVersion{kProcessingModuleSchemaVersion};
QVariantMap parameters;
};
@@ -265,7 +256,6 @@ namespace scopeone::core
};
SCOPEONE_CORE_EXPORT QString recordingAxisName(RecordingAxis axis);
- SCOPEONE_CORE_EXPORT QString processingModuleKindName(ProcessingModuleKind kind);
SCOPEONE_CORE_EXPORT QString experimentRunStateName(ExperimentRunState state);
SCOPEONE_CORE_EXPORT QString documentLayerKindName(DocumentLayerKind kind);
SCOPEONE_CORE_EXPORT QString documentMarkupTypeName(DocumentMarkupType type);
diff --git a/ScopeOneCore/include/scopeone/HardwareCapabilities.h b/ScopeOneCore/include/scopeone/HardwareCapabilities.h
new file mode 100644
index 0000000..6930952
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/HardwareCapabilities.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#include
+#include
+
+#include "scopeone/scopeone_sdk_export.h"
+
+namespace scopeone::core
+{
+ class SCOPEONE_SDK_EXPORT DevicePropertyProvider
+ {
+ public:
+ virtual ~DevicePropertyProvider();
+
+ virtual QStringList listProperties(const QString& deviceId) = 0;
+ virtual QString getProperty(const QString& deviceId,
+ const QString& name,
+ bool fromCache = false) = 0;
+ virtual bool setProperty(const QString& deviceId,
+ const QString& name,
+ const QString& value,
+ QString* errorMessage = nullptr) = 0;
+ virtual QString getPropertyType(const QString& deviceId, const QString& name) = 0;
+ virtual bool isPropertyReadOnly(const QString& deviceId, const QString& name) = 0;
+ virtual bool isPropertyPreInit(const QString& deviceId, const QString& name) = 0;
+ virtual QStringList getAllowedPropertyValues(const QString& deviceId,
+ const QString& name) = 0;
+ virtual bool hasPropertyLimits(const QString& deviceId, const QString& name) = 0;
+ virtual double getPropertyLowerLimit(const QString& deviceId, const QString& name) = 0;
+ virtual double getPropertyUpperLimit(const QString& deviceId, const QString& name) = 0;
+ };
+
+ class SCOPEONE_SDK_EXPORT StageProvider
+ {
+ public:
+ virtual ~StageProvider();
+
+ virtual QString defaultXYStage() const = 0;
+ virtual QString defaultZStage() const = 0;
+ virtual bool getXYPosition(const QString& deviceId,
+ double& x,
+ double& y,
+ QString* errorMessage = nullptr) const = 0;
+ virtual bool getZPosition(const QString& deviceId,
+ double& z,
+ QString* errorMessage = nullptr) const = 0;
+ virtual bool setRelativeXYPosition(const QString& deviceId,
+ double dx,
+ double dy,
+ QString* errorMessage = nullptr) = 0;
+ virtual bool setRelativeZPosition(const QString& deviceId,
+ double dz,
+ QString* errorMessage = nullptr) = 0;
+ virtual bool setXYPosition(const QString& deviceId,
+ double x,
+ double y,
+ QString* errorMessage = nullptr) = 0;
+ virtual bool setZPosition(const QString& deviceId,
+ double z,
+ QString* errorMessage = nullptr) = 0;
+ };
+
+ class SCOPEONE_SDK_EXPORT ShutterProvider
+ {
+ public:
+ virtual ~ShutterProvider();
+
+ virtual bool isShutterOpen(const QString& deviceId,
+ bool& open,
+ QString* errorMessage = nullptr) const = 0;
+ virtual bool setShutterOpen(const QString& deviceId,
+ bool open,
+ QString* errorMessage = nullptr) = 0;
+ };
+
+ class SCOPEONE_SDK_EXPORT StateProvider
+ {
+ public:
+ virtual ~StateProvider();
+
+ virtual bool getState(const QString& deviceId,
+ long& state,
+ QString* errorMessage = nullptr) const = 0;
+ virtual bool setState(const QString& deviceId,
+ long state,
+ QString* errorMessage = nullptr) = 0;
+ virtual QString stateLabel(const QString& deviceId, long state) const = 0;
+ };
+
+ class SCOPEONE_SDK_EXPORT ConfigurationProvider
+ {
+ public:
+ virtual ~ConfigurationProvider();
+
+ virtual QStringList availableConfigGroups() const = 0;
+ virtual QStringList availableConfigs(const QString& groupName) const = 0;
+ virtual QString currentConfig(const QString& groupName) const = 0;
+ virtual bool setConfig(const QString& groupName,
+ const QString& configName,
+ QString* errorMessage = nullptr) = 0;
+ };
+}
diff --git a/ScopeOneCore/include/scopeone/HardwareProvider.h b/ScopeOneCore/include/scopeone/HardwareProvider.h
new file mode 100644
index 0000000..351b38d
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/HardwareProvider.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include
+#include
+
+#include
+
+#include "scopeone/HardwareTypes.h"
+#include "scopeone/scopeone_sdk_export.h"
+
+namespace scopeone::core
+{
+ class SCOPEONE_SDK_EXPORT HardwareProvider
+ {
+ public:
+ virtual ~HardwareProvider();
+
+ virtual HardwareProviderDescriptor descriptor() const = 0;
+ virtual QList devices() const = 0;
+ };
+
+ using HardwareProviderPtr = std::shared_ptr;
+}
diff --git a/ScopeOneCore/include/scopeone/HardwareTypes.h b/ScopeOneCore/include/scopeone/HardwareTypes.h
new file mode 100644
index 0000000..19730af
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/HardwareTypes.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "scopeone/scopeone_sdk_export.h"
+
+namespace scopeone::core
+{
+ enum class HardwareDeviceKind
+ {
+ Unknown,
+ Camera,
+ XYStage,
+ ZStage,
+ Shutter,
+ State,
+ Hub,
+ Serial,
+ Generic,
+ AutoFocus,
+ ImageProcessor,
+ SignalIO,
+ Magnifier,
+ SLM,
+ Galvo,
+ PressurePump,
+ VolumetricPump
+ };
+
+ enum class HardwareDeviceState
+ {
+ Unknown,
+ Discovered,
+ Initialized,
+ Faulted,
+ Unavailable
+ };
+
+ enum class HardwareEndpointKind
+ {
+ InProcess,
+ DriverHost
+ };
+
+ struct SCOPEONE_SDK_EXPORT HardwareProviderDescriptor
+ {
+ QString id;
+ QString name;
+ QString version;
+ };
+
+ struct SCOPEONE_SDK_EXPORT HardwareDeviceDescriptor
+ {
+ QString logicalId;
+ QString providerId;
+ QString providerDeviceId;
+ QString hardwareId;
+ QString name;
+ HardwareDeviceKind kind{HardwareDeviceKind::Unknown};
+ HardwareDeviceState state{HardwareDeviceState::Unknown};
+ HardwareEndpointKind endpoint{HardwareEndpointKind::InProcess};
+ QVariantMap properties;
+ };
+
+}
+
+Q_DECLARE_METATYPE(scopeone::core::HardwareDeviceKind)
+Q_DECLARE_METATYPE(scopeone::core::HardwareDeviceState)
+Q_DECLARE_METATYPE(scopeone::core::HardwareEndpointKind)
+Q_DECLARE_METATYPE(scopeone::core::HardwareProviderDescriptor)
+Q_DECLARE_METATYPE(scopeone::core::HardwareDeviceDescriptor)
diff --git a/ScopeOneCore/include/scopeone/PluginManifest.h b/ScopeOneCore/include/scopeone/PluginManifest.h
new file mode 100644
index 0000000..b7dd545
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/PluginManifest.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "scopeone/scopeone_sdk_export.h"
+
+#include
+#include
+
+namespace scopeone::core
+{
+ enum class PluginKind
+ {
+ Processing,
+ Tool,
+ Hardware
+ };
+
+ struct PluginManifest
+ {
+ QString id;
+ QString name;
+ QString version;
+ PluginKind kind{PluginKind::Processing};
+ bool autoLoad{false};
+ QJsonObject metadata;
+ };
+
+ inline constexpr int ScopeOnePluginApiVersion = 1;
+
+ SCOPEONE_SDK_EXPORT QString pluginKindName(PluginKind kind);
+ SCOPEONE_SDK_EXPORT bool parsePluginManifest(const QJsonObject& metadata,
+ PluginKind expectedKind,
+ PluginManifest& manifest,
+ QString* errorMessage = nullptr);
+}
diff --git a/ScopeOneCore/include/scopeone/PluginSDK.h b/ScopeOneCore/include/scopeone/PluginSDK.h
new file mode 100644
index 0000000..4cdb85a
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/PluginSDK.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "scopeone/DriverHostProviderPlugin.h"
+#include "scopeone/DaqDevice.h"
+#include "scopeone/HardwareCapabilities.h"
+#include "scopeone/HardwareProvider.h"
+#include "scopeone/HardwareTypes.h"
+#include "scopeone/ImageFrame.h"
+#include "scopeone/PluginManifest.h"
+#include "scopeone/ProcessingPlugin.h"
+#include "scopeone/ScanImageAssembler.h"
+#include "scopeone/SignalSource.h"
+#include "scopeone/ToolFrameStream.h"
+#include "scopeone/ToolPlugin.h"
+#include "scopeone/ToolTask.h"
diff --git a/ScopeOneCore/include/scopeone/ProcessingPipeline.h b/ScopeOneCore/include/scopeone/ProcessingPipeline.h
new file mode 100644
index 0000000..a9d8948
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/ProcessingPipeline.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "scopeone/ProcessingPlugin.h"
+#include "scopeone/scopeone_core_export.h"
+
+#include
+
+namespace scopeone::core
+{
+ class SCOPEONE_CORE_EXPORT ProcessingPipeline
+ {
+ public:
+ ProcessingPipeline();
+ ~ProcessingPipeline();
+
+ ProcessingPipeline(ProcessingPipeline&&) noexcept;
+ ProcessingPipeline& operator=(ProcessingPipeline&&) noexcept;
+
+ ProcessingPipeline(const ProcessingPipeline&) = delete;
+ ProcessingPipeline& operator=(const ProcessingPipeline&) = delete;
+
+ bool addModule(std::unique_ptr module);
+ bool removeModule(int index);
+ bool setModuleParameters(int index, const QVariantMap& parameters);
+ bool resetModuleState(int index);
+ int moduleCount() const;
+
+ ProcessingResult process(const ProcessingValue& input, int processingBitDepth = 16) const;
+
+ private:
+ struct Impl;
+ std::unique_ptr m_impl;
+ };
+}
diff --git a/ScopeOneCore/include/scopeone/ProcessingPlugin.h b/ScopeOneCore/include/scopeone/ProcessingPlugin.h
new file mode 100644
index 0000000..d48b9d6
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/ProcessingPlugin.h
@@ -0,0 +1,158 @@
+#pragma once
+
+#include "scopeone/ImageFrame.h"
+#include "scopeone/scopeone_sdk_export.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace scopeone::core
+{
+ enum class ProcessingParameterType
+ {
+ Integer,
+ Real,
+ Boolean,
+ Choice
+ };
+
+ struct ProcessingParameterChoice
+ {
+ QString name;
+ QVariant value;
+ };
+
+ struct ProcessingParameterDescriptor
+ {
+ QString key;
+ QString name;
+ ProcessingParameterType type{ProcessingParameterType::Integer};
+ QVariant defaultValue;
+ QVariant minimum;
+ QVariant maximum;
+ QVariant step;
+ int decimals{0};
+ QList choices;
+ };
+
+ struct ProcessingModuleDescriptor
+ {
+ QString id;
+ QString name;
+ int schemaVersion{1};
+ QList parameters;
+ bool resettable{false};
+ };
+
+ struct ComplexFrame
+ {
+ QString sourceId;
+ int width{0};
+ int height{0};
+ int stride{0};
+ int sourceWidth{0};
+ int sourceHeight{0};
+ quint64 frameIndex{0};
+ quint64 timestampNs{0};
+ QByteArray real;
+ QByteArray imaginary;
+
+ bool isValid() const
+ {
+ const qint64 samples = static_cast(stride) * height;
+ const qint64 bytes = samples * static_cast(sizeof(float));
+ return width > 0 && height > 0 && stride >= width && bytes > 0
+ && real.size() == bytes && imaginary.size() == bytes;
+ }
+ };
+
+ using ProcessingValue = std::variant;
+
+ struct ProcessingResult
+ {
+ ImageFrame frame;
+ ProcessingValue value;
+ QString error;
+
+ ProcessingResult() = default;
+ ProcessingResult(const ImageFrame& output, QString message = {})
+ : frame(output), value(output), error(std::move(message)) {}
+ ProcessingResult(ImageFrame&& output, QString message = {})
+ : frame(output), value(std::move(output)), error(std::move(message)) {}
+ ProcessingResult(ProcessingValue output, QString message = {})
+ : value(std::move(output)), error(std::move(message))
+ {
+ if (std::holds_alternative(value))
+ {
+ frame = std::get(value);
+ }
+ }
+
+ bool succeeded() const
+ {
+ if (!error.isEmpty())
+ {
+ return false;
+ }
+ if (std::holds_alternative(value))
+ {
+ return std::get(value).isValid();
+ }
+ return std::get(value).isValid();
+ }
+
+ bool hasImage() const
+ {
+ return std::holds_alternative(value)
+ && std::get(value).isValid();
+ }
+ };
+
+ class SCOPEONE_SDK_EXPORT ProcessingModule
+ {
+ public:
+ virtual ~ProcessingModule() = default;
+
+ bool isEnabled() const { return m_enabled; }
+ void setEnabled(bool enabled) { m_enabled = enabled; }
+
+ virtual QString id() const = 0;
+ virtual QString name() const = 0;
+ virtual QVariantMap parameters() const = 0;
+ virtual void setParameters(const QVariantMap& parameters) = 0;
+ virtual std::unique_ptr createRuntime() const = 0;
+ virtual bool resetState() { return false; }
+ virtual ProcessingResult process(const ImageFrame& frame, int processingBitDepth) = 0;
+
+ virtual ProcessingResult processValue(const ProcessingValue& input,
+ int processingBitDepth)
+ {
+ if (!std::holds_alternative(input))
+ {
+ return ProcessingResult(ImageFrame{}, QStringLiteral("Module requires an image input"));
+ }
+ return process(std::get(input), processingBitDepth);
+ }
+
+ private:
+ bool m_enabled{true};
+ };
+
+ class ProcessingPlugin
+ {
+ public:
+ virtual ~ProcessingPlugin() = default;
+
+ virtual QList processingModules() const = 0;
+ virtual std::unique_ptr createProcessingModule(const QString& moduleId) = 0;
+ };
+}
+
+#define ScopeOneProcessingPlugin_iid "org.scopeone.ProcessingPlugin/1.0"
+Q_DECLARE_INTERFACE(scopeone::core::ProcessingPlugin, ScopeOneProcessingPlugin_iid)
diff --git a/ScopeOneCore/include/scopeone/ScanImageAssembler.h b/ScopeOneCore/include/scopeone/ScanImageAssembler.h
new file mode 100644
index 0000000..a513681
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/ScanImageAssembler.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "scopeone/ImageFrame.h"
+#include "scopeone/SignalSource.h"
+#include "scopeone/scopeone_sdk_export.h"
+
+#include
+#include
+
+namespace scopeone::core
+{
+ class SCOPEONE_SDK_EXPORT ScanImageAssembler final
+ {
+ public:
+ explicit ScanImageAssembler(const QString& sourceId,
+ const ScanImageConfig& config);
+
+ bool isValid() const;
+ void reset();
+ QList append(const TimestampedEventChunk& chunk);
+ QList finish();
+
+ private:
+ void handleMarker(quint64 tick, quint32 code);
+ void beginFrame();
+ void beginLine(quint64 tick);
+ void finishLine(quint64 tick);
+ void finishFrame(quint64 tick);
+ void appendEvent(quint64 tick);
+ void emitFrame(const QVector& pixels, quint64 tick);
+ void emitAveragedFrame(quint64 tick);
+
+ QString m_sourceId;
+ ScanImageConfig m_config;
+ QVector m_framePixels;
+ QVector m_accumulatedPixels;
+ QVector m_lineEventTicks;
+ QList m_readyFrames;
+ int m_nextRow{0};
+ int m_accumulatedFrameCount{0};
+ bool m_frameActive{false};
+ bool m_lineActive{false};
+ quint64 m_lineStartTick{0};
+ quint64 m_lastLineDurationTicks{0};
+ quint64 m_lastFrameTick{0};
+ double m_tickPeriodSeconds{0.0};
+ quint64 m_nextFrameIndex{0};
+ };
+}
+
+Q_DECLARE_METATYPE(scopeone::core::ScanImageConfig)
diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h
index 29104b5..958fb37 100644
--- a/ScopeOneCore/include/scopeone/ScopeOneCore.h
+++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h
@@ -7,6 +7,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -14,12 +16,19 @@
#include
#include
#include
+#include
#include
#include
#include
#include "scopeone/ExperimentDocument.h"
+#include "scopeone/DaqDevice.h"
+#include "scopeone/HardwareTypes.h"
+#include "scopeone/HardwareProvider.h"
#include "scopeone/ImageFrame.h"
+#include "scopeone/ProcessingPlugin.h"
+#include "scopeone/ProcessingPipeline.h"
+#include "scopeone/SignalSource.h"
#include "scopeone/scopeone_core_export.h"
class CMMCore;
@@ -49,7 +58,6 @@ namespace scopeone::core
public:
using RecordingAxis = scopeone::core::RecordingAxis;
- using ProcessingModuleKind = scopeone::core::ProcessingModuleKind;
using ProcessingBitDepth = scopeone::core::ProcessingBitDepth;
using RecordingFileManifest = scopeone::core::RecordingFileManifest;
using RecordingOutputManifest = scopeone::core::RecordingOutputManifest;
@@ -71,6 +79,7 @@ namespace scopeone::core
int failCount{0};
int skippedCameraCount{0};
bool foundCamera{false};
+ QList devices;
};
struct HistogramStats
@@ -447,17 +456,23 @@ namespace scopeone::core
class ProcessingModuleInfo
{
public:
- ProcessingModuleKind kind() const { return m_kind; }
+ const QString& id() const { return m_id; }
const QString& name() const { return m_name; }
const QVariantMap& parameters() const { return m_parameters; }
- void setKind(ProcessingModuleKind kind) { m_kind = kind; }
+ const ProcessingModuleDescriptor& descriptor() const { return m_descriptor; }
+ bool enabled() const { return m_enabled; }
+ void setId(const QString& id) { m_id = id; }
void setName(const QString& name) { m_name = name; }
void setParameters(const QVariantMap& parameters) { m_parameters = parameters; }
+ void setDescriptor(const ProcessingModuleDescriptor& descriptor) { m_descriptor = descriptor; }
+ void setEnabled(bool enabled) { m_enabled = enabled; }
private:
- ProcessingModuleKind m_kind{ProcessingModuleKind::Unknown};
+ QString m_id;
QString m_name;
QVariantMap m_parameters;
+ ProcessingModuleDescriptor m_descriptor;
+ bool m_enabled{true};
};
class DevicePropertyInfo
@@ -508,10 +523,12 @@ namespace scopeone::core
static QString getZlibVersion();
static QString rawLayerKey(const QString& cameraId);
static QString processedLayerKey(const QString& cameraId);
+ static QString toolLayerKey(const QString& sourceId);
static QString staticLayerKey(const QString& sourceId);
static QString sourceIdFromLayerKey(const QString& layerKey);
static bool isRawLayerKey(const QString& layerKey);
static bool isProcessedLayerKey(const QString& layerKey);
+ static bool isToolLayerKey(const QString& layerKey);
static bool isStaticLayerKey(const QString& layerKey);
ImageSceneModel* imageSceneModel() const { return m_imageSceneModel; }
@@ -527,6 +544,13 @@ namespace scopeone::core
bool setAdditionalDeviceAdapterSearchPaths(const QStringList& paths);
QStringList cameraIds() const { return m_cameraIds; }
+ QList hardwareDevices() const;
+ bool registerHardwareProvider(const HardwareProviderPtr& provider);
+ bool registerDriverHostProvider(const QString& providerId,
+ const QString& modulePath,
+ const QVariantMap& options = {},
+ QString* errorMessage = nullptr);
+ bool unregisterHardwareProvider(const QString& providerId);
QStringList runningPreviewCameraIds() const;
double cameraPixelSizeUm(const QString& cameraId) const;
bool setCameraPixelSizeUm(const QString& cameraId, double pixelSizeUm);
@@ -538,8 +562,22 @@ namespace scopeone::core
bool setHalfROI(const QString& cameraId);
bool clearROI(const QString& cameraId);
bool getROI(const QString& cameraId, int& x, int& y, int& width, int& height);
+ QList signalSources() const;
+ bool startSignalTrace(const SignalAcquisitionConfig& config,
+ QString* errorMessage = nullptr);
+ void stopSignalTrace(const QString& sourceId);
+ SignalSourceState signalSourceState(const QString& sourceId) const;
+ QString signalSourceStateMessage(const QString& sourceId) const;
+ QList daqDevices() const;
+ bool startDaqSession(const DaqSessionConfig& config,
+ QString* errorMessage = nullptr);
+ void stopDaqSession(const QString& deviceId);
+ DaqState daqState(const QString& deviceId) const;
+ QString daqStateMessage(const QString& deviceId) const;
ImageFrame graphFrame(const QString& layerKey) const;
QList graphFrames(const QStringList& layerKeys) const;
+ double layerFrameRate(const QString& layerKey) const;
+ QMap layerFrameRates() const;
bool graphPixelValue(const QString& layerKey, const QPoint& imagePos, int& value) const;
std::shared_ptr createFrameSession(
const QList& frames,
@@ -547,6 +585,17 @@ namespace scopeone::core
ImageFrame publishStaticFrame(const QString& sourceId,
const ImageFrame& frame,
const QString& displayName = QString());
+ ImageFrame importImageAsStaticLayer(const QString& filePath,
+ QString* outLayerKey = nullptr,
+ QString* errorMessage = nullptr);
+ void importImageAsStaticLayerAsync(const QString& filePath);
+ QString importSessionAsStaticLayer(
+ const std::shared_ptr& session);
+ int layerSliceCount(const QString& layerKey) const;
+ bool setLayerSliceIndex(const QString& layerKey, int sliceIndex);
+ ImageFrame publishToolStreamFrame(const QString& sourceId,
+ const ImageFrame& frame,
+ const QString& displayName = QString());
ImageFrame publishExternalFrame(const QString& sourceId, const ImageFrame& frame);
void removeStaticFrame(const QString& sourceId);
void clearStaticFrames();
@@ -568,6 +617,12 @@ namespace scopeone::core
int minArea,
int maxArea,
int maxParticles = 10000);
+ quint64 detectParticles(const ImageFrame& frame,
+ const QString& resultLayerKey,
+ int threshold,
+ int minArea,
+ int maxArea,
+ int maxParticles = 10000);
static bool computeHistogramStats(const ImageFrame& frame, HistogramStats& stats);
bool startStageMosaic(const StageMosaicPlan& plan,
@@ -586,6 +641,15 @@ namespace scopeone::core
quint64 moveZRelative(const QString& zStageLabel, double dz);
quint64 moveXYTo(const QString& xyStageLabel, double x, double y);
quint64 moveZTo(const QString& zStageLabel, double z);
+ bool readShutterOpen(const QString& shutterLabel, bool& open) const;
+ bool setShutterOpen(const QString& shutterLabel,
+ bool open,
+ QString* errorMessage = nullptr);
+ bool readDeviceState(const QString& deviceLabel, long& state) const;
+ bool setDeviceState(const QString& deviceLabel,
+ long state,
+ QString* errorMessage = nullptr);
+ QString deviceStateLabel(const QString& deviceLabel, long state) const;
bool readExposure(const QString& cameraIdOrAll, double& exposureMs) const;
QStringList availableConfigGroups() const;
@@ -617,16 +681,32 @@ namespace scopeone::core
bool setRealTimeProcessingEnabled(bool enabled);
ProcessingBitDepth processingBitDepth() const;
bool setProcessingBitDepth(ProcessingBitDepth bitDepth);
+ QString realTimeProcessingSource() const;
+ bool setRealTimeProcessingSource(const QString& cameraId);
ProcessingRecipe processingRecipe() const;
bool applyProcessingRecipe(const ProcessingRecipe& recipe, QString* errorMessage = nullptr);
ImageFrame processFrame(const ImageFrame& frame) const;
ImageFrame processFrameFrom(int startModuleIndex, const ImageFrame& frame) const;
ImageFrame processFrameThrough(int endModuleIndex, const ImageFrame& frame) const;
+ QList availableProcessingModules() const;
+ bool registerProcessingModule(
+ const ProcessingModuleDescriptor& descriptor,
+ std::function()> factory);
+ std::unique_ptr createProcessingModule(const QString& moduleId) const;
+ std::unique_ptr createProcessingPipeline() const;
QList processingModules() const;
- bool addProcessingModule(ProcessingModuleKind kind);
+ bool addProcessingModule(const QString& moduleId);
bool removeProcessingModule(int index);
+ bool moveProcessingModule(int from, int to);
+ bool setProcessingModuleEnabled(int index, bool enabled);
bool setProcessingModuleParameters(int index, const QVariantMap& parameters);
bool resetProcessingModuleState(int index);
+ quint64 requestImageProcessing(const ImageFrame& frame,
+ const QString& sourceId = QString());
+ quint64 requestRecordingSessionStackProcessing(const QString& sessionId,
+ const QString& cameraId);
+ quint64 requestLayerStackProcessing(const QString& layerKey);
+ bool cancelProcessingRequest(quint64 requestId);
void setRecordingMaxPendingWriteBytes(qint64 bytes);
@@ -655,6 +735,10 @@ namespace scopeone::core
bool saveRecordingSession(const std::shared_ptr& session);
bool saveRecordingSession(const std::shared_ptr& session,
const RecordingSaveOptions& saveOptions);
+ bool saveRecordingSessionCamera(const std::shared_ptr& session,
+ const QString& cameraId,
+ const RecordingSaveOptions& saveOptions,
+ const ExperimentDocument* presentation = nullptr);
quint64 requestRecordingSessionFrame(
const std::shared_ptr& session,
const QString& cameraId,
@@ -666,6 +750,10 @@ namespace scopeone::core
const QString& errorMessage);
void configurationUnloadFinished(bool success, const QString& errorMessage);
void hardwareConfigurationChanged();
+ void hardwareDevicesChanged();
+ void hardwareProviderRegistrationFinished(const QString& providerId,
+ bool success,
+ const QString& errorMessage);
void deviceStateChanged();
void stagePositionChanged();
void stageMoveFinished(quint64 commandId,
@@ -676,13 +764,31 @@ namespace scopeone::core
void rawFramesAcquired(const QString& cameraId, quint64 frameCount);
void previewRawFrameReady(const ImageFrame& frame);
void previewStateChanged(bool running);
- void agentControlServerListening(const QString& cameraId, const QString& serverName);
+ void signalTimeSeriesReady(const TimeSeriesChunk& chunk);
+ void timestampedSignalEventsReady(const TimestampedEventChunk& chunk);
+ void signalSourceStateChanged(const QString& sourceId,
+ SignalSourceState state,
+ const QString& message);
+ void signalSourceError(const QString& sourceId,
+ const QString& errorMessage);
+ void scanImageSessionReady(
+ const std::shared_ptr& session);
+ void daqStateChanged(const QString& deviceId,
+ DaqState state,
+ const QString& message);
+ void daqError(const QString& deviceId,
+ const QString& errorMessage);
+ void daqInputDataReady(const DaqInputChunk& chunk);
+ void driverHostControlServerListening(const QString& cameraId, const QString& serverName);
void processedFrameReady(const ImageFrame& frame);
void processedFramesCompleted(const QString& cameraId, quint64 frameCount);
void previewProcessedFrameReady(const ImageFrame& frame);
void staticFramePublished(const QString& sourceId,
const QString& displayName,
const ImageFrame& frame);
+ void toolStreamFramePublished(const QString& sourceId,
+ const QString& displayName,
+ const ImageFrame& frame);
void staticFrameRemoved(const QString& sourceId);
void staticFramesCleared();
void liveFramesCleared(const QString& cameraId);
@@ -707,6 +813,19 @@ namespace scopeone::core
void processingModulesChanged();
void processingModuleParametersChanged(int index);
void processingSettingsChanged();
+ void imageProcessingFinished(quint64 requestId,
+ const QString& sourceId,
+ const ImageFrame& frame,
+ const QString& errorMessage);
+ void stackProcessingProgress(quint64 requestId, qint64 completed, qint64 total);
+ void stackProcessingFinished(
+ quint64 requestId,
+ const std::shared_ptr& session,
+ const QString& errorMessage);
+ void layerStackProcessingFinished(
+ quint64 requestId,
+ const QString& outputLayerKey,
+ const QString& errorMessage);
void recordingProgressChanged(int phase,
qint64 frameCurrent,
@@ -729,13 +848,23 @@ namespace scopeone::core
void recordingStateChanged(bool isRecording);
void recordingStopped(const std::shared_ptr& session);
void recordingSessionSaveFinished(const std::shared_ptr& session);
+ void recordingSessionCameraSaveFinished(
+ const std::shared_ptr& session,
+ const QString& cameraId,
+ bool success,
+ const QString& message);
void recordingSessionClosed(const QString& sessionId);
+ void recordingSessionsChanged();
void recordingSessionFrameReady(
quint64 requestId,
const std::shared_ptr& session,
const QString& cameraId,
int index,
const ImageFrame& frame);
+ void staticImageImportProgress(const QString& filePath, int percent, const QString& statusText);
+ void staticImageImportFinished(const QString& filePath, const QString& layerKey, bool success, const QString& errorMessage);
+ void layerFrameRateChanged(const QString& layerKey, double fps);
+ void layerFrameRatesUpdated(const QMap& frameRates);
private:
struct Managers;
@@ -744,6 +873,7 @@ namespace scopeone::core
{
Raw,
Processed,
+ Tool,
Static,
External
};
@@ -764,6 +894,7 @@ namespace scopeone::core
QHash m_rawFrames;
QHash m_processedFrames;
+ QHash m_toolFrames;
QHash m_staticFrames;
QHash m_externalFrames;
};
@@ -779,8 +910,9 @@ namespace scopeone::core
void unloadConfigurationForShutdown();
void applySystemShutdownPreset();
- void applyLoadedConfiguration(const QString& configPath,
+ bool applyLoadedConfiguration(const QString& configPath,
const LoadConfigResult& result);
+ void synchronizeCameraIdsFromRegistry();
void finishConfigurationLoadFailure(const LoadConfigResult& result,
const QString& errorMessage);
void clearConfigurationRuntime(bool notify, bool shutdownCameraBackend);
@@ -790,18 +922,25 @@ namespace scopeone::core
void startConfigurationUnloadTask();
quint64 queueStageMove(
const QString& deviceLabel,
- std::function command);
+ std::function command);
std::shared_ptr core() const;
- bool isConfiguredCamera(const QString& deviceLabel) const;
- bool isNativeCamera(const QString& deviceLabel) const;
bool isPropertyPreInit(const QString& deviceLabel, const QString& name) const;
void ensureSceneLayer(const QString& layerKey,
const QString& sourceId,
const QString& name,
DocumentLayerKind kind);
void handleIncomingRawFrame(const ImageFrame& frame);
+ void handleSignalTimeSeries(const TimeSeriesChunk& chunk);
+ void handleTimestampedSignalEvents(const TimestampedEventChunk& chunk);
+ void publishScanFrames(const QString& sourceId,
+ const QList& frames);
+ void finishScanImageSession(const QString& sourceId,
+ ExperimentRunState finalState,
+ const QString& message);
void submitProcessingFrame(const ImageFrame& frame, quint64 processingToken = 0);
void handleProcessedFrame(const ImageFrame& frame);
+ void recordLayerFrame(const QString& layerKey, quint64 count = 1);
+ void updateLayerFrameRates();
void flushProcessedFrames();
void queuePreviewRawFrame(const ImageFrame& frame);
void schedulePreviewFlush();
@@ -812,12 +951,14 @@ namespace scopeone::core
void clearLayerAnalysisByPrefix(const QString& prefix);
void updateLineProfile(const QString& cameraId,
bool processed,
+ bool toolSource,
const ImageFrame& frame);
bool updateStaticLineProfile(const QString& sourceId, const ImageFrame& frame);
void setLineProfile(const QString& cameraId,
const QPoint& start,
const QPoint& end,
- bool processed);
+ bool processed,
+ bool toolSource = false);
void setStaticLineProfile(const QString& sourceId,
const QPoint& start,
const QPoint& end);
@@ -825,6 +966,10 @@ namespace scopeone::core
void syncLineProfileFromScene();
void registerRecordingSession(const std::shared_ptr& session);
void finalizeActiveExperiment(const std::shared_ptr& session);
+ bool queueRecordingSessionSave(
+ const std::shared_ptr& sourceSession,
+ const std::shared_ptr& saveSession,
+ const QString& cameraId = QString());
struct ActiveLineProfile
{
@@ -832,6 +977,7 @@ namespace scopeone::core
QPoint start;
QPoint end;
bool processed{false};
+ bool toolSource{false};
bool staticSource{false};
bool active{false};
};
@@ -853,12 +999,19 @@ namespace scopeone::core
std::unique_ptr m_hardwareThreadPool;
std::unique_ptr m_analysisThreadPool;
std::unique_ptr m_sessionFrameThreadPool;
+ std::unique_ptr m_offlineProcessingThreadPool;
QString m_activeHistogramLayerKey;
quint64 m_nextHistogramSequence{0};
QElapsedTimer m_lineProfileUpdateTimer;
QElapsedTimer m_previewPublishTimer;
QTimer* m_previewFlushTimer{nullptr};
+ QTimer* m_layerFrameRateTimer{nullptr};
+ mutable QMutex m_layerFrameRateMutex;
+ QElapsedTimer m_layerFrameRateElapsed;
+ QHash m_layerFrameCounts;
+ QMap m_layerFrameRates;
QSet m_sessionsSaving;
+ QSet m_pendingProviderRegistrations;
enum class ConfigurationState
{
Unloaded,
@@ -875,6 +1028,10 @@ namespace scopeone::core
quint64 m_nextAnalysisRequestId{0};
quint64 m_analysisGeneration{0};
quint64 m_nextSessionFrameRequestId{0};
+ QString m_realTimeProcessingSource;
+ QHash> m_processingRequestCancelTokens;
+ quint64 m_nextProcessingRequestId{0};
+ QHash> m_layerStacks;
};
}
diff --git a/ScopeOneCore/include/scopeone/SignalSource.h b/ScopeOneCore/include/scopeone/SignalSource.h
new file mode 100644
index 0000000..3a937d4
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/SignalSource.h
@@ -0,0 +1,222 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "scopeone/scopeone_sdk_export.h"
+
+namespace scopeone::core
+{
+ enum class SignalSourceState
+ {
+ Idle,
+ Starting,
+ Running,
+ Stopping,
+ Error
+ };
+
+ enum class SignalStreamType
+ {
+ TimeSeries,
+ TimestampedEvents
+ };
+
+ enum class SignalParameterType
+ {
+ Integer,
+ Real,
+ String,
+ File,
+ Choice
+ };
+
+ struct SignalParameterDescriptor
+ {
+ QString key;
+ QString name;
+ SignalParameterType type{SignalParameterType::String};
+ QVariant defaultValue;
+ bool hasRange{false};
+ double minimum{0.0};
+ double maximum{0.0};
+ QString suffix;
+ QVariantList choices;
+ QStringList choiceNames;
+ QString fileFilter;
+ };
+
+ struct SignalSourceDescriptor
+ {
+ QString id;
+ QString name;
+ QString provider;
+ QString quantity;
+ QString unit;
+ SignalStreamType streamType{SignalStreamType::TimeSeries};
+ QList parameters;
+ };
+
+ struct ScanImageConfig
+ {
+ bool enabled{false};
+ int width{256};
+ int height{256};
+ quint32 gain{1};
+ int averageFrames{1};
+ quint32 frameStartMarker{1};
+ quint32 lineMarker{2};
+ quint32 frameEndMarker{0};
+ bool serpentine{false};
+ bool mirrorHorizontal{false};
+ };
+
+ struct SignalAcquisitionConfig
+ {
+ QString sourceId;
+ double sampleIntervalSeconds{0.01};
+ int durationMs{360000000};
+ bool publishTimestampedEvents{false};
+ ScanImageConfig scanImage;
+ QVariantMap sourceSettings;
+ };
+
+ struct SignalMarker
+ {
+ double timeSeconds{0.0};
+ quint32 code{0};
+
+ bool isValid() const
+ {
+ return qIsFinite(timeSeconds) && timeSeconds >= 0.0 && code != 0;
+ }
+ };
+
+ struct TimeSeriesChunk
+ {
+ QString sourceId;
+ QString quantity;
+ QString unit;
+ double startTimeSeconds{0.0};
+ double sampleIntervalSeconds{0.0};
+ QVector values;
+ QVector markers;
+ quint64 totalInputEvents{0};
+ quint64 totalMarkers{0};
+
+ bool isValid() const
+ {
+ return !sourceId.isEmpty()
+ && sampleIntervalSeconds > 0.0
+ && !values.isEmpty();
+ }
+ };
+
+ struct TimestampedEventChunk
+ {
+ QString sourceId;
+ double tickPeriodSeconds{0.0};
+ QVector eventTicks;
+ QVector eventCodes;
+ QVector markerTicks;
+ QVector markerCodes;
+
+ bool isValid() const
+ {
+ return !sourceId.isEmpty()
+ && tickPeriodSeconds > 0.0
+ && eventTicks.size() == eventCodes.size()
+ && markerTicks.size() == markerCodes.size()
+ && (!eventTicks.isEmpty() || !markerTicks.isEmpty());
+ }
+ };
+
+ class SCOPEONE_SDK_EXPORT EventCountBinner
+ {
+ public:
+ EventCountBinner(const QString& sourceId,
+ const QString& quantity,
+ const QString& unit,
+ double tickPeriodSeconds,
+ double sampleIntervalSeconds);
+
+ void addEvent(quint64 tick);
+ void addMarker(quint64 tick, quint32 code);
+ void advanceToTick(quint64 tick);
+ void advanceToElapsedSeconds(double elapsedSeconds);
+ bool hasReadyChunks() const;
+ QList takeReadyChunks();
+ QList takeCompletedChunks();
+
+ private:
+ quint64 binForTick(quint64 tick) const;
+ void appendCompletedValue(double value);
+ void finishChunk();
+ void advanceToBin(quint64 targetBin);
+
+ QString m_sourceId;
+ QString m_quantity;
+ QString m_unit;
+ double m_tickPeriodSeconds{0.0};
+ quint64 m_ticksPerSample{1};
+ quint64 m_currentSampleTick{0};
+ quint64 m_nextSampleTick{1};
+ double m_sampleIntervalSeconds{0.0};
+ quint64 m_currentSample{0};
+ quint64 m_firstCompletedSample{0};
+ quint64 m_currentEventCount{0};
+ quint64 m_totalInputEvents{0};
+ quint64 m_totalMarkers{0};
+ QVector m_completedValues;
+ QVector m_pendingMarkers;
+ QList m_readyChunks;
+ };
+
+ class SCOPEONE_SDK_EXPORT SignalSource : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ explicit SignalSource(QObject* parent = nullptr);
+ ~SignalSource() override;
+
+ virtual bool start(const SignalAcquisitionConfig& config,
+ QString* errorMessage = nullptr) = 0;
+ virtual void stop() = 0;
+ virtual SignalSourceState state() const = 0;
+ virtual QString stateMessage() const = 0;
+
+ signals:
+ void timeSeriesReady(const scopeone::core::TimeSeriesChunk& chunk);
+ void timestampedEventsReady(const scopeone::core::TimestampedEventChunk& chunk);
+ void stateChanged(scopeone::core::SignalSourceState state,
+ const QString& message);
+ void sourceError(const QString& errorMessage);
+ };
+
+ class SignalSourcePlugin
+ {
+ public:
+ virtual ~SignalSourcePlugin() = default;
+ virtual QList signalSources() const = 0;
+ virtual SignalSource* createSignalSource(const QString& sourceId,
+ QObject* parent = nullptr) = 0;
+ };
+}
+
+#define SCOPEONE_SIGNAL_SOURCE_PLUGIN_IID "org.scopeone.SignalSourcePlugin/1.0"
+Q_DECLARE_INTERFACE(scopeone::core::SignalSourcePlugin, SCOPEONE_SIGNAL_SOURCE_PLUGIN_IID)
+
+Q_DECLARE_METATYPE(scopeone::core::SignalSourceState)
+Q_DECLARE_METATYPE(scopeone::core::SignalMarker)
+Q_DECLARE_METATYPE(scopeone::core::TimeSeriesChunk)
+Q_DECLARE_METATYPE(scopeone::core::TimestampedEventChunk)
diff --git a/ScopeOneCore/include/scopeone/SimulatorProvider.h b/ScopeOneCore/include/scopeone/SimulatorProvider.h
new file mode 100644
index 0000000..c653e04
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/SimulatorProvider.h
@@ -0,0 +1,87 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "scopeone/CameraProvider.h"
+#include "scopeone/HardwareProvider.h"
+#include "scopeone/scopeone_core_export.h"
+
+namespace scopeone::core
+{
+ class SCOPEONE_CORE_EXPORT SimulatorProvider final
+ : public QObject,
+ public HardwareProvider,
+ public CameraProvider
+ {
+ public:
+ explicit SimulatorProvider(const QString& logicalCameraId = QStringLiteral("camera.simulator"),
+ int width = 512,
+ int height = 512,
+ const QString& providerId = {});
+
+ HardwareProviderDescriptor descriptor() const override;
+ QList devices() const override;
+ void setFrameSink(FrameSink sink) override;
+ void setPreviewStateSink(PreviewStateSink sink) override;
+ bool startPreview() override;
+ bool stopPreview() override;
+ bool startPreviewFor(const QString& cameraId) override;
+ bool stopPreviewFor(const QString& cameraId) override;
+ bool isPreviewRunning(const QString& cameraId) const override;
+ bool getExposure(const QString& cameraIdOrAll, double& exposureMs) const override;
+ bool setExposure(const QString& cameraIdOrAll, double exposureMs) override;
+ QStringList listProperties(const QString& cameraId) override;
+ QString getProperty(const QString& cameraId,
+ const QString& name,
+ bool fromCache) override;
+ bool setProperty(const QString& cameraId,
+ const QString& name,
+ const QString& value,
+ QString* errorMessage) override;
+ QString getPropertyType(const QString& cameraId, const QString& name) override;
+ bool isPropertyReadOnly(const QString& cameraId, const QString& name) override;
+ bool isPropertyPreInit(const QString& cameraId, const QString& name) override;
+ QStringList getAllowedPropertyValues(const QString& cameraId,
+ const QString& name) override;
+ bool hasPropertyLimits(const QString& cameraId, const QString& name) override;
+ double getPropertyLowerLimit(const QString& cameraId, const QString& name) override;
+ double getPropertyUpperLimit(const QString& cameraId, const QString& name) override;
+ bool setROI(const QString& cameraId, int x, int y, int width, int height) override;
+ bool clearROI(const QString& cameraId) override;
+ bool getROI(const QString& cameraId,
+ int& x,
+ int& y,
+ int& width,
+ int& height) override;
+ bool captureEventFrame(const QString& cameraId,
+ ImageFrame& frame,
+ int timeoutMs) override;
+
+ private:
+ enum class ImageMode
+ {
+ Gradient,
+ Hologram
+ };
+
+ bool accepts(const QString& cameraIdOrAll) const;
+ ImageFrame makeFrame();
+ void updateTimerInterval();
+
+ QString m_providerId;
+ QString m_cameraId;
+ int m_sensorWidth{512};
+ int m_sensorHeight{512};
+ QRect m_roi;
+ double m_exposureMs{10.0};
+ ImageMode m_imageMode{ImageMode::Gradient};
+ quint64 m_frameIndex{0};
+ FrameSink m_frameSink;
+ PreviewStateSink m_previewStateSink;
+ QTimer m_timer;
+ mutable QMutex m_mutex;
+ };
+}
diff --git a/ScopeOneCore/include/scopeone/ToolFrameStream.h b/ScopeOneCore/include/scopeone/ToolFrameStream.h
new file mode 100644
index 0000000..1308627
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/ToolFrameStream.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "scopeone/ImageFrame.h"
+#include "scopeone/scopeone_sdk_export.h"
+
+#include
+#include
+
+namespace scopeone::core
+{
+ class ScopeOneCore;
+}
+
+namespace scopeone::ui
+{
+ class SCOPEONE_SDK_EXPORT ScopeOneToolFrameStream final : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ explicit ScopeOneToolFrameStream(scopeone::core::ScopeOneCore& core,
+ QObject* parent = nullptr);
+
+ void setSourceId(const QString& cameraId);
+
+ void setEnabled(bool enabled);
+ void setProcessing(bool processing);
+ void clearPendingFrame();
+
+ signals:
+ void frameReady(const scopeone::core::ImageFrame& frame);
+
+ private:
+ void acceptFrame(const scopeone::core::ImageFrame& frame);
+
+ scopeone::core::ScopeOneCore& m_core;
+ QString m_sourceId;
+ scopeone::core::ImageFrame m_pendingFrame;
+ bool m_enabled{true};
+ bool m_processing{false};
+ };
+}
diff --git a/ScopeOneCore/include/scopeone/ToolPlugin.h b/ScopeOneCore/include/scopeone/ToolPlugin.h
new file mode 100644
index 0000000..71dd013
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/ToolPlugin.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "scopeone/ScopeOneCore.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class QWidget;
+
+namespace scopeone::ui
+{
+ enum class ToolWindowMode
+ {
+ Modal,
+ ModelessSingleton
+ };
+
+ struct ToolDescriptor
+ {
+ QString id;
+ QString name;
+ QString category;
+ ToolWindowMode windowMode{ToolWindowMode::ModelessSingleton};
+ bool requiresCamera{false};
+ };
+
+ class ScopeOneToolContext
+ {
+ public:
+ virtual ~ScopeOneToolContext() = default;
+
+ virtual scopeone::core::ScopeOneCore& core() const = 0;
+ virtual QString currentLayerKey() const = 0;
+ virtual scopeone::core::ImageFrame currentFrame() const = 0;
+ virtual double layerFrameRate(const QString& layerKey) const = 0;
+ virtual QMap layerFrameRates() const = 0;
+ virtual scopeone::core::ImageFrame publishToolStreamFrame(
+ const QString& sourceId,
+ const scopeone::core::ImageFrame& frame,
+ const QString& displayName = QString()) = 0;
+ virtual void showLayers(const QStringList& layerKeys, bool sideBySide = false) = 0;
+ virtual void showToolStatus(const QString& message, int timeoutMs = 5000) = 0;
+ virtual void presentSession(
+ const std::shared_ptr& session,
+ const QString& title) = 0;
+ };
+
+ class ScopeOneToolPlugin
+ {
+ public:
+ virtual ~ScopeOneToolPlugin() = default;
+
+ virtual QList tools() const = 0;
+ virtual QWidget* createTool(const QString& toolId,
+ ScopeOneToolContext& context,
+ QWidget* parent) = 0;
+ };
+}
+
+#define ScopeOneToolPlugin_iid "org.scopeone.ToolPlugin/1.0"
+Q_DECLARE_INTERFACE(scopeone::ui::ScopeOneToolPlugin, ScopeOneToolPlugin_iid)
diff --git a/ScopeOneCore/include/scopeone/ToolTask.h b/ScopeOneCore/include/scopeone/ToolTask.h
new file mode 100644
index 0000000..fa2e131
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/ToolTask.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "scopeone/scopeone_sdk_export.h"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace scopeone::ui
+{
+ class SCOPEONE_SDK_EXPORT ScopeOneToolTask final : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ using Work = std::function&)>;
+
+ explicit ScopeOneToolTask(Work work, QObject* parent = nullptr);
+ ~ScopeOneToolTask() override;
+
+ void start();
+ void cancel();
+
+ signals:
+ void progressChanged(int percent);
+ void finished();
+ void canceled();
+ void failed(const QString& message);
+
+ private:
+ struct Impl;
+ std::unique_ptr m_impl;
+ };
+}
diff --git a/ScopeOneCore/include/scopeone/scopeone_sdk_export.h b/ScopeOneCore/include/scopeone/scopeone_sdk_export.h
new file mode 100644
index 0000000..6460fba
--- /dev/null
+++ b/ScopeOneCore/include/scopeone/scopeone_sdk_export.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+# if defined(SCOPEONE_CORE_EXPORTS)
+# define SCOPEONE_SDK_EXPORT __declspec(dllexport)
+# else
+# define SCOPEONE_SDK_EXPORT __declspec(dllimport)
+# endif
+#else
+# if defined(__GNUC__) && __GNUC__ >= 4
+# define SCOPEONE_SDK_EXPORT __attribute__((visibility("default")))
+# else
+# define SCOPEONE_SDK_EXPORT
+# endif
+#endif
diff --git a/ScopeOneCore/internal/AcquisitionEngine.h b/ScopeOneCore/internal/AcquisitionEngine.h
new file mode 100644
index 0000000..aae9b16
--- /dev/null
+++ b/ScopeOneCore/internal/AcquisitionEngine.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include
+
+#include "scopeone/CameraProvider.h"
+
+namespace scopeone::core::internal
+{
+ class DeviceRegistry;
+
+ class AcquisitionEngine
+ {
+ public:
+ explicit AcquisitionEngine(DeviceRegistry& deviceRegistry);
+
+ bool start(const QString& cameraIdOrAll);
+ bool stop(const QString& cameraIdOrAll);
+
+ private:
+ DeviceRegistry& m_deviceRegistry;
+ };
+}
diff --git a/ScopeOneCore/internal/BackgroundCalibrationModule.h b/ScopeOneCore/internal/BackgroundCalibrationModule.h
index dd9285d..5bf0573 100644
--- a/ScopeOneCore/internal/BackgroundCalibrationModule.h
+++ b/ScopeOneCore/internal/BackgroundCalibrationModule.h
@@ -31,7 +31,7 @@ namespace scopeone::core::internal
class BackgroundCalibrationModule : public ProcessingModule
{
public:
- ProcessingModuleKind kind() const noexcept override { return ProcessingModuleKind::BackgroundCalibration; }
+ QString id() const override { return QStringLiteral("background_calibration"); }
QString name() const override { return "Background Calibration"; }
QVariantMap parameters() const override;
void setParameters(const QVariantMap& params) override;
diff --git a/ScopeOneCore/internal/CameraBackend.h b/ScopeOneCore/internal/CameraBackend.h
index 99e3be1..c8fd814 100644
--- a/ScopeOneCore/internal/CameraBackend.h
+++ b/ScopeOneCore/internal/CameraBackend.h
@@ -52,7 +52,7 @@ namespace scopeone::core::internal
enum class Kind
{
Native,
- Agent
+ DriverHost
};
explicit CameraBackend(ProcessingFrameGate& processingFrameGate,
@@ -71,7 +71,7 @@ namespace scopeone::core::internal
virtual bool configureNativeCamera(const std::shared_ptr& core,
const QString& cameraId,
double exposureMs);
- virtual bool addAgentCamera(const QString& cameraId,
+ virtual bool addDriverHostCamera(const QString& cameraId,
const QString& adapter,
const QString& device,
const QStringList& preInitProperties,
@@ -113,7 +113,7 @@ namespace scopeone::core::internal
void recordingFramesReady(const QList& frames);
void frameDeliveryFailed(const QString& errorMessage, quint64 droppedFrames);
void previewStateChanged(bool running);
- void agentControlServerListening(const QString& cameraId, const QString& serverName);
+ void driverHostControlServerListening(const QString& cameraId, const QString& serverName);
protected:
bool applyWithPreviewRestart(const QString& cameraId, const std::function& operation);
@@ -170,5 +170,5 @@ namespace scopeone::core::internal
};
std::unique_ptr createNativeCameraBackend(ProcessingFrameGate& processingFrameGate);
- std::unique_ptr createAgentCameraBackend(ProcessingFrameGate& processingFrameGate);
+ std::unique_ptr createDriverHostCameraBackend(ProcessingFrameGate& processingFrameGate);
}
diff --git a/ScopeOneCore/internal/CameraManager.h b/ScopeOneCore/internal/CameraManager.h
index 9be2d48..d279088 100644
--- a/ScopeOneCore/internal/CameraManager.h
+++ b/ScopeOneCore/internal/CameraManager.h
@@ -9,12 +9,14 @@
#include
#include "internal/CameraBackend.h"
+#include "scopeone/CameraProvider.h"
+#include "internal/CameraRuntimeControl.h"
class CMMCore;
namespace scopeone::core::internal
{
- class CameraManager : public QObject
+ class CameraManager : public QObject, public CameraProvider, public CameraRuntimeControl
{
Q_OBJECT
@@ -22,10 +24,13 @@ namespace scopeone::core::internal
explicit CameraManager(QObject* parent = nullptr);
~CameraManager() override;
+ void setFrameSink(FrameSink sink) override;
+ void setPreviewStateSink(PreviewStateSink sink) override;
+
bool configureNativeCamera(const std::shared_ptr& core,
const QString& cameraId,
double exposureMs = 0.0);
- bool addAgentCamera(const QString& cameraId,
+ bool addDriverHostCamera(const QString& cameraId,
const QString& adapter,
const QString& device,
const QStringList& preInitProperties = QStringList(),
@@ -34,57 +39,60 @@ namespace scopeone::core::internal
void shutdownNow();
void shutdown(std::function completion);
- bool startPreview();
- bool stopPreview();
- bool usesAgentBackend() const;
- bool startPreviewFor(const QString& cameraId);
- bool stopPreviewFor(const QString& cameraId);
- bool isPreviewRunning(const QString& cameraId) const;
- void setFrameDeliveryPaused(bool paused);
- bool setRecordingFrameDeliveryEnabled(bool enabled);
- bool setHighRateFrameDeliveryEnabled(bool enabled);
- bool isProcessingFrameTokenCurrent(const QString& cameraId, quint64 token);
- void finishProcessingFrame(const QString& cameraId, quint64 token);
+ bool startPreview() override;
+ bool stopPreview() override;
+ bool usesDriverHostBackend() const;
+ bool startPreviewFor(const QString& cameraId) override;
+ bool stopPreviewFor(const QString& cameraId) override;
+ bool isPreviewRunning(const QString& cameraId) const override;
+ void setFrameDeliveryPaused(const QStringList& cameraIds, bool paused) override;
+ bool setRecordingFrameDeliveryEnabled(const QStringList& cameraIds,
+ bool enabled) override;
+ bool setHighRateFrameDeliveryEnabled(const QStringList& cameraIds,
+ bool enabled) override;
+ bool isProcessingFrameTokenCurrent(const QString& cameraId, quint64 token) override;
+ void finishProcessingFrame(const QString& cameraId, quint64 token) override;
- bool getExposure(const QString& cameraIdOrAll, double& exposureMs) const;
- bool setExposure(const QString& cameraIdOrAll, double exposureMs);
- QStringList listProperties(const QString& cameraId);
+ bool getExposure(const QString& cameraIdOrAll, double& exposureMs) const override;
+ bool setExposure(const QString& cameraIdOrAll, double exposureMs) override;
+ QStringList listProperties(const QString& cameraId) override;
QString getProperty(const QString& cameraId,
const QString& name,
- bool fromCache = false);
+ bool fromCache = false) override;
bool setProperty(const QString& cameraId,
const QString& name,
const QString& value,
- QString* errorMessage = nullptr);
- QString getPropertyType(const QString& cameraId, const QString& name);
- bool isPropertyReadOnly(const QString& cameraId, const QString& name);
- bool isPropertyPreInit(const QString& cameraId, const QString& name);
- QStringList getAllowedPropertyValues(const QString& cameraId, const QString& name);
- bool hasPropertyLimits(const QString& cameraId, const QString& name);
- double getPropertyLowerLimit(const QString& cameraId, const QString& name);
- double getPropertyUpperLimit(const QString& cameraId, const QString& name);
+ QString* errorMessage = nullptr) override;
+ QString getPropertyType(const QString& cameraId, const QString& name) override;
+ bool isPropertyReadOnly(const QString& cameraId, const QString& name) override;
+ bool isPropertyPreInit(const QString& cameraId, const QString& name) override;
+ QStringList getAllowedPropertyValues(const QString& cameraId, const QString& name) override;
+ bool hasPropertyLimits(const QString& cameraId, const QString& name) override;
+ double getPropertyLowerLimit(const QString& cameraId, const QString& name) override;
+ double getPropertyUpperLimit(const QString& cameraId, const QString& name) override;
- bool setROI(const QString& cameraId, int x, int y, int width, int height);
- bool clearROI(const QString& cameraId);
- bool getROI(const QString& cameraId, int& x, int& y, int& width, int& height);
+ bool setROI(const QString& cameraId, int x, int y, int width, int height) override;
+ bool clearROI(const QString& cameraId) override;
+ bool getROI(const QString& cameraId, int& x, int& y, int& width, int& height) override;
bool captureEventFrame(const QString& cameraId,
scopeone::core::ImageFrame& frame,
- int timeoutMs = 1500);
+ int timeoutMs = 1500) override;
signals:
- void newRawFrameReady(const scopeone::core::ImageFrame& frame);
void processingFrameReady(const scopeone::core::ImageFrame& frame, quint64 token);
void rawFramesAcquired(const QString& cameraId, quint64 frameCount);
void recordingFramesReady(const QList& frames);
void frameDeliveryFailed(const QString& errorMessage, quint64 droppedFrames);
void previewStateChanged(bool running);
- void agentControlServerListening(const QString& cameraId, const QString& serverName);
+ void driverHostControlServerListening(const QString& cameraId, const QString& serverName);
private:
bool activateBackend(CameraBackend::Kind kind);
ProcessingFrameGate m_processingFrameGate;
std::unique_ptr m_backend;
+ FrameSink m_frameSink;
+ PreviewStateSink m_previewStateSink;
bool m_recordingFrameDeliveryEnabled{false};
bool m_highRateFrameDeliveryEnabled{false};
QMap m_propertyNamesCache;
diff --git a/ScopeOneCore/internal/CameraRuntimeControl.h b/ScopeOneCore/internal/CameraRuntimeControl.h
new file mode 100644
index 0000000..7eab518
--- /dev/null
+++ b/ScopeOneCore/internal/CameraRuntimeControl.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include
+#include
+#include
+
+namespace scopeone::core::internal
+{
+ class CameraRuntimeControl
+ {
+ public:
+ virtual ~CameraRuntimeControl() = default;
+
+ virtual void setFrameDeliveryPaused(const QStringList& cameraIds, bool paused) = 0;
+ virtual bool setRecordingFrameDeliveryEnabled(const QStringList& cameraIds,
+ bool enabled) = 0;
+ virtual bool setHighRateFrameDeliveryEnabled(const QStringList& cameraIds,
+ bool enabled) = 0;
+ virtual bool isProcessingFrameTokenCurrent(const QString& cameraId,
+ quint64 token) = 0;
+ virtual void finishProcessingFrame(const QString& cameraId, quint64 token) = 0;
+ };
+}
diff --git a/ScopeOneCore/internal/DaqDeviceManager.h b/ScopeOneCore/internal/DaqDeviceManager.h
new file mode 100644
index 0000000..e8bbc99
--- /dev/null
+++ b/ScopeOneCore/internal/DaqDeviceManager.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "scopeone/DaqDevice.h"
+
+class QPluginLoader;
+
+namespace scopeone::core::internal
+{
+ class DaqDeviceManager final : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ explicit DaqDeviceManager(QObject* parent = nullptr);
+ ~DaqDeviceManager() override;
+
+ QList devices() const;
+ bool start(const DaqSessionConfig& config,
+ QString* errorMessage = nullptr);
+ void stop(const QString& deviceId);
+ DaqState state(const QString& deviceId) const;
+ QString stateMessage(const QString& deviceId) const;
+
+ signals:
+ void stateChanged(const QString& deviceId,
+ scopeone::core::DaqState state,
+ const QString& message);
+ void deviceError(const QString& deviceId, const QString& errorMessage);
+ void inputDataReady(const scopeone::core::DaqInputChunk& chunk);
+
+ private:
+ void loadPlugins();
+ DaqController* controller(const QString& deviceId,
+ QString* errorMessage);
+
+ std::vector> m_loaders;
+ mutable bool m_pluginsLoaded{false};
+ QHash m_plugins;
+ QHash m_descriptors;
+ QHash> m_controllers;
+ QHash m_states;
+ QHash m_messages;
+ };
+}
diff --git a/ScopeOneCore/internal/DifferentialRollingModule.h b/ScopeOneCore/internal/DifferentialRollingModule.h
index 60b1901..48915fc 100644
--- a/ScopeOneCore/internal/DifferentialRollingModule.h
+++ b/ScopeOneCore/internal/DifferentialRollingModule.h
@@ -10,7 +10,7 @@ namespace scopeone::core::internal
class DifferentialRollingModule : public ProcessingModule
{
public:
- ProcessingModuleKind kind() const noexcept override { return ProcessingModuleKind::DifferentialRolling; }
+ QString id() const override { return QStringLiteral("differential_rolling"); }
QString name() const override { return "Differential Rolling"; }
QVariantMap parameters() const override;
void setParameters(const QVariantMap& params) override;
diff --git a/ScopeOneCore/internal/AgentProtocol.h b/ScopeOneCore/internal/DriverHostProtocol.h
similarity index 50%
rename from ScopeOneCore/internal/AgentProtocol.h
rename to ScopeOneCore/internal/DriverHostProtocol.h
index ae1d07c..1e660b0 100644
--- a/ScopeOneCore/internal/AgentProtocol.h
+++ b/ScopeOneCore/internal/DriverHostProtocol.h
@@ -2,28 +2,48 @@
#include
#include
+#include
#include
#include
+#include
+#include
#include
-namespace scopeone::core::internal::agent
+namespace scopeone::core::internal::driverhost
{
- inline constexpr quint32 kProtocolVersion = 3;
+ inline constexpr quint32 kProtocolVersion = 5;
inline constexpr quint32 kMaxControlMessageBytes = 256 * 1024;
inline const QString kEnvelopeKindField = QStringLiteral("kind");
inline const QString kEnvelopeVersionField = QStringLiteral("version");
inline const QString kEnvelopeRequestIdField = QStringLiteral("requestId");
inline const QString kMessageTypeField = QStringLiteral("type");
+ inline const QString kProviderIdField = QStringLiteral("providerId");
+ inline const QString kDeviceIdField = QStringLiteral("deviceId");
+ inline const QString kDeviceKindField = QStringLiteral("deviceKind");
+ inline const QString kCapabilitiesField = QStringLiteral("capabilities");
+ inline const QString kDevicesField = QStringLiteral("devices");
+ inline const QString kProviderNameField = QStringLiteral("providerName");
+ inline const QString kProviderVersionField = QStringLiteral("providerVersion");
+ inline const QString kProviderDeviceIdField = QStringLiteral("providerDeviceId");
+ inline const QString kHardwareIdField = QStringLiteral("hardwareId");
+ inline const QString kDeviceNameField = QStringLiteral("deviceName");
+ inline const QString kDeviceStateField = QStringLiteral("deviceState");
+ inline const QString kDevicePropertiesField = QStringLiteral("deviceProperties");
+ inline const QString kSharedMemoryKeyField = QStringLiteral("sharedMemoryKey");
+ inline const QString kDefaultXYStageField = QStringLiteral("defaultXYStage");
+ inline const QString kDefaultZStageField = QStringLiteral("defaultZStage");
inline const QString kMessageKindRequest = QStringLiteral("Request");
inline const QString kMessageKindResponse = QStringLiteral("Response");
inline const QString kMessageKindEvent = QStringLiteral("Event");
+ inline const QString kCommandDescribe = QStringLiteral("Describe");
inline const QString kCommandShutdown = QStringLiteral("Shutdown");
inline const QString kCommandStartPreview = QStringLiteral("StartPreview");
inline const QString kCommandStopPreview = QStringLiteral("StopPreview");
inline const QString kCommandSetFrameDeliveryMode = QStringLiteral("SetFrameDeliveryMode");
+ inline const QString kCommandGetExposure = QStringLiteral("GetExposure");
inline const QString kCommandSetExposure = QStringLiteral("SetExposure");
inline const QString kCommandListProperties = QStringLiteral("ListProperties");
inline const QString kCommandGetProperty = QStringLiteral("GetProperty");
@@ -32,6 +52,28 @@ namespace scopeone::core::internal::agent
inline const QString kCommandSetRoi = QStringLiteral("SetROI");
inline const QString kCommandClearRoi = QStringLiteral("ClearROI");
inline const QString kCommandGetRoi = QStringLiteral("GetROI");
+ inline const QString kCommandGetXYPosition = QStringLiteral("GetXYPosition");
+ inline const QString kCommandGetZPosition = QStringLiteral("GetZPosition");
+ inline const QString kCommandSetRelativeXYPosition = QStringLiteral("SetRelativeXYPosition");
+ inline const QString kCommandSetRelativeZPosition = QStringLiteral("SetRelativeZPosition");
+ inline const QString kCommandSetXYPosition = QStringLiteral("SetXYPosition");
+ inline const QString kCommandSetZPosition = QStringLiteral("SetZPosition");
+ inline const QString kCommandGetShutterOpen = QStringLiteral("GetShutterOpen");
+ inline const QString kCommandSetShutterOpen = QStringLiteral("SetShutterOpen");
+ inline const QString kCommandGetState = QStringLiteral("GetState");
+ inline const QString kCommandSetState = QStringLiteral("SetState");
+ inline const QString kCommandGetStateLabel = QStringLiteral("GetStateLabel");
+ inline const QString kCommandListConfigGroups = QStringLiteral("ListConfigGroups");
+ inline const QString kCommandListConfigs = QStringLiteral("ListConfigs");
+ inline const QString kCommandGetCurrentConfig = QStringLiteral("GetCurrentConfig");
+ inline const QString kCommandSetConfig = QStringLiteral("SetConfig");
+
+ inline const QString kCapabilityCamera = QStringLiteral("Camera");
+ inline const QString kCapabilityProperties = QStringLiteral("Properties");
+ inline const QString kCapabilityStage = QStringLiteral("Stage");
+ inline const QString kCapabilityShutter = QStringLiteral("Shutter");
+ inline const QString kCapabilityState = QStringLiteral("State");
+ inline const QString kCapabilityConfiguration = QStringLiteral("Configuration");
inline const QString kFrameDeliveryModePreviewLatest = QStringLiteral("PreviewLatest");
inline const QString kFrameDeliveryModeLatestOnly = QStringLiteral("LatestOnly");
@@ -40,18 +82,29 @@ namespace scopeone::core::internal::agent
inline const QString kEventHello = QStringLiteral("Hello");
inline const QString kEventFrameAvailable = QStringLiteral("FrameAvailable");
inline const QString kEventPreviewState = QStringLiteral("PreviewState");
- inline const QString kEventAgentError = QStringLiteral("AgentError");
+ inline const QString kEventDriverHostError = QStringLiteral("DriverHostError");
- inline const QString kExecutableFileName = QStringLiteral("ScopeOne_Agent.exe");
+#ifdef Q_OS_WIN
+ inline const QString kExecutableFileName = QStringLiteral("ScopeOne_DriverHost.exe");
+#else
+ inline const QString kExecutableFileName = QStringLiteral("ScopeOne_DriverHost");
+#endif
- inline QString controlServerName(const QString& cameraId)
+ inline QString controlServerName(const QString& deviceId)
{
- return QStringLiteral("ScopeOne.%1.ctrl").arg(cameraId);
+ return QStringLiteral("ScopeOne.DriverHost.%1.ctrl").arg(deviceId);
}
- inline QString sharedMemoryKey(const QString& cameraId)
+ inline QString sharedMemoryKey(const QString& deviceId)
{
- return QStringLiteral("ScopeOne.%1.shm").arg(cameraId);
+ return QStringLiteral("ScopeOne.DriverHost.%1.shm").arg(deviceId);
+ }
+
+ inline QString sharedMemoryKey(const QString& hostKey, int cameraIndex)
+ {
+ return QStringLiteral("ScopeOne.DriverHost.%1.%2.shm")
+ .arg(hostKey)
+ .arg(cameraIndex);
}
inline QString encodeUInt64(quint64 value)
@@ -70,7 +123,7 @@ namespace scopeone::core::internal::agent
if (value.isDouble())
{
const double numeric = value.toDouble(static_cast(defaultValue));
- return (numeric >= 0.0) ? static_cast(numeric) : defaultValue;
+ return numeric >= 0.0 ? static_cast(numeric) : defaultValue;
}
return defaultValue;
}
@@ -79,15 +132,15 @@ namespace scopeone::core::internal::agent
const QString& type,
quint64 requestId = 0)
{
- QJsonObject obj;
- obj.insert(kEnvelopeKindField, kind);
- obj.insert(kEnvelopeVersionField, static_cast(kProtocolVersion));
- obj.insert(kMessageTypeField, type);
+ QJsonObject object;
+ object.insert(kEnvelopeKindField, kind);
+ object.insert(kEnvelopeVersionField, static_cast(kProtocolVersion));
+ object.insert(kMessageTypeField, type);
if (requestId != 0)
{
- obj.insert(kEnvelopeRequestIdField, encodeUInt64(requestId));
+ object.insert(kEnvelopeRequestIdField, encodeUInt64(requestId));
}
- return obj;
+ return object;
}
inline QByteArray encodeMessage(const QJsonObject& message)
@@ -108,13 +161,14 @@ namespace scopeone::core::internal::agent
Error
};
- inline DecodeResult tryDecodeMessage(QByteArray& buffer, QJsonObject& message, QString* error = nullptr)
+ inline DecodeResult tryDecodeMessage(QByteArray& buffer,
+ QJsonObject& message,
+ QString* error = nullptr)
{
if (buffer.size() < static_cast(sizeof(quint32)))
{
return DecodeResult::Incomplete;
}
-
const quint32 payloadSize =
qFromLittleEndian(reinterpret_cast(buffer.constData()));
if (payloadSize == 0 || payloadSize > kMaxControlMessageBytes)
@@ -126,19 +180,18 @@ namespace scopeone::core::internal::agent
buffer.clear();
return DecodeResult::Error;
}
-
const int frameSize = static_cast(sizeof(quint32) + payloadSize);
if (buffer.size() < frameSize)
{
return DecodeResult::Incomplete;
}
- const QByteArray payload = buffer.mid(static_cast(sizeof(quint32)), static_cast(payloadSize));
+ const QByteArray payload = buffer.mid(static_cast(sizeof(quint32)),
+ static_cast(payloadSize));
buffer.remove(0, frameSize);
-
QJsonParseError parseError{};
- const QJsonDocument doc = QJsonDocument::fromJson(payload, &parseError);
- if (parseError.error != QJsonParseError::NoError || !doc.isObject())
+ const QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
+ if (parseError.error != QJsonParseError::NoError || !document.isObject())
{
if (error)
{
@@ -146,8 +199,7 @@ namespace scopeone::core::internal::agent
}
return DecodeResult::Error;
}
-
- message = doc.object();
+ message = document.object();
return DecodeResult::Complete;
}
-} // namespace scopeone::core::internal::agent
+}
diff --git a/ScopeOneCore/internal/DriverHostProviderProxy.h b/ScopeOneCore/internal/DriverHostProviderProxy.h
new file mode 100644
index 0000000..d164a97
--- /dev/null
+++ b/ScopeOneCore/internal/DriverHostProviderProxy.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+#include
+
+#include "scopeone/HardwareProvider.h"
+
+namespace scopeone::core::internal
+{
+ HardwareProviderPtr createDriverHostProviderProxy(const QString& providerId,
+ const QString& pluginPath,
+ const QJsonObject& options,
+ QString* errorMessage = nullptr);
+}
diff --git a/ScopeOneCore/internal/FFTModule.h b/ScopeOneCore/internal/FFTModule.h
index 534bc99..35fb45d 100644
--- a/ScopeOneCore/internal/FFTModule.h
+++ b/ScopeOneCore/internal/FFTModule.h
@@ -9,47 +9,20 @@ namespace scopeone::core::internal
class FFTModule : public ProcessingModule
{
public:
- enum class FilterKind
- {
- Smooth = 0,
- Hard = 1
- };
-
- enum class OutputMode
- {
- Spectrum = 0,
- BandpassSpectrum = 1,
- BandpassImage = 2
- };
-
- ProcessingModuleKind kind() const noexcept override { return ProcessingModuleKind::FFT; }
+ QString id() const override { return QStringLiteral("fft"); }
QString name() const override { return "FFT"; }
QVariantMap parameters() const override;
void setParameters(const QVariantMap& params) override;
std::unique_ptr createRuntime() const override;
ProcessingResult process(const ImageFrame& frame, int processingBitDepth) override;
+ ProcessingResult processValue(const ProcessingValue& input,
+ int processingBitDepth) override;
private:
- const cv::Mat& maskForSize(const cv::Size& size);
- void invalidateMask();
-
- double m_minFeatureSize{2.0};
- double m_maxFeatureSize{10.0};
- FilterKind m_filterKind{FilterKind::Smooth};
- OutputMode m_outputMode{OutputMode::BandpassImage};
-
- cv::Mat m_mask;
- cv::Size m_maskSize;
- double m_maskMinFeatureSize{-1.0};
- double m_maskMaxFeatureSize{-1.0};
- FilterKind m_maskFilterKind{FilterKind::Smooth};
-
cv::Mat m_grayFloat;
cv::Mat m_padded;
cv::Mat m_complex;
cv::Mat m_planes[2];
- cv::Mat m_filteredComplex;
- cv::Mat m_filtered;
cv::Mat m_spectrumMagnitude;
cv::Mat m_shiftedSpectrum;
};
diff --git a/ScopeOneCore/internal/FrequencyDomainFilterModule.h b/ScopeOneCore/internal/FrequencyDomainFilterModule.h
new file mode 100644
index 0000000..5f081a1
--- /dev/null
+++ b/ScopeOneCore/internal/FrequencyDomainFilterModule.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "internal/ProcessingModule.h"
+
+#include
+
+namespace scopeone::core::internal
+{
+ class FrequencyDomainFilterModule final : public ProcessingModule
+ {
+ public:
+ enum class FilterKind
+ {
+ Smooth = 0,
+ Hard = 1
+ };
+
+ enum class OutputMode
+ {
+ Spectrum = 0,
+ FilteredSpectrum = 1,
+ FilteredImage = 2
+ };
+
+ QString id() const override { return QStringLiteral("frequency_domain_filter"); }
+ QString name() const override { return QStringLiteral("Frequency Domain Filter"); }
+ QVariantMap parameters() const override;
+ void setParameters(const QVariantMap& parameters) override;
+ std::unique_ptr createRuntime() const override;
+ ProcessingResult process(const ImageFrame& frame, int processingBitDepth) override;
+
+ private:
+ const cv::Mat& maskForSize(const cv::Size& size);
+ void invalidateMask();
+
+ double m_minFeatureSize{2.0};
+ double m_maxFeatureSize{10.0};
+ FilterKind m_filterKind{FilterKind::Smooth};
+ OutputMode m_outputMode{OutputMode::FilteredImage};
+
+ cv::Mat m_mask;
+ cv::Size m_maskSize;
+ double m_maskMinFeatureSize{-1.0};
+ double m_maskMaxFeatureSize{-1.0};
+ FilterKind m_maskFilterKind{FilterKind::Smooth};
+ cv::Mat m_grayFloat;
+ cv::Mat m_padded;
+ cv::Mat m_complex;
+ cv::Mat m_planes[2];
+ cv::Mat m_filteredComplex;
+ cv::Mat m_filtered;
+ cv::Mat m_spectrumMagnitude;
+ cv::Mat m_shiftedSpectrum;
+ };
+}
diff --git a/ScopeOneCore/internal/GaussianBlurModule.h b/ScopeOneCore/internal/GaussianBlurModule.h
index 9b77c68..0ffb6e3 100644
--- a/ScopeOneCore/internal/GaussianBlurModule.h
+++ b/ScopeOneCore/internal/GaussianBlurModule.h
@@ -7,7 +7,7 @@ namespace scopeone::core::internal
class GaussianBlurModule : public ProcessingModule
{
public:
- ProcessingModuleKind kind() const noexcept override { return ProcessingModuleKind::GaussianBlur; }
+ QString id() const override { return QStringLiteral("gaussian_blur"); }
QString name() const override { return "Gaussian Blur"; }
QVariantMap parameters() const override;
void setParameters(const QVariantMap& params) override;
diff --git a/ScopeOneCore/internal/HardwareRuntime.h b/ScopeOneCore/internal/HardwareRuntime.h
new file mode 100644
index 0000000..1c00444
--- /dev/null
+++ b/ScopeOneCore/internal/HardwareRuntime.h
@@ -0,0 +1,178 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include