From 6672bbeb9229bde94205aeaff061b472c09676bf Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Sun, 16 Aug 2026 04:10:17 +0200 Subject: [PATCH 01/37] Add hardware provider architecture --- ScopeOneCore/CMakeLists.txt | 17 + ScopeOneCore/README.md | 8 +- .../include/scopeone/CameraProvider.h | 54 ++ ScopeOneCore/include/scopeone/ClockService.h | 12 + .../include/scopeone/HardwareProvider.h | 22 + ScopeOneCore/include/scopeone/HardwareTypes.h | 83 +++ ScopeOneCore/include/scopeone/ImageFrame.h | 8 + ScopeOneCore/include/scopeone/ScopeOneCore.h | 8 + .../include/scopeone/SimulatorProvider.h | 74 +++ ScopeOneCore/internal/AcquisitionEngine.h | 36 ++ ScopeOneCore/internal/AgentProtocol.h | 127 +---- ScopeOneCore/internal/CameraManager.h | 59 ++- ScopeOneCore/internal/CameraRuntimeControl.h | 20 + ScopeOneCore/internal/DriverHostProtocol.h | 118 +++++ ScopeOneCore/internal/FrameRouter.h | 29 ++ ScopeOneCore/internal/HardwareRuntime.h | 113 +++++ ScopeOneCore/internal/MDAManager.h | 7 +- ScopeOneCore/internal/MMCoreManager.h | 2 + ScopeOneCore/internal/MicroManagerProvider.h | 58 +++ ScopeOneCore/internal/RecordingManager.h | 13 +- ScopeOneCore/src/AcquisitionEngine.cpp | 129 +++++ ScopeOneCore/src/CameraManager.cpp | 15 +- ScopeOneCore/src/ClockService.cpp | 18 + ScopeOneCore/src/FrameBufferUtils.cpp | 1 + ScopeOneCore/src/FrameRouter.cpp | 22 + ScopeOneCore/src/HardwareRuntime.cpp | 477 ++++++++++++++++++ ScopeOneCore/src/MDAManager.cpp | 14 +- ScopeOneCore/src/MMCoreManager.cpp | 45 ++ ScopeOneCore/src/MicroManagerProvider.cpp | 164 ++++++ ScopeOneCore/src/NativeCameraBackend.cpp | 2 + ScopeOneCore/src/RecordingManager.cpp | 30 +- ScopeOneCore/src/ScopeOneCore.cpp | 204 ++++++-- ScopeOneCore/src/SimulatorProvider.cpp | 306 +++++++++++ 33 files changed, 2078 insertions(+), 217 deletions(-) create mode 100644 ScopeOneCore/include/scopeone/CameraProvider.h create mode 100644 ScopeOneCore/include/scopeone/ClockService.h create mode 100644 ScopeOneCore/include/scopeone/HardwareProvider.h create mode 100644 ScopeOneCore/include/scopeone/HardwareTypes.h create mode 100644 ScopeOneCore/include/scopeone/SimulatorProvider.h create mode 100644 ScopeOneCore/internal/AcquisitionEngine.h create mode 100644 ScopeOneCore/internal/CameraRuntimeControl.h create mode 100644 ScopeOneCore/internal/DriverHostProtocol.h create mode 100644 ScopeOneCore/internal/FrameRouter.h create mode 100644 ScopeOneCore/internal/HardwareRuntime.h create mode 100644 ScopeOneCore/internal/MicroManagerProvider.h create mode 100644 ScopeOneCore/src/AcquisitionEngine.cpp create mode 100644 ScopeOneCore/src/ClockService.cpp create mode 100644 ScopeOneCore/src/FrameRouter.cpp create mode 100644 ScopeOneCore/src/HardwareRuntime.cpp create mode 100644 ScopeOneCore/src/MicroManagerProvider.cpp create mode 100644 ScopeOneCore/src/SimulatorProvider.cpp diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 38568aa..45b91fa 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -77,6 +77,12 @@ endfunction() set(CORE_SOURCES src/ExperimentDocument.cpp + src/AcquisitionEngine.cpp + src/ClockService.cpp + src/FrameRouter.cpp + src/HardwareRuntime.cpp + src/MicroManagerProvider.cpp + src/SimulatorProvider.cpp src/ImageSceneModel.cpp src/MMCoreManager.cpp src/FrameBufferUtils.cpp @@ -99,7 +105,17 @@ set(CORE_SOURCES set(CORE_HEADERS include/scopeone/ExperimentDocument.h + internal/AcquisitionEngine.h + include/scopeone/CameraProvider.h + internal/CameraRuntimeControl.h + include/scopeone/ClockService.h + include/scopeone/HardwareTypes.h + include/scopeone/HardwareProvider.h + include/scopeone/SimulatorProvider.h include/scopeone/ImageSceneModel.h + internal/HardwareRuntime.h + internal/FrameRouter.h + internal/MicroManagerProvider.h internal/MMCoreManager.h internal/FrameBufferUtils.h internal/ParticleAnalysis.h @@ -107,6 +123,7 @@ set(CORE_HEADERS internal/ImageProcessingFramework.h internal/ProcessingModule.h internal/AgentProtocol.h + internal/DriverHostProtocol.h internal/SpatiotemporalBinningModule.h internal/GaussianBlurModule.h internal/FFTModule.h diff --git a/ScopeOneCore/README.md b/ScopeOneCore/README.md index f504d66..e7f6836 100644 --- a/ScopeOneCore/README.md +++ b/ScopeOneCore/README.md @@ -44,7 +44,8 @@ Use this placement rule: |---|---|---| | `scopeone::core` | Stable Core-facing types and public facades | `ScopeOneCore`, `ImageFrame`, `ExperimentDocument`, `ImageSceneModel` | | `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::core::internal::agent` | Micro-Manager DriverHost commands | Camera commands and shared-memory endpoint names | | `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. @@ -99,6 +100,9 @@ Outputs: The installed headers are the source of truth for the public API: - `ScopeOneCore.h` provides the main hardware, acquisition, processing, recording and frame-graph facade. +- `HardwareProvider.h` and `CameraProvider.h` define provider discovery, control and frame delivery. +- `HardwareTypes.h` defines provider-independent device identity, state, endpoint and clock metadata. +- `SimulatorProvider.h` provides an in-process reference provider. - `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. @@ -107,6 +111,8 @@ The installed headers are the source of truth for the public API: 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 them with `ScopeOneCore::registerHardwareProvider(...)`; ScopeOne owns acquisition routing, clocks and downstream frame delivery. Micro-Manager uses the same provider boundary and may run in process or through the existing DriverHost transport. + ## 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. diff --git a/ScopeOneCore/include/scopeone/CameraProvider.h b/ScopeOneCore/include/scopeone/CameraProvider.h new file mode 100644 index 0000000..b9d06e9 --- /dev/null +++ b/ScopeOneCore/include/scopeone/CameraProvider.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include + +#include "scopeone/ImageFrame.h" + +namespace scopeone::core +{ + class CameraProvider + { + public: + using FrameSink = std::function; + + virtual ~CameraProvider() = default; + + virtual void setFrameSink(FrameSink 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 QStringList listProperties(const QString& cameraId) = 0; + virtual QString getProperty(const QString& cameraId, + const QString& name, + bool fromCache = false) = 0; + virtual bool setProperty(const QString& cameraId, + const QString& name, + const QString& value, + QString* errorMessage = nullptr) = 0; + virtual QString getPropertyType(const QString& cameraId, const QString& name) = 0; + virtual bool isPropertyReadOnly(const QString& cameraId, const QString& name) = 0; + virtual bool isPropertyPreInit(const QString& cameraId, const QString& name) = 0; + virtual QStringList getAllowedPropertyValues(const QString& cameraId, + const QString& name) = 0; + virtual bool hasPropertyLimits(const QString& cameraId, const QString& name) = 0; + virtual double getPropertyLowerLimit(const QString& cameraId, const QString& name) = 0; + virtual double getPropertyUpperLimit(const QString& cameraId, const QString& name) = 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/ClockService.h b/ScopeOneCore/include/scopeone/ClockService.h new file mode 100644 index 0000000..77dc5ac --- /dev/null +++ b/ScopeOneCore/include/scopeone/ClockService.h @@ -0,0 +1,12 @@ +#pragma once + +#include "scopeone/HardwareTypes.h" + +namespace scopeone::core +{ + class SCOPEONE_CORE_EXPORT ClockService + { + public: + ClockStamp now() const; + }; +} diff --git a/ScopeOneCore/include/scopeone/HardwareProvider.h b/ScopeOneCore/include/scopeone/HardwareProvider.h new file mode 100644 index 0000000..223998e --- /dev/null +++ b/ScopeOneCore/include/scopeone/HardwareProvider.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include + +#include "scopeone/HardwareTypes.h" + +namespace scopeone::core +{ + class HardwareProvider + { + public: + virtual ~HardwareProvider() = default; + + 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..9183df5 --- /dev/null +++ b/ScopeOneCore/include/scopeone/HardwareTypes.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include + +#include + +#include "scopeone/scopeone_core_export.h" + +namespace scopeone::core +{ + enum class HardwareDeviceKind + { + Unknown, + Camera, + XYStage, + ZStage, + Shutter, + State, + Hub + }; + + enum class HardwareDeviceState + { + Unknown, + Discovered, + Initialized, + Faulted, + Unavailable + }; + + enum class HardwareEndpointKind + { + InProcess, + DriverHost + }; + + struct SCOPEONE_CORE_EXPORT HardwareProviderDescriptor + { + QString id; + QString name; + QString version; + }; + + struct SCOPEONE_CORE_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; + }; + + struct SCOPEONE_CORE_EXPORT ClockStamp + { + std::int64_t ticks{0}; + std::int64_t tickPeriodNumerator{1}; + std::int64_t tickPeriodDenominator{1000000000}; + std::int64_t hostMonotonicNs{0}; + QString clockDomain; + QString source; + + bool isValid() const + { + return tickPeriodNumerator > 0 + && tickPeriodDenominator > 0 + && !clockDomain.isEmpty() + && !source.isEmpty(); + } + }; +} + +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) +Q_DECLARE_METATYPE(scopeone::core::ClockStamp) diff --git a/ScopeOneCore/include/scopeone/ImageFrame.h b/ScopeOneCore/include/scopeone/ImageFrame.h index c2754b5..5241cef 100644 --- a/ScopeOneCore/include/scopeone/ImageFrame.h +++ b/ScopeOneCore/include/scopeone/ImageFrame.h @@ -8,6 +8,7 @@ #include #include "scopeone/SharedFrame.h" +#include "scopeone/HardwareTypes.h" namespace scopeone::core { @@ -28,6 +29,7 @@ namespace scopeone::core ImagePixelFormat pixelFormat{ImagePixelFormat::Invalid}; quint64 frameIndex{0}; quint64 timestampNs{0}; + ClockStamp clockStamp; int sourceRoiX{0}; int sourceRoiY{0}; int sourceRoiWidth{0}; @@ -169,6 +171,12 @@ namespace scopeone::core frame.bitsPerSample = static_cast(header.bitsPerSample); frame.frameIndex = header.frameIndex; frame.timestampNs = header.timestampNs; + if (frame.timestampNs > 0) + { + frame.clockStamp.ticks = static_cast(frame.timestampNs); + frame.clockStamp.clockDomain = QStringLiteral("provider.timestampNs"); + frame.clockStamp.source = QStringLiteral("Driver"); + } if (header.pixelFormat == static_cast(SharedPixelFormat::Mono16)) { diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 29104b5..f8675d5 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -19,6 +19,8 @@ #include #include "scopeone/ExperimentDocument.h" +#include "scopeone/HardwareTypes.h" +#include "scopeone/HardwareProvider.h" #include "scopeone/ImageFrame.h" #include "scopeone/scopeone_core_export.h" @@ -71,6 +73,7 @@ namespace scopeone::core int failCount{0}; int skippedCameraCount{0}; bool foundCamera{false}; + QList devices; }; struct HistogramStats @@ -527,6 +530,9 @@ namespace scopeone::core bool setAdditionalDeviceAdapterSearchPaths(const QStringList& paths); QStringList cameraIds() const { return m_cameraIds; } + QList hardwareDevices() const; + bool registerHardwareProvider(const HardwareProviderPtr& provider); + bool unregisterHardwareProvider(const QString& providerId); QStringList runningPreviewCameraIds() const; double cameraPixelSizeUm(const QString& cameraId) const; bool setCameraPixelSizeUm(const QString& cameraId, double pixelSizeUm); @@ -666,6 +672,7 @@ namespace scopeone::core const QString& errorMessage); void configurationUnloadFinished(bool success, const QString& errorMessage); void hardwareConfigurationChanged(); + void hardwareDevicesChanged(); void deviceStateChanged(); void stagePositionChanged(); void stageMoveFinished(quint64 commandId, @@ -781,6 +788,7 @@ namespace scopeone::core void applySystemShutdownPreset(); void applyLoadedConfiguration(const QString& configPath, const LoadConfigResult& result); + void synchronizeCameraIdsFromRegistry(); void finishConfigurationLoadFailure(const LoadConfigResult& result, const QString& errorMessage); void clearConfigurationRuntime(bool notify, bool shutdownCameraBackend); diff --git a/ScopeOneCore/include/scopeone/SimulatorProvider.h b/ScopeOneCore/include/scopeone/SimulatorProvider.h new file mode 100644 index 0000000..ae63351 --- /dev/null +++ b/ScopeOneCore/include/scopeone/SimulatorProvider.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +#include "scopeone/CameraProvider.h" +#include "scopeone/HardwareProvider.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); + + HardwareProviderDescriptor descriptor() const override; + QList devices() const override; + void setFrameSink(FrameSink 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: + 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}; + quint64 m_frameIndex{0}; + FrameSink m_frameSink; + QTimer m_timer; + }; +} diff --git a/ScopeOneCore/internal/AcquisitionEngine.h b/ScopeOneCore/internal/AcquisitionEngine.h new file mode 100644 index 0000000..1401a95 --- /dev/null +++ b/ScopeOneCore/internal/AcquisitionEngine.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "scopeone/CameraProvider.h" + +namespace scopeone::core::internal +{ + class DeviceRegistry; + + class AcquisitionEngine : public QObject + { + Q_OBJECT + + public: + enum class State + { + Idle, + Prepared, + Running + }; + + AcquisitionEngine(DeviceRegistry* deviceRegistry, QObject* parent = nullptr); + + void prepare(); + void reset(); + bool start(const QString& cameraIdOrAll); + bool stop(const QString& cameraIdOrAll); + State state() const { return m_state; } + + private: + DeviceRegistry* m_deviceRegistry{nullptr}; + State m_state{State::Idle}; + }; +} diff --git a/ScopeOneCore/internal/AgentProtocol.h b/ScopeOneCore/internal/AgentProtocol.h index ae1d07c..e97ea2a 100644 --- a/ScopeOneCore/internal/AgentProtocol.h +++ b/ScopeOneCore/internal/AgentProtocol.h @@ -1,24 +1,25 @@ #pragma once -#include -#include -#include #include -#include + +#include "internal/DriverHostProtocol.h" namespace scopeone::core::internal::agent { - inline constexpr quint32 kProtocolVersion = 3; - 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 kMessageKindRequest = QStringLiteral("Request"); - inline const QString kMessageKindResponse = QStringLiteral("Response"); - inline const QString kMessageKindEvent = QStringLiteral("Event"); + using driverhost::kProtocolVersion; + using driverhost::kEnvelopeKindField; + using driverhost::kEnvelopeVersionField; + using driverhost::kEnvelopeRequestIdField; + using driverhost::kMessageTypeField; + using driverhost::kMessageKindRequest; + using driverhost::kMessageKindResponse; + using driverhost::kMessageKindEvent; + using driverhost::encodeUInt64; + using driverhost::decodeUInt64; + using driverhost::makeEnvelope; + using driverhost::encodeMessage; + using driverhost::DecodeResult; + using driverhost::tryDecodeMessage; inline const QString kCommandShutdown = QStringLiteral("Shutdown"); inline const QString kCommandStartPreview = QStringLiteral("StartPreview"); @@ -54,100 +55,4 @@ namespace scopeone::core::internal::agent return QStringLiteral("ScopeOne.%1.shm").arg(cameraId); } - inline QString encodeUInt64(quint64 value) - { - return QString::number(value); - } - - inline quint64 decodeUInt64(const QJsonValue& value, quint64 defaultValue = 0) - { - if (value.isString()) - { - bool ok = false; - const quint64 parsed = value.toString().toULongLong(&ok); - return ok ? parsed : defaultValue; - } - if (value.isDouble()) - { - const double numeric = value.toDouble(static_cast(defaultValue)); - return (numeric >= 0.0) ? static_cast(numeric) : defaultValue; - } - return defaultValue; - } - - inline QJsonObject makeEnvelope(const QString& kind, - const QString& type, - quint64 requestId = 0) - { - QJsonObject obj; - obj.insert(kEnvelopeKindField, kind); - obj.insert(kEnvelopeVersionField, static_cast(kProtocolVersion)); - obj.insert(kMessageTypeField, type); - if (requestId != 0) - { - obj.insert(kEnvelopeRequestIdField, encodeUInt64(requestId)); - } - return obj; - } - - inline QByteArray encodeMessage(const QJsonObject& message) - { - const QByteArray payload = QJsonDocument(message).toJson(QJsonDocument::Compact); - QByteArray framed; - framed.resize(static_cast(sizeof(quint32))); - qToLittleEndian(static_cast(payload.size()), - reinterpret_cast(framed.data())); - framed += payload; - return framed; - } - - enum class DecodeResult - { - Incomplete, - Complete, - Error - }; - - 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) - { - if (error) - { - *error = QStringLiteral("Invalid control message size"); - } - 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)); - buffer.remove(0, frameSize); - - QJsonParseError parseError{}; - const QJsonDocument doc = QJsonDocument::fromJson(payload, &parseError); - if (parseError.error != QJsonParseError::NoError || !doc.isObject()) - { - if (error) - { - *error = QStringLiteral("Malformed control message payload"); - } - return DecodeResult::Error; - } - - message = doc.object(); - return DecodeResult::Complete; - } } // namespace scopeone::core::internal::agent diff --git a/ScopeOneCore/internal/CameraManager.h b/ScopeOneCore/internal/CameraManager.h index 9be2d48..bc44bff 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,6 +24,8 @@ namespace scopeone::core::internal explicit CameraManager(QObject* parent = nullptr); ~CameraManager() override; + void setFrameSink(FrameSink sink) override; + bool configureNativeCamera(const std::shared_ptr& core, const QString& cameraId, double exposureMs = 0.0); @@ -34,42 +38,42 @@ namespace scopeone::core::internal void shutdownNow(); void shutdown(std::function completion); - bool startPreview(); - bool stopPreview(); + bool startPreview() override; + bool stopPreview() override; 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 startPreviewFor(const QString& cameraId) override; + bool stopPreviewFor(const QString& cameraId) override; + bool isPreviewRunning(const QString& cameraId) const override; + void setFrameDeliveryPaused(bool paused) override; + bool setRecordingFrameDeliveryEnabled(bool enabled) override; + bool setHighRateFrameDeliveryEnabled(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); @@ -85,6 +89,7 @@ namespace scopeone::core::internal ProcessingFrameGate m_processingFrameGate; std::unique_ptr m_backend; + FrameSink m_frameSink; 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..b104b2e --- /dev/null +++ b/ScopeOneCore/internal/CameraRuntimeControl.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace scopeone::core::internal +{ + class CameraRuntimeControl + { + public: + virtual ~CameraRuntimeControl() = default; + + virtual void setFrameDeliveryPaused(bool paused) = 0; + virtual bool setRecordingFrameDeliveryEnabled(bool enabled) = 0; + virtual bool setHighRateFrameDeliveryEnabled(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/DriverHostProtocol.h b/ScopeOneCore/internal/DriverHostProtocol.h new file mode 100644 index 0000000..36793d3 --- /dev/null +++ b/ScopeOneCore/internal/DriverHostProtocol.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace scopeone::core::internal::driverhost +{ + inline constexpr quint32 kProtocolVersion = 3; + 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 kMessageKindRequest = QStringLiteral("Request"); + inline const QString kMessageKindResponse = QStringLiteral("Response"); + inline const QString kMessageKindEvent = QStringLiteral("Event"); + + inline QString encodeUInt64(quint64 value) + { + return QString::number(value); + } + + inline quint64 decodeUInt64(const QJsonValue& value, quint64 defaultValue = 0) + { + if (value.isString()) + { + bool ok = false; + const quint64 parsed = value.toString().toULongLong(&ok); + return ok ? parsed : defaultValue; + } + if (value.isDouble()) + { + const double numeric = value.toDouble(static_cast(defaultValue)); + return numeric >= 0.0 ? static_cast(numeric) : defaultValue; + } + return defaultValue; + } + + inline QJsonObject makeEnvelope(const QString& kind, + const QString& type, + quint64 requestId = 0) + { + QJsonObject object; + object.insert(kEnvelopeKindField, kind); + object.insert(kEnvelopeVersionField, static_cast(kProtocolVersion)); + object.insert(kMessageTypeField, type); + if (requestId != 0) + { + object.insert(kEnvelopeRequestIdField, encodeUInt64(requestId)); + } + return object; + } + + inline QByteArray encodeMessage(const QJsonObject& message) + { + const QByteArray payload = QJsonDocument(message).toJson(QJsonDocument::Compact); + QByteArray framed; + framed.resize(static_cast(sizeof(quint32))); + qToLittleEndian(static_cast(payload.size()), + reinterpret_cast(framed.data())); + framed += payload; + return framed; + } + + enum class DecodeResult + { + Incomplete, + Complete, + Error + }; + + 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) + { + if (error) + { + *error = QStringLiteral("Invalid control message size"); + } + 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)); + buffer.remove(0, frameSize); + QJsonParseError parseError{}; + const QJsonDocument document = QJsonDocument::fromJson(payload, &parseError); + if (parseError.error != QJsonParseError::NoError || !document.isObject()) + { + if (error) + { + *error = QStringLiteral("Malformed control message payload"); + } + return DecodeResult::Error; + } + message = document.object(); + return DecodeResult::Complete; + } +} diff --git a/ScopeOneCore/internal/FrameRouter.h b/ScopeOneCore/internal/FrameRouter.h new file mode 100644 index 0000000..f7650c2 --- /dev/null +++ b/ScopeOneCore/internal/FrameRouter.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "scopeone/ImageFrame.h" + +namespace scopeone::core +{ + class ClockService; +} + +namespace scopeone::core::internal +{ + class FrameRouter : public QObject + { + Q_OBJECT + + public: + FrameRouter(scopeone::core::ClockService* clockService, + QObject* parent = nullptr); + void publish(const scopeone::core::ImageFrame& frame); + + signals: + void frameReady(const scopeone::core::ImageFrame& frame); + + private: + scopeone::core::ClockService* m_clockService{nullptr}; + }; +} diff --git a/ScopeOneCore/internal/HardwareRuntime.h b/ScopeOneCore/internal/HardwareRuntime.h new file mode 100644 index 0000000..0458291 --- /dev/null +++ b/ScopeOneCore/internal/HardwareRuntime.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "scopeone/HardwareProvider.h" +#include "scopeone/ClockService.h" +#include "scopeone/CameraProvider.h" +#include "internal/AcquisitionEngine.h" +#include "internal/FrameRouter.h" + +namespace scopeone::core::internal +{ + class DeviceRegistry : public QObject + { + Q_OBJECT + + public: + explicit DeviceRegistry(QObject* parent = nullptr); + + void clear(); + bool registerProvider(const HardwareProviderPtr& provider); + void unregisterProvider(const QString& providerId); + void refreshProvider(const QString& providerId); + QList providers() const; + QList devices() const; + HardwareDeviceDescriptor device(const QString& logicalId) const; + HardwareProviderPtr provider(const QString& providerId) const; + HardwareProviderPtr providerForDevice(const QString& logicalId) const; + + signals: + void changed(); + + private: + struct ProviderEntry + { + HardwareProviderPtr provider; + QList devices; + }; + + QHash m_providers; + }; + + class HardwareRuntime : public QObject, public CameraProvider + { + Q_OBJECT + + public: + explicit HardwareRuntime(QObject* parent = nullptr); + + void setFrameSink(FrameSink 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; + + DeviceRegistry* deviceRegistry() { return &m_registry; } + const DeviceRegistry* deviceRegistry() const { return &m_registry; } + AcquisitionEngine* acquisitionEngine() { return &m_acquisitionEngine; } + ClockService* clockService() { return &m_clockService; } + FrameRouter* frameRouter() { return &m_frameRouter; } + void clear(); + bool registerProvider(const HardwareProviderPtr& provider); + void unregisterProvider(const QString& providerId); + void refreshProvider(const QString& providerId); + + signals: + void devicesChanged(); + + private: + CameraProvider* cameraProviderForDevice(const QString& logicalId) const; + QList cameraProviders() const; + + DeviceRegistry m_registry; + ClockService m_clockService; + FrameRouter m_frameRouter; + AcquisitionEngine m_acquisitionEngine; + FrameSink m_frameSink; + }; +} diff --git a/ScopeOneCore/internal/MDAManager.h b/ScopeOneCore/internal/MDAManager.h index 0f24a06..eb50e47 100644 --- a/ScopeOneCore/internal/MDAManager.h +++ b/ScopeOneCore/internal/MDAManager.h @@ -10,13 +10,12 @@ #include #include "scopeone/ExperimentDocument.h" +#include "scopeone/CameraProvider.h" class CMMCore; namespace scopeone::core::internal { - class CameraManager; - struct MDAOutput { AcquisitionEvent event; @@ -37,7 +36,7 @@ namespace scopeone::core::internal bool isRunning() const { return m_running.load(); } - void setCameraManager(CameraManager* cameraManager); + void setCameraProvider(CameraProvider* cameraProvider); bool start(const QList& events, bool block = false); void requestCancel(); void cancelAndWait(); @@ -59,7 +58,7 @@ namespace scopeone::core::internal void runSequence(QList events); std::shared_ptr m_mmcore; - CameraManager* m_cameraManager{nullptr}; + CameraProvider* m_cameraProvider{nullptr}; QThreadPool m_threadPool; std::atomic m_running{false}; std::atomic m_cancelRequested{false}; diff --git a/ScopeOneCore/internal/MMCoreManager.h b/ScopeOneCore/internal/MMCoreManager.h index fc7f6e8..0f384c1 100644 --- a/ScopeOneCore/internal/MMCoreManager.h +++ b/ScopeOneCore/internal/MMCoreManager.h @@ -5,6 +5,7 @@ #include #include #include "MMCore.h" +#include "scopeone/HardwareTypes.h" namespace scopeone::core::internal { @@ -35,6 +36,7 @@ namespace scopeone::core::internal int skippedCameraCount{0}; bool foundCamera{false}; bool useSingleCamera{false}; + QList devices; }; explicit MMCoreManager(QObject* parent = nullptr); diff --git a/ScopeOneCore/internal/MicroManagerProvider.h b/ScopeOneCore/internal/MicroManagerProvider.h new file mode 100644 index 0000000..423064b --- /dev/null +++ b/ScopeOneCore/internal/MicroManagerProvider.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "scopeone/HardwareProvider.h" +#include "scopeone/CameraProvider.h" + +namespace scopeone::core::internal +{ + class MicroManagerProvider final : public HardwareProvider, public CameraProvider + { + public: + explicit MicroManagerProvider(CameraProvider* cameraProvider); + + HardwareProviderDescriptor descriptor() const override; + QList devices() const override; + void setDevices(const QList& devices); + + void setFrameSink(FrameSink 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, + scopeone::core::ImageFrame& frame, + int timeoutMs) override; + + private: + CameraProvider* m_cameraProvider{nullptr}; + QList m_devices; + }; +} diff --git a/ScopeOneCore/internal/RecordingManager.h b/ScopeOneCore/internal/RecordingManager.h index ae99e69..4859b20 100644 --- a/ScopeOneCore/internal/RecordingManager.h +++ b/ScopeOneCore/internal/RecordingManager.h @@ -1,7 +1,9 @@ #pragma once #include "scopeone/ScopeOneCore.h" +#include "scopeone/CameraProvider.h" #include "internal/MDAManager.h" +#include "internal/CameraRuntimeControl.h" #include #include #include @@ -25,8 +27,6 @@ namespace scopeone::core::internal using RecordingWriterPhase = scopeone::core::ScopeOneCore::RecordingWriterPhase; using RecordingWriterStatus = scopeone::core::ScopeOneCore::RecordingWriterStatus; - class CameraManager; - class RecordingManager : public QObject { Q_OBJECT @@ -35,7 +35,11 @@ namespace scopeone::core::internal explicit RecordingManager(QObject* parent = nullptr); ~RecordingManager() override; - void setCameraManager(CameraManager* cameraManager) { m_cameraManager = cameraManager; } + void setCameraProvider(CameraProvider* cameraProvider) { m_cameraProvider = cameraProvider; } + void setCameraRuntimeControl(CameraRuntimeControl* cameraRuntimeControl) + { + m_cameraRuntimeControl = cameraRuntimeControl; + } void setMMCore(const std::shared_ptr& core) { m_mmcore = core; } void setLatestFrameFetcher(std::function fetcher) @@ -203,7 +207,8 @@ namespace scopeone::core::internal bool allCamerasReachedTarget() const; void advanceBurstStateIfNeeded(); - CameraManager* m_cameraManager{nullptr}; + CameraProvider* m_cameraProvider{nullptr}; + CameraRuntimeControl* m_cameraRuntimeControl{nullptr}; std::shared_ptr m_mmcore; std::function m_latestFrameFetcher; std::function m_sessionPreparationCallback; diff --git a/ScopeOneCore/src/AcquisitionEngine.cpp b/ScopeOneCore/src/AcquisitionEngine.cpp new file mode 100644 index 0000000..7e44f9b --- /dev/null +++ b/ScopeOneCore/src/AcquisitionEngine.cpp @@ -0,0 +1,129 @@ +#include "internal/AcquisitionEngine.h" + +#include "scopeone/CameraProvider.h" +#include "internal/HardwareRuntime.h" + +#include + +namespace scopeone::core::internal +{ + AcquisitionEngine::AcquisitionEngine(DeviceRegistry* deviceRegistry, QObject* parent) + : QObject(parent) + , m_deviceRegistry(deviceRegistry) + { + } + + void AcquisitionEngine::prepare() + { + m_state = State::Prepared; + } + + void AcquisitionEngine::reset() + { + m_state = State::Idle; + } + + bool AcquisitionEngine::start(const QString& cameraIdOrAll) + { + if (!m_deviceRegistry || m_state == State::Idle) + { + return false; + } + const QString target = cameraIdOrAll.trimmed(); + if (target.isEmpty()) + { + return false; + } + bool started = false; + if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) + { + QList startedProviders; + QSet visited; + started = true; + for (const HardwareDeviceDescriptor& device : m_deviceRegistry->devices()) + { + if (device.kind != HardwareDeviceKind::Camera) + { + continue; + } + const HardwareProviderPtr provider = m_deviceRegistry->provider(device.providerId); + auto* cameraProvider = dynamic_cast(provider.get()); + if (!cameraProvider || visited.contains(cameraProvider)) + { + continue; + } + visited.insert(cameraProvider); + if (!cameraProvider->startPreview()) + { + started = false; + for (CameraProvider* activeProvider : startedProviders) + { + activeProvider->stopPreview(); + } + break; + } + startedProviders.append(cameraProvider); + } + started = started && !startedProviders.isEmpty(); + } + else + { + const HardwareProviderPtr provider = m_deviceRegistry->providerForDevice(target); + auto* cameraProvider = dynamic_cast(provider.get()); + started = cameraProvider && cameraProvider->startPreviewFor(target); + } + if (started) + { + m_state = State::Running; + } + return started; + } + + bool AcquisitionEngine::stop(const QString& cameraIdOrAll) + { + if (!m_deviceRegistry || m_state == State::Idle) + { + return false; + } + const QString target = cameraIdOrAll.trimmed(); + if (target.isEmpty()) + { + return false; + } + bool stopped = false; + if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) + { + QSet visited; + stopped = true; + bool found = false; + for (const HardwareDeviceDescriptor& device : m_deviceRegistry->devices()) + { + if (device.kind != HardwareDeviceKind::Camera) + { + continue; + } + const HardwareProviderPtr provider = m_deviceRegistry->provider(device.providerId); + auto* cameraProvider = dynamic_cast(provider.get()); + if (!cameraProvider || visited.contains(cameraProvider)) + { + continue; + } + found = true; + visited.insert(cameraProvider); + stopped = cameraProvider->stopPreview() && stopped; + } + stopped = found && stopped; + } + else + { + const HardwareProviderPtr provider = m_deviceRegistry->providerForDevice(target); + auto* cameraProvider = dynamic_cast(provider.get()); + stopped = cameraProvider && cameraProvider->stopPreviewFor(target); + } + if (stopped && target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) + { + m_state = State::Prepared; + } + return stopped; + } +} diff --git a/ScopeOneCore/src/CameraManager.cpp b/ScopeOneCore/src/CameraManager.cpp index 1703583..838d754 100644 --- a/ScopeOneCore/src/CameraManager.cpp +++ b/ScopeOneCore/src/CameraManager.cpp @@ -1,6 +1,7 @@ #include "internal/CameraManager.h" #include +#include namespace scopeone::core::internal { @@ -28,6 +29,11 @@ namespace scopeone::core::internal shutdownNow(); } + void CameraManager::setFrameSink(FrameSink sink) + { + m_frameSink = std::move(sink); + } + // Selects one camera backend and forwards its runtime signals bool CameraManager::activateBackend(CameraBackend::Kind kind) { @@ -46,7 +52,14 @@ namespace scopeone::core::internal } connect(m_backend.get(), &CameraBackend::rawFrameReady, - this, &CameraManager::newRawFrameReady); + this, [this](const scopeone::core::ImageFrame& frame) + { + if (m_frameSink) + { + m_frameSink(frame); + } + emit newRawFrameReady(frame); + }); // Keeps processing input on the producer thread connect(m_backend.get(), &CameraBackend::processingFrameReady, this, &CameraManager::processingFrameReady, diff --git a/ScopeOneCore/src/ClockService.cpp b/ScopeOneCore/src/ClockService.cpp new file mode 100644 index 0000000..ccd4b82 --- /dev/null +++ b/ScopeOneCore/src/ClockService.cpp @@ -0,0 +1,18 @@ +#include "scopeone/ClockService.h" + +#include + +namespace scopeone::core +{ + ClockStamp ClockService::now() const + { + const auto ticks = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + ClockStamp stamp; + stamp.ticks = ticks; + stamp.hostMonotonicNs = ticks; + stamp.clockDomain = QStringLiteral("scopeone.host.monotonic"); + stamp.source = QStringLiteral("HostEstimated"); + return stamp; + } +} diff --git a/ScopeOneCore/src/FrameBufferUtils.cpp b/ScopeOneCore/src/FrameBufferUtils.cpp index 17f7d3e..56113df 100644 --- a/ScopeOneCore/src/FrameBufferUtils.cpp +++ b/ScopeOneCore/src/FrameBufferUtils.cpp @@ -16,6 +16,7 @@ namespace scopeone::core::internal dst.cameraId = src.cameraId; dst.frameIndex = src.frameIndex; dst.timestampNs = src.timestampNs; + dst.clockStamp = src.clockStamp; dst.sourceRoiX = src.sourceRoiX; dst.sourceRoiY = src.sourceRoiY; dst.sourceRoiWidth = src.sourceRoiWidth; diff --git a/ScopeOneCore/src/FrameRouter.cpp b/ScopeOneCore/src/FrameRouter.cpp new file mode 100644 index 0000000..31b4446 --- /dev/null +++ b/ScopeOneCore/src/FrameRouter.cpp @@ -0,0 +1,22 @@ +#include "internal/FrameRouter.h" + +#include "scopeone/ClockService.h" + +namespace scopeone::core::internal +{ + FrameRouter::FrameRouter(scopeone::core::ClockService* clockService, QObject* parent) + : QObject(parent) + , m_clockService(clockService) + { + } + + void FrameRouter::publish(const scopeone::core::ImageFrame& frame) + { + scopeone::core::ImageFrame routedFrame(frame); + if (!routedFrame.clockStamp.isValid() && m_clockService) + { + routedFrame.clockStamp = m_clockService->now(); + } + emit frameReady(routedFrame); + } +} diff --git a/ScopeOneCore/src/HardwareRuntime.cpp b/ScopeOneCore/src/HardwareRuntime.cpp new file mode 100644 index 0000000..5314467 --- /dev/null +++ b/ScopeOneCore/src/HardwareRuntime.cpp @@ -0,0 +1,477 @@ +#include "internal/HardwareRuntime.h" + +#include "scopeone/CameraProvider.h" + +#include +#include +#include + +namespace scopeone::core::internal +{ + DeviceRegistry::DeviceRegistry(QObject* parent) + : QObject(parent) + { + } + + void DeviceRegistry::clear() + { + if (m_providers.isEmpty()) + { + return; + } + m_providers.clear(); + emit changed(); + } + + bool DeviceRegistry::registerProvider(const HardwareProviderPtr& provider) + { + if (!provider) + { + return false; + } + const HardwareProviderDescriptor descriptor = provider->descriptor(); + const QString providerId = descriptor.id.trimmed(); + if (providerId.isEmpty()) + { + return false; + } + const QList devices = provider->devices(); + QSet logicalIds; + for (const HardwareDeviceDescriptor& device : devices) + { + const QString logicalId = device.logicalId.trimmed(); + if (logicalId.isEmpty() + || device.providerId.trimmed() != providerId + || logicalIds.contains(logicalId)) + { + return false; + } + const HardwareDeviceDescriptor existing = this->device(logicalId); + if (!existing.logicalId.isEmpty() && existing.providerId != providerId) + { + return false; + } + logicalIds.insert(logicalId); + } + ProviderEntry entry; + entry.provider = provider; + entry.devices = devices; + m_providers.insert(providerId, std::move(entry)); + emit changed(); + return true; + } + + void DeviceRegistry::unregisterProvider(const QString& providerId) + { + if (m_providers.remove(providerId.trimmed()) > 0) + { + emit changed(); + } + } + + void DeviceRegistry::refreshProvider(const QString& providerId) + { + const QString normalizedId = providerId.trimmed(); + const auto it = m_providers.constFind(normalizedId); + if (it == m_providers.constEnd() || !it->provider) + { + return; + } + const HardwareProviderPtr provider = it->provider; + registerProvider(provider); + } + + QList DeviceRegistry::providers() const + { + QList result; + result.reserve(m_providers.size()); + for (auto it = m_providers.constBegin(); it != m_providers.constEnd(); ++it) + { + result.append(it->provider->descriptor()); + } + std::sort(result.begin(), result.end(), [](const auto& lhs, const auto& rhs) + { + return lhs.id < rhs.id; + }); + return result; + } + + QList DeviceRegistry::devices() const + { + QList result; + for (auto it = m_providers.constBegin(); it != m_providers.constEnd(); ++it) + { + result.append(it->devices); + } + std::sort(result.begin(), result.end(), [](const auto& lhs, const auto& rhs) + { + return lhs.logicalId < rhs.logicalId; + }); + return result; + } + + HardwareDeviceDescriptor DeviceRegistry::device(const QString& logicalId) const + { + const QString normalizedId = logicalId.trimmed(); + for (auto it = m_providers.constBegin(); it != m_providers.constEnd(); ++it) + { + for (const HardwareDeviceDescriptor& candidate : it->devices) + { + if (candidate.logicalId == normalizedId) + { + return candidate; + } + } + } + return {}; + } + + HardwareProviderPtr DeviceRegistry::provider(const QString& providerId) const + { + const auto it = m_providers.constFind(providerId.trimmed()); + return it == m_providers.constEnd() ? HardwareProviderPtr{} : it->provider; + } + + HardwareProviderPtr DeviceRegistry::providerForDevice(const QString& logicalId) const + { + const HardwareDeviceDescriptor descriptor = device(logicalId); + return descriptor.logicalId.isEmpty() ? HardwareProviderPtr{} : provider(descriptor.providerId); + } + + HardwareRuntime::HardwareRuntime(QObject* parent) + : QObject(parent) + , m_registry(this) + , m_frameRouter(&m_clockService, this) + , m_acquisitionEngine(&m_registry, this) + { + connect(&m_registry, &DeviceRegistry::changed, + this, &HardwareRuntime::devicesChanged); + connect(&m_frameRouter, &FrameRouter::frameReady, + this, [this](const ImageFrame& frame) + { + if (m_frameSink) + { + m_frameSink(frame); + } + }); + } + + void HardwareRuntime::setFrameSink(FrameSink sink) + { + m_frameSink = std::move(sink); + } + + CameraProvider* HardwareRuntime::cameraProviderForDevice(const QString& logicalId) const + { + const HardwareProviderPtr provider = m_registry.providerForDevice(logicalId); + return dynamic_cast(provider.get()); + } + + QList HardwareRuntime::cameraProviders() const + { + QList result; + QSet visited; + for (const HardwareDeviceDescriptor& device : m_registry.devices()) + { + if (device.kind != HardwareDeviceKind::Camera) + { + continue; + } + const HardwareProviderPtr provider = m_registry.provider(device.providerId); + auto* cameraProvider = dynamic_cast(provider.get()); + if (cameraProvider && !visited.contains(cameraProvider)) + { + visited.insert(cameraProvider); + result.append(cameraProvider); + } + } + return result; + } + + bool HardwareRuntime::startPreview() + { + return m_acquisitionEngine.start(QStringLiteral("All")); + } + + bool HardwareRuntime::stopPreview() + { + return m_acquisitionEngine.stop(QStringLiteral("All")); + } + + bool HardwareRuntime::startPreviewFor(const QString& cameraId) + { + return m_acquisitionEngine.start(cameraId); + } + + bool HardwareRuntime::stopPreviewFor(const QString& cameraId) + { + return m_acquisitionEngine.stop(cameraId); + } + + bool HardwareRuntime::isPreviewRunning(const QString& cameraId) const + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider && provider->isPreviewRunning(cameraId); + } + + bool HardwareRuntime::getExposure(const QString& cameraIdOrAll, double& exposureMs) const + { + const QString target = cameraIdOrAll.trimmed(); + if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) != 0) + { + CameraProvider* provider = cameraProviderForDevice(target); + return provider && provider->getExposure(target, exposureMs); + } + const QList providers = cameraProviders(); + return !providers.isEmpty() && providers.first()->getExposure(QStringLiteral("All"), exposureMs); + } + + bool HardwareRuntime::setExposure(const QString& cameraIdOrAll, double exposureMs) + { + const QString target = cameraIdOrAll.trimmed(); + if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) != 0) + { + CameraProvider* provider = cameraProviderForDevice(target); + return provider && provider->setExposure(target, exposureMs); + } + const QList providers = cameraProviders(); + bool ok = !providers.isEmpty(); + for (CameraProvider* provider : providers) + { + ok = provider->setExposure(QStringLiteral("All"), exposureMs) && ok; + } + return ok; + } + + QStringList HardwareRuntime::listProperties(const QString& cameraId) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider ? provider->listProperties(cameraId) : QStringList{}; + } + + QString HardwareRuntime::getProperty(const QString& cameraId, + const QString& name, + bool fromCache) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider ? provider->getProperty(cameraId, name, fromCache) : QString{}; + } + + bool HardwareRuntime::setProperty(const QString& cameraId, + const QString& name, + const QString& value, + QString* errorMessage) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider && provider->setProperty(cameraId, name, value, errorMessage); + } + + QString HardwareRuntime::getPropertyType(const QString& cameraId, const QString& name) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider ? provider->getPropertyType(cameraId, name) : QStringLiteral("Unknown"); + } + + bool HardwareRuntime::isPropertyReadOnly(const QString& cameraId, const QString& name) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return !provider || provider->isPropertyReadOnly(cameraId, name); + } + + bool HardwareRuntime::isPropertyPreInit(const QString& cameraId, const QString& name) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider && provider->isPropertyPreInit(cameraId, name); + } + + QStringList HardwareRuntime::getAllowedPropertyValues(const QString& cameraId, + const QString& name) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider ? provider->getAllowedPropertyValues(cameraId, name) : QStringList{}; + } + + bool HardwareRuntime::hasPropertyLimits(const QString& cameraId, const QString& name) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider && provider->hasPropertyLimits(cameraId, name); + } + + double HardwareRuntime::getPropertyLowerLimit(const QString& cameraId, const QString& name) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider ? provider->getPropertyLowerLimit(cameraId, name) : 0.0; + } + + double HardwareRuntime::getPropertyUpperLimit(const QString& cameraId, const QString& name) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider ? provider->getPropertyUpperLimit(cameraId, name) : 0.0; + } + + bool HardwareRuntime::setROI(const QString& cameraId, + int x, + int y, + int width, + int height) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider && provider->setROI(cameraId, x, y, width, height); + } + + bool HardwareRuntime::clearROI(const QString& cameraId) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider && provider->clearROI(cameraId); + } + + bool HardwareRuntime::getROI(const QString& cameraId, + int& x, + int& y, + int& width, + int& height) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider && provider->getROI(cameraId, x, y, width, height); + } + + bool HardwareRuntime::captureEventFrame(const QString& cameraId, + ImageFrame& frame, + int timeoutMs) + { + CameraProvider* provider = cameraProviderForDevice(cameraId); + return provider && provider->captureEventFrame(cameraId, frame, timeoutMs); + } + + void HardwareRuntime::clear() + { + m_acquisitionEngine.reset(); + for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + { + const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + if (auto* cameraProvider = dynamic_cast(provider.get())) + { + cameraProvider->stopPreview(); + cameraProvider->setFrameSink({}); + } + } + m_registry.clear(); + } + + bool HardwareRuntime::registerProvider(const HardwareProviderPtr& provider) + { + if (!provider || provider->descriptor().id.trimmed().isEmpty()) + { + return false; + } + const HardwareProviderPtr previous = m_registry.provider(provider->descriptor().id); + auto* cameraProvider = dynamic_cast(provider.get()); + if (cameraProvider) + { + cameraProvider->setFrameSink([this](const ImageFrame& frame) + { + m_frameRouter.publish(frame); + }); + } + const QList providerDevices = provider->devices(); + const bool providerHasCamera = std::any_of( + providerDevices.cbegin(), + providerDevices.cend(), + [](const HardwareDeviceDescriptor& device) + { + return device.kind == HardwareDeviceKind::Camera; + }); + if (providerHasCamera) + { + m_acquisitionEngine.prepare(); + } + if (!m_registry.registerProvider(provider)) + { + if (cameraProvider && previous != provider) + { + cameraProvider->setFrameSink({}); + } + const QList registeredDevices = m_registry.devices(); + const bool hasRegisteredCamera = std::any_of( + registeredDevices.cbegin(), + registeredDevices.cend(), + [](const HardwareDeviceDescriptor& device) + { + return device.kind == HardwareDeviceKind::Camera; + }); + if (!hasRegisteredCamera) + { + m_acquisitionEngine.reset(); + } + return false; + } + if (previous && previous != provider) + { + if (auto* previousCameraProvider = dynamic_cast(previous.get())) + { + previousCameraProvider->stopPreview(); + previousCameraProvider->setFrameSink({}); + } + } + if (!providerHasCamera) + { + const QList registeredDevices = m_registry.devices(); + const bool hasRegisteredCamera = std::any_of( + registeredDevices.cbegin(), + registeredDevices.cend(), + [](const HardwareDeviceDescriptor& device) + { + return device.kind == HardwareDeviceKind::Camera; + }); + if (!hasRegisteredCamera) + { + m_acquisitionEngine.reset(); + } + } + return true; + } + + void HardwareRuntime::unregisterProvider(const QString& providerId) + { + const HardwareProviderPtr provider = m_registry.provider(providerId); + if (auto* cameraProvider = dynamic_cast(provider.get())) + { + cameraProvider->stopPreview(); + cameraProvider->setFrameSink({}); + } + m_registry.unregisterProvider(providerId); + const QList devices = m_registry.devices(); + const bool hasCamera = std::any_of( + devices.cbegin(), + devices.cend(), + [](const HardwareDeviceDescriptor& device) + { + return device.kind == HardwareDeviceKind::Camera; + }); + if (!hasCamera) + { + m_acquisitionEngine.reset(); + } + } + + void HardwareRuntime::refreshProvider(const QString& providerId) + { + m_registry.refreshProvider(providerId); + const QList devices = m_registry.devices(); + const bool hasCamera = std::any_of( + devices.cbegin(), + devices.cend(), + [](const HardwareDeviceDescriptor& device) + { + return device.kind == HardwareDeviceKind::Camera; + }); + if (hasCamera) + { + m_acquisitionEngine.prepare(); + } + else + { + m_acquisitionEngine.reset(); + } + } +} diff --git a/ScopeOneCore/src/MDAManager.cpp b/ScopeOneCore/src/MDAManager.cpp index 0015bbc..efbd1cf 100644 --- a/ScopeOneCore/src/MDAManager.cpp +++ b/ScopeOneCore/src/MDAManager.cpp @@ -1,7 +1,8 @@ #include "internal/MDAManager.h" -#include "internal/CameraManager.h" +#include "scopeone/CameraProvider.h" #include "MMCore.h" +#include "scopeone/ClockService.h" #include #include @@ -38,9 +39,9 @@ namespace scopeone::core::internal } // Connects MDA capture to the active camera backend - void MDAManager::setCameraManager(CameraManager* cameraManager) + void MDAManager::setCameraProvider(CameraProvider* cameraProvider) { - m_cameraManager = cameraManager; + m_cameraProvider = cameraProvider; } // Starts one immutable acquisition event sequence @@ -186,9 +187,9 @@ namespace scopeone::core::internal { if (event.cameraIds.size() > 1) { - if (!m_cameraManager) + if (!m_cameraProvider) { - if (errorMessage) *errorMessage = QStringLiteral("CameraManager not available"); + if (errorMessage) *errorMessage = QStringLiteral("Camera provider not available"); return false; } return execEventMultiCamera(event, output, errorMessage); @@ -249,6 +250,7 @@ namespace scopeone::core::internal frame.pixelFormat, static_cast(m_mmcore->getImageBitDepth())); frame.timestampNs = currentTimestampNs(); + frame.clockStamp = scopeone::core::ClockService{}.now(); frame.sourceRoiWidth = frame.width; frame.sourceRoiHeight = frame.height; if (!frame.cameraId.isEmpty()) @@ -303,7 +305,7 @@ namespace scopeone::core::internal { CaptureResult result; result.cameraId = cameraId; - if (!m_cameraManager->captureEventFrame(cameraId, result.frame, captureTimeoutMs)) + if (!m_cameraProvider->captureEventFrame(cameraId, result.frame, captureTimeoutMs)) { result.error = QStringLiteral("Failed to capture frame from camera: %1").arg(cameraId); return result; diff --git a/ScopeOneCore/src/MMCoreManager.cpp b/ScopeOneCore/src/MMCoreManager.cpp index e1f30d5..6ee53f3 100644 --- a/ScopeOneCore/src/MMCoreManager.cpp +++ b/ScopeOneCore/src/MMCoreManager.cpp @@ -16,6 +16,27 @@ namespace scopeone::core::internal { namespace { + scopeone::core::HardwareDeviceKind hardwareDeviceKind(MM::DeviceType type) + { + switch (type) + { + case MM::CameraDevice: + return scopeone::core::HardwareDeviceKind::Camera; + case MM::XYStageDevice: + return scopeone::core::HardwareDeviceKind::XYStage; + case MM::StageDevice: + return scopeone::core::HardwareDeviceKind::ZStage; + case MM::ShutterDevice: + return scopeone::core::HardwareDeviceKind::Shutter; + case MM::StateDevice: + return scopeone::core::HardwareDeviceKind::State; + case MM::HubDevice: + return scopeone::core::HardwareDeviceKind::Hub; + default: + return scopeone::core::HardwareDeviceKind::Unknown; + } + } + struct DevicePropertyState { QStringList preInitProperties; @@ -370,8 +391,28 @@ namespace scopeone::core::internal continue; } + scopeone::core::HardwareDeviceDescriptor descriptor; + descriptor.logicalId = deviceName; + descriptor.providerId = QStringLiteral("micro-manager"); + descriptor.providerDeviceId = deviceName; + descriptor.name = deviceName; + descriptor.kind = hardwareDeviceKind(deviceType); + descriptor.state = scopeone::core::HardwareDeviceState::Discovered; + descriptor.endpoint = deviceType == MM::CameraDevice && !useSingleCamera + ? scopeone::core::HardwareEndpointKind::DriverHost + : scopeone::core::HardwareEndpointKind::InProcess; + try + { + descriptor.hardwareId = QString::fromStdString( + m_mmcore->getDeviceName(label.c_str())); + } + catch (const CMMError&) + { + } + if (deviceType == MM::CameraDevice && !useSingleCamera) { + result.devices.append(descriptor); skippedCameraCount++; continue; } @@ -382,11 +423,13 @@ namespace scopeone::core::internal try { m_mmcore->initializeDevice(label.c_str()); + descriptor.state = scopeone::core::HardwareDeviceState::Initialized; successCount++; } catch (const CMMError& error) { failCount++; + descriptor.state = scopeone::core::HardwareDeviceState::Faulted; result.failedDevices.append(deviceName); qWarning().noquote() << QString("Failed to initialize device '%1': %2") @@ -395,8 +438,10 @@ namespace scopeone::core::internal } else { + descriptor.state = scopeone::core::HardwareDeviceState::Initialized; successCount++; } + result.devices.append(descriptor); } catch (const CMMError& error) { diff --git a/ScopeOneCore/src/MicroManagerProvider.cpp b/ScopeOneCore/src/MicroManagerProvider.cpp new file mode 100644 index 0000000..390eb66 --- /dev/null +++ b/ScopeOneCore/src/MicroManagerProvider.cpp @@ -0,0 +1,164 @@ +#include "internal/MicroManagerProvider.h" + +#include + +namespace scopeone::core::internal +{ + MicroManagerProvider::MicroManagerProvider(CameraProvider* cameraProvider) + : m_cameraProvider(cameraProvider) + { + } + + HardwareProviderDescriptor MicroManagerProvider::descriptor() const + { + return { + QStringLiteral("micro-manager"), + QStringLiteral("Micro-Manager"), + QStringLiteral("MMCore") + }; + } + + QList MicroManagerProvider::devices() const + { + return m_devices; + } + + void MicroManagerProvider::setDevices(const QList& devices) + { + m_devices = devices; + } + + void MicroManagerProvider::setFrameSink(FrameSink sink) + { + if (m_cameraProvider) + { + m_cameraProvider->setFrameSink(std::move(sink)); + } + } + + bool MicroManagerProvider::startPreview() + { + return m_cameraProvider && m_cameraProvider->startPreview(); + } + + bool MicroManagerProvider::stopPreview() + { + return m_cameraProvider && m_cameraProvider->stopPreview(); + } + + bool MicroManagerProvider::startPreviewFor(const QString& cameraId) + { + return m_cameraProvider && m_cameraProvider->startPreviewFor(cameraId); + } + + bool MicroManagerProvider::stopPreviewFor(const QString& cameraId) + { + return m_cameraProvider && m_cameraProvider->stopPreviewFor(cameraId); + } + + bool MicroManagerProvider::isPreviewRunning(const QString& cameraId) const + { + return m_cameraProvider && m_cameraProvider->isPreviewRunning(cameraId); + } + + bool MicroManagerProvider::getExposure(const QString& cameraIdOrAll, double& exposureMs) const + { + return m_cameraProvider && m_cameraProvider->getExposure(cameraIdOrAll, exposureMs); + } + + bool MicroManagerProvider::setExposure(const QString& cameraIdOrAll, double exposureMs) + { + return m_cameraProvider && m_cameraProvider->setExposure(cameraIdOrAll, exposureMs); + } + + QStringList MicroManagerProvider::listProperties(const QString& cameraId) + { + return m_cameraProvider ? m_cameraProvider->listProperties(cameraId) : QStringList{}; + } + + QString MicroManagerProvider::getProperty(const QString& cameraId, + const QString& name, + bool fromCache) + { + return m_cameraProvider ? m_cameraProvider->getProperty(cameraId, name, fromCache) : QString{}; + } + + bool MicroManagerProvider::setProperty(const QString& cameraId, + const QString& name, + const QString& value, + QString* errorMessage) + { + return m_cameraProvider + && m_cameraProvider->setProperty(cameraId, name, value, errorMessage); + } + + QString MicroManagerProvider::getPropertyType(const QString& cameraId, const QString& name) + { + return m_cameraProvider + ? m_cameraProvider->getPropertyType(cameraId, name) + : QStringLiteral("Unknown"); + } + + bool MicroManagerProvider::isPropertyReadOnly(const QString& cameraId, const QString& name) + { + return !m_cameraProvider || m_cameraProvider->isPropertyReadOnly(cameraId, name); + } + + bool MicroManagerProvider::isPropertyPreInit(const QString& cameraId, const QString& name) + { + return m_cameraProvider && m_cameraProvider->isPropertyPreInit(cameraId, name); + } + + QStringList MicroManagerProvider::getAllowedPropertyValues(const QString& cameraId, + const QString& name) + { + return m_cameraProvider + ? m_cameraProvider->getAllowedPropertyValues(cameraId, name) + : QStringList{}; + } + + bool MicroManagerProvider::hasPropertyLimits(const QString& cameraId, const QString& name) + { + return m_cameraProvider && m_cameraProvider->hasPropertyLimits(cameraId, name); + } + + double MicroManagerProvider::getPropertyLowerLimit(const QString& cameraId, const QString& name) + { + return m_cameraProvider ? m_cameraProvider->getPropertyLowerLimit(cameraId, name) : 0.0; + } + + double MicroManagerProvider::getPropertyUpperLimit(const QString& cameraId, const QString& name) + { + return m_cameraProvider ? m_cameraProvider->getPropertyUpperLimit(cameraId, name) : 0.0; + } + + bool MicroManagerProvider::setROI(const QString& cameraId, + int x, + int y, + int width, + int height) + { + return m_cameraProvider && m_cameraProvider->setROI(cameraId, x, y, width, height); + } + + bool MicroManagerProvider::clearROI(const QString& cameraId) + { + return m_cameraProvider && m_cameraProvider->clearROI(cameraId); + } + + bool MicroManagerProvider::getROI(const QString& cameraId, + int& x, + int& y, + int& width, + int& height) + { + return m_cameraProvider && m_cameraProvider->getROI(cameraId, x, y, width, height); + } + + bool MicroManagerProvider::captureEventFrame(const QString& cameraId, + scopeone::core::ImageFrame& frame, + int timeoutMs) + { + return m_cameraProvider && m_cameraProvider->captureEventFrame(cameraId, frame, timeoutMs); + } +} diff --git a/ScopeOneCore/src/NativeCameraBackend.cpp b/ScopeOneCore/src/NativeCameraBackend.cpp index 7f28cea..2bb519d 100644 --- a/ScopeOneCore/src/NativeCameraBackend.cpp +++ b/ScopeOneCore/src/NativeCameraBackend.cpp @@ -1,6 +1,7 @@ #include "internal/CameraBackend.h" #include "MMCore.h" +#include "scopeone/ClockService.h" #include #include @@ -279,6 +280,7 @@ namespace scopeone::core::internal frame.pixelFormat = m_configuration.pixelFormat; frame.frameIndex = frameIndex; frame.timestampNs = static_cast(QDateTime::currentMSecsSinceEpoch()) * 1000000ull; + frame.clockStamp = scopeone::core::ClockService{}.now(); frame.sourceRoiX = m_configuration.sourceRoiX; frame.sourceRoiY = m_configuration.sourceRoiY; frame.sourceRoiWidth = m_configuration.sourceRoiWidth; diff --git a/ScopeOneCore/src/RecordingManager.cpp b/ScopeOneCore/src/RecordingManager.cpp index bd8b8ad..3ad1902 100644 --- a/ScopeOneCore/src/RecordingManager.cpp +++ b/ScopeOneCore/src/RecordingManager.cpp @@ -1,6 +1,6 @@ #include "internal/RecordingManager.h" -#include "internal/CameraManager.h" +#include "scopeone/CameraProvider.h" #include "MMCore.h" #include @@ -833,7 +833,7 @@ namespace scopeone::core::internal { return false; } - if (!planUsesMda(plan) && !planStreamsMda(plan) && !m_cameraManager && !m_latestFrameFetcher) + if (!planUsesMda(plan) && !planStreamsMda(plan) && !m_cameraProvider && !m_latestFrameFetcher) { errorMessage = QStringLiteral("Frame source is not available for recording"); return false; @@ -1329,8 +1329,8 @@ namespace scopeone::core::internal if (!usesMda) { primeLastFrameIndices(); - if (m_cameraManager - && !m_cameraManager->setRecordingFrameDeliveryEnabled(true)) + if (m_cameraRuntimeControl + && !m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(true)) { if (plan.streamToDisk) { @@ -1346,9 +1346,9 @@ namespace scopeone::core::internal { if (!startStreamingOutputs(plan)) { - if (m_cameraManager) + if (m_cameraRuntimeControl) { - m_cameraManager->setRecordingFrameDeliveryEnabled(false); + m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(false); } const QString writerError = writerErrorSnapshot(); qWarning().noquote() << (writerError.isEmpty() @@ -1414,17 +1414,17 @@ namespace scopeone::core::internal emit recordingStateChanged(false); emitProgress(true); - if (m_cameraManager) + if (m_cameraRuntimeControl) { - m_cameraManager->setRecordingFrameDeliveryEnabled(false); + m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(false); } if (m_mdaState.usingMda && m_mdaState.manager && m_mdaState.manager->isRunning()) { m_mdaState.manager->requestCancel(); } - if (m_cameraManager) + if (m_cameraRuntimeControl) { - m_cameraManager->setFrameDeliveryPaused(false); + m_cameraRuntimeControl->setFrameDeliveryPaused(false); } qInfo().noquote() << "Recording stopped"; @@ -1975,9 +1975,9 @@ namespace scopeone::core::internal qWarning().noquote() << message; return false; } - if (m_captureState.activeCameraIds.size() > 1 && !m_cameraManager) + if (m_captureState.activeCameraIds.size() > 1 && !m_cameraProvider) { - const QString message = QStringLiteral("Multi-camera MDA requires CameraManager"); + const QString message = QStringLiteral("Multi-camera MDA requires a camera provider"); if (errorMessage) *errorMessage = message; qWarning().noquote() << message; return false; @@ -1994,11 +1994,11 @@ namespace scopeone::core::internal qWarning().noquote() << message; return false; } - m_mdaState.manager->setCameraManager(m_cameraManager); + m_mdaState.manager->setCameraProvider(m_cameraProvider); - if (m_captureState.activeCameraIds.size() > 1) + if (m_captureState.activeCameraIds.size() > 1 && m_cameraRuntimeControl) { - m_cameraManager->setFrameDeliveryPaused(true); + m_cameraRuntimeControl->setFrameDeliveryPaused(true); } m_mdaState.cameraId = m_captureState.activeCameraIds.first(); diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 1f8c909..3b5e413 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -2,11 +2,15 @@ #include "scopeone/ImageSceneModel.h" #include "internal/BackgroundCalibrationModule.h" +#include "internal/AcquisitionEngine.h" #include "internal/DifferentialRollingModule.h" #include "internal/FFTModule.h" #include "internal/GaussianBlurModule.h" #include "internal/ImageProcessingFramework.h" +#include "internal/FrameRouter.h" +#include "internal/HardwareRuntime.h" #include "internal/MMCoreManager.h" +#include "internal/MicroManagerProvider.h" #include "internal/CameraManager.h" #include "internal/ParticleAnalysis.h" #include "internal/RecordingManager.h" @@ -364,6 +368,7 @@ namespace facade.failCount = result.failCount; facade.skippedCameraCount = result.skippedCameraCount; facade.foundCamera = result.foundCamera; + facade.devices = result.devices; return facade; } @@ -911,6 +916,10 @@ namespace scopeone::core quint64 completedCount{0}; }; + HardwareRuntime* hardwareRuntime{nullptr}; + CameraProvider* cameraProvider{nullptr}; + CameraRuntimeControl* cameraRuntimeControl{nullptr}; + std::shared_ptr microManagerProvider; MMCoreManager* mmcoreManager{nullptr}; CameraManager* cameraManager{nullptr}; RecordingManager* recordingManager{nullptr}; @@ -938,6 +947,48 @@ namespace scopeone::core return QStringLiteral(SCOPEONE_CORE_VERSION_STRING); } + // Return the unified device catalog owned by the hardware runtime + QList ScopeOneCore::hardwareDevices() const + { + return m_managers->hardwareRuntime->deviceRegistry()->devices(); + } + + bool ScopeOneCore::registerHardwareProvider(const HardwareProviderPtr& provider) + { + if (!provider + || provider->descriptor().id.trimmed().isEmpty() + || m_configurationOperationRunning + || isRecording()) + { + return false; + } + const bool registered = m_managers->hardwareRuntime->registerProvider(provider); + if (registered) + { + synchronizeCameraIdsFromRegistry(); + } + return registered; + } + + bool ScopeOneCore::unregisterHardwareProvider(const QString& providerId) + { + const QString normalizedId = providerId.trimmed(); + if (normalizedId.isEmpty() + || normalizedId == QStringLiteral("micro-manager") + || m_configurationOperationRunning + || isRecording()) + { + return false; + } + if (!m_managers->hardwareRuntime->deviceRegistry()->provider(normalizedId)) + { + return false; + } + m_managers->hardwareRuntime->unregisterProvider(normalizedId); + synchronizeCameraIdsFromRegistry(); + return true; + } + // Return the linked MMCore version QString ScopeOneCore::getMMCoreVersion() { @@ -1100,12 +1151,25 @@ namespace scopeone::core this, &ScopeOneCore::syncLineProfileFromScene); m_managers->mmcoreManager = new MMCoreManager(this); m_managers->cameraManager = new CameraManager(this); + m_managers->cameraRuntimeControl = m_managers->cameraManager; + m_managers->microManagerProvider = + std::make_shared(m_managers->cameraManager); + m_managers->hardwareRuntime = new HardwareRuntime(this); + m_managers->cameraProvider = m_managers->hardwareRuntime; + connect(m_managers->hardwareRuntime, &HardwareRuntime::devicesChanged, + this, [this]() + { + synchronizeCameraIdsFromRegistry(); + emit hardwareDevicesChanged(); + }); + m_managers->hardwareRuntime->registerProvider(m_managers->microManagerProvider); m_managers->recordingManager = new RecordingManager(this); m_managers->imageProcessingManager = new ImageProcessingManager(this); m_managers->stageMosaicManager = new StageMosaicManager(this, this); m_managers->recordingWriterStatus.reset( m_managers->recordingManager->recordedMaxBytes()); - m_managers->recordingManager->setCameraManager(m_managers->cameraManager); + m_managers->recordingManager->setCameraProvider(m_managers->cameraProvider); + m_managers->recordingManager->setCameraRuntimeControl(m_managers->cameraRuntimeControl); m_managers->recordingManager->setMMCore(m_managers->mmcoreManager->getCore()); m_managers->recordingManager->setLatestFrameFetcher( [this](const QString& cameraId, ImageFrame& frame) @@ -1122,8 +1186,20 @@ namespace scopeone::core session.setPresentationState(layers, markups); }); - connect(m_managers->cameraManager, &CameraManager::newRawFrameReady, + connect(m_managers->hardwareRuntime->frameRouter(), &FrameRouter::frameReady, this, &ScopeOneCore::handleIncomingRawFrame); + connect(m_managers->hardwareRuntime->frameRouter(), &FrameRouter::frameReady, + this, [this](const ImageFrame& frame) + { + const HardwareDeviceDescriptor device = + m_managers->hardwareRuntime->deviceRegistry()->device(frame.cameraId); + if (device.providerId == QStringLiteral("micro-manager")) + { + return; + } + submitProcessingFrame(frame); + m_managers->recordingManager->onRawFramesReady(QList{frame}); + }); connect(m_managers->cameraManager, &CameraManager::processingFrameReady, this, &ScopeOneCore::submitProcessingFrame, Qt::DirectConnection); @@ -1152,8 +1228,13 @@ namespace scopeone::core connect(m_managers->recordingManager, &RecordingManager::mdaRawFrameReady, this, [this](const ImageFrame& frame) { - handleIncomingRawFrame(frame); - submitProcessingFrame(frame); + m_managers->hardwareRuntime->frameRouter()->publish(frame); + const HardwareDeviceDescriptor device = + m_managers->hardwareRuntime->deviceRegistry()->device(frame.cameraId); + if (device.providerId == QStringLiteral("micro-manager")) + { + submitProcessingFrame(frame); + } }, Qt::QueuedConnection); @@ -1316,7 +1397,7 @@ namespace scopeone::core QStringList running; for (const QString& cameraId : m_cameraIds) { - if (m_managers->cameraManager->isPreviewRunning(cameraId)) + if (m_managers->cameraProvider->isPreviewRunning(cameraId)) { running.append(cameraId); } @@ -1397,23 +1478,14 @@ namespace scopeone::core void ScopeOneCore::applyLoadedConfiguration(const QString& configPath, const LoadConfigResult& result) { - m_cameraIds = result.cameraIds; m_configurationFailedDevices = result.failedDevices; m_configurationError.clear(); m_configurationState = result.failedDevices.isEmpty() ? ConfigurationState::Loaded : ConfigurationState::PartiallyLoaded; - for (const QString& cameraId : m_cameraIds) - { - ensureSceneLayer(rawLayerKey(cameraId), - cameraId, - QStringLiteral("%1 Raw").arg(cameraId), - DocumentLayerKind::Raw); - ensureSceneLayer(processedLayerKey(cameraId), - cameraId, - QStringLiteral("%1 Processed").arg(cameraId), - DocumentLayerKind::Processed); - } + m_managers->microManagerProvider->setDevices(result.devices); + m_managers->hardwareRuntime->refreshProvider(QStringLiteral("micro-manager")); + synchronizeCameraIdsFromRegistry(); const QFileInfo configFile(configPath); m_loadedConfigPath = configPath.trimmed().isEmpty() ? QString() @@ -1428,6 +1500,45 @@ namespace scopeone::core emit hardwareConfigurationChanged(); } + void ScopeOneCore::synchronizeCameraIdsFromRegistry() + { + QStringList nextCameraIds; + for (const HardwareDeviceDescriptor& device + : m_managers->hardwareRuntime->deviceRegistry()->devices()) + { + const QString logicalId = device.logicalId.trimmed(); + if (device.kind == HardwareDeviceKind::Camera + && !logicalId.isEmpty() + && !nextCameraIds.contains(logicalId)) + { + nextCameraIds.append(logicalId); + } + } + nextCameraIds.sort(Qt::CaseInsensitive); + + for (const QString& cameraId : m_cameraIds) + { + if (!nextCameraIds.contains(cameraId)) + { + clearLiveFrames(cameraId); + m_imageSceneModel->removeLayer(rawLayerKey(cameraId)); + m_imageSceneModel->removeLayer(processedLayerKey(cameraId)); + } + } + m_cameraIds = nextCameraIds; + for (const QString& cameraId : m_cameraIds) + { + ensureSceneLayer(rawLayerKey(cameraId), + cameraId, + QStringLiteral("%1 Raw").arg(cameraId), + DocumentLayerKind::Raw); + ensureSceneLayer(processedLayerKey(cameraId), + cameraId, + QStringLiteral("%1 Processed").arg(cameraId), + DocumentLayerKind::Processed); + } + } + // Complete a failed configuration load and publish one consistent result void ScopeOneCore::finishConfigurationLoadFailure(const LoadConfigResult& result, const QString& errorMessage) @@ -1455,6 +1566,7 @@ namespace scopeone::core catch (const CMMError&) { } + m_managers->hardwareRuntime->clear(); } // Apply the configured hardware shutdown state before releasing devices @@ -1487,6 +1599,7 @@ namespace scopeone::core const bool processingWasEnabled = isRealTimeProcessingEnabled(); m_managers->imageProcessingManager->enableRealTimeProcessing(false); const QStringList cameraIds = m_cameraIds; + m_managers->hardwareRuntime->acquisitionEngine()->reset(); if (shutdownCameraBackend) { m_managers->cameraManager->stopPreview(); @@ -1509,6 +1622,9 @@ namespace scopeone::core m_latestHistogramStats.clear(); m_activeHistogramLayerKey.clear(); m_imageSceneModel->reset(); + m_managers->microManagerProvider->setDevices(QList{}); + m_managers->hardwareRuntime->refreshProvider(QStringLiteral("micro-manager")); + synchronizeCameraIdsFromRegistry(); if (notify && processingWasEnabled) { emit processingSettingsChanged(); @@ -1779,11 +1895,7 @@ namespace scopeone::core { return false; } - if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) - { - return m_managers->cameraManager->startPreview(); - } - return m_managers->cameraManager->startPreviewFor(target); + return m_managers->hardwareRuntime->acquisitionEngine()->start(target); } // Stop preview for one camera or the full camera set @@ -1794,11 +1906,7 @@ namespace scopeone::core { return false; } - if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) - { - return m_managers->cameraManager->stopPreview(); - } - return m_managers->cameraManager->stopPreviewFor(target); + return m_managers->hardwareRuntime->acquisitionEngine()->stop(target); } // Submit exposure changes through the active camera manager @@ -1809,7 +1917,7 @@ namespace scopeone::core { return false; } - const bool ok = m_managers->cameraManager->setExposure(target, exposureMs); + const bool ok = m_managers->cameraProvider->setExposure(target, exposureMs); if (ok) { emit deviceStateChanged(); @@ -1825,7 +1933,7 @@ namespace scopeone::core { return false; } - const bool ok = m_managers->cameraManager->setROI(target, x, y, width, height); + const bool ok = m_managers->cameraProvider->setROI(target, x, y, width, height); if (ok) { clearLiveFrames(target); @@ -1888,7 +1996,7 @@ namespace scopeone::core bool changed = false; for (const QString& cameraId : targets) { - if (!m_managers->cameraManager->clearROI(cameraId)) + if (!m_managers->cameraProvider->clearROI(cameraId)) { ok = false; continue; @@ -1911,7 +2019,7 @@ namespace scopeone::core { return false; } - return m_managers->cameraManager->getROI(target, x, y, width, height); + return m_managers->cameraProvider->getROI(target, x, y, width, height); } // Track the active line profile request for future frames @@ -2069,7 +2177,7 @@ namespace scopeone::core processingToken, [this, cameraId, processingToken]() { - return m_managers->cameraManager->isProcessingFrameTokenCurrent( + return m_managers->cameraRuntimeControl->isProcessingFrameTokenCurrent( cameraId, processingToken); }); @@ -3168,7 +3276,7 @@ namespace scopeone::core { if (isConfiguredCamera(device)) { - const QString value = m_managers->cameraManager->getProperty( + const QString value = m_managers->cameraProvider->getProperty( device, property, false); if (value.isNull()) { @@ -3285,7 +3393,7 @@ namespace scopeone::core if (isConfiguredCamera(device)) { QString error; - if (!m_managers->cameraManager->setProperty( + if (!m_managers->cameraProvider->setProperty( device, property, value, &error)) { failed.push_back(setting); @@ -3383,7 +3491,7 @@ namespace scopeone::core resolvedTarget = m_cameraIds.first(); } if (m_cameraIds.contains(resolvedTarget) - && m_managers->cameraManager->getExposure(resolvedTarget, exposureMs)) + && m_managers->cameraProvider->getExposure(resolvedTarget, exposureMs)) { return true; } @@ -3473,7 +3581,7 @@ namespace scopeone::core } if (isConfiguredCamera(device)) { - return m_managers->cameraManager->listProperties(device); + return m_managers->cameraProvider->listProperties(device); } auto handle = core(); try @@ -3497,7 +3605,7 @@ namespace scopeone::core } if (isConfiguredCamera(device)) { - return m_managers->cameraManager->getProperty(device, property, fromCache); + return m_managers->cameraProvider->getProperty(device, property, fromCache); } auto handle = core(); try @@ -3528,7 +3636,7 @@ namespace scopeone::core } if (isConfiguredCamera(device)) { - return m_managers->cameraManager->getPropertyType(device, property); + return m_managers->cameraProvider->getPropertyType(device, property); } auto handle = core(); try @@ -3560,7 +3668,7 @@ namespace scopeone::core } if (isConfiguredCamera(device)) { - return m_managers->cameraManager->isPropertyReadOnly(device, property); + return m_managers->cameraProvider->isPropertyReadOnly(device, property); } auto handle = core(); try @@ -3584,7 +3692,7 @@ namespace scopeone::core } if (isConfiguredCamera(device)) { - return m_managers->cameraManager->isPropertyPreInit(device, property); + return m_managers->cameraProvider->isPropertyPreInit(device, property); } auto handle = core(); try @@ -3608,7 +3716,7 @@ namespace scopeone::core } if (isConfiguredCamera(device)) { - return m_managers->cameraManager->getAllowedPropertyValues(device, property); + return m_managers->cameraProvider->getAllowedPropertyValues(device, property); } auto handle = core(); try @@ -3639,12 +3747,12 @@ namespace scopeone::core } if (isConfiguredCamera(device)) { - if (!m_managers->cameraManager->hasPropertyLimits(device, property)) + if (!m_managers->cameraProvider->hasPropertyLimits(device, property)) { return false; } - lower = m_managers->cameraManager->getPropertyLowerLimit(device, property); - upper = m_managers->cameraManager->getPropertyUpperLimit(device, property); + lower = m_managers->cameraProvider->getPropertyLowerLimit(device, property); + upper = m_managers->cameraProvider->getPropertyUpperLimit(device, property); return true; } @@ -3699,7 +3807,7 @@ namespace scopeone::core if (isConfiguredCamera(device)) { QString cameraError; - if (!m_managers->cameraManager->setProperty(device, property, value, &cameraError)) + if (!m_managers->cameraProvider->setProperty(device, property, value, &cameraError)) { if (errorMessage) { @@ -3770,7 +3878,7 @@ namespace scopeone::core } if (m_managers->imageProcessingManager->isRealTimeProcessingEnabled() == enabled) { - if (!m_managers->cameraManager->setHighRateFrameDeliveryEnabled(enabled)) + if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled(enabled)) { return false; } @@ -3783,7 +3891,7 @@ namespace scopeone::core if (enabled) { m_managers->imageProcessingManager->enableRealTimeProcessing(true); - if (!m_managers->cameraManager->setHighRateFrameDeliveryEnabled(true)) + if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled(true)) { m_managers->imageProcessingManager->enableRealTimeProcessing(false); return false; @@ -3791,7 +3899,7 @@ namespace scopeone::core } else { - if (!m_managers->cameraManager->setHighRateFrameDeliveryEnabled(false)) + if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled(false)) { return false; } diff --git a/ScopeOneCore/src/SimulatorProvider.cpp b/ScopeOneCore/src/SimulatorProvider.cpp new file mode 100644 index 0000000..c5f043b --- /dev/null +++ b/ScopeOneCore/src/SimulatorProvider.cpp @@ -0,0 +1,306 @@ +#include "scopeone/SimulatorProvider.h" + +#include "scopeone/ClockService.h" + +#include +#include +#include +#include +#include + +namespace scopeone::core +{ + SimulatorProvider::SimulatorProvider(const QString& logicalCameraId, int width, int height) + : m_providerId(QStringLiteral("simulator.%1").arg( + QUuid::createUuid().toString(QUuid::WithoutBraces))) + , m_cameraId(logicalCameraId.trimmed().isEmpty() + ? QStringLiteral("camera.simulator") + : logicalCameraId.trimmed()) + , m_sensorWidth((std::max)(1, width)) + , m_sensorHeight((std::max)(1, height)) + , m_roi(0, 0, m_sensorWidth, m_sensorHeight) + { + m_timer.setTimerType(Qt::PreciseTimer); + updateTimerInterval(); + connect(&m_timer, &QTimer::timeout, this, [this]() + { + if (m_frameSink) + { + m_frameSink(makeFrame()); + } + }); + } + + HardwareProviderDescriptor SimulatorProvider::descriptor() const + { + return {m_providerId, QStringLiteral("ScopeOne Simulator"), QStringLiteral("1")}; + } + + QList SimulatorProvider::devices() const + { + HardwareDeviceDescriptor camera; + camera.logicalId = m_cameraId; + camera.providerId = m_providerId; + camera.providerDeviceId = m_cameraId; + camera.hardwareId = m_cameraId; + camera.name = QStringLiteral("Simulator Camera"); + camera.kind = HardwareDeviceKind::Camera; + camera.state = HardwareDeviceState::Initialized; + camera.endpoint = HardwareEndpointKind::InProcess; + return {camera}; + } + + void SimulatorProvider::setFrameSink(FrameSink sink) + { + m_frameSink = std::move(sink); + } + + bool SimulatorProvider::startPreview() + { + if (!m_frameSink) + { + return false; + } + m_timer.start(); + return true; + } + + bool SimulatorProvider::stopPreview() + { + m_timer.stop(); + return true; + } + + bool SimulatorProvider::startPreviewFor(const QString& cameraId) + { + return accepts(cameraId) && startPreview(); + } + + bool SimulatorProvider::stopPreviewFor(const QString& cameraId) + { + return accepts(cameraId) && stopPreview(); + } + + bool SimulatorProvider::isPreviewRunning(const QString& cameraId) const + { + return accepts(cameraId) && m_timer.isActive(); + } + + bool SimulatorProvider::getExposure(const QString& cameraIdOrAll, double& exposureMs) const + { + if (!accepts(cameraIdOrAll)) + { + return false; + } + exposureMs = m_exposureMs; + return true; + } + + bool SimulatorProvider::setExposure(const QString& cameraIdOrAll, double exposureMs) + { + if (!accepts(cameraIdOrAll) || !std::isfinite(exposureMs) || exposureMs <= 0.0) + { + return false; + } + m_exposureMs = exposureMs; + updateTimerInterval(); + return true; + } + + QStringList SimulatorProvider::listProperties(const QString& cameraId) + { + return accepts(cameraId) + ? QStringList{QStringLiteral("Exposure"), + QStringLiteral("SensorWidth"), + QStringLiteral("SensorHeight")} + : QStringList{}; + } + + QString SimulatorProvider::getProperty(const QString& cameraId, + const QString& name, + bool) + { + if (!accepts(cameraId)) + { + return {}; + } + if (name == QStringLiteral("Exposure")) + { + return QString::number(m_exposureMs, 'g', 12); + } + if (name == QStringLiteral("SensorWidth")) + { + return QString::number(m_sensorWidth); + } + if (name == QStringLiteral("SensorHeight")) + { + return QString::number(m_sensorHeight); + } + return {}; + } + + bool SimulatorProvider::setProperty(const QString& cameraId, + const QString& name, + const QString& value, + QString* errorMessage) + { + if (errorMessage) + { + errorMessage->clear(); + } + if (!accepts(cameraId) || name != QStringLiteral("Exposure")) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Property is not writable"); + } + return false; + } + bool ok = false; + const double exposureMs = value.toDouble(&ok); + if (!ok || !setExposure(cameraId, exposureMs)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Invalid exposure value"); + } + return false; + } + return true; + } + + QString SimulatorProvider::getPropertyType(const QString& cameraId, const QString& name) + { + return accepts(cameraId) && listProperties(cameraId).contains(name) + ? (name == QStringLiteral("Exposure") + ? QStringLiteral("Float") + : QStringLiteral("Integer")) + : QStringLiteral("Unknown"); + } + + bool SimulatorProvider::isPropertyReadOnly(const QString& cameraId, const QString& name) + { + return !accepts(cameraId) || name != QStringLiteral("Exposure"); + } + + bool SimulatorProvider::isPropertyPreInit(const QString&, const QString&) + { + return false; + } + + QStringList SimulatorProvider::getAllowedPropertyValues(const QString&, const QString&) + { + return {}; + } + + bool SimulatorProvider::hasPropertyLimits(const QString& cameraId, const QString& name) + { + return accepts(cameraId) && name == QStringLiteral("Exposure"); + } + + double SimulatorProvider::getPropertyLowerLimit(const QString& cameraId, const QString& name) + { + return accepts(cameraId) && name == QStringLiteral("Exposure") ? 0.1 : 0.0; + } + + double SimulatorProvider::getPropertyUpperLimit(const QString& cameraId, const QString& name) + { + return accepts(cameraId) && name == QStringLiteral("Exposure") ? 1000.0 : 0.0; + } + + bool SimulatorProvider::setROI(const QString& cameraId, + int x, + int y, + int width, + int height) + { + const QRect roi(x, y, width, height); + const QRect sensor(0, 0, m_sensorWidth, m_sensorHeight); + if (!accepts(cameraId) || width <= 0 || height <= 0 || !sensor.contains(roi)) + { + return false; + } + m_roi = roi; + return true; + } + + bool SimulatorProvider::clearROI(const QString& cameraId) + { + if (!accepts(cameraId)) + { + return false; + } + m_roi = QRect(0, 0, m_sensorWidth, m_sensorHeight); + return true; + } + + bool SimulatorProvider::getROI(const QString& cameraId, + int& x, + int& y, + int& width, + int& height) + { + if (!accepts(cameraId)) + { + return false; + } + x = m_roi.x(); + y = m_roi.y(); + width = m_roi.width(); + height = m_roi.height(); + return true; + } + + bool SimulatorProvider::captureEventFrame(const QString& cameraId, + ImageFrame& frame, + int) + { + if (!accepts(cameraId)) + { + return false; + } + frame = makeFrame(); + return frame.isValid(); + } + + bool SimulatorProvider::accepts(const QString& cameraIdOrAll) const + { + const QString target = cameraIdOrAll.trimmed(); + return target == m_cameraId + || target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0; + } + + ImageFrame SimulatorProvider::makeFrame() + { + ImageFrame frame; + frame.cameraId = m_cameraId; + frame.width = m_roi.width(); + frame.height = m_roi.height(); + frame.stride = frame.width; + frame.bitsPerSample = 8; + frame.pixelFormat = ImagePixelFormat::Mono8; + frame.frameIndex = ++m_frameIndex; + frame.timestampNs = static_cast(QDateTime::currentMSecsSinceEpoch()) * 1000000ull; + frame.clockStamp = ClockService{}.now(); + frame.sourceRoiX = m_roi.x(); + frame.sourceRoiY = m_roi.y(); + frame.sourceRoiWidth = m_roi.width(); + frame.sourceRoiHeight = m_roi.height(); + frame.bytes.resize(static_cast(frame.width) * frame.height); + for (int y = 0; y < frame.height; ++y) + { + uchar* row = reinterpret_cast(frame.bytes.data()) + + static_cast(y) * frame.stride; + for (int x = 0; x < frame.width; ++x) + { + row[x] = static_cast((x + y + frame.frameIndex) & 0xffu); + } + } + return frame; + } + + void SimulatorProvider::updateTimerInterval() + { + m_timer.setInterval((std::max)(1, static_cast(std::ceil(m_exposureMs)))); + } +} From d22e69da6d1848cb3643cc3c34f4c90223174a70 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:04:30 +0200 Subject: [PATCH 02/37] Complete hardware provider architecture --- CMakeLists.txt | 31 +- README.md | 4 +- ScopeOneCore/CMakeLists.txt | 41 +- ScopeOneCore/README.md | 16 +- .../include/scopeone/CameraProvider.h | 21 +- ScopeOneCore/include/scopeone/ClockService.h | 12 - .../scopeone/DriverHostProviderPlugin.h | 23 + .../include/scopeone/HardwareCapabilities.h | 102 + .../include/scopeone/HardwareProvider.h | 4 +- ScopeOneCore/include/scopeone/HardwareTypes.h | 32 +- ScopeOneCore/include/scopeone/ImageFrame.h | 8 - ScopeOneCore/include/scopeone/ScopeOneCore.h | 21 +- .../include/scopeone/SimulatorProvider.h | 5 +- ScopeOneCore/internal/AcquisitionEngine.h | 11 - ScopeOneCore/internal/AgentProtocol.h | 58 - ScopeOneCore/internal/CameraBackend.h | 8 +- ScopeOneCore/internal/CameraManager.h | 7 +- ScopeOneCore/internal/DriverHostProtocol.h | 89 +- .../internal/DriverHostProviderProxy.h | 14 + ScopeOneCore/internal/FrameRouter.h | 10 +- ScopeOneCore/internal/HardwareRuntime.h | 75 +- ScopeOneCore/internal/MDAManager.h | 19 +- ScopeOneCore/internal/MicroManagerProvider.h | 68 +- ScopeOneCore/internal/RecordingManager.h | 8 +- ScopeOneCore/src/AcquisitionEngine.cpp | 97 +- ScopeOneCore/src/AgentMain.cpp | 1808 ---------- ScopeOneCore/src/CameraBackend.cpp | 2 +- ScopeOneCore/src/CameraManager.cpp | 21 +- ScopeOneCore/src/ClockService.cpp | 18 - ...ackend.cpp => DriverHostCameraBackend.cpp} | 369 +- ScopeOneCore/src/DriverHostMain.cpp | 3084 +++++++++++++++++ ScopeOneCore/src/DriverHostProviderProxy.cpp | 1606 +++++++++ ScopeOneCore/src/FrameBufferUtils.cpp | 1 - ScopeOneCore/src/FrameRouter.cpp | 12 +- ScopeOneCore/src/HardwareProvider.cpp | 14 + ScopeOneCore/src/HardwareRuntime.cpp | 650 +++- ScopeOneCore/src/MDAManager.cpp | 196 +- ScopeOneCore/src/MMCoreManager.cpp | 40 +- ScopeOneCore/src/MicroManagerProvider.cpp | 772 ++++- ScopeOneCore/src/NativeCameraBackend.cpp | 2 - ScopeOneCore/src/RecordingManager.cpp | 35 +- ScopeOneCore/src/ScopeOneCore.cpp | 798 ++--- ScopeOneCore/src/SimulatorProvider.cpp | 56 +- ScopeOneCore/src/SimulatorProviderPlugin.cpp | 49 + 44 files changed, 7164 insertions(+), 3153 deletions(-) delete mode 100644 ScopeOneCore/include/scopeone/ClockService.h create mode 100644 ScopeOneCore/include/scopeone/DriverHostProviderPlugin.h create mode 100644 ScopeOneCore/include/scopeone/HardwareCapabilities.h delete mode 100644 ScopeOneCore/internal/AgentProtocol.h create mode 100644 ScopeOneCore/internal/DriverHostProviderProxy.h delete mode 100644 ScopeOneCore/src/AgentMain.cpp delete mode 100644 ScopeOneCore/src/ClockService.cpp rename ScopeOneCore/src/{AgentCameraBackend.cpp => DriverHostCameraBackend.cpp} (82%) create mode 100644 ScopeOneCore/src/DriverHostMain.cpp create mode 100644 ScopeOneCore/src/DriverHostProviderProxy.cpp create mode 100644 ScopeOneCore/src/HardwareProvider.cpp create mode 100644 ScopeOneCore/src/SimulatorProviderPlugin.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b1f968b..cb66019 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,11 +176,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 +200,16 @@ if (SCOPEONE_CORE_RUNTIME_FILES) ) endif () +set(SCOPEONE_PROVIDER_DIR "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/providers") +if (EXISTS "${SCOPEONE_PROVIDER_DIR}") + add_custom_command(TARGET ScopeOne POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${SCOPEONE_PROVIDER_DIR}" + "$/providers" + ) + install(DIRECTORY "${SCOPEONE_PROVIDER_DIR}/" DESTINATION providers) +endif () + if (WIN32) if (WINDEPLOYQT_EXECUTABLE) add_custom_command(TARGET ScopeOne POST_BUILD @@ -227,7 +246,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..4321523 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@

-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.


diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 45b91fa..f1db249 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -78,8 +78,9 @@ endfunction() set(CORE_SOURCES src/ExperimentDocument.cpp src/AcquisitionEngine.cpp - src/ClockService.cpp + src/DriverHostProviderProxy.cpp src/FrameRouter.cpp + src/HardwareProvider.cpp src/HardwareRuntime.cpp src/MicroManagerProvider.cpp src/SimulatorProvider.cpp @@ -99,7 +100,7 @@ set(CORE_SOURCES src/CameraBackend.cpp src/CameraManager.cpp src/NativeCameraBackend.cpp - src/AgentCameraBackend.cpp + src/DriverHostCameraBackend.cpp src/ScopeOneCore.cpp ) @@ -107,8 +108,9 @@ set(CORE_HEADERS include/scopeone/ExperimentDocument.h internal/AcquisitionEngine.h include/scopeone/CameraProvider.h + include/scopeone/DriverHostProviderPlugin.h internal/CameraRuntimeControl.h - include/scopeone/ClockService.h + include/scopeone/HardwareCapabilities.h include/scopeone/HardwareTypes.h include/scopeone/HardwareProvider.h include/scopeone/SimulatorProvider.h @@ -122,8 +124,8 @@ set(CORE_HEADERS internal/StageMosaicManager.h internal/ImageProcessingFramework.h internal/ProcessingModule.h - internal/AgentProtocol.h internal/DriverHostProtocol.h + internal/DriverHostProviderProxy.h internal/SpatiotemporalBinningModule.h internal/GaussianBlurModule.h internal/FFTModule.h @@ -204,24 +206,31 @@ 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 $) + +add_library(ScopeOne_SimulatorProvider MODULE + src/SimulatorProviderPlugin.cpp +) +target_link_libraries(ScopeOne_SimulatorProvider PRIVATE ScopeOneCore Qt::Core) +set_target_properties(ScopeOne_SimulatorProvider PROPERTIES PREFIX "") # Fast Release build # if (MSVC) @@ -239,10 +248,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 () @@ -265,9 +274,13 @@ install(TARGETS ScopeOneCore LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) -install(TARGETS ScopeOne_Agent +install(TARGETS ScopeOne_DriverHost RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) +install(TARGETS ScopeOne_SimulatorProvider + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}/providers" + LIBRARY DESTINATION "${CMAKE_INSTALL_BINDIR}/providers" +) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" FILES_MATCHING diff --git a/ScopeOneCore/README.md b/ScopeOneCore/README.md index e7f6836..3243f13 100644 --- a/ScopeOneCore/README.md +++ b/ScopeOneCore/README.md @@ -25,7 +25,7 @@ ScopeOneCore/ #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. @@ -45,7 +45,6 @@ Use this placement rule: | `scopeone::core` | Stable Core-facing types and public facades | `ScopeOneCore`, `ImageFrame`, `ExperimentDocument`, `ImageSceneModel` | | `scopeone::core::internal` | Core-only managers and processing implementations | `CameraManager`, `MMCoreManager`, `RecordingManager`, processing modules | | `scopeone::core::internal::driverhost` | Shared DriverHost message framing | Versioned request, response and event envelopes | -| `scopeone::core::internal::agent` | Micro-Manager DriverHost commands | Camera commands and shared-memory endpoint names | | `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. @@ -88,10 +87,12 @@ Outputs: - `build/Release/ScopeOneCore.dll` - `build/Release/ScopeOneCore.lib` -- `build/Release/ScopeOne_Agent.exe` +- `build/Release/ScopeOne_DriverHost.exe` +- `build/Release/ScopeOne_SimulatorProvider.dll` - `build/ScopeOneCoreConfig.cmake` - `install/bin/ScopeOneCore.dll` -- `install/bin/ScopeOne_Agent.exe` +- `install/bin/ScopeOne_DriverHost.exe` +- `install/bin/providers/ScopeOne_SimulatorProvider.dll` - `install/lib/cmake/ScopeOneCore/ScopeOneCoreConfig.cmake` @@ -100,8 +101,9 @@ Outputs: The installed headers are the source of truth for the public API: - `ScopeOneCore.h` provides the main hardware, acquisition, processing, recording and frame-graph facade. -- `HardwareProvider.h` and `CameraProvider.h` define provider discovery, control and frame delivery. -- `HardwareTypes.h` defines provider-independent device identity, state, endpoint and clock metadata. +- `HardwareProvider.h`, `HardwareCapabilities.h` and `CameraProvider.h` define provider discovery, device control and frame delivery. +- `DriverHostProviderPlugin.h` defines the module factory used to load external providers in isolated DriverHost processes. +- `HardwareTypes.h` defines provider-independent device identity, state and endpoint metadata. - `SimulatorProvider.h` provides an in-process reference provider. - `ImageFrame.h` defines the image payload and metadata exchanged across Core features. - `ExperimentDocument.h` defines experiment plans, results, persistence and provenance. @@ -111,7 +113,7 @@ The installed headers are the source of truth for the public API: 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 them with `ScopeOneCore::registerHardwareProvider(...)`; ScopeOne owns acquisition routing, clocks and downstream frame delivery. Micro-Manager uses the same provider boundary and may run in process or through the existing DriverHost transport. +Providers use ScopeOne logical device IDs and publish `ImageFrame` objects through `CameraProvider::FrameSink`. Register in-process providers with `ScopeOneCore::registerHardwareProvider(...)`. Register an isolated module with `ScopeOneCore::registerDriverHostProvider(providerId, modulePath, options)`. 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. ## Processing Data Flow diff --git a/ScopeOneCore/include/scopeone/CameraProvider.h b/ScopeOneCore/include/scopeone/CameraProvider.h index b9d06e9..f01ff8d 100644 --- a/ScopeOneCore/include/scopeone/CameraProvider.h +++ b/ScopeOneCore/include/scopeone/CameraProvider.h @@ -5,16 +5,17 @@ #include +#include "scopeone/HardwareCapabilities.h" #include "scopeone/ImageFrame.h" namespace scopeone::core { - class CameraProvider + class SCOPEONE_CORE_EXPORT CameraProvider : public DevicePropertyProvider { public: using FrameSink = std::function; - virtual ~CameraProvider() = default; + ~CameraProvider() override; virtual void setFrameSink(FrameSink sink) = 0; virtual bool startPreview() = 0; @@ -24,22 +25,6 @@ namespace scopeone::core 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 QStringList listProperties(const QString& cameraId) = 0; - virtual QString getProperty(const QString& cameraId, - const QString& name, - bool fromCache = false) = 0; - virtual bool setProperty(const QString& cameraId, - const QString& name, - const QString& value, - QString* errorMessage = nullptr) = 0; - virtual QString getPropertyType(const QString& cameraId, const QString& name) = 0; - virtual bool isPropertyReadOnly(const QString& cameraId, const QString& name) = 0; - virtual bool isPropertyPreInit(const QString& cameraId, const QString& name) = 0; - virtual QStringList getAllowedPropertyValues(const QString& cameraId, - const QString& name) = 0; - virtual bool hasPropertyLimits(const QString& cameraId, const QString& name) = 0; - virtual double getPropertyLowerLimit(const QString& cameraId, const QString& name) = 0; - virtual double getPropertyUpperLimit(const QString& cameraId, const QString& name) = 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, diff --git a/ScopeOneCore/include/scopeone/ClockService.h b/ScopeOneCore/include/scopeone/ClockService.h deleted file mode 100644 index 77dc5ac..0000000 --- a/ScopeOneCore/include/scopeone/ClockService.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include "scopeone/HardwareTypes.h" - -namespace scopeone::core -{ - class SCOPEONE_CORE_EXPORT ClockService - { - public: - ClockStamp now() const; - }; -} 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/HardwareCapabilities.h b/ScopeOneCore/include/scopeone/HardwareCapabilities.h new file mode 100644 index 0000000..0ff44cd --- /dev/null +++ b/ScopeOneCore/include/scopeone/HardwareCapabilities.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +#include "scopeone/scopeone_core_export.h" + +namespace scopeone::core +{ + class SCOPEONE_CORE_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_CORE_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_CORE_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_CORE_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_CORE_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 index 223998e..d6cf26c 100644 --- a/ScopeOneCore/include/scopeone/HardwareProvider.h +++ b/ScopeOneCore/include/scopeone/HardwareProvider.h @@ -9,10 +9,10 @@ namespace scopeone::core { - class HardwareProvider + class SCOPEONE_CORE_EXPORT HardwareProvider { public: - virtual ~HardwareProvider() = default; + virtual ~HardwareProvider(); virtual HardwareProviderDescriptor descriptor() const = 0; virtual QList devices() const = 0; diff --git a/ScopeOneCore/include/scopeone/HardwareTypes.h b/ScopeOneCore/include/scopeone/HardwareTypes.h index 9183df5..0b0a267 100644 --- a/ScopeOneCore/include/scopeone/HardwareTypes.h +++ b/ScopeOneCore/include/scopeone/HardwareTypes.h @@ -4,8 +4,6 @@ #include #include -#include - #include "scopeone/scopeone_core_export.h" namespace scopeone::core @@ -18,7 +16,17 @@ namespace scopeone::core ZStage, Shutter, State, - Hub + Hub, + Serial, + Generic, + AutoFocus, + ImageProcessor, + SignalIO, + Magnifier, + SLM, + Galvo, + PressurePump, + VolumetricPump }; enum class HardwareDeviceState @@ -56,23 +64,6 @@ namespace scopeone::core QVariantMap properties; }; - struct SCOPEONE_CORE_EXPORT ClockStamp - { - std::int64_t ticks{0}; - std::int64_t tickPeriodNumerator{1}; - std::int64_t tickPeriodDenominator{1000000000}; - std::int64_t hostMonotonicNs{0}; - QString clockDomain; - QString source; - - bool isValid() const - { - return tickPeriodNumerator > 0 - && tickPeriodDenominator > 0 - && !clockDomain.isEmpty() - && !source.isEmpty(); - } - }; } Q_DECLARE_METATYPE(scopeone::core::HardwareDeviceKind) @@ -80,4 +71,3 @@ Q_DECLARE_METATYPE(scopeone::core::HardwareDeviceState) Q_DECLARE_METATYPE(scopeone::core::HardwareEndpointKind) Q_DECLARE_METATYPE(scopeone::core::HardwareProviderDescriptor) Q_DECLARE_METATYPE(scopeone::core::HardwareDeviceDescriptor) -Q_DECLARE_METATYPE(scopeone::core::ClockStamp) diff --git a/ScopeOneCore/include/scopeone/ImageFrame.h b/ScopeOneCore/include/scopeone/ImageFrame.h index 5241cef..c2754b5 100644 --- a/ScopeOneCore/include/scopeone/ImageFrame.h +++ b/ScopeOneCore/include/scopeone/ImageFrame.h @@ -8,7 +8,6 @@ #include #include "scopeone/SharedFrame.h" -#include "scopeone/HardwareTypes.h" namespace scopeone::core { @@ -29,7 +28,6 @@ namespace scopeone::core ImagePixelFormat pixelFormat{ImagePixelFormat::Invalid}; quint64 frameIndex{0}; quint64 timestampNs{0}; - ClockStamp clockStamp; int sourceRoiX{0}; int sourceRoiY{0}; int sourceRoiWidth{0}; @@ -171,12 +169,6 @@ namespace scopeone::core frame.bitsPerSample = static_cast(header.bitsPerSample); frame.frameIndex = header.frameIndex; frame.timestampNs = header.timestampNs; - if (frame.timestampNs > 0) - { - frame.clockStamp.ticks = static_cast(frame.timestampNs); - frame.clockStamp.clockDomain = QStringLiteral("provider.timestampNs"); - frame.clockStamp.source = QStringLiteral("Driver"); - } if (header.pixelFormat == static_cast(SharedPixelFormat::Mono16)) { diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index f8675d5..21c63b5 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -532,6 +532,10 @@ namespace scopeone::core 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; @@ -592,6 +596,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; @@ -683,7 +696,7 @@ 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 driverHostControlServerListening(const QString& cameraId, const QString& serverName); void processedFrameReady(const ImageFrame& frame); void processedFramesCompleted(const QString& cameraId, quint64 frameCount); void previewProcessedFrameReady(const ImageFrame& frame); @@ -786,7 +799,7 @@ 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, @@ -798,10 +811,8 @@ 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, diff --git a/ScopeOneCore/include/scopeone/SimulatorProvider.h b/ScopeOneCore/include/scopeone/SimulatorProvider.h index ae63351..94ff509 100644 --- a/ScopeOneCore/include/scopeone/SimulatorProvider.h +++ b/ScopeOneCore/include/scopeone/SimulatorProvider.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -17,7 +18,8 @@ namespace scopeone::core public: explicit SimulatorProvider(const QString& logicalCameraId = QStringLiteral("camera.simulator"), int width = 512, - int height = 512); + int height = 512, + const QString& providerId = {}); HardwareProviderDescriptor descriptor() const override; QList devices() const override; @@ -70,5 +72,6 @@ namespace scopeone::core quint64 m_frameIndex{0}; FrameSink m_frameSink; QTimer m_timer; + mutable QMutex m_mutex; }; } diff --git a/ScopeOneCore/internal/AcquisitionEngine.h b/ScopeOneCore/internal/AcquisitionEngine.h index 1401a95..2293794 100644 --- a/ScopeOneCore/internal/AcquisitionEngine.h +++ b/ScopeOneCore/internal/AcquisitionEngine.h @@ -14,23 +14,12 @@ namespace scopeone::core::internal Q_OBJECT public: - enum class State - { - Idle, - Prepared, - Running - }; - AcquisitionEngine(DeviceRegistry* deviceRegistry, QObject* parent = nullptr); - void prepare(); - void reset(); bool start(const QString& cameraIdOrAll); bool stop(const QString& cameraIdOrAll); - State state() const { return m_state; } private: DeviceRegistry* m_deviceRegistry{nullptr}; - State m_state{State::Idle}; }; } diff --git a/ScopeOneCore/internal/AgentProtocol.h b/ScopeOneCore/internal/AgentProtocol.h deleted file mode 100644 index e97ea2a..0000000 --- a/ScopeOneCore/internal/AgentProtocol.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -#include "internal/DriverHostProtocol.h" - -namespace scopeone::core::internal::agent -{ - using driverhost::kProtocolVersion; - using driverhost::kEnvelopeKindField; - using driverhost::kEnvelopeVersionField; - using driverhost::kEnvelopeRequestIdField; - using driverhost::kMessageTypeField; - using driverhost::kMessageKindRequest; - using driverhost::kMessageKindResponse; - using driverhost::kMessageKindEvent; - using driverhost::encodeUInt64; - using driverhost::decodeUInt64; - using driverhost::makeEnvelope; - using driverhost::encodeMessage; - using driverhost::DecodeResult; - using driverhost::tryDecodeMessage; - - 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 kCommandSetExposure = QStringLiteral("SetExposure"); - inline const QString kCommandListProperties = QStringLiteral("ListProperties"); - inline const QString kCommandGetProperty = QStringLiteral("GetProperty"); - inline const QString kCommandSetProperty = QStringLiteral("SetProperty"); - inline const QString kCommandCaptureEvent = QStringLiteral("CaptureEvent"); - inline const QString kCommandSetRoi = QStringLiteral("SetROI"); - inline const QString kCommandClearRoi = QStringLiteral("ClearROI"); - inline const QString kCommandGetRoi = QStringLiteral("GetROI"); - - inline const QString kFrameDeliveryModePreviewLatest = QStringLiteral("PreviewLatest"); - inline const QString kFrameDeliveryModeLatestOnly = QStringLiteral("LatestOnly"); - inline const QString kFrameDeliveryModeAllFrames = QStringLiteral("AllFrames"); - - 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 kExecutableFileName = QStringLiteral("ScopeOne_Agent.exe"); - - inline QString controlServerName(const QString& cameraId) - { - return QStringLiteral("ScopeOne.%1.ctrl").arg(cameraId); - } - - inline QString sharedMemoryKey(const QString& cameraId) - { - return QStringLiteral("ScopeOne.%1.shm").arg(cameraId); - } - -} // namespace scopeone::core::internal::agent 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 bc44bff..7a622be 100644 --- a/ScopeOneCore/internal/CameraManager.h +++ b/ScopeOneCore/internal/CameraManager.h @@ -29,7 +29,7 @@ namespace scopeone::core::internal 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(), @@ -40,7 +40,7 @@ namespace scopeone::core::internal bool startPreview() override; bool stopPreview() override; - bool usesAgentBackend() const; + bool usesDriverHostBackend() const; bool startPreviewFor(const QString& cameraId) override; bool stopPreviewFor(const QString& cameraId) override; bool isPreviewRunning(const QString& cameraId) const override; @@ -76,13 +76,12 @@ namespace scopeone::core::internal 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); diff --git a/ScopeOneCore/internal/DriverHostProtocol.h b/ScopeOneCore/internal/DriverHostProtocol.h index 36793d3..1e660b0 100644 --- a/ScopeOneCore/internal/DriverHostProtocol.h +++ b/ScopeOneCore/internal/DriverHostProtocol.h @@ -2,24 +2,111 @@ #include #include +#include #include #include +#include +#include #include 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"); + inline const QString kCommandSetProperty = QStringLiteral("SetProperty"); + inline const QString kCommandCaptureEvent = QStringLiteral("CaptureEvent"); + 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"); + inline const QString kFrameDeliveryModeAllFrames = QStringLiteral("AllFrames"); + + inline const QString kEventHello = QStringLiteral("Hello"); + inline const QString kEventFrameAvailable = QStringLiteral("FrameAvailable"); + inline const QString kEventPreviewState = QStringLiteral("PreviewState"); + inline const QString kEventDriverHostError = QStringLiteral("DriverHostError"); + +#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& deviceId) + { + return QStringLiteral("ScopeOne.DriverHost.%1.ctrl").arg(deviceId); + } + + inline QString sharedMemoryKey(const QString& deviceId) + { + 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) { return QString::number(value); 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/FrameRouter.h b/ScopeOneCore/internal/FrameRouter.h index f7650c2..1e9a062 100644 --- a/ScopeOneCore/internal/FrameRouter.h +++ b/ScopeOneCore/internal/FrameRouter.h @@ -4,11 +4,6 @@ #include "scopeone/ImageFrame.h" -namespace scopeone::core -{ - class ClockService; -} - namespace scopeone::core::internal { class FrameRouter : public QObject @@ -16,14 +11,11 @@ namespace scopeone::core::internal Q_OBJECT public: - FrameRouter(scopeone::core::ClockService* clockService, - QObject* parent = nullptr); + explicit FrameRouter(QObject* parent = nullptr); void publish(const scopeone::core::ImageFrame& frame); signals: void frameReady(const scopeone::core::ImageFrame& frame); - private: - scopeone::core::ClockService* m_clockService{nullptr}; }; } diff --git a/ScopeOneCore/internal/HardwareRuntime.h b/ScopeOneCore/internal/HardwareRuntime.h index 0458291..f733c2a 100644 --- a/ScopeOneCore/internal/HardwareRuntime.h +++ b/ScopeOneCore/internal/HardwareRuntime.h @@ -3,14 +3,15 @@ #include #include #include +#include #include #include #include "scopeone/HardwareProvider.h" -#include "scopeone/ClockService.h" #include "scopeone/CameraProvider.h" #include "internal/AcquisitionEngine.h" +#include "internal/CameraRuntimeControl.h" #include "internal/FrameRouter.h" namespace scopeone::core::internal @@ -23,9 +24,10 @@ namespace scopeone::core::internal explicit DeviceRegistry(QObject* parent = nullptr); void clear(); - bool registerProvider(const HardwareProviderPtr& provider); + bool registerProvider(const HardwareProviderPtr& provider, + const HardwareProviderDescriptor& descriptor, + const QList& devices); void unregisterProvider(const QString& providerId); - void refreshProvider(const QString& providerId); QList providers() const; QList devices() const; HardwareDeviceDescriptor device(const QString& logicalId) const; @@ -39,13 +41,21 @@ namespace scopeone::core::internal struct ProviderEntry { HardwareProviderPtr provider; + HardwareProviderDescriptor descriptor; QList devices; }; + mutable QReadWriteLock m_lock; QHash m_providers; }; - class HardwareRuntime : public QObject, public CameraProvider + class HardwareRuntime : public QObject, + public CameraProvider, + public StageProvider, + public ShutterProvider, + public StateProvider, + public ConfigurationProvider, + public CameraRuntimeControl { Q_OBJECT @@ -86,26 +96,75 @@ namespace scopeone::core::internal bool captureEventFrame(const QString& cameraId, ImageFrame& frame, int timeoutMs) override; + void setFrameDeliveryPaused(bool paused) override; + bool setRecordingFrameDeliveryEnabled(bool enabled) override; + bool setHighRateFrameDeliveryEnabled(bool enabled) override; + bool isProcessingFrameTokenCurrent(const QString& cameraId, quint64 token) override; + void finishProcessingFrame(const QString& cameraId, quint64 token) override; + QString defaultXYStage() const override; + QString defaultZStage() const override; + bool getXYPosition(const QString& deviceId, + double& x, + double& y, + QString* errorMessage) const override; + bool getZPosition(const QString& deviceId, + double& z, + QString* errorMessage) const override; + bool setRelativeXYPosition(const QString& deviceId, + double dx, + double dy, + QString* errorMessage) override; + bool setRelativeZPosition(const QString& deviceId, + double dz, + QString* errorMessage) override; + bool setXYPosition(const QString& deviceId, + double x, + double y, + QString* errorMessage) override; + bool setZPosition(const QString& deviceId, + double z, + QString* errorMessage) override; + bool isShutterOpen(const QString& deviceId, + bool& open, + QString* errorMessage) const override; + bool setShutterOpen(const QString& deviceId, + bool open, + QString* errorMessage) override; + bool getState(const QString& deviceId, + long& state, + QString* errorMessage) const override; + bool setState(const QString& deviceId, + long state, + QString* errorMessage) override; + QString stateLabel(const QString& deviceId, long state) const override; + QStringList availableConfigGroups() const override; + QStringList availableConfigs(const QString& groupName) const override; + QString currentConfig(const QString& groupName) const override; + bool setConfig(const QString& groupName, + const QString& configName, + QString* errorMessage) override; DeviceRegistry* deviceRegistry() { return &m_registry; } const DeviceRegistry* deviceRegistry() const { return &m_registry; } - AcquisitionEngine* acquisitionEngine() { return &m_acquisitionEngine; } - ClockService* clockService() { return &m_clockService; } FrameRouter* frameRouter() { return &m_frameRouter; } void clear(); bool registerProvider(const HardwareProviderPtr& provider); void unregisterProvider(const QString& providerId); - void refreshProvider(const QString& providerId); + bool refreshProvider(const QString& providerId); signals: void devicesChanged(); private: CameraProvider* cameraProviderForDevice(const QString& logicalId) const; + DevicePropertyProvider* propertyProviderForDevice(const QString& logicalId) const; + StageProvider* stageProviderForDevice(const QString& logicalId) const; + ShutterProvider* shutterProviderForDevice(const QString& logicalId) const; + StateProvider* stateProviderForDevice(const QString& logicalId) const; + ConfigurationProvider* configurationProviderForGroup(const QString& groupName) const; QList cameraProviders() const; DeviceRegistry m_registry; - ClockService m_clockService; FrameRouter m_frameRouter; AcquisitionEngine m_acquisitionEngine; FrameSink m_frameSink; diff --git a/ScopeOneCore/internal/MDAManager.h b/ScopeOneCore/internal/MDAManager.h index eb50e47..5c2ee8f 100644 --- a/ScopeOneCore/internal/MDAManager.h +++ b/ScopeOneCore/internal/MDAManager.h @@ -7,12 +7,9 @@ #include #include #include -#include - #include "scopeone/ExperimentDocument.h" #include "scopeone/CameraProvider.h" - -class CMMCore; +#include "scopeone/HardwareCapabilities.h" namespace scopeone::core::internal { @@ -31,12 +28,13 @@ namespace scopeone::core::internal Q_OBJECT public: - explicit MDAManager(std::shared_ptr core, QObject* parent = nullptr); + explicit MDAManager(QObject* parent = nullptr); ~MDAManager() override; bool isRunning() const { return m_running.load(); } void setCameraProvider(CameraProvider* cameraProvider); + void setStageProvider(StageProvider* stageProvider); bool start(const QList& events, bool block = false); void requestCancel(); void cancelAndWait(); @@ -50,15 +48,18 @@ namespace scopeone::core::internal private: bool setupEvent(const AcquisitionEvent& event, QString* errorMessage); bool execEvent(const AcquisitionEvent& event, MDAOutput& output, QString* errorMessage); - bool execEventSingleCamera(const AcquisitionEvent& event, MDAOutput& output, QString* errorMessage); - bool execEventMultiCamera(const AcquisitionEvent& event, MDAOutput& output, QString* errorMessage); - bool setExposure(double exposureMs, QString* errorMessage); + bool captureCameras(const AcquisitionEvent& event, + MDAOutput& output, + QString* errorMessage); + bool setExposure(const QStringList& cameraIds, + double exposureMs, + QString* errorMessage); bool moveXY(double x, double y, QString* errorMessage); bool moveZ(double z, QString* errorMessage); void runSequence(QList events); - std::shared_ptr m_mmcore; CameraProvider* m_cameraProvider{nullptr}; + StageProvider* m_stageProvider{nullptr}; QThreadPool m_threadPool; std::atomic m_running{false}; std::atomic m_cancelRequested{false}; diff --git a/ScopeOneCore/internal/MicroManagerProvider.h b/ScopeOneCore/internal/MicroManagerProvider.h index 423064b..7f10347 100644 --- a/ScopeOneCore/internal/MicroManagerProvider.h +++ b/ScopeOneCore/internal/MicroManagerProvider.h @@ -2,15 +2,28 @@ #include +#include + #include "scopeone/HardwareProvider.h" #include "scopeone/CameraProvider.h" +#include "internal/CameraRuntimeControl.h" + +class CMMCore; namespace scopeone::core::internal { - class MicroManagerProvider final : public HardwareProvider, public CameraProvider + class MicroManagerProvider final : public HardwareProvider, + public CameraProvider, + public StageProvider, + public ShutterProvider, + public StateProvider, + public ConfigurationProvider, + public CameraRuntimeControl { public: - explicit MicroManagerProvider(CameraProvider* cameraProvider); + MicroManagerProvider(std::shared_ptr core, + CameraProvider* cameraProvider, + CameraRuntimeControl* cameraRuntimeControl); HardwareProviderDescriptor descriptor() const override; QList devices() const override; @@ -50,9 +63,60 @@ namespace scopeone::core::internal bool captureEventFrame(const QString& cameraId, scopeone::core::ImageFrame& frame, int timeoutMs) override; + void setFrameDeliveryPaused(bool paused) override; + bool setRecordingFrameDeliveryEnabled(bool enabled) override; + bool setHighRateFrameDeliveryEnabled(bool enabled) override; + bool isProcessingFrameTokenCurrent(const QString& cameraId, quint64 token) override; + void finishProcessingFrame(const QString& cameraId, quint64 token) override; + QString defaultXYStage() const override; + QString defaultZStage() const override; + bool getXYPosition(const QString& deviceId, + double& x, + double& y, + QString* errorMessage) const override; + bool getZPosition(const QString& deviceId, + double& z, + QString* errorMessage) const override; + bool setRelativeXYPosition(const QString& deviceId, + double dx, + double dy, + QString* errorMessage) override; + bool setRelativeZPosition(const QString& deviceId, + double dz, + QString* errorMessage) override; + bool setXYPosition(const QString& deviceId, + double x, + double y, + QString* errorMessage) override; + bool setZPosition(const QString& deviceId, + double z, + QString* errorMessage) override; + bool isShutterOpen(const QString& deviceId, + bool& open, + QString* errorMessage) const override; + bool setShutterOpen(const QString& deviceId, + bool open, + QString* errorMessage) override; + bool getState(const QString& deviceId, + long& state, + QString* errorMessage) const override; + bool setState(const QString& deviceId, + long state, + QString* errorMessage) override; + QString stateLabel(const QString& deviceId, long state) const override; + QStringList availableConfigGroups() const override; + QStringList availableConfigs(const QString& groupName) const override; + QString currentConfig(const QString& groupName) const override; + bool setConfig(const QString& groupName, + const QString& configName, + QString* errorMessage) override; private: + bool isCamera(const QString& deviceId) const; + + std::shared_ptr m_core; CameraProvider* m_cameraProvider{nullptr}; + CameraRuntimeControl* m_cameraRuntimeControl{nullptr}; QList m_devices; }; } diff --git a/ScopeOneCore/internal/RecordingManager.h b/ScopeOneCore/internal/RecordingManager.h index 4859b20..9285da4 100644 --- a/ScopeOneCore/internal/RecordingManager.h +++ b/ScopeOneCore/internal/RecordingManager.h @@ -2,6 +2,7 @@ #include "scopeone/ScopeOneCore.h" #include "scopeone/CameraProvider.h" +#include "scopeone/HardwareCapabilities.h" #include "internal/MDAManager.h" #include "internal/CameraRuntimeControl.h" #include @@ -14,8 +15,6 @@ #include #include -class CMMCore; - namespace scopeone::core::internal { using scopeone::core::RecordingFormat; @@ -36,12 +35,11 @@ namespace scopeone::core::internal ~RecordingManager() override; void setCameraProvider(CameraProvider* cameraProvider) { m_cameraProvider = cameraProvider; } + void setStageProvider(StageProvider* stageProvider) { m_stageProvider = stageProvider; } void setCameraRuntimeControl(CameraRuntimeControl* cameraRuntimeControl) { m_cameraRuntimeControl = cameraRuntimeControl; } - void setMMCore(const std::shared_ptr& core) { m_mmcore = core; } - void setLatestFrameFetcher(std::function fetcher) { m_latestFrameFetcher = std::move(fetcher); @@ -208,8 +206,8 @@ namespace scopeone::core::internal void advanceBurstStateIfNeeded(); CameraProvider* m_cameraProvider{nullptr}; + StageProvider* m_stageProvider{nullptr}; CameraRuntimeControl* m_cameraRuntimeControl{nullptr}; - std::shared_ptr m_mmcore; std::function m_latestFrameFetcher; std::function m_sessionPreparationCallback; diff --git a/ScopeOneCore/src/AcquisitionEngine.cpp b/ScopeOneCore/src/AcquisitionEngine.cpp index 7e44f9b..792e022 100644 --- a/ScopeOneCore/src/AcquisitionEngine.cpp +++ b/ScopeOneCore/src/AcquisitionEngine.cpp @@ -3,8 +3,6 @@ #include "scopeone/CameraProvider.h" #include "internal/HardwareRuntime.h" -#include - namespace scopeone::core::internal { AcquisitionEngine::AcquisitionEngine(DeviceRegistry* deviceRegistry, QObject* parent) @@ -13,19 +11,9 @@ namespace scopeone::core::internal { } - void AcquisitionEngine::prepare() - { - m_state = State::Prepared; - } - - void AcquisitionEngine::reset() - { - m_state = State::Idle; - } - bool AcquisitionEngine::start(const QString& cameraIdOrAll) { - if (!m_deviceRegistry || m_state == State::Idle) + if (!m_deviceRegistry) { return false; } @@ -34,54 +22,56 @@ namespace scopeone::core::internal { return false; } - bool started = false; if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) { - QList startedProviders; - QSet visited; - started = true; + struct StartedCamera + { + CameraProvider* provider; + QString cameraId; + }; + QList startedCameras; + bool found = false; for (const HardwareDeviceDescriptor& device : m_deviceRegistry->devices()) { if (device.kind != HardwareDeviceKind::Camera) { continue; } + found = true; const HardwareProviderPtr provider = m_deviceRegistry->provider(device.providerId); auto* cameraProvider = dynamic_cast(provider.get()); - if (!cameraProvider || visited.contains(cameraProvider)) + if (!cameraProvider) + { + for (auto it = startedCameras.crbegin(); it != startedCameras.crend(); ++it) + { + it->provider->stopPreviewFor(it->cameraId); + } + return false; + } + if (cameraProvider->isPreviewRunning(device.logicalId)) { continue; } - visited.insert(cameraProvider); - if (!cameraProvider->startPreview()) + if (!cameraProvider->startPreviewFor(device.logicalId)) { - started = false; - for (CameraProvider* activeProvider : startedProviders) + for (auto it = startedCameras.crbegin(); it != startedCameras.crend(); ++it) { - activeProvider->stopPreview(); + it->provider->stopPreviewFor(it->cameraId); } - break; + return false; } - startedProviders.append(cameraProvider); + startedCameras.append({cameraProvider, device.logicalId}); } - started = started && !startedProviders.isEmpty(); - } - else - { - const HardwareProviderPtr provider = m_deviceRegistry->providerForDevice(target); - auto* cameraProvider = dynamic_cast(provider.get()); - started = cameraProvider && cameraProvider->startPreviewFor(target); + return found; } - if (started) - { - m_state = State::Running; - } - return started; + const HardwareProviderPtr provider = m_deviceRegistry->providerForDevice(target); + auto* cameraProvider = dynamic_cast(provider.get()); + return cameraProvider && cameraProvider->startPreviewFor(target); } bool AcquisitionEngine::stop(const QString& cameraIdOrAll) { - if (!m_deviceRegistry || m_state == State::Idle) + if (!m_deviceRegistry) { return false; } @@ -90,40 +80,33 @@ namespace scopeone::core::internal { return false; } - bool stopped = false; if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) { - QSet visited; - stopped = true; bool found = false; + bool stopped = true; for (const HardwareDeviceDescriptor& device : m_deviceRegistry->devices()) { if (device.kind != HardwareDeviceKind::Camera) { continue; } + found = true; const HardwareProviderPtr provider = m_deviceRegistry->provider(device.providerId); auto* cameraProvider = dynamic_cast(provider.get()); - if (!cameraProvider || visited.contains(cameraProvider)) + if (!cameraProvider) { + stopped = false; continue; } - found = true; - visited.insert(cameraProvider); - stopped = cameraProvider->stopPreview() && stopped; + if (cameraProvider->isPreviewRunning(device.logicalId)) + { + stopped = cameraProvider->stopPreviewFor(device.logicalId) && stopped; + } } - stopped = found && stopped; - } - else - { - const HardwareProviderPtr provider = m_deviceRegistry->providerForDevice(target); - auto* cameraProvider = dynamic_cast(provider.get()); - stopped = cameraProvider && cameraProvider->stopPreviewFor(target); - } - if (stopped && target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) - { - m_state = State::Prepared; + return found && stopped; } - return stopped; + const HardwareProviderPtr provider = m_deviceRegistry->providerForDevice(target); + auto* cameraProvider = dynamic_cast(provider.get()); + return cameraProvider && cameraProvider->stopPreviewFor(target); } } diff --git a/ScopeOneCore/src/AgentMain.cpp b/ScopeOneCore/src/AgentMain.cpp deleted file mode 100644 index d112669..0000000 --- a/ScopeOneCore/src/AgentMain.cpp +++ /dev/null @@ -1,1808 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "MMCore.h" -#include "internal/AgentProtocol.h" -#include "scopeone/SharedFrame.h" - -namespace scopeone::core::internal -{ - static_assert(std::atomic_ref::is_always_lock_free, - "Shared frame state requires lock-free 32-bit atomics"); - - using scopeone::core::SharedFrameHeader; - using scopeone::core::SharedMemoryControl; - using scopeone::core::SharedPixelFormat; - using scopeone::core::computeMaxFrameBytes; - using scopeone::core::kSharedFrameHeaderSize; - using scopeone::core::kSharedFrameMaxBytes; - using scopeone::core::kSharedFrameNumSlots; - using scopeone::core::kSharedFrameSlotStride; - using scopeone::core::kSharedMemoryControlSize; - - constexpr int kMinimumPollIntervalMs = 1; - constexpr int kMaximumPollIntervalMs = 50; - constexpr int kPreviewFrameDeliveryIntervalMs = 16; - - int pollingIntervalFor(double frameIntervalMs) - { - if (!std::isfinite(frameIntervalMs) || frameIntervalMs <= 0.0) - { - return kMinimumPollIntervalMs; - } - return static_cast(std::clamp(frameIntervalMs / 4.0, - static_cast(kMinimumPollIntervalMs), - static_cast(kMaximumPollIntervalMs))); - } - - // Normalize frame bit depth before publishing shared frame metadata - static quint16 normalizedSharedBitDepth(SharedPixelFormat format, int bitsPerSample) - { - if (format == SharedPixelFormat::Mono8) - { - return 8; - } - if (format == SharedPixelFormat::Mono16) - { - return bitsPerSample >= 1 && bitsPerSample <= 16 - ? static_cast(bitsPerSample) - : 16; - } - return 0; - } - - class ControlConnection final : public QObject - { - Q_OBJECT - - public: - // Wrap one local control socket connection - ControlConnection(quint64 connectionId, QLocalSocket* socket, QObject* parent = nullptr) - : QObject(parent) - , m_connectionId(connectionId) - , m_socket(socket) - { - if (!socket) - { - qFatal("ControlConnection requires QLocalSocket"); - } - m_socket->setParent(this); - - connect(m_socket, &QLocalSocket::readyRead, - this, &ControlConnection::onReadyRead); - connect(m_socket, &QLocalSocket::disconnected, - this, &ControlConnection::onDisconnected); - connect(m_socket, &QLocalSocket::errorOccurred, this, - [this](QLocalSocket::LocalSocketError socketError) - { - qWarning().noquote() - << QString("Agent control socket error (%1) on connection %2") - .arg(static_cast(socketError)) - .arg(m_connectionId); - }); - } - - // Send one encoded protocol message to the client - void sendMessage(const QJsonObject& message) - { - if (m_socket->state() != QLocalSocket::ConnectedState) - { - return; - } - m_socket->write(agent::encodeMessage(message)); - } - - signals: - void requestReceived(quint64 connectionId, - quint64 requestId, - const QString& type, - const QJsonObject& message); - void connectionClosed(quint64 connectionId); - - private slots: - // Decode queued socket bytes into control requests - void onReadyRead() - { - m_readBuffer += m_socket->readAll(); - while (true) - { - QJsonObject message; - QString error; - const agent::DecodeResult result = - agent::tryDecodeMessage(m_readBuffer, message, &error); - if (result == agent::DecodeResult::Incomplete) - { - return; - } - if (result == agent::DecodeResult::Error) - { - qWarning().noquote() - << QString("Agent control protocol error on connection %1: %2") - .arg(m_connectionId) - .arg(error); - m_socket->disconnectFromServer(); - return; - } - - if (message.value(agent::kEnvelopeVersionField).toInt(0) - != static_cast(agent::kProtocolVersion)) - { - qWarning().noquote() - << QString("Agent control protocol version mismatch on connection %1") - .arg(m_connectionId); - m_socket->disconnectFromServer(); - return; - } - - if (message.value(agent::kEnvelopeKindField).toString() - != agent::kMessageKindRequest) - { - qWarning().noquote() - << QString("Ignoring non-request control message on connection %1") - .arg(m_connectionId); - continue; - } - - const quint64 requestId = - agent::decodeUInt64(message.value(agent::kEnvelopeRequestIdField)); - const QString type = message.value(agent::kMessageTypeField).toString(); - if (requestId == 0 || type.isEmpty()) - { - qWarning().noquote() - << QString("Ignoring malformed request on connection %1") - .arg(m_connectionId); - continue; - } - - emit requestReceived(m_connectionId, requestId, type, message); - } - } - - // Notify the agent when this connection closes - void onDisconnected() - { - emit connectionClosed(m_connectionId); - deleteLater(); - } - - private: - quint64 m_connectionId{0}; - QLocalSocket* m_socket{nullptr}; - QByteArray m_readBuffer; - }; - - class AgentRuntime final : public QObject - { - Q_OBJECT - - public: - // Store launch settings for one camera runtime - AgentRuntime(QString cameraId, - QString adapter, - QString device, - QString shmKey, - QStringList preInitProperties, - QStringList properties, - double exposureMs, - bool autoPreview, - QObject* parent = nullptr) - : QObject(parent) - , m_cameraId(std::move(cameraId)) - , m_adapter(std::move(adapter)) - , m_device(std::move(device)) - , m_shmKey(std::move(shmKey)) - , m_preInitProperties(std::move(preInitProperties)) - , m_properties(std::move(properties)) - , m_exposureMs(exposureMs) - , m_autoPreview(autoPreview) - { - } - - QString lastError() const - { - return m_lastError; - } - - // Initialize MMCore camera state and shared memory transport - bool initializeRuntime() - { - m_lastError.clear(); - setState(State::Starting); - - if (!m_timer) - { - m_timer = new QTimer(this); - m_timer->setTimerType(Qt::PreciseTimer); - connect(m_timer, &QTimer::timeout, - this, &AgentRuntime::pollAndWrite); - } - - try - { - m_mmcore = std::make_unique(); - const std::string label = m_cameraId.toStdString(); - const std::string adapter = m_adapter.toStdString(); - const std::string device = m_device.toStdString(); - - m_mmcore->loadDevice(label.c_str(), adapter.c_str(), device.c_str()); - QString preInitError; - if (!applyProperties(m_preInitProperties, label, QStringLiteral("pre-init property"), &preInitError)) - { - m_lastError = preInitError; - setState(State::Error, m_lastError); - return false; - } - m_mmcore->initializeDevice(label.c_str()); - m_mmcore->setCameraDevice(label.c_str()); - - QString propertyError; - if (!applyProperties(m_properties, label, QStringLiteral("property"), &propertyError)) - { - m_lastError = propertyError; - setState(State::Error, m_lastError); - return false; - } - - try - { - m_mmcore->setCircularBufferMemoryFootprint(2048); - } - catch (const CMMError&) - { - } - - double finalExposure = 0.0; - if (m_exposureMs > 0.0) - { - m_mmcore->setExposure(m_exposureMs); - } - m_mmcore->waitForDevice(label.c_str()); - finalExposure = m_mmcore->getExposure(); - if (finalExposure <= 0.0) - { - m_lastError = QStringLiteral("Camera reported an invalid exposure"); - setState(State::Error, m_lastError); - return false; - } - m_exposureMs = finalExposure; - } - catch (const CMMError& error) - { - m_lastError = QString::fromStdString(error.getMsg()); - setState(State::Error, m_lastError); - return false; - } - - m_shm = std::make_unique(); - m_shm->setNativeKey(m_shmKey); - const int totalBytes = - kSharedMemoryControlSize + kSharedFrameNumSlots * kSharedFrameSlotStride; - if (!m_shm->create(totalBytes)) - { - if (m_shm->attach()) - { - m_shm->detach(); - } - if (!m_shm->create(totalBytes)) - { - m_lastError = QStringLiteral("Cannot create shared memory '%1'") - .arg(m_shmKey); - setState(State::Error, m_lastError); - return false; - } - } - - if (m_shm->lock()) - { - auto* base = static_cast(m_shm->data()); - if (base) - { - const SharedMemoryControl control{}; - memcpy(base, &control, sizeof(control)); - for (int i = 0; i < kSharedFrameNumSlots; ++i) - { - const SharedFrameHeader header{}; - uchar* slot = base + kSharedMemoryControlSize - + i * kSharedFrameSlotStride; - memcpy(slot, &header, sizeof(header)); - } - } - m_shm->unlock(); - } - - if (m_autoPreview) - { - QString error; - if (!startPreviewInternal(&error)) - { - m_lastError = error; - setState(State::Error, m_lastError); - return false; - } - } - else - { - setState(State::Idle); - } - - return true; - } - - public slots: - void publishHello(); - void handleRequest(quint64 connectionId, - quint64 requestId, - const QString& type, - const QJsonObject& message); - void stopForExit(); - - signals: - void responseReady(quint64 connectionId, const QJsonObject& response); - void eventReady(const QJsonObject& event); - void shutdownRequested(); - - private: - // Runtime state is mirrored to control clients - enum class State - { - Starting, - Idle, - Previewing, - Error, - ShuttingDown - }; - - enum class FrameDeliveryMode - { - PreviewLatest, - LatestOnly, - AllFrames - }; - - // Frame layout describes one shared memory payload shape - struct FrameLayout - { - unsigned width{0}; - unsigned height{0}; - unsigned bytesPerPixel{0}; - SharedPixelFormat format{SharedPixelFormat::Mono8}; - unsigned stride{0}; - quint64 byteCount{0}; - quint16 bitDepth{8}; - quint16 channels{1}; - }; - - bool previewRunning() const; - void setState(State state, const QString& error = QString()); - QJsonObject makeResponse(const QString& type, quint64 requestId, bool ok) const; - QJsonObject makeErrorResponse(const QString& type, - quint64 requestId, - const QString& error) const; - QJsonObject makeEvent(const QString& type) const; - bool applyProperties(const QStringList& encodedProperties, - const std::string& label, - const QString& propertyKind, - QString* errorMessage); - void emitPreviewStateEvent(); - void emitAgentErrorEvent(const QString& error); - void emitFrameAvailableEvent(quint64 frameIndex); - bool startPreviewInternal(QString* errorMessage); - bool stopPreviewInternal(QString* errorMessage); - bool captureEventFrameInternal(quint64& frameIndex, QString* errorMessage); - bool writeFrameToSharedMemory(const void* pixels, - quint64 frameAdvance = 1, - quint64* frameIndexOut = nullptr); - void pollAndWrite(); - void updatePollingInterval(quint64 frameCount); - bool ensureFrameLayout(unsigned width, unsigned height, unsigned bytesPerPixel); - void refreshSourceRoi(); - - QString m_cameraId; - QString m_adapter; - QString m_device; - QString m_shmKey; - QStringList m_preInitProperties; - QStringList m_properties; - double m_exposureMs{10.0}; - bool m_autoPreview{false}; - - QString m_lastError; - State m_state{State::Starting}; - - std::unique_ptr m_mmcore; - std::unique_ptr m_shm; - QTimer* m_timer{nullptr}; - QElapsedTimer m_frameIntervalTimer; - QElapsedTimer m_deliveryTimer; - double m_observedFrameIntervalMs{0.0}; - - FrameLayout m_frameLayout{}; - bool m_frameLayoutValid{false}; - bool m_loggedOversizedFrame{false}; - bool m_loggedUnsupportedFormat{false}; - int m_sourceRoiX{0}; - int m_sourceRoiY{0}; - int m_sourceRoiWidth{0}; - int m_sourceRoiHeight{0}; - - quint64 m_frameIndex{0}; - quint64 m_pendingFrameAdvance{0}; - int m_nextWriteSlot{0}; - FrameDeliveryMode m_frameDeliveryMode{FrameDeliveryMode::PreviewLatest}; - }; - - // Publish the initial hello event to connected clients - void AgentRuntime::publishHello() - { - emit eventReady(makeEvent(agent::kEventHello)); - } - - // Replay encoded cfg properties into the agent MMCore instance - bool AgentRuntime::applyProperties(const QStringList& encodedProperties, - const std::string& label, - const QString& propertyKind, - QString* errorMessage) - { - for (const QString& encodedProperty : encodedProperties) - { - if (encodedProperty.isEmpty()) - { - continue; - } - - QJsonParseError parseError; - const QJsonDocument doc = QJsonDocument::fromJson(encodedProperty.toUtf8(), &parseError); - if (parseError.error != QJsonParseError::NoError || !doc.isObject()) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("Invalid %1 payload for '%2'") - .arg(propertyKind, m_cameraId); - } - return false; - } - - const QJsonObject property = doc.object(); - const QString propertyName = property.value(QStringLiteral("name")).toString().trimmed(); - const QString propertyValue = property.value(QStringLiteral("value")).toString(); - if (propertyName.isEmpty()) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("Missing %1 name for '%2'") - .arg(propertyKind, m_cameraId); - } - return false; - } - - try - { - m_mmcore->setProperty(label.c_str(), - propertyName.toStdString().c_str(), - propertyValue.toStdString().c_str()); - } - catch (const CMMError& mmError) - { - if (errorMessage) - { - *errorMessage = QString("Failed to apply %1 '%2': %3") - .arg(propertyKind, propertyName, QString::fromStdString(mmError.getMsg())); - } - return false; - } - } - - return true; - } - - // Dispatch one control request and emit a matching response - void AgentRuntime::handleRequest(quint64 connectionId, - quint64 requestId, - const QString& type, - const QJsonObject& message) - { - if (requestId == 0 || type.isEmpty()) - { - return; - } - - if (!m_mmcore && type != agent::kCommandShutdown) - { - emit responseReady(connectionId, - makeErrorResponse(type, - requestId, - QStringLiteral("Agent runtime not initialized"))); - return; - } - - if (type == agent::kCommandStartPreview) - { - QString error; - const bool ok = startPreviewInternal(&error); - emit responseReady(connectionId, - ok - ? makeResponse(type, requestId, true) - : makeErrorResponse(type, requestId, error)); - return; - } - - if (type == agent::kCommandStopPreview) - { - QString error; - const bool ok = stopPreviewInternal(&error); - emit responseReady(connectionId, - ok - ? makeResponse(type, requestId, true) - : makeErrorResponse(type, requestId, error)); - return; - } - - if (type == agent::kCommandSetFrameDeliveryMode) - { - const QString mode = message.value(QStringLiteral("mode")).toString(); - if (mode == agent::kFrameDeliveryModePreviewLatest) - { - m_frameDeliveryMode = FrameDeliveryMode::PreviewLatest; - m_deliveryTimer.invalidate(); - } - else if (mode == agent::kFrameDeliveryModeLatestOnly) - { - m_frameDeliveryMode = FrameDeliveryMode::LatestOnly; - } - else if (mode == agent::kFrameDeliveryModeAllFrames) - { - m_frameDeliveryMode = FrameDeliveryMode::AllFrames; - m_pendingFrameAdvance = 0; - m_deliveryTimer.invalidate(); - } - else - { - emit responseReady(connectionId, - makeErrorResponse(type, - requestId, - QStringLiteral("Unknown frame delivery mode"))); - return; - } - - QJsonObject response = makeResponse(type, requestId, true); - response.insert(QStringLiteral("mode"), mode); - emit responseReady(connectionId, response); - return; - } - - if (type == agent::kCommandSetExposure) - { - const double exposureMs = message.value(QStringLiteral("value")).toDouble(-1.0); - bool ok = exposureMs > 0.0; - QString error; - if (!ok) - { - error = QStringLiteral("Invalid exposure value"); - } - else - { - try - { - m_mmcore->setExposure(exposureMs); - m_exposureMs = m_mmcore->getExposure(); - } - catch (const CMMError& mmError) - { - ok = false; - error = QString::fromStdString(mmError.getMsg()); - } - } - - if (ok) - { - QJsonObject response = makeResponse(type, requestId, true); - response.insert(QStringLiteral("exposureMs"), m_exposureMs); - emit responseReady(connectionId, response); - } - else - { - emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); - } - return; - } - - if (type == agent::kCommandListProperties) - { - QJsonArray properties; - QString error; - bool ok = true; - try - { - const auto names = - m_mmcore->getDevicePropertyNames(m_cameraId.toStdString().c_str()); - for (const auto& name : names) - { - properties.append(QString::fromStdString(name)); - } - } - catch (const CMMError& mmError) - { - ok = false; - error = QString::fromStdString(mmError.getMsg()); - } - - if (ok) - { - QJsonObject response = makeResponse(type, requestId, true); - response.insert(QStringLiteral("properties"), properties); - emit responseReady(connectionId, response); - } - else - { - emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); - } - return; - } - - if (type == agent::kCommandGetProperty) - { - const QString name = message.value(QStringLiteral("name")).toString(); - const bool fromCache = message.value(QStringLiteral("fromCache")).toBool(false); - const std::string camera = m_cameraId.toStdString(); - const std::string property = name.toStdString(); - QString value; - QString propertyType = QStringLiteral("Unknown"); - bool readOnly = true; - bool preInit = false; - QJsonArray allowedValues; - bool hasLimits = false; - double lowerLimit = 0.0; - double upperLimit = 0.0; - QString error; - bool ok = true; - - try - { - value = QString::fromStdString( - fromCache - ? m_mmcore->getPropertyFromCache(camera.c_str(), property.c_str()) - : m_mmcore->getProperty(camera.c_str(), property.c_str())); - - try - { - switch (m_mmcore->getPropertyType(camera.c_str(), property.c_str())) - { - case MM::String: - propertyType = QStringLiteral("String"); - break; - case MM::Float: - propertyType = QStringLiteral("Float"); - break; - case MM::Integer: - propertyType = QStringLiteral("Integer"); - break; - default: - propertyType = QStringLiteral("Unknown"); - break; - } - } - catch (const CMMError&) - { - } - - try - { - preInit = m_mmcore->isPropertyPreInit(camera.c_str(), property.c_str()); - } - catch (const CMMError&) - { - } - - try - { - readOnly = m_mmcore->isPropertyReadOnly(camera.c_str(), property.c_str()); - } - catch (const CMMError&) - { - } - - try - { - const auto values = - m_mmcore->getAllowedPropertyValues(camera.c_str(), property.c_str()); - for (const auto& allowedValue : values) - { - allowedValues.append(QString::fromStdString(allowedValue)); - } - } - catch (const CMMError&) - { - } - - try - { - hasLimits = m_mmcore->hasPropertyLimits(camera.c_str(), property.c_str()); - if (hasLimits) - { - lowerLimit = m_mmcore->getPropertyLowerLimit(camera.c_str(), property.c_str()); - upperLimit = m_mmcore->getPropertyUpperLimit(camera.c_str(), property.c_str()); - } - } - catch (const CMMError&) - { - hasLimits = false; - } - } - catch (const CMMError& mmError) - { - ok = false; - error = QString::fromStdString(mmError.getMsg()); - } - - if (ok) - { - QJsonObject response = makeResponse(type, requestId, true); - response.insert(QStringLiteral("value"), value); - response.insert(QStringLiteral("propertyType"), propertyType); - response.insert(QStringLiteral("readOnly"), readOnly); - response.insert(QStringLiteral("preInit"), preInit); - response.insert(QStringLiteral("allowedValues"), allowedValues); - response.insert(QStringLiteral("hasLimits"), hasLimits); - response.insert(QStringLiteral("lowerLimit"), lowerLimit); - response.insert(QStringLiteral("upperLimit"), upperLimit); - emit responseReady(connectionId, response); - } - else - { - emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); - } - return; - } - - if (type == agent::kCommandSetProperty) - { - const QString name = message.value(QStringLiteral("name")).toString(); - const QString value = message.value(QStringLiteral("value")).toString(); - QString error; - bool ok = true; - try - { - m_mmcore->setProperty(m_cameraId.toStdString().c_str(), - name.toStdString().c_str(), - value.toStdString().c_str()); - m_mmcore->waitForDevice(m_cameraId.toStdString().c_str()); - } - catch (const CMMError& mmError) - { - ok = false; - error = QString::fromStdString(mmError.getMsg()); - } - - emit responseReady(connectionId, - ok - ? makeResponse(type, requestId, true) - : makeErrorResponse(type, requestId, error)); - return; - } - - if (type == agent::kCommandSetRoi) - { - const int x = message.value(QStringLiteral("x")).toInt(0); - const int y = message.value(QStringLiteral("y")).toInt(0); - const int width = message.value(QStringLiteral("width")).toInt(0); - const int height = message.value(QStringLiteral("height")).toInt(0); - QString error; - bool ok = true; - try - { - m_mmcore->setROI(m_cameraId.toStdString().c_str(), x, y, width, height); - m_mmcore->waitForDevice(m_cameraId.toStdString().c_str()); - refreshSourceRoi(); - } - catch (const CMMError& mmError) - { - ok = false; - error = QString::fromStdString(mmError.getMsg()); - } - - emit responseReady(connectionId, - ok - ? makeResponse(type, requestId, true) - : makeErrorResponse(type, requestId, error)); - return; - } - - if (type == agent::kCommandClearRoi) - { - QString error; - bool ok = true; - try - { - m_mmcore->setCameraDevice(m_cameraId.toStdString().c_str()); - m_mmcore->clearROI(); - m_mmcore->waitForDevice(m_cameraId.toStdString().c_str()); - refreshSourceRoi(); - } - catch (const CMMError& mmError) - { - ok = false; - error = QString::fromStdString(mmError.getMsg()); - } - - emit responseReady(connectionId, - ok - ? makeResponse(type, requestId, true) - : makeErrorResponse(type, requestId, error)); - return; - } - - if (type == agent::kCommandGetRoi) - { - int x = 0; - int y = 0; - int width = 0; - int height = 0; - QString error; - bool ok = true; - try - { - m_mmcore->getROI(m_cameraId.toStdString().c_str(), x, y, width, height); - } - catch (const CMMError& mmError) - { - ok = false; - error = QString::fromStdString(mmError.getMsg()); - } - - if (ok) - { - QJsonObject response = makeResponse(type, requestId, true); - response.insert(QStringLiteral("x"), x); - response.insert(QStringLiteral("y"), y); - response.insert(QStringLiteral("width"), width); - response.insert(QStringLiteral("height"), height); - emit responseReady(connectionId, response); - } - else - { - emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); - } - return; - } - - if (type == agent::kCommandCaptureEvent) - { - QString error; - quint64 frameIndex = 0; - const bool ok = captureEventFrameInternal(frameIndex, &error); - if (ok) - { - QJsonObject response = makeResponse(type, requestId, true); - response.insert(QStringLiteral("frameIndex"), agent::encodeUInt64(frameIndex)); - emit responseReady(connectionId, response); - } - else - { - emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); - } - return; - } - - if (type == agent::kCommandShutdown) - { - stopPreviewInternal(nullptr); - setState(State::ShuttingDown); - emit responseReady(connectionId, makeResponse(type, requestId, true)); - QTimer::singleShot(50, this, [this]() { emit shutdownRequested(); }); - return; - } - - emit responseReady(connectionId, - makeErrorResponse(type, - requestId, - QStringLiteral("Unknown control command"))); - } - - // Stop preview before the runtime thread exits - void AgentRuntime::stopForExit() - { - stopPreviewInternal(nullptr); - setState(State::ShuttingDown); - } - - // Check whether the runtime is currently previewing - bool AgentRuntime::previewRunning() const - { - return m_state == State::Previewing; - } - - // Update runtime state and publish state changes - void AgentRuntime::setState(State state, const QString& error) - { - const bool changed = (m_state != state); - m_state = state; - if (!error.isEmpty()) - { - m_lastError = error; - } - if (changed && (state == State::Previewing - || state == State::Idle - || state == State::Error - || state == State::ShuttingDown)) - { - emitPreviewStateEvent(); - } - if (!error.isEmpty()) - { - emitAgentErrorEvent(error); - } - } - - // Build a protocol response envelope - QJsonObject AgentRuntime::makeResponse(const QString& type, quint64 requestId, bool ok) const - { - QJsonObject response = - agent::makeEnvelope(agent::kMessageKindResponse, type, requestId); - response.insert(QStringLiteral("ok"), ok); - return response; - } - - // Build a protocol error response envelope - QJsonObject AgentRuntime::makeErrorResponse(const QString& type, - quint64 requestId, - const QString& error) const - { - QJsonObject response = makeResponse(type, requestId, false); - response.insert(QStringLiteral("error"), error); - return response; - } - - // Build a protocol event envelope - QJsonObject AgentRuntime::makeEvent(const QString& type) const - { - return agent::makeEnvelope(agent::kMessageKindEvent, type); - } - - // Publish current preview state to clients - void AgentRuntime::emitPreviewStateEvent() - { - QJsonObject event = makeEvent(agent::kEventPreviewState); - event.insert(QStringLiteral("running"), previewRunning()); - emit eventReady(event); - } - - // Publish an agent error event to clients - void AgentRuntime::emitAgentErrorEvent(const QString& error) - { - QJsonObject event = makeEvent(agent::kEventAgentError); - event.insert(QStringLiteral("error"), error); - emit eventReady(event); - } - - // Publish the newest shared memory frame index - void AgentRuntime::emitFrameAvailableEvent(quint64 frameIndex) - { - QJsonObject event = makeEvent(agent::kEventFrameAvailable); - event.insert(QStringLiteral("frameIndex"), agent::encodeUInt64(frameIndex)); - emit eventReady(event); - } - - // Start continuous acquisition and polling - bool AgentRuntime::startPreviewInternal(QString* errorMessage) - { - if (!m_mmcore) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("MMCore not available"); - } - return false; - } - if (m_state == State::ShuttingDown || m_state == State::Error) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("Agent is not in a runnable state"); - } - return false; - } - if (previewRunning()) - { - return true; - } - - try - { - while (m_mmcore->getRemainingImageCount() > 0) - { - m_mmcore->popNextImage(); - } - refreshSourceRoi(); - if (!ensureFrameLayout(m_mmcore->getImageWidth(), - m_mmcore->getImageHeight(), - m_mmcore->getBytesPerPixel())) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("Unsupported frame format"); - } - return false; - } - m_mmcore->startContinuousSequenceAcquisition(0.0); - m_observedFrameIntervalMs = 0.0; - m_pendingFrameAdvance = 0; - m_frameIntervalTimer.restart(); - m_deliveryTimer.invalidate(); - m_timer->setInterval(pollingIntervalFor(m_exposureMs)); - if (m_timer && !m_timer->isActive()) - { - m_timer->start(); - } - setState(State::Previewing); - return true; - } - catch (const CMMError& error) - { - if (errorMessage) - { - *errorMessage = QString::fromStdString(error.getMsg()); - } - return false; - } - } - - // Stop continuous acquisition and polling - bool AgentRuntime::stopPreviewInternal(QString* errorMessage) - { - if (!m_mmcore) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("MMCore not available"); - } - return false; - } - - try - { - if (m_mmcore->isSequenceRunning()) - { - m_mmcore->stopSequenceAcquisition(); - } - if (m_timer && m_timer->isActive()) - { - m_timer->stop(); - } - m_frameIntervalTimer.invalidate(); - m_deliveryTimer.invalidate(); - m_observedFrameIntervalMs = 0.0; - m_pendingFrameAdvance = 0; - if (m_state != State::ShuttingDown && m_state != State::Error) - { - setState(State::Idle); - } - return true; - } - catch (const CMMError& error) - { - if (errorMessage) - { - *errorMessage = QString::fromStdString(error.getMsg()); - } - return false; - } - } - - // Capture one frame for recording or API requests - bool AgentRuntime::captureEventFrameInternal(quint64& frameIndex, QString* errorMessage) - { - frameIndex = 0; - try - { - const void* pixels = nullptr; - if (previewRunning()) - { - QElapsedTimer waitTimer; - waitTimer.start(); - while (m_mmcore->getRemainingImageCount() <= 0 && waitTimer.elapsed() < 2000) - { - QThread::msleep(1); - } - if (m_mmcore->getRemainingImageCount() > 0) - { - pixels = m_mmcore->popNextImage(); - } - else if (errorMessage) - { - *errorMessage = QStringLiteral("No frame available from running sequence"); - } - } - else - { - m_mmcore->snapImage(); - pixels = m_mmcore->getImage(); - } - - if (!pixels) - { - if (errorMessage && errorMessage->isEmpty()) - { - *errorMessage = QStringLiteral("Empty image buffer"); - } - return false; - } - - const unsigned width = m_mmcore->getImageWidth(); - const unsigned height = m_mmcore->getImageHeight(); - const unsigned bytesPerPixel = m_mmcore->getBytesPerPixel(); - if (!ensureFrameLayout(width, height, bytesPerPixel)) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("Unsupported frame format"); - } - return false; - } - - const quint64 frameAdvance = m_pendingFrameAdvance + 1; - m_pendingFrameAdvance = 0; - if (!writeFrameToSharedMemory(pixels, frameAdvance, &frameIndex)) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("Shared memory unavailable"); - } - return false; - } - - if (m_frameDeliveryMode == FrameDeliveryMode::PreviewLatest) - { - m_deliveryTimer.restart(); - } - - emitFrameAvailableEvent(frameIndex); - return true; - } - catch (const CMMError& error) - { - if (errorMessage) - { - *errorMessage = QString::fromStdString(error.getMsg()); - } - return false; - } - } - - // Copy one camera frame into the shared memory ring buffer - bool AgentRuntime::writeFrameToSharedMemory(const void* pixels, - quint64 frameAdvance, - quint64* frameIndexOut) - { - if (!m_shm || !m_shm->isAttached()) - { - return false; - } - uchar* base = static_cast(m_shm->data()); - if (!base) - { - return false; - } - - const quint64 nextFrameIndex = m_frameIndex + (std::max)(quint64{1}, frameAdvance); - const int preferredSlotIndex = m_nextWriteSlot; - int slotIndex = -1; - uchar* ptr = nullptr; - for (int offset = 0; offset < kSharedFrameNumSlots; ++offset) - { - const int candidate = (preferredSlotIndex + offset) % kSharedFrameNumSlots; - uchar* candidatePtr = base + kSharedMemoryControlSize - + candidate * kSharedFrameSlotStride; - auto& stateValue = *reinterpret_cast(candidatePtr); - std::atomic_ref state(stateValue); - quint32 expected = state.load(std::memory_order_acquire); - while (expected == 0 || expected == 2) - { - if (state.compare_exchange_weak(expected, - 1, - std::memory_order_acq_rel, - std::memory_order_acquire)) - { - slotIndex = candidate; - ptr = candidatePtr; - break; - } - } - if (slotIndex >= 0) - { - break; - } - } - - if (slotIndex < 0 || !ptr) - { - m_frameIndex = nextFrameIndex; - if (frameIndexOut) - { - *frameIndexOut = m_frameIndex; - } - return false; - } - - auto* control = reinterpret_cast(base); - m_nextWriteSlot = (slotIndex + 1) % kSharedFrameNumSlots; - SharedFrameHeader header{}; - header.state = 1; - header.width = m_frameLayout.width; - header.height = m_frameLayout.height; - header.stride = m_frameLayout.stride; - header.pixelFormat = static_cast(m_frameLayout.format); - header.bitsPerSample = m_frameLayout.bitDepth; - header.channels = m_frameLayout.channels; - header.frameIndex = nextFrameIndex; - header.timestampNs = - static_cast(QDateTime::currentMSecsSinceEpoch()) * 1000000ull; - setSharedFrameSourceRoi(header, - m_sourceRoiX, - m_sourceRoiY, - m_sourceRoiWidth, - m_sourceRoiHeight); - memcpy(ptr + sizeof(header.state), - reinterpret_cast(&header) + sizeof(header.state), - sizeof(header) - sizeof(header.state)); - - uchar* dst = ptr + kSharedFrameHeaderSize; - memcpy(dst, pixels, static_cast(m_frameLayout.byteCount)); - - auto& stateValue = *reinterpret_cast(ptr); - std::atomic_ref(stateValue).store(2, std::memory_order_release); - std::atomic_ref(control->latestSlotIndex) - .store(static_cast(slotIndex), std::memory_order_release); - m_frameIndex = nextFrameIndex; - if (frameIndexOut) - { - *frameIndexOut = m_frameIndex; - } - - return true; - } - - // Drain camera frames into shared memory and publish the newest index - void AgentRuntime::pollAndWrite() - { - if (!m_mmcore) - { - return; - } - - try - { - long remaining = m_mmcore->getRemainingImageCount(); - - if (remaining <= 0) - { - return; - } - - quint64 newestFrameIndex = 0; - quint64 acquiredFrameCount = 0; - if (m_frameDeliveryMode != FrameDeliveryMode::AllFrames) - { - const bool previewRateLimited = - m_frameDeliveryMode == FrameDeliveryMode::PreviewLatest; - const bool publishFrame = !previewRateLimited - || !m_deliveryTimer.isValid() - || m_deliveryTimer.elapsed() >= kPreviewFrameDeliveryIntervalMs; - while (remaining-- > 0) - { - const void* pixels = m_mmcore->popNextImage(); - if (!pixels) - { - break; - } - ++acquiredFrameCount; - ++m_pendingFrameAdvance; - if (remaining > 0) - { - continue; - } - if (publishFrame && m_frameLayoutValid) - { - quint64 writtenFrameIndex = 0; - const bool written = writeFrameToSharedMemory( - pixels, - m_pendingFrameAdvance, - &writtenFrameIndex); - m_pendingFrameAdvance = 0; - if (previewRateLimited) - { - m_deliveryTimer.restart(); - } - if (written) - { - newestFrameIndex = writtenFrameIndex; - } - } - } - } - else - { - while (remaining-- > 0) - { - const void* pixels = m_mmcore->popNextImage(); - if (!pixels) - { - break; - } - ++acquiredFrameCount; - - if (!m_frameLayoutValid) - { - break; - } - - quint64 writtenFrameIndex = 0; - if (!writeFrameToSharedMemory(pixels, 1, &writtenFrameIndex)) - { - break; - } - newestFrameIndex = writtenFrameIndex; - } - } - - if (newestFrameIndex != 0) - { - emitFrameAvailableEvent(newestFrameIndex); - } - updatePollingInterval(acquiredFrameCount); - } - catch (const CMMError& error) - { - const QString message = - QStringLiteral("Agent capture error: %1") - .arg(QString::fromStdString(error.getMsg())); - setState(State::Error, message); - } - } - - // Adapts MMCore polling to the observed camera frame interval - void AgentRuntime::updatePollingInterval(quint64 frameCount) - { - if (frameCount == 0 || !m_frameIntervalTimer.isValid()) - { - return; - } - - const double elapsedMs = static_cast(m_frameIntervalTimer.nsecsElapsed()) / 1000000.0; - m_frameIntervalTimer.restart(); - const double measuredIntervalMs = elapsedMs / static_cast(frameCount); - if (!std::isfinite(measuredIntervalMs) || measuredIntervalMs <= 0.0) - { - return; - } - - m_observedFrameIntervalMs = m_observedFrameIntervalMs > 0.0 - ? 0.75 * m_observedFrameIntervalMs - + 0.25 * measuredIntervalMs - : measuredIntervalMs; - const int intervalMs = pollingIntervalFor(m_observedFrameIntervalMs); - if (m_timer->interval() != intervalMs) - { - m_timer->setInterval(intervalMs); - } - } - - // Validate and cache the current frame memory layout - bool AgentRuntime::ensureFrameLayout(unsigned width, unsigned height, unsigned bytesPerPixel) - { - const auto logOversized = [this](quint64 byteCount) - { - if (!m_loggedOversizedFrame) - { - qWarning().noquote() - << QString("[Agent %1] Frame payload (%2 bytes) exceeds shared memory slot capacity (%3 bytes)") - .arg(m_cameraId) - .arg(static_cast(byteCount)) - .arg(kSharedFrameMaxBytes); - m_loggedOversizedFrame = true; - } - }; - - if (bytesPerPixel != 1 && bytesPerPixel != 2) - { - if (!m_loggedUnsupportedFormat) - { - qWarning().noquote() - << QString("[Agent %1] Unsupported bytes-per-pixel (%2). Only Mono8/Mono16 are supported.") - .arg(m_cameraId) - .arg(bytesPerPixel); - m_loggedUnsupportedFormat = true; - } - return false; - } - m_loggedUnsupportedFormat = false; - - const bool geometryChanged = - !m_frameLayoutValid - || m_frameLayout.width != width - || m_frameLayout.height != height - || m_frameLayout.bytesPerPixel != bytesPerPixel; - - if (!geometryChanged) - { - if (m_frameLayout.byteCount == 0 - || m_frameLayout.byteCount > static_cast(kSharedFrameMaxBytes)) - { - logOversized(m_frameLayout.byteCount); - return false; - } - if (m_sourceRoiWidth <= 0 || m_sourceRoiHeight <= 0) - { - refreshSourceRoi(); - } - m_loggedOversizedFrame = false; - return true; - } - - m_frameLayoutValid = false; - - FrameLayout updated; - updated.width = width; - updated.height = height; - updated.bytesPerPixel = bytesPerPixel; - updated.format = - (bytesPerPixel == 2) ? SharedPixelFormat::Mono16 : SharedPixelFormat::Mono8; - updated.channels = 1; - const quint64 stride = static_cast(width) * bytesPerPixel; - if (stride == 0 || stride > (std::numeric_limits::max)()) - { - logOversized(stride); - return false; - } - updated.stride = static_cast(stride); - updated.byteCount = computeMaxFrameBytes(width, height, updated.format); - - if (updated.byteCount == 0 - || updated.byteCount > static_cast(kSharedFrameMaxBytes)) - { - logOversized(updated.byteCount); - return false; - } - - quint16 bitDepth = - (updated.format == SharedPixelFormat::Mono16) ? 16 : 8; - try - { - bitDepth = normalizedSharedBitDepth( - updated.format, - static_cast(m_mmcore->getImageBitDepth())); - } - catch (const CMMError&) - { - bitDepth = - (updated.format == SharedPixelFormat::Mono16) ? 16 : 8; - } - updated.bitDepth = normalizedSharedBitDepth(updated.format, bitDepth); - - m_frameLayout = updated; - m_frameLayoutValid = true; - refreshSourceRoi(); - m_loggedOversizedFrame = false; - return true; - } - - // Read the active camera ROI used by following frame metadata - void AgentRuntime::refreshSourceRoi() - { - int x = 0; - int y = 0; - int width = 0; - int height = 0; - try - { - m_mmcore->getROI(m_cameraId.toStdString().c_str(), x, y, width, height); - } - catch (const CMMError&) - { - width = static_cast(m_frameLayout.width); - height = static_cast(m_frameLayout.height); - } - - if (width <= 0 || height <= 0) - { - width = static_cast(m_frameLayout.width); - height = static_cast(m_frameLayout.height); - } - - m_sourceRoiX = x; - m_sourceRoiY = y; - m_sourceRoiWidth = width; - m_sourceRoiHeight = height; - } - - class Agent final : public QObject - { - Q_OBJECT - - public: - // Create the control server wrapper for one camera agent - Agent(QString cameraId, - QString adapter, - QString device, - QString shmKey, - QStringList preInitProperties, - QStringList properties, - double exposureMs, - bool autoPreview, - QObject* parent = nullptr) - : QObject(parent) - , m_cameraId(std::move(cameraId)) - , m_adapter(std::move(adapter)) - , m_device(std::move(device)) - , m_shmKey(std::move(shmKey)) - , m_serverName(agent::controlServerName(m_cameraId)) - , m_preInitProperties(std::move(preInitProperties)) - , m_properties(std::move(properties)) - , m_exposureMs(exposureMs) - , m_autoPreview(autoPreview) - { - qRegisterMetaType("QJsonObject"); - qRegisterMetaType("quint64"); - } - - // Stop the runtime thread before destruction - ~Agent() override - { - stopRuntime(); - } - - bool start(); - - private slots: - void onNewControlConnection(); - void onRuntimeResponse(quint64 connectionId, const QJsonObject& response); - void broadcastEvent(const QJsonObject& event); - void onConnectionClosed(quint64 connectionId); - - private: - void stopRuntime(); - - QString m_cameraId; - QString m_adapter; - QString m_device; - QString m_shmKey; - QString m_serverName; - QStringList m_preInitProperties; - QStringList m_properties; - double m_exposureMs{0.0}; - bool m_autoPreview{false}; - - std::unique_ptr m_ctrlServer; - QThread m_runtimeThread; - AgentRuntime* m_runtime{nullptr}; - quint64 m_nextConnectionId{1}; - QHash m_connections; - }; - - // Start the runtime thread and local control server - bool Agent::start() - { - m_runtime = new AgentRuntime(m_cameraId, - m_adapter, - m_device, - m_shmKey, - m_preInitProperties, - m_properties, - m_exposureMs, - m_autoPreview); - m_runtime->moveToThread(&m_runtimeThread); - connect(&m_runtimeThread, &QThread::finished, - m_runtime, &QObject::deleteLater); - connect(m_runtime, &AgentRuntime::responseReady, - this, &Agent::onRuntimeResponse); - connect(m_runtime, &AgentRuntime::eventReady, - this, &Agent::broadcastEvent); - connect(m_runtime, &AgentRuntime::shutdownRequested, this, - []() { QCoreApplication::quit(); }); - - m_runtimeThread.start(); - - bool initialized = false; - QString initError; - QMetaObject::invokeMethod( - m_runtime, - [this, &initialized, &initError]() - { - initialized = m_runtime->initializeRuntime(); - initError = m_runtime->lastError(); - }, - Qt::BlockingQueuedConnection); - - if (!initialized) - { - qCritical().noquote() - << QString("Agent init failed for '%1': %2") - .arg(m_cameraId, initError); - stopRuntime(); - return false; - } - - QLocalServer::removeServer(m_serverName); - m_ctrlServer = std::make_unique(this); - connect(m_ctrlServer.get(), &QLocalServer::newConnection, - this, &Agent::onNewControlConnection); - if (!m_ctrlServer->listen(m_serverName)) - { - qCritical().noquote() - << QString("Agent control server failed to listen on %1") - .arg(m_serverName); - stopRuntime(); - return false; - } - - qInfo().noquote() - << QString("Agent control server listening on %1").arg(m_serverName); - return true; - } - - // Accept pending local control connections - void Agent::onNewControlConnection() - { - while (m_ctrlServer && m_ctrlServer->hasPendingConnections()) - { - QLocalSocket* socket = m_ctrlServer->nextPendingConnection(); - if (!socket) - { - continue; - } - - const quint64 connectionId = m_nextConnectionId++; - auto* connection = new ControlConnection(connectionId, socket, this); - m_connections.insert(connectionId, connection); - - connect(connection, &ControlConnection::requestReceived, - m_runtime, &AgentRuntime::handleRequest, - Qt::QueuedConnection); - connect(connection, &ControlConnection::connectionClosed, - this, &Agent::onConnectionClosed); - - QMetaObject::invokeMethod(m_runtime, - &AgentRuntime::publishHello, - Qt::QueuedConnection); - } - } - - // Route one runtime response back to its connection - void Agent::onRuntimeResponse(quint64 connectionId, const QJsonObject& response) - { - auto it = m_connections.find(connectionId); - if (it == m_connections.end() || !it.value()) - { - return; - } - it.value()->sendMessage(response); - } - - // Broadcast one runtime event to all clients - void Agent::broadcastEvent(const QJsonObject& event) - { - for (auto it = m_connections.begin(); it != m_connections.end(); ++it) - { - if (it.value()) - { - it.value()->sendMessage(event); - } - } - } - - // Remove a closed control connection - void Agent::onConnectionClosed(quint64 connectionId) - { - m_connections.remove(connectionId); - } - - // Stop the runtime worker thread cleanly - void Agent::stopRuntime() - { - if (!m_runtime) - { - return; - } - - QMetaObject::invokeMethod(m_runtime, - &AgentRuntime::stopForExit, - Qt::BlockingQueuedConnection); - m_runtimeThread.quit(); - m_runtimeThread.wait(); - m_runtime = nullptr; - } -} // namespace scopeone::core::internal - -// Launch one camera agent process from command line arguments -int main(int argc, char* argv[]) -{ - QCoreApplication app(argc, argv); - - QCommandLineParser parser; - parser.setApplicationDescription("ScopeOne Camera Agent"); - parser.addHelpOption(); - - QCommandLineOption optCamId({QStringLiteral("c"), QStringLiteral("cameraId")}, - QStringLiteral("Camera ID"), - QStringLiteral("id")); - QCommandLineOption optAdapter(QStringLiteral("adapter"), - QStringLiteral("MM adapter"), - QStringLiteral("adapter")); - QCommandLineOption optDevice(QStringLiteral("device"), - QStringLiteral("MM device"), - QStringLiteral("device")); - QCommandLineOption optShm(QStringLiteral("shm"), - QStringLiteral("Shared memory key"), - QStringLiteral("key")); - QCommandLineOption optExp(QStringLiteral("exposure"), - QStringLiteral("Exposure ms"), - QStringLiteral("ms")); - QCommandLineOption optPreInit(QStringLiteral("preinit"), - QStringLiteral("JSON-encoded pre-init property"), - QStringLiteral("json")); - QCommandLineOption optProperty(QStringLiteral("property"), - QStringLiteral("JSON-encoded initialized property"), - QStringLiteral("json")); - QCommandLineOption optAuto(QStringLiteral("autoPreview"), - QStringLiteral("Start preview immediately")); - - parser.addOption(optCamId); - parser.addOption(optAdapter); - parser.addOption(optDevice); - parser.addOption(optShm); - parser.addOption(optExp); - parser.addOption(optPreInit); - parser.addOption(optProperty); - parser.addOption(optAuto); - parser.process(app); - - if (!parser.isSet(optCamId) - || !parser.isSet(optAdapter) - || !parser.isSet(optDevice) - || !parser.isSet(optShm)) - { - qCritical().noquote() - << "Missing required arguments: --cameraId, --adapter, --device, --shm"; - return 2; - } - - scopeone::core::internal::Agent agent(parser.value(optCamId), - parser.value(optAdapter), - parser.value(optDevice), - parser.value(optShm), - parser.values(optPreInit), - parser.values(optProperty), - parser.isSet(optExp) - ? parser.value(optExp).toDouble() - : 0.0, - parser.isSet(optAuto)); - if (!agent.start()) - { - return 2; - } - - return app.exec(); -} - -#include "AgentMain.moc" diff --git a/ScopeOneCore/src/CameraBackend.cpp b/ScopeOneCore/src/CameraBackend.cpp index 7cce6d0..b7b3877 100644 --- a/ScopeOneCore/src/CameraBackend.cpp +++ b/ScopeOneCore/src/CameraBackend.cpp @@ -85,7 +85,7 @@ namespace scopeone::core::internal return false; } - bool CameraBackend::addAgentCamera(const QString&, + bool CameraBackend::addDriverHostCamera(const QString&, const QString&, const QString&, const QStringList&, diff --git a/ScopeOneCore/src/CameraManager.cpp b/ScopeOneCore/src/CameraManager.cpp index 838d754..46b62d3 100644 --- a/ScopeOneCore/src/CameraManager.cpp +++ b/ScopeOneCore/src/CameraManager.cpp @@ -45,7 +45,7 @@ namespace scopeone::core::internal shutdownNow(); m_backend = kind == CameraBackend::Kind::Native ? createNativeCameraBackend(m_processingFrameGate) - : createAgentCameraBackend(m_processingFrameGate); + : createDriverHostCameraBackend(m_processingFrameGate); if (!m_backend) { return false; @@ -58,7 +58,6 @@ namespace scopeone::core::internal { m_frameSink(frame); } - emit newRawFrameReady(frame); }); // Keeps processing input on the producer thread connect(m_backend.get(), &CameraBackend::processingFrameReady, @@ -72,8 +71,8 @@ namespace scopeone::core::internal this, &CameraManager::frameDeliveryFailed); connect(m_backend.get(), &CameraBackend::previewStateChanged, this, &CameraManager::previewStateChanged); - connect(m_backend.get(), &CameraBackend::agentControlServerListening, - this, &CameraManager::agentControlServerListening); + connect(m_backend.get(), &CameraBackend::driverHostControlServerListening, + this, &CameraManager::driverHostControlServerListening); if (!m_backend->setHighRateFrameDeliveryEnabled(m_highRateFrameDeliveryEnabled) || !m_backend->setRecordingFrameDeliveryEnabled(m_recordingFrameDeliveryEnabled)) { @@ -101,8 +100,8 @@ namespace scopeone::core::internal return configured; } - // Adds one process isolated camera to the agent backend - bool CameraManager::addAgentCamera(const QString& cameraId, + // Add one process isolated camera to the DriverHost backend + bool CameraManager::addDriverHostCamera(const QString& cameraId, const QString& adapter, const QString& device, const QStringList& preInitProperties, @@ -111,8 +110,8 @@ namespace scopeone::core::internal { const QString normalizedId = normalizedCameraId(cameraId); const bool configured = !normalizedId.isEmpty() - && activateBackend(CameraBackend::Kind::Agent) - && m_backend->addAgentCamera(normalizedId, + && activateBackend(CameraBackend::Kind::DriverHost) + && m_backend->addDriverHostCamera(normalizedId, adapter, device, preInitProperties, @@ -192,10 +191,10 @@ namespace scopeone::core::internal return m_backend && m_backend->stopPreview(); } - // Report whether cameras are isolated in agent processes - bool CameraManager::usesAgentBackend() const + // Report whether cameras are isolated in DriverHost processes + bool CameraManager::usesDriverHostBackend() const { - return m_backend && m_backend->kind() == CameraBackend::Kind::Agent; + return m_backend && m_backend->kind() == CameraBackend::Kind::DriverHost; } bool CameraManager::startPreviewFor(const QString& cameraId) diff --git a/ScopeOneCore/src/ClockService.cpp b/ScopeOneCore/src/ClockService.cpp deleted file mode 100644 index ccd4b82..0000000 --- a/ScopeOneCore/src/ClockService.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "scopeone/ClockService.h" - -#include - -namespace scopeone::core -{ - ClockStamp ClockService::now() const - { - const auto ticks = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count(); - ClockStamp stamp; - stamp.ticks = ticks; - stamp.hostMonotonicNs = ticks; - stamp.clockDomain = QStringLiteral("scopeone.host.monotonic"); - stamp.source = QStringLiteral("HostEstimated"); - return stamp; - } -} diff --git a/ScopeOneCore/src/AgentCameraBackend.cpp b/ScopeOneCore/src/DriverHostCameraBackend.cpp similarity index 82% rename from ScopeOneCore/src/AgentCameraBackend.cpp rename to ScopeOneCore/src/DriverHostCameraBackend.cpp index 0f27673..3c8f2ab 100644 --- a/ScopeOneCore/src/AgentCameraBackend.cpp +++ b/ScopeOneCore/src/DriverHostCameraBackend.cpp @@ -1,5 +1,5 @@ #include "internal/CameraBackend.h" -#include "internal/AgentProtocol.h" +#include "internal/DriverHostProtocol.h" #include "scopeone/SharedFrame.h" #include @@ -45,10 +45,10 @@ namespace scopeone::core::internal namespace { - constexpr int kAgentControlReadyTimeoutMs = 15000; + constexpr int kDriverHostControlReadyTimeoutMs = 15000; constexpr int kPreviewFrameDeliveryIntervalMs = 16; - enum class AgentFrameDeliveryMode + enum class DriverHostFrameDeliveryMode { PreviewLatest, LatestOnly, @@ -61,30 +61,30 @@ namespace scopeone::core::internal return cameraId.trimmed(); } - const QString& frameDeliveryModeName(AgentFrameDeliveryMode mode) + const QString& frameDeliveryModeName(DriverHostFrameDeliveryMode mode) { switch (mode) { - case AgentFrameDeliveryMode::PreviewLatest: - return agent::kFrameDeliveryModePreviewLatest; - case AgentFrameDeliveryMode::LatestOnly: - return agent::kFrameDeliveryModeLatestOnly; - case AgentFrameDeliveryMode::AllFrames: - return agent::kFrameDeliveryModeAllFrames; + case DriverHostFrameDeliveryMode::PreviewLatest: + return driverhost::kFrameDeliveryModePreviewLatest; + case DriverHostFrameDeliveryMode::LatestOnly: + return driverhost::kFrameDeliveryModeLatestOnly; + case DriverHostFrameDeliveryMode::AllFrames: + return driverhost::kFrameDeliveryModeAllFrames; } - return agent::kFrameDeliveryModePreviewLatest; + return driverhost::kFrameDeliveryModePreviewLatest; } - AgentFrameDeliveryMode nonRecordingDeliveryMode(bool highRate) + DriverHostFrameDeliveryMode nonRecordingDeliveryMode(bool highRate) { return highRate - ? AgentFrameDeliveryMode::LatestOnly - : AgentFrameDeliveryMode::PreviewLatest; + ? DriverHostFrameDeliveryMode::LatestOnly + : DriverHostFrameDeliveryMode::PreviewLatest; } } // namespace - struct AgentControlSession final : QObject + struct DriverHostControlSession final : QObject { struct PendingRequest { @@ -93,10 +93,12 @@ namespace scopeone::core::internal std::function completion; }; - explicit AgentControlSession(const QString& cameraId, - const QString& serverName, - QObject* parent = nullptr) + explicit DriverHostControlSession(const QString& providerId, + const QString& cameraId, + const QString& serverName, + QObject* parent = nullptr) : QObject(parent) + , m_providerId(providerId) , m_cameraId(cameraId) , m_serverName(serverName) { @@ -208,7 +210,7 @@ namespace scopeone::core::internal int timeoutMs, std::function completion) { - const QString type = request.value(agent::kMessageTypeField).toString(); + const QString type = request.value(driverhost::kMessageTypeField).toString(); if (type.isEmpty()) { return false; @@ -216,12 +218,12 @@ namespace scopeone::core::internal const quint64 requestId = m_nextRequestId++; QJsonObject envelope = request; - envelope.insert(agent::kEnvelopeKindField, agent::kMessageKindRequest); - envelope.insert(agent::kEnvelopeVersionField, static_cast(agent::kProtocolVersion)); - envelope.insert(agent::kEnvelopeRequestIdField, agent::encodeUInt64(requestId)); + envelope.insert(driverhost::kEnvelopeKindField, driverhost::kMessageKindRequest); + envelope.insert(driverhost::kEnvelopeVersionField, static_cast(driverhost::kProtocolVersion)); + envelope.insert(driverhost::kEnvelopeRequestIdField, driverhost::encodeUInt64(requestId)); PendingRequest pending; - pending.encoded = agent::encodeMessage(envelope); + pending.encoded = driverhost::encodeMessage(envelope); pending.completion = std::move(completion); pending.timer = new QTimer(this); pending.timer->setSingleShot(true); @@ -295,30 +297,30 @@ namespace scopeone::core::internal { QJsonObject message; QString error; - const agent::DecodeResult result = - agent::tryDecodeMessage(m_readBuffer, message, &error); - if (result == agent::DecodeResult::Incomplete) + const driverhost::DecodeResult result = + driverhost::tryDecodeMessage(m_readBuffer, message, &error); + if (result == driverhost::DecodeResult::Incomplete) { return; } - if (result == agent::DecodeResult::Error) + if (result == driverhost::DecodeResult::Error) { resetWithError(error); return; } - if (message.value(agent::kEnvelopeVersionField).toInt(0) - != static_cast(agent::kProtocolVersion)) + if (message.value(driverhost::kEnvelopeVersionField).toInt(0) + != static_cast(driverhost::kProtocolVersion)) { resetWithError(QStringLiteral("Control protocol version mismatch")); return; } - const QString kind = message.value(agent::kEnvelopeKindField).toString(); - if (kind == agent::kMessageKindResponse) + const QString kind = message.value(driverhost::kEnvelopeKindField).toString(); + if (kind == driverhost::kMessageKindResponse) { const quint64 requestId = - agent::decodeUInt64(message.value(agent::kEnvelopeRequestIdField)); + driverhost::decodeUInt64(message.value(driverhost::kEnvelopeRequestIdField)); if (requestId == 0) { continue; @@ -327,11 +329,21 @@ namespace scopeone::core::internal continue; } - if (kind == agent::kMessageKindEvent) + if (kind == driverhost::kMessageKindEvent) { - const QString type = message.value(agent::kMessageTypeField).toString(); - if (type == agent::kEventHello) + const QString type = message.value(driverhost::kMessageTypeField).toString(); + if (type == driverhost::kEventHello) { + const QJsonArray capabilities = + message.value(driverhost::kCapabilitiesField).toArray(); + if (message.value(driverhost::kProviderIdField).toString() + != m_providerId + || message.value(driverhost::kDeviceIdField).toString() != m_cameraId + || !capabilities.contains(driverhost::kCapabilityCamera)) + { + resetWithError(QStringLiteral("DriverHost identity mismatch")); + return; + } setReady(true); } for (const auto& handler : m_eventHandlers) @@ -392,6 +404,7 @@ namespace scopeone::core::internal } } + QString m_providerId; QString m_cameraId; QString m_serverName; QLocalSocket m_socket; @@ -406,12 +419,12 @@ namespace scopeone::core::internal bool m_closing{false}; }; - class AgentCameraBackend; + class DriverHostCameraBackend; - class AgentFrameWorker final : public QObject + class DriverHostFrameWorker final : public QObject { public: - explicit AgentFrameWorker(AgentCameraBackend* owner) + explicit DriverHostFrameWorker(DriverHostCameraBackend* owner) : m_owner(owner) { } @@ -448,36 +461,36 @@ namespace scopeone::core::internal QList& frames, quint64& acquiredFrameCount); - AgentCameraBackend* const m_owner; + DriverHostCameraBackend* const m_owner; QMap> m_readers; }; - struct AgentCameraSlot + struct DriverHostCameraSlot { QString cameraId; QString shmKey; std::shared_ptr process; - std::shared_ptr control; + std::shared_ptr control; bool isRunning{false}; double exposureMs{10.0}; bool frameReadQueued{false}; bool frameReadRequested{false}; }; - class AgentCameraBackend final : public CameraBackend + class DriverHostCameraBackend final : public CameraBackend { public: - explicit AgentCameraBackend(ProcessingFrameGate& processingFrameGate); - ~AgentCameraBackend() override; + explicit DriverHostCameraBackend(ProcessingFrameGate& processingFrameGate); + ~DriverHostCameraBackend() override; - Kind kind() const override { return Kind::Agent; } - bool addAgentCamera(const QString& cameraId, + Kind kind() const override { return Kind::DriverHost; } + bool addDriverHostCamera(const QString& cameraId, const QString& adapter, const QString& device, const QStringList& preInitProperties, const QStringList& properties, double exposureMs) override; - void removeAgentCamera(const QString& cameraId); + void removeDriverHostCamera(const QString& cameraId); bool isPreviewRunning(const QString& cameraId) const override { const auto it = m_cameras.constFind(cameraId); @@ -518,7 +531,7 @@ namespace scopeone::core::internal } startedCameraIds.append(cameraId); } - qInfo().noquote() << "Agent preview started"; + qInfo().noquote() << "DriverHost preview started"; return true; } @@ -531,7 +544,7 @@ namespace scopeone::core::internal } if (ok) { - qInfo().noquote() << "Agent preview stopped"; + qInfo().noquote() << "DriverHost preview stopped"; } return ok; } @@ -544,18 +557,18 @@ namespace scopeone::core::internal return false; } - AgentCameraSlot& slot = *m_cameras[cameraId]; + DriverHostCameraSlot& slot = *m_cameras[cameraId]; if (slot.isRunning) { return true; } - AgentFrameWorker* const worker = m_frameWorker; + DriverHostFrameWorker* const worker = m_frameWorker; QMetaObject::invokeMethod( worker, [worker, cameraId]() { worker->resetPreviewDeliveryState(cameraId); }, Qt::BlockingQueuedConnection); QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandStartPreview); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandStartPreview); QJsonObject resp; if (!sendControlCommand(cameraId, req, &resp, 1200)) { @@ -564,7 +577,7 @@ namespace scopeone::core::internal } if (!resp.value(QStringLiteral("ok")).toBool(false)) { - qWarning().noquote() << QString("Agent refused to start preview for '%1'").arg(cameraId); + qWarning().noquote() << QString("DriverHost refused to start preview for '%1'").arg(cameraId); return false; } @@ -581,7 +594,7 @@ namespace scopeone::core::internal { qWarning().noquote() << QString("Shared memory unavailable for '%1'").arg(cameraId); QJsonObject stopRequest; - stopRequest.insert(agent::kMessageTypeField, agent::kCommandStopPreview); + stopRequest.insert(driverhost::kMessageTypeField, driverhost::kCommandStopPreview); sendControlCommand(cameraId, stopRequest, nullptr, 1200); return false; } @@ -604,7 +617,7 @@ namespace scopeone::core::internal } QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandStopPreview); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandStopPreview); QJsonObject resp; if (!sendControlCommand(cameraId, req, &resp, 1200)) { @@ -667,19 +680,22 @@ namespace scopeone::core::internal bool readExposureFor(const QString& cameraId, double& exposureMs) const override { - const auto it = m_cameras.constFind(cameraId); - if (it == m_cameras.constEnd() || !it.value()) + QJsonObject request; + request.insert(driverhost::kMessageTypeField, driverhost::kCommandGetExposure); + QJsonObject response; + if (!sendControlCommand(cameraId, request, &response, 1200) + || !response.value(QStringLiteral("ok")).toBool(false)) { return false; } - exposureMs = it.value()->exposureMs; + exposureMs = response.value(QStringLiteral("exposureMs")).toDouble(0.0); return exposureMs > 0.0; } bool writeExposureFor(const QString& cameraId, double exposureMs) override { QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandSetExposure); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandSetExposure); req.insert(QStringLiteral("value"), exposureMs); QJsonObject resp; if (!sendControlCommand(cameraId, req, &resp, 1200) @@ -696,7 +712,7 @@ namespace scopeone::core::internal { QStringList out; QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandListProperties); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandListProperties); QJsonObject resp; if (!sendControlCommand(cameraId, req, &resp, 4000)) { @@ -720,7 +736,7 @@ namespace scopeone::core::internal CameraPropertyReadback& readback) override { QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandGetProperty); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandGetProperty); req.insert(QStringLiteral("name"), name); req.insert(QStringLiteral("fromCache"), fromCache); QJsonObject resp; @@ -755,7 +771,7 @@ namespace scopeone::core::internal QString* errorMessage) override { QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandSetProperty); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandSetProperty); req.insert(QStringLiteral("name"), name); req.insert(QStringLiteral("value"), value); QJsonObject resp; @@ -763,7 +779,7 @@ namespace scopeone::core::internal { if (errorMessage) { - *errorMessage = QStringLiteral("Agent control request failed"); + *errorMessage = QStringLiteral("DriverHost control request failed"); } return false; } @@ -781,7 +797,7 @@ namespace scopeone::core::internal bool setROIFor(const QString& cameraId, int x, int y, int width, int height) override { QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandSetRoi); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandSetRoi); req.insert(QStringLiteral("x"), x); req.insert(QStringLiteral("y"), y); req.insert(QStringLiteral("width"), width); @@ -804,7 +820,7 @@ namespace scopeone::core::internal bool clearROIFor(const QString& cameraId) override { QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandClearRoi); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandClearRoi); QJsonObject resp; if (!sendControlCommand(cameraId, req, &resp, 1200)) { @@ -823,7 +839,7 @@ namespace scopeone::core::internal bool getROIFor(const QString& cameraId, int& x, int& y, int& width, int& height) override { QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandGetRoi); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandGetRoi); QJsonObject resp; if (!sendControlCommand(cameraId, req, &resp, 1200)) { @@ -845,9 +861,9 @@ namespace scopeone::core::internal return true; } private: - friend class AgentFrameWorker; + friend class DriverHostFrameWorker; - bool waitForControlReady(AgentCameraSlot& slot, int timeoutMs); + bool waitForControlReady(DriverHostCameraSlot& slot, int timeoutMs); bool addFrameReader(const QString& cameraId, const QString& shmKey); void removeFrameReader(const QString& cameraId); bool prepareFrameReader(const QString& cameraId); @@ -857,13 +873,13 @@ namespace scopeone::core::internal bool sendControlCommand(const QString& cameraId, const QJsonObject& request, QJsonObject* response, - int timeoutMs); + int timeoutMs) const; bool sendFrameDeliveryModes(const QStringList& cameraIds, - AgentFrameDeliveryMode mode); - void stopAgentProcessesAsync(); + DriverHostFrameDeliveryMode mode); + void stopDriverHostProcessesAsync(); void completeAsyncShutdown(); - QMap> m_cameras; + QMap> m_cameras; std::atomic_bool m_frameDeliveryPaused{false}; bool m_shuttingDown{false}; bool m_shutdownHadRunningCamera{false}; @@ -872,7 +888,7 @@ namespace scopeone::core::internal QString m_shutdownError; std::function m_shutdownCompletion; QThread m_frameThread; - AgentFrameWorker* m_frameWorker{nullptr}; + DriverHostFrameWorker* m_frameWorker{nullptr}; }; // Returns payload byte count from a shared frame header @@ -951,18 +967,18 @@ namespace scopeone::core::internal std::atomic_ref(stateValue).store(2, std::memory_order_release); } - AgentCameraBackend::AgentCameraBackend(ProcessingFrameGate& processingFrameGate) + DriverHostCameraBackend::DriverHostCameraBackend(ProcessingFrameGate& processingFrameGate) : CameraBackend(processingFrameGate) { - m_frameThread.setObjectName(QStringLiteral("ScopeOneAgentFrameReader")); - m_frameWorker = new AgentFrameWorker(this); + m_frameThread.setObjectName(QStringLiteral("ScopeOneDriverHostFrameReader")); + m_frameWorker = new DriverHostFrameWorker(this); m_frameWorker->moveToThread(&m_frameThread); QObject::connect(&m_frameThread, &QThread::finished, m_frameWorker, &QObject::deleteLater); m_frameThread.start(); } - AgentCameraBackend::~AgentCameraBackend() + DriverHostCameraBackend::~DriverHostCameraBackend() { shutdownNow(); m_frameThread.quit(); @@ -970,7 +986,7 @@ namespace scopeone::core::internal m_frameWorker = nullptr; } - void AgentCameraBackend::shutdownNow() + void DriverHostCameraBackend::shutdownNow() { if (m_shuttingDown && m_cameras.isEmpty()) { @@ -987,7 +1003,7 @@ namespace scopeone::core::internal } if (m_frameWorker && m_frameThread.isRunning()) { - AgentFrameWorker* const worker = m_frameWorker; + DriverHostFrameWorker* const worker = m_frameWorker; QMetaObject::invokeMethod(worker, [worker]() { worker->clear(); }, Qt::BlockingQueuedConnection); @@ -997,7 +1013,7 @@ namespace scopeone::core::internal if (slot->control) { QJsonObject request; - request.insert(agent::kMessageTypeField, agent::kCommandShutdown); + request.insert(driverhost::kMessageTypeField, driverhost::kCommandShutdown); sendControlCommand(slot->cameraId, request, nullptr, 800); slot->control->stop(); } @@ -1019,8 +1035,8 @@ namespace scopeone::core::internal } } - // Stops agent processes without waiting on the facade thread - void AgentCameraBackend::shutdown(std::function completion) + // Stop DriverHost processes without waiting on the facade thread + void DriverHostCameraBackend::shutdown(std::function completion) { if (m_shuttingDown) { @@ -1038,7 +1054,7 @@ namespace scopeone::core::internal completion(m_cameras.isEmpty() ? QString() : m_shutdownError.isEmpty() - ? QStringLiteral("Camera agent shutdown is already in progress") + ? QStringLiteral("Camera DriverHost shutdown is already in progress") : m_shutdownError); } return; @@ -1056,8 +1072,8 @@ namespace scopeone::core::internal } if (m_frameWorker && m_frameThread.isRunning()) { - AgentFrameWorker* const worker = m_frameWorker; - const QPointer guardedThis(this); + DriverHostFrameWorker* const worker = m_frameWorker; + const QPointer guardedThis(this); const bool queued = QMetaObject::invokeMethod( worker, [guardedThis, worker]() @@ -1071,7 +1087,7 @@ namespace scopeone::core::internal { if (guardedThis) { - guardedThis->stopAgentProcessesAsync(); + guardedThis->stopDriverHostProcessesAsync(); } }, Qt::QueuedConnection); @@ -1084,11 +1100,11 @@ namespace scopeone::core::internal } } - stopAgentProcessesAsync(); + stopDriverHostProcessesAsync(); } - // Requests agent exit and falls back to bounded process termination - void AgentCameraBackend::stopAgentProcessesAsync() + // Request DriverHost exit and fall back to bounded process termination + void DriverHostCameraBackend::stopDriverHostProcessesAsync() { if (!m_shutdownCompletion) { @@ -1131,7 +1147,7 @@ namespace scopeone::core::internal if (slot->control) { QJsonObject request; - request.insert(agent::kMessageTypeField, agent::kCommandShutdown); + request.insert(driverhost::kMessageTypeField, driverhost::kCommandShutdown); const QPointer guardedProcess(process); shutdownQueued = slot->control->sendRequest( request, @@ -1176,8 +1192,8 @@ namespace scopeone::core::internal }); } - // Releases agent state and completes the owning manager callback - void AgentCameraBackend::completeAsyncShutdown() + // Release DriverHost state and complete the owning manager callback + void DriverHostCameraBackend::completeAsyncShutdown() { if (!m_shutdownCompletion && m_shutdownError.isEmpty()) { @@ -1193,7 +1209,7 @@ namespace scopeone::core::internal } if (m_shutdownKillPollsRemaining <= 0) { - m_shutdownError = QStringLiteral("Camera agent process did not stop after termination"); + m_shutdownError = QStringLiteral("Camera DriverHost process did not stop after termination"); auto completion = std::move(m_shutdownCompletion); completion(m_shutdownError); return; @@ -1222,7 +1238,7 @@ namespace scopeone::core::internal } // Registers one camera shared memory reader on the worker thread - void AgentFrameWorker::addCamera(const QString& cameraId, const QString& shmKey) + void DriverHostFrameWorker::addCamera(const QString& cameraId, const QString& shmKey) { removeCamera(cameraId); auto slot = std::make_shared(); @@ -1234,19 +1250,19 @@ namespace scopeone::core::internal } // Removes one camera reader and releases its shared memory mapping - void AgentFrameWorker::removeCamera(const QString& cameraId) + void DriverHostFrameWorker::removeCamera(const QString& cameraId) { m_readers.remove(cameraId); } // Releases every shared memory reader owned by the worker - void AgentFrameWorker::clear() + void DriverHostFrameWorker::clear() { m_readers.clear(); } - // Resets preview coalescing after an agent delivery mode change - void AgentFrameWorker::resetPreviewDeliveryState(const QString& cameraId) + // Reset preview coalescing after a DriverHost delivery mode change + void DriverHostFrameWorker::resetPreviewDeliveryState(const QString& cameraId) { const auto it = m_readers.find(cameraId); if (it == m_readers.end()) @@ -1258,14 +1274,14 @@ namespace scopeone::core::internal } // Attaches the named camera reader when its mapping is unavailable - bool AgentFrameWorker::ensureSharedMemory(const QString& cameraId) + bool DriverHostFrameWorker::ensureSharedMemory(const QString& cameraId) { const auto it = m_readers.find(cameraId); return it != m_readers.end() && ensureSharedMemory(*it.value()); } // Validates and attaches one shared memory reader slot - bool AgentFrameWorker::ensureSharedMemory(ReaderSlot& slot) + bool DriverHostFrameWorker::ensureSharedMemory(ReaderSlot& slot) { const int expectedSize = kSharedMemoryControlSize + kSharedFrameNumSlots * kSharedFrameSlotStride; @@ -1298,20 +1314,20 @@ namespace scopeone::core::internal return true; } - // Waits for one agent control channel to become ready - bool AgentCameraBackend::waitForControlReady(AgentCameraSlot& slot, int timeoutMs) + // Wait for one DriverHost control channel to become ready + bool DriverHostCameraBackend::waitForControlReady(DriverHostCameraSlot& slot, int timeoutMs) { return slot.control && slot.control->waitForReady(timeoutMs); } // Creates one shared memory reader on the dedicated frame thread - bool AgentCameraBackend::addFrameReader(const QString& cameraId, const QString& shmKey) + bool DriverHostCameraBackend::addFrameReader(const QString& cameraId, const QString& shmKey) { if (!m_frameWorker || !m_frameThread.isRunning()) { return false; } - AgentFrameWorker* const worker = m_frameWorker; + DriverHostFrameWorker* const worker = m_frameWorker; return QMetaObject::invokeMethod( worker, [worker, cameraId, shmKey]() { worker->addCamera(cameraId, shmKey); }, @@ -1319,11 +1335,11 @@ namespace scopeone::core::internal } // Removes one shared memory reader on the dedicated frame thread - void AgentCameraBackend::removeFrameReader(const QString& cameraId) + void DriverHostCameraBackend::removeFrameReader(const QString& cameraId) { if (m_frameWorker && m_frameThread.isRunning()) { - AgentFrameWorker* const worker = m_frameWorker; + DriverHostFrameWorker* const worker = m_frameWorker; QMetaObject::invokeMethod( worker, [worker, cameraId]() { worker->removeCamera(cameraId); }, @@ -1332,14 +1348,14 @@ namespace scopeone::core::internal } // Attaches one reader before preview begins - bool AgentCameraBackend::prepareFrameReader(const QString& cameraId) + bool DriverHostCameraBackend::prepareFrameReader(const QString& cameraId) { if (!m_frameWorker || !m_frameThread.isRunning()) { return false; } bool attached = false; - AgentFrameWorker* const worker = m_frameWorker; + DriverHostFrameWorker* const worker = m_frameWorker; const bool invoked = QMetaObject::invokeMethod( worker, [worker, cameraId, &attached]() { attached = worker->ensureSharedMemory(cameraId); }, @@ -1347,11 +1363,11 @@ namespace scopeone::core::internal return invoked && attached; } - // Sends one JSON command to an agent control channel - bool AgentCameraBackend::sendControlCommand(const QString& cameraId, + // Send one JSON command to a DriverHost control channel + bool DriverHostCameraBackend::sendControlCommand(const QString& cameraId, const QJsonObject& request, QJsonObject* response, - int timeoutMs) + int timeoutMs) const { const QString normalizedId = normalizedCameraId(cameraId); const auto it = m_cameras.constFind(normalizedId); @@ -1412,12 +1428,12 @@ namespace scopeone::core::internal return true; } - // Switches multiple agent producers within one bounded wait - bool AgentCameraBackend::sendFrameDeliveryModes(const QStringList& cameraIds, - AgentFrameDeliveryMode mode) + // Switch multiple DriverHost producers within one bounded wait + bool DriverHostCameraBackend::sendFrameDeliveryModes(const QStringList& cameraIds, + DriverHostFrameDeliveryMode mode) { QJsonObject request; - request.insert(agent::kMessageTypeField, agent::kCommandSetFrameDeliveryMode); + request.insert(driverhost::kMessageTypeField, driverhost::kCommandSetFrameDeliveryMode); request.insert(QStringLiteral("mode"), frameDeliveryModeName(mode)); struct BatchState @@ -1471,7 +1487,7 @@ namespace scopeone::core::internal } // Copies one claimed shared memory slot into an owned frame - bool AgentFrameWorker::copyFrame(ReaderSlot& slot, + bool DriverHostFrameWorker::copyFrame(ReaderSlot& slot, const SharedFrameHeader& header, const uchar* pixelData, QList& frames) @@ -1492,7 +1508,7 @@ namespace scopeone::core::internal } // Reads only the newest ready frame for responsive preview delivery - bool AgentFrameWorker::readLatestFrame(ReaderSlot& slot, + bool DriverHostFrameWorker::readLatestFrame(ReaderSlot& slot, QList& frames, quint64& acquiredFrameCount) { @@ -1575,7 +1591,7 @@ namespace scopeone::core::internal } // Reads every retained shared memory frame in order for recording delivery - bool AgentFrameWorker::readAllFrames(ReaderSlot& slot, + bool DriverHostFrameWorker::readAllFrames(ReaderSlot& slot, QList& frames, quint64& acquiredFrameCount) { @@ -1649,7 +1665,7 @@ namespace scopeone::core::internal } // Reads frames on the worker thread and optionally publishes the batch - ImageFrame AgentFrameWorker::consumeFrames(const QString& cameraId, bool publishFrames) + ImageFrame DriverHostFrameWorker::consumeFrames(const QString& cameraId, bool publishFrames) { const auto it = m_readers.find(cameraId); if (it == m_readers.end()) @@ -1717,21 +1733,21 @@ namespace scopeone::core::internal return slot.latestFrame; } - // Consumes one agent batch and reports completion on the backend thread - void AgentFrameWorker::consumeFramesAsync(const QString& cameraId) + // Consume one DriverHost batch and report completion on the backend thread + void DriverHostFrameWorker::consumeFramesAsync(const QString& cameraId) { if (!m_owner->m_frameDeliveryPaused.load(std::memory_order_relaxed)) { consumeFrames(cameraId, true); } - AgentCameraBackend* const owner = m_owner; + DriverHostCameraBackend* const owner = m_owner; QMetaObject::invokeMethod(owner, [owner, cameraId]() { owner->completeFrameRead(cameraId); }, Qt::QueuedConnection); } // Reads the newest frame synchronously for event capture - ImageFrame AgentCameraBackend::consumeFrameNow(const QString& cameraId) + ImageFrame DriverHostCameraBackend::consumeFrameNow(const QString& cameraId) { if (!m_frameWorker || !m_frameThread.isRunning()) { @@ -1739,7 +1755,7 @@ namespace scopeone::core::internal } ImageFrame frame; - AgentFrameWorker* const worker = m_frameWorker; + DriverHostFrameWorker* const worker = m_frameWorker; const bool invoked = QMetaObject::invokeMethod( worker, [worker, cameraId, &frame]() { frame = worker->consumeFrames(cameraId, false); }, @@ -1748,14 +1764,14 @@ namespace scopeone::core::internal } // Coalesces frame notifications into one worker request per camera - void AgentCameraBackend::scheduleFrameRead(const QString& cameraId) + void DriverHostCameraBackend::scheduleFrameRead(const QString& cameraId) { const auto it = m_cameras.find(cameraId); if (it == m_cameras.end() || !it.value() || !m_frameWorker || m_shuttingDown) { return; } - AgentCameraSlot& slot = *it.value(); + DriverHostCameraSlot& slot = *it.value(); if (!slot.isRunning || m_frameDeliveryPaused.load(std::memory_order_relaxed)) { return; @@ -1767,7 +1783,7 @@ namespace scopeone::core::internal } slot.frameReadQueued = true; - AgentFrameWorker* const worker = m_frameWorker; + DriverHostFrameWorker* const worker = m_frameWorker; if (!QMetaObject::invokeMethod( worker, [worker, cameraId]() { worker->consumeFramesAsync(cameraId); }, @@ -1778,14 +1794,14 @@ namespace scopeone::core::internal } // Schedules one deferred read when notifications arrived during a request - void AgentCameraBackend::completeFrameRead(const QString& cameraId) + void DriverHostCameraBackend::completeFrameRead(const QString& cameraId) { const auto it = m_cameras.find(cameraId); if (it == m_cameras.end() || !it.value()) { return; } - AgentCameraSlot& slot = *it.value(); + DriverHostCameraSlot& slot = *it.value(); slot.frameReadQueued = false; if (!slot.frameReadRequested) { @@ -1797,7 +1813,7 @@ namespace scopeone::core::internal } // Triggers one camera and waits for its event frame - bool AgentCameraBackend::captureEventFrame(const QString& cameraId, + bool DriverHostCameraBackend::captureEventFrame(const QString& cameraId, ImageFrame& frame, int timeoutMs) { @@ -1815,7 +1831,7 @@ namespace scopeone::core::internal : 0; QJsonObject req; - req.insert(agent::kMessageTypeField, agent::kCommandCaptureEvent); + req.insert(driverhost::kMessageTypeField, driverhost::kCommandCaptureEvent); QJsonObject resp; const int waitMs = (timeoutMs > 0) ? timeoutMs : 1500; if (!sendControlCommand(normalizedId, req, &resp, waitMs + 1000)) @@ -1828,7 +1844,7 @@ namespace scopeone::core::internal } const quint64 targetFrameIndex = - agent::decodeUInt64(resp.value(QStringLiteral("frameIndex"))); + driverhost::decodeUInt64(resp.value(QStringLiteral("frameIndex"))); QElapsedTimer timer; timer.start(); @@ -1850,7 +1866,7 @@ namespace scopeone::core::internal } // Pauses or resumes polling while another workflow controls capture - void AgentCameraBackend::setFrameDeliveryPaused(bool paused) + void DriverHostCameraBackend::setFrameDeliveryPaused(bool paused) { if (m_frameDeliveryPaused.exchange(paused, std::memory_order_relaxed) == paused) { @@ -1868,15 +1884,15 @@ namespace scopeone::core::internal } } - // Switches agent producers before changing the local recording consumer mode - bool AgentCameraBackend::setRecordingFrameDeliveryEnabled(bool enabled) + // Switch DriverHost producers before changing the local recording consumer mode + bool DriverHostCameraBackend::setRecordingFrameDeliveryEnabled(bool enabled) { if (enabled && recordingFrameDeliveryEnabled()) { return true; } - const AgentFrameDeliveryMode previewMode = + const DriverHostFrameDeliveryMode previewMode = nonRecordingDeliveryMode(highRateFrameDeliveryEnabled()); if (!enabled) { @@ -1885,7 +1901,7 @@ namespace scopeone::core::internal } const QStringList cameraIds = m_cameras.keys(); - if (!sendFrameDeliveryModes(cameraIds, AgentFrameDeliveryMode::AllFrames)) + if (!sendFrameDeliveryModes(cameraIds, DriverHostFrameDeliveryMode::AllFrames)) { sendFrameDeliveryModes(cameraIds, previewMode); return false; @@ -1904,7 +1920,7 @@ namespace scopeone::core::internal } // Switches preview producers between display-rate and processing-rate delivery - bool AgentCameraBackend::setHighRateFrameDeliveryEnabled(bool enabled) + bool DriverHostCameraBackend::setHighRateFrameDeliveryEnabled(bool enabled) { if (highRateFrameDeliveryEnabled() == enabled) { @@ -1916,8 +1932,8 @@ namespace scopeone::core::internal return CameraBackend::setHighRateFrameDeliveryEnabled(enabled); } - const AgentFrameDeliveryMode targetMode = nonRecordingDeliveryMode(enabled); - const AgentFrameDeliveryMode rollbackMode = + const DriverHostFrameDeliveryMode targetMode = nonRecordingDeliveryMode(enabled); + const DriverHostFrameDeliveryMode rollbackMode = nonRecordingDeliveryMode(highRateFrameDeliveryEnabled()); const QStringList cameraIds = m_cameras.keys(); if (!sendFrameDeliveryModes(cameraIds, targetMode)) @@ -1928,8 +1944,8 @@ namespace scopeone::core::internal return CameraBackend::setHighRateFrameDeliveryEnabled(enabled); } - // Starts one camera agent process and connects its control channel - bool AgentCameraBackend::addAgentCamera(const QString& cameraId, + // Start one camera DriverHost process and connect its control channel + bool DriverHostCameraBackend::addDriverHostCamera(const QString& cameraId, const QString& adapter, const QString& device, const QStringList& preInitProperties, @@ -1947,22 +1963,25 @@ namespace scopeone::core::internal { return true; } - auto slot = std::make_shared(); + auto slot = std::make_shared(); slot->cameraId = normalizedId; - slot->shmKey = agent::sharedMemoryKey(normalizedId); + slot->shmKey = driverhost::sharedMemoryKey(normalizedId); slot->process = std::make_shared(); - slot->control = std::make_shared(normalizedId, - agent::controlServerName(normalizedId)); + slot->control = std::make_shared( + QStringLiteral("micro-manager"), + normalizedId, + driverhost::controlServerName(normalizedId)); slot->exposureMs = exposureMs; - const QString agentPath = - QDir(QCoreApplication::applicationDirPath()).filePath(agent::kExecutableFileName); - if (!QFileInfo::exists(agentPath)) + const QString driverHostPath = + QDir(QCoreApplication::applicationDirPath()).filePath(driverhost::kExecutableFileName); + if (!QFileInfo::exists(driverHostPath)) { - qWarning().noquote() << QString("Agent executable not found: %1").arg(agentPath); + qWarning().noquote() << QString("DriverHost executable not found: %1").arg(driverHostPath); return false; } QStringList args; - args << "--cameraId" << normalizedId + args << "--provider" << QStringLiteral("micro-manager") + << "--deviceId" << normalizedId << "--adapter" << adapterName << "--device" << deviceName << "--shm" << slot->shmKey; @@ -1984,7 +2003,7 @@ namespace scopeone::core::internal args << "--property" << encodedProperty; } } - slot->process->setProgram(agentPath); + slot->process->setProgram(driverHostPath); slot->process->setArguments(args); slot->process->setProcessChannelMode(QProcess::MergedChannels); @@ -1998,7 +2017,7 @@ namespace scopeone::core::internal for (const QString& line : lines) { const QString trimmed = line.trimmed(); - qInfo().noquote() << QString("[Agent %1] %2").arg(normalizedId, trimmed); + qInfo().noquote() << QString("[DriverHost %1] %2").arg(normalizedId, trimmed); } } }); @@ -2024,7 +2043,7 @@ namespace scopeone::core::internal if (wasRecording) { emit frameDeliveryFailed( - QStringLiteral("Camera agent exited for '%1'").arg(normalizedId), + QStringLiteral("Camera DriverHost exited for '%1'").arg(normalizedId), 0); } }); @@ -2035,7 +2054,7 @@ namespace scopeone::core::internal { if (ready) { - emit agentControlServerListening(normalizedId, agent::controlServerName(normalizedId)); + emit driverHostControlServerListening(normalizedId, driverhost::controlServerName(normalizedId)); } }); slot->control->addEventHandler([this, normalizedId](const QJsonObject& event) @@ -2045,14 +2064,14 @@ namespace scopeone::core::internal { return; } - AgentCameraSlot& slot = *it.value(); - const QString type = event.value(agent::kMessageTypeField).toString(); - if (type == agent::kEventFrameAvailable) + DriverHostCameraSlot& slot = *it.value(); + const QString type = event.value(driverhost::kMessageTypeField).toString(); + if (type == driverhost::kEventFrameAvailable) { scheduleFrameRead(normalizedId); return; } - if (type == agent::kEventPreviewState) + if (type == driverhost::kEventPreviewState) { const bool wasRunning = slot.isRunning; slot.isRunning = event.value(QStringLiteral("running")).toBool(slot.isRunning); @@ -2062,9 +2081,9 @@ namespace scopeone::core::internal } return; } - if (type == agent::kEventAgentError) + if (type == driverhost::kEventDriverHostError) { - const QString error = QStringLiteral("Agent '%1' error: %2") + const QString error = QStringLiteral("DriverHost '%1' error: %2") .arg(slot.cameraId, event.value(QStringLiteral("error")).toString()); qWarning().noquote() << error; @@ -2081,7 +2100,7 @@ namespace scopeone::core::internal slot->process->start(); if (!slot->process->waitForStarted(3000)) { - qWarning().noquote() << QString("Failed to start agent for %1").arg(normalizedId); + qWarning().noquote() << QString("Failed to start DriverHost for %1").arg(normalizedId); return false; } m_cameras.insert(normalizedId, slot); @@ -2089,34 +2108,34 @@ namespace scopeone::core::internal { slot->control->start(); } - if (!waitForControlReady(*slot, kAgentControlReadyTimeoutMs)) + if (!waitForControlReady(*slot, kDriverHostControlReadyTimeoutMs)) { qWarning().noquote() - << QString("Agent control session did not become ready for %1 within %2 ms") + << QString("DriverHost control session did not become ready for %1 within %2 ms") .arg(normalizedId) - .arg(kAgentControlReadyTimeoutMs); - removeAgentCamera(normalizedId); + .arg(kDriverHostControlReadyTimeoutMs); + removeDriverHostCamera(normalizedId); return false; } if (!addFrameReader(normalizedId, slot->shmKey)) { - removeAgentCamera(normalizedId); + removeDriverHostCamera(normalizedId); return false; } - const AgentFrameDeliveryMode deliveryMode = recordingFrameDeliveryEnabled() - ? AgentFrameDeliveryMode::AllFrames + const DriverHostFrameDeliveryMode deliveryMode = recordingFrameDeliveryEnabled() + ? DriverHostFrameDeliveryMode::AllFrames : nonRecordingDeliveryMode( highRateFrameDeliveryEnabled()); if (!sendFrameDeliveryModes(QStringList{normalizedId}, deliveryMode)) { - removeAgentCamera(normalizedId); + removeDriverHostCamera(normalizedId); return false; } return true; } - // Stops one camera agent process and releases its resources - void AgentCameraBackend::removeAgentCamera(const QString& cameraId) + // Stop one camera DriverHost process and release its resources + void DriverHostCameraBackend::removeDriverHostCamera(const QString& cameraId) { const QString normalizedId = normalizedCameraId(cameraId); if (!m_cameras.contains(normalizedId)) @@ -2130,7 +2149,7 @@ namespace scopeone::core::internal if (slot->control) { QJsonObject request; - request.insert(agent::kMessageTypeField, agent::kCommandShutdown); + request.insert(driverhost::kMessageTypeField, driverhost::kCommandShutdown); sendControlCommand(normalizedId, request, nullptr, 800); } m_cameras.remove(normalizedId); @@ -2153,9 +2172,9 @@ namespace scopeone::core::internal } } - std::unique_ptr createAgentCameraBackend( + std::unique_ptr createDriverHostCameraBackend( ProcessingFrameGate& processingFrameGate) { - return std::make_unique(processingFrameGate); + return std::make_unique(processingFrameGate); } } diff --git a/ScopeOneCore/src/DriverHostMain.cpp b/ScopeOneCore/src/DriverHostMain.cpp new file mode 100644 index 0000000..8150ccd --- /dev/null +++ b/ScopeOneCore/src/DriverHostMain.cpp @@ -0,0 +1,3084 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MMCore.h" +#include "internal/DriverHostProtocol.h" +#include "scopeone/CameraProvider.h" +#include "scopeone/DriverHostProviderPlugin.h" +#include "scopeone/HardwareCapabilities.h" +#include "scopeone/SharedFrame.h" + +namespace scopeone::core::internal +{ + static_assert(std::atomic_ref::is_always_lock_free, + "Shared frame state requires lock-free 32-bit atomics"); + + using scopeone::core::SharedFrameHeader; + using scopeone::core::SharedMemoryControl; + using scopeone::core::SharedPixelFormat; + using scopeone::core::computeMaxFrameBytes; + using scopeone::core::kSharedFrameHeaderSize; + using scopeone::core::kSharedFrameMaxBytes; + using scopeone::core::kSharedFrameNumSlots; + using scopeone::core::kSharedFrameSlotStride; + using scopeone::core::kSharedMemoryControlSize; + + constexpr int kMinimumPollIntervalMs = 1; + constexpr int kMaximumPollIntervalMs = 50; + constexpr int kPreviewFrameDeliveryIntervalMs = 16; + + int pollingIntervalFor(double frameIntervalMs) + { + if (!std::isfinite(frameIntervalMs) || frameIntervalMs <= 0.0) + { + return kMinimumPollIntervalMs; + } + return static_cast(std::clamp(frameIntervalMs / 4.0, + static_cast(kMinimumPollIntervalMs), + static_cast(kMaximumPollIntervalMs))); + } + + // Normalize frame bit depth before publishing shared frame metadata + static quint16 normalizedSharedBitDepth(SharedPixelFormat format, int bitsPerSample) + { + if (format == SharedPixelFormat::Mono8) + { + return 8; + } + if (format == SharedPixelFormat::Mono16) + { + return bitsPerSample >= 1 && bitsPerSample <= 16 + ? static_cast(bitsPerSample) + : 16; + } + return 0; + } + + class ControlConnection final : public QObject + { + Q_OBJECT + + public: + // Wrap one local control socket connection + ControlConnection(quint64 connectionId, QLocalSocket* socket, QObject* parent = nullptr) + : QObject(parent) + , m_connectionId(connectionId) + , m_socket(socket) + { + if (!socket) + { + qFatal("ControlConnection requires QLocalSocket"); + } + m_socket->setParent(this); + + connect(m_socket, &QLocalSocket::readyRead, + this, &ControlConnection::onReadyRead); + connect(m_socket, &QLocalSocket::disconnected, + this, &ControlConnection::onDisconnected); + connect(m_socket, &QLocalSocket::errorOccurred, this, + [this](QLocalSocket::LocalSocketError socketError) + { + qWarning().noquote() + << QString("DriverHost control socket error (%1) on connection %2") + .arg(static_cast(socketError)) + .arg(m_connectionId); + }); + } + + // Send one encoded protocol message to the client + void sendMessage(const QJsonObject& message) + { + if (m_socket->state() != QLocalSocket::ConnectedState) + { + return; + } + m_socket->write(driverhost::encodeMessage(message)); + } + + signals: + void requestReceived(quint64 connectionId, + quint64 requestId, + const QString& type, + const QJsonObject& message); + void connectionClosed(quint64 connectionId); + + private slots: + // Decode queued socket bytes into control requests + void onReadyRead() + { + m_readBuffer += m_socket->readAll(); + while (true) + { + QJsonObject message; + QString error; + const driverhost::DecodeResult result = + driverhost::tryDecodeMessage(m_readBuffer, message, &error); + if (result == driverhost::DecodeResult::Incomplete) + { + return; + } + if (result == driverhost::DecodeResult::Error) + { + qWarning().noquote() + << QString("DriverHost control protocol error on connection %1: %2") + .arg(m_connectionId) + .arg(error); + m_socket->disconnectFromServer(); + return; + } + + if (message.value(driverhost::kEnvelopeVersionField).toInt(0) + != static_cast(driverhost::kProtocolVersion)) + { + qWarning().noquote() + << QString("DriverHost control protocol version mismatch on connection %1") + .arg(m_connectionId); + m_socket->disconnectFromServer(); + return; + } + + if (message.value(driverhost::kEnvelopeKindField).toString() + != driverhost::kMessageKindRequest) + { + qWarning().noquote() + << QString("Ignoring non-request control message on connection %1") + .arg(m_connectionId); + continue; + } + + const quint64 requestId = + driverhost::decodeUInt64(message.value(driverhost::kEnvelopeRequestIdField)); + const QString type = message.value(driverhost::kMessageTypeField).toString(); + if (requestId == 0 || type.isEmpty()) + { + qWarning().noquote() + << QString("Ignoring malformed request on connection %1") + .arg(m_connectionId); + continue; + } + + emit requestReceived(m_connectionId, requestId, type, message); + } + } + + // Notify DriverHost when this connection closes + void onDisconnected() + { + emit connectionClosed(m_connectionId); + deleteLater(); + } + + private: + quint64 m_connectionId{0}; + QLocalSocket* m_socket{nullptr}; + QByteArray m_readBuffer; + }; + + class DriverHostRuntime : public QObject + { + Q_OBJECT + + public: + using QObject::QObject; + virtual bool initializeRuntime() = 0; + virtual QString lastError() const = 0; + virtual void publishHello() = 0; + virtual void handleRequest(quint64 connectionId, + quint64 requestId, + const QString& type, + const QJsonObject& message) = 0; + virtual void stopForExit() = 0; + + signals: + void responseReady(quint64 connectionId, const QJsonObject& response); + void eventReady(const QJsonObject& event); + void shutdownRequested(); + }; + + class MicroManagerDriverRuntime final : public DriverHostRuntime + { + Q_OBJECT + + public: + // Store launch settings for one camera runtime + MicroManagerDriverRuntime(QString providerId, + QString cameraId, + QString adapter, + QString device, + QString shmKey, + QStringList preInitProperties, + QStringList properties, + double exposureMs, + bool autoPreview, + QObject* parent = nullptr) + : DriverHostRuntime(parent) + , m_providerId(std::move(providerId)) + , m_cameraId(std::move(cameraId)) + , m_adapter(std::move(adapter)) + , m_device(std::move(device)) + , m_shmKey(std::move(shmKey)) + , m_preInitProperties(std::move(preInitProperties)) + , m_properties(std::move(properties)) + , m_exposureMs(exposureMs) + , m_autoPreview(autoPreview) + { + } + + QString lastError() const override + { + return m_lastError; + } + + // Initialize MMCore camera state and shared memory transport + bool initializeRuntime() override + { + m_lastError.clear(); + setState(State::Starting); + + if (!m_timer) + { + m_timer = new QTimer(this); + m_timer->setTimerType(Qt::PreciseTimer); + connect(m_timer, &QTimer::timeout, + this, &MicroManagerDriverRuntime::pollAndWrite); + } + + try + { + m_mmcore = std::make_unique(); + const std::string label = m_cameraId.toStdString(); + const std::string adapter = m_adapter.toStdString(); + const std::string device = m_device.toStdString(); + + m_mmcore->loadDevice(label.c_str(), adapter.c_str(), device.c_str()); + QString preInitError; + if (!applyProperties(m_preInitProperties, label, QStringLiteral("pre-init property"), &preInitError)) + { + m_lastError = preInitError; + setState(State::Error, m_lastError); + return false; + } + m_mmcore->initializeDevice(label.c_str()); + m_mmcore->setCameraDevice(label.c_str()); + + QString propertyError; + if (!applyProperties(m_properties, label, QStringLiteral("property"), &propertyError)) + { + m_lastError = propertyError; + setState(State::Error, m_lastError); + return false; + } + + try + { + m_mmcore->setCircularBufferMemoryFootprint(2048); + } + catch (const CMMError&) + { + } + + double finalExposure = 0.0; + if (m_exposureMs > 0.0) + { + m_mmcore->setExposure(m_exposureMs); + } + m_mmcore->waitForDevice(label.c_str()); + finalExposure = m_mmcore->getExposure(); + if (finalExposure <= 0.0) + { + m_lastError = QStringLiteral("Camera reported an invalid exposure"); + setState(State::Error, m_lastError); + return false; + } + m_exposureMs = finalExposure; + } + catch (const CMMError& error) + { + m_lastError = QString::fromStdString(error.getMsg()); + setState(State::Error, m_lastError); + return false; + } + + m_shm = std::make_unique(); + m_shm->setNativeKey(m_shmKey); + const int totalBytes = + kSharedMemoryControlSize + kSharedFrameNumSlots * kSharedFrameSlotStride; + if (!m_shm->create(totalBytes)) + { + if (m_shm->attach()) + { + m_shm->detach(); + } + if (!m_shm->create(totalBytes)) + { + m_lastError = QStringLiteral("Cannot create shared memory '%1'") + .arg(m_shmKey); + setState(State::Error, m_lastError); + return false; + } + } + + if (m_shm->lock()) + { + auto* base = static_cast(m_shm->data()); + if (base) + { + const SharedMemoryControl control{}; + memcpy(base, &control, sizeof(control)); + for (int i = 0; i < kSharedFrameNumSlots; ++i) + { + const SharedFrameHeader header{}; + uchar* slot = base + kSharedMemoryControlSize + + i * kSharedFrameSlotStride; + memcpy(slot, &header, sizeof(header)); + } + } + m_shm->unlock(); + } + + if (m_autoPreview) + { + QString error; + if (!startPreviewInternal(&error)) + { + m_lastError = error; + setState(State::Error, m_lastError); + return false; + } + } + else + { + setState(State::Idle); + } + + return true; + } + + void publishHello() override; + void handleRequest(quint64 connectionId, + quint64 requestId, + const QString& type, + const QJsonObject& message) override; + void stopForExit() override; + + private: + // Runtime state is mirrored to control clients + enum class State + { + Starting, + Idle, + Previewing, + Error, + ShuttingDown + }; + + enum class FrameDeliveryMode + { + PreviewLatest, + LatestOnly, + AllFrames + }; + + // Frame layout describes one shared memory payload shape + struct FrameLayout + { + unsigned width{0}; + unsigned height{0}; + unsigned bytesPerPixel{0}; + SharedPixelFormat format{SharedPixelFormat::Mono8}; + unsigned stride{0}; + quint64 byteCount{0}; + quint16 bitDepth{8}; + quint16 channels{1}; + }; + + bool previewRunning() const; + void setState(State state, const QString& error = QString()); + QJsonObject makeResponse(const QString& type, quint64 requestId, bool ok) const; + QJsonObject makeErrorResponse(const QString& type, + quint64 requestId, + const QString& error) const; + QJsonObject makeEvent(const QString& type) const; + bool applyProperties(const QStringList& encodedProperties, + const std::string& label, + const QString& propertyKind, + QString* errorMessage); + void emitPreviewStateEvent(); + void emitDriverHostErrorEvent(const QString& error); + void emitFrameAvailableEvent(quint64 frameIndex); + bool startPreviewInternal(QString* errorMessage); + bool stopPreviewInternal(QString* errorMessage); + bool captureEventFrameInternal(quint64& frameIndex, QString* errorMessage); + bool writeFrameToSharedMemory(const void* pixels, + quint64 frameAdvance = 1, + quint64* frameIndexOut = nullptr); + void pollAndWrite(); + void updatePollingInterval(quint64 frameCount); + bool ensureFrameLayout(unsigned width, unsigned height, unsigned bytesPerPixel); + void refreshSourceRoi(); + + QString m_providerId; + QString m_cameraId; + QString m_adapter; + QString m_device; + QString m_shmKey; + QStringList m_preInitProperties; + QStringList m_properties; + double m_exposureMs{10.0}; + bool m_autoPreview{false}; + + QString m_lastError; + State m_state{State::Starting}; + + std::unique_ptr m_mmcore; + std::unique_ptr m_shm; + QTimer* m_timer{nullptr}; + QElapsedTimer m_frameIntervalTimer; + QElapsedTimer m_deliveryTimer; + double m_observedFrameIntervalMs{0.0}; + + FrameLayout m_frameLayout{}; + bool m_frameLayoutValid{false}; + bool m_loggedOversizedFrame{false}; + bool m_loggedUnsupportedFormat{false}; + int m_sourceRoiX{0}; + int m_sourceRoiY{0}; + int m_sourceRoiWidth{0}; + int m_sourceRoiHeight{0}; + + quint64 m_frameIndex{0}; + quint64 m_pendingFrameAdvance{0}; + int m_nextWriteSlot{0}; + FrameDeliveryMode m_frameDeliveryMode{FrameDeliveryMode::PreviewLatest}; + }; + + // Publish the initial hello event to connected clients + void MicroManagerDriverRuntime::publishHello() + { + QJsonObject event = makeEvent(driverhost::kEventHello); + event.insert(driverhost::kProviderIdField, m_providerId); + event.insert(driverhost::kDeviceIdField, m_cameraId); + event.insert(driverhost::kDeviceKindField, driverhost::kCapabilityCamera); + event.insert(driverhost::kCapabilitiesField, + QJsonArray{driverhost::kCapabilityCamera, + driverhost::kCapabilityProperties}); + emit eventReady(event); + } + + // Replay encoded cfg properties into the DriverHost MMCore instance + bool MicroManagerDriverRuntime::applyProperties(const QStringList& encodedProperties, + const std::string& label, + const QString& propertyKind, + QString* errorMessage) + { + for (const QString& encodedProperty : encodedProperties) + { + if (encodedProperty.isEmpty()) + { + continue; + } + + QJsonParseError parseError; + const QJsonDocument doc = QJsonDocument::fromJson(encodedProperty.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError || !doc.isObject()) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Invalid %1 payload for '%2'") + .arg(propertyKind, m_cameraId); + } + return false; + } + + const QJsonObject property = doc.object(); + const QString propertyName = property.value(QStringLiteral("name")).toString().trimmed(); + const QString propertyValue = property.value(QStringLiteral("value")).toString(); + if (propertyName.isEmpty()) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Missing %1 name for '%2'") + .arg(propertyKind, m_cameraId); + } + return false; + } + + try + { + m_mmcore->setProperty(label.c_str(), + propertyName.toStdString().c_str(), + propertyValue.toStdString().c_str()); + } + catch (const CMMError& mmError) + { + if (errorMessage) + { + *errorMessage = QString("Failed to apply %1 '%2': %3") + .arg(propertyKind, propertyName, QString::fromStdString(mmError.getMsg())); + } + return false; + } + } + + return true; + } + + // Dispatch one control request and emit a matching response + void MicroManagerDriverRuntime::handleRequest(quint64 connectionId, + quint64 requestId, + const QString& type, + const QJsonObject& message) + { + if (requestId == 0 || type.isEmpty()) + { + return; + } + + if (!m_mmcore && type != driverhost::kCommandShutdown) + { + emit responseReady(connectionId, + makeErrorResponse(type, + requestId, + QStringLiteral("DriverHost runtime not initialized"))); + return; + } + + if (type == driverhost::kCommandDescribe) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(driverhost::kProviderIdField, m_providerId); + response.insert(driverhost::kDeviceIdField, m_cameraId); + response.insert(driverhost::kDeviceKindField, driverhost::kCapabilityCamera); + response.insert(driverhost::kCapabilitiesField, + QJsonArray{driverhost::kCapabilityCamera, + driverhost::kCapabilityProperties}); + emit responseReady(connectionId, response); + return; + } + + if (type == driverhost::kCommandStartPreview) + { + QString error; + const bool ok = startPreviewInternal(&error); + emit responseReady(connectionId, + ok + ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + if (type == driverhost::kCommandStopPreview) + { + QString error; + const bool ok = stopPreviewInternal(&error); + emit responseReady(connectionId, + ok + ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + if (type == driverhost::kCommandSetFrameDeliveryMode) + { + const QString mode = message.value(QStringLiteral("mode")).toString(); + if (mode == driverhost::kFrameDeliveryModePreviewLatest) + { + m_frameDeliveryMode = FrameDeliveryMode::PreviewLatest; + m_deliveryTimer.invalidate(); + } + else if (mode == driverhost::kFrameDeliveryModeLatestOnly) + { + m_frameDeliveryMode = FrameDeliveryMode::LatestOnly; + } + else if (mode == driverhost::kFrameDeliveryModeAllFrames) + { + m_frameDeliveryMode = FrameDeliveryMode::AllFrames; + m_pendingFrameAdvance = 0; + m_deliveryTimer.invalidate(); + } + else + { + emit responseReady(connectionId, + makeErrorResponse(type, + requestId, + QStringLiteral("Unknown frame delivery mode"))); + return; + } + + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("mode"), mode); + emit responseReady(connectionId, response); + return; + } + + if (type == driverhost::kCommandGetExposure) + { + try + { + m_exposureMs = m_mmcore->getExposure(); + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("exposureMs"), m_exposureMs); + emit responseReady(connectionId, response); + } + catch (const CMMError& mmError) + { + emit responseReady( + connectionId, + makeErrorResponse(type, + requestId, + QString::fromStdString(mmError.getMsg()))); + } + return; + } + + if (type == driverhost::kCommandSetExposure) + { + const double exposureMs = message.value(QStringLiteral("value")).toDouble(-1.0); + bool ok = exposureMs > 0.0; + QString error; + if (!ok) + { + error = QStringLiteral("Invalid exposure value"); + } + else + { + try + { + m_mmcore->setExposure(exposureMs); + m_exposureMs = m_mmcore->getExposure(); + } + catch (const CMMError& mmError) + { + ok = false; + error = QString::fromStdString(mmError.getMsg()); + } + } + + if (ok) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("exposureMs"), m_exposureMs); + emit responseReady(connectionId, response); + } + else + { + emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); + } + return; + } + + if (type == driverhost::kCommandListProperties) + { + QJsonArray properties; + QString error; + bool ok = true; + try + { + const auto names = + m_mmcore->getDevicePropertyNames(m_cameraId.toStdString().c_str()); + for (const auto& name : names) + { + properties.append(QString::fromStdString(name)); + } + } + catch (const CMMError& mmError) + { + ok = false; + error = QString::fromStdString(mmError.getMsg()); + } + + if (ok) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("properties"), properties); + emit responseReady(connectionId, response); + } + else + { + emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); + } + return; + } + + if (type == driverhost::kCommandGetProperty) + { + const QString name = message.value(QStringLiteral("name")).toString(); + const bool fromCache = message.value(QStringLiteral("fromCache")).toBool(false); + const std::string camera = m_cameraId.toStdString(); + const std::string property = name.toStdString(); + QString value; + QString propertyType = QStringLiteral("Unknown"); + bool readOnly = true; + bool preInit = false; + QJsonArray allowedValues; + bool hasLimits = false; + double lowerLimit = 0.0; + double upperLimit = 0.0; + QString error; + bool ok = true; + + try + { + value = QString::fromStdString( + fromCache + ? m_mmcore->getPropertyFromCache(camera.c_str(), property.c_str()) + : m_mmcore->getProperty(camera.c_str(), property.c_str())); + + try + { + switch (m_mmcore->getPropertyType(camera.c_str(), property.c_str())) + { + case MM::String: + propertyType = QStringLiteral("String"); + break; + case MM::Float: + propertyType = QStringLiteral("Float"); + break; + case MM::Integer: + propertyType = QStringLiteral("Integer"); + break; + default: + propertyType = QStringLiteral("Unknown"); + break; + } + } + catch (const CMMError&) + { + } + + try + { + preInit = m_mmcore->isPropertyPreInit(camera.c_str(), property.c_str()); + } + catch (const CMMError&) + { + } + + try + { + readOnly = m_mmcore->isPropertyReadOnly(camera.c_str(), property.c_str()); + } + catch (const CMMError&) + { + } + + try + { + const auto values = + m_mmcore->getAllowedPropertyValues(camera.c_str(), property.c_str()); + for (const auto& allowedValue : values) + { + allowedValues.append(QString::fromStdString(allowedValue)); + } + } + catch (const CMMError&) + { + } + + try + { + hasLimits = m_mmcore->hasPropertyLimits(camera.c_str(), property.c_str()); + if (hasLimits) + { + lowerLimit = m_mmcore->getPropertyLowerLimit(camera.c_str(), property.c_str()); + upperLimit = m_mmcore->getPropertyUpperLimit(camera.c_str(), property.c_str()); + } + } + catch (const CMMError&) + { + hasLimits = false; + } + } + catch (const CMMError& mmError) + { + ok = false; + error = QString::fromStdString(mmError.getMsg()); + } + + if (ok) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("value"), value); + response.insert(QStringLiteral("propertyType"), propertyType); + response.insert(QStringLiteral("readOnly"), readOnly); + response.insert(QStringLiteral("preInit"), preInit); + response.insert(QStringLiteral("allowedValues"), allowedValues); + response.insert(QStringLiteral("hasLimits"), hasLimits); + response.insert(QStringLiteral("lowerLimit"), lowerLimit); + response.insert(QStringLiteral("upperLimit"), upperLimit); + emit responseReady(connectionId, response); + } + else + { + emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); + } + return; + } + + if (type == driverhost::kCommandSetProperty) + { + const QString name = message.value(QStringLiteral("name")).toString(); + const QString value = message.value(QStringLiteral("value")).toString(); + QString error; + bool ok = true; + try + { + m_mmcore->setProperty(m_cameraId.toStdString().c_str(), + name.toStdString().c_str(), + value.toStdString().c_str()); + m_mmcore->waitForDevice(m_cameraId.toStdString().c_str()); + } + catch (const CMMError& mmError) + { + ok = false; + error = QString::fromStdString(mmError.getMsg()); + } + + emit responseReady(connectionId, + ok + ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + if (type == driverhost::kCommandSetRoi) + { + const int x = message.value(QStringLiteral("x")).toInt(0); + const int y = message.value(QStringLiteral("y")).toInt(0); + const int width = message.value(QStringLiteral("width")).toInt(0); + const int height = message.value(QStringLiteral("height")).toInt(0); + QString error; + bool ok = true; + try + { + m_mmcore->setROI(m_cameraId.toStdString().c_str(), x, y, width, height); + m_mmcore->waitForDevice(m_cameraId.toStdString().c_str()); + refreshSourceRoi(); + } + catch (const CMMError& mmError) + { + ok = false; + error = QString::fromStdString(mmError.getMsg()); + } + + emit responseReady(connectionId, + ok + ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + if (type == driverhost::kCommandClearRoi) + { + QString error; + bool ok = true; + try + { + m_mmcore->setCameraDevice(m_cameraId.toStdString().c_str()); + m_mmcore->clearROI(); + m_mmcore->waitForDevice(m_cameraId.toStdString().c_str()); + refreshSourceRoi(); + } + catch (const CMMError& mmError) + { + ok = false; + error = QString::fromStdString(mmError.getMsg()); + } + + emit responseReady(connectionId, + ok + ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + if (type == driverhost::kCommandGetRoi) + { + int x = 0; + int y = 0; + int width = 0; + int height = 0; + QString error; + bool ok = true; + try + { + m_mmcore->getROI(m_cameraId.toStdString().c_str(), x, y, width, height); + } + catch (const CMMError& mmError) + { + ok = false; + error = QString::fromStdString(mmError.getMsg()); + } + + if (ok) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("x"), x); + response.insert(QStringLiteral("y"), y); + response.insert(QStringLiteral("width"), width); + response.insert(QStringLiteral("height"), height); + emit responseReady(connectionId, response); + } + else + { + emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); + } + return; + } + + if (type == driverhost::kCommandCaptureEvent) + { + QString error; + quint64 frameIndex = 0; + const bool ok = captureEventFrameInternal(frameIndex, &error); + if (ok) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("frameIndex"), driverhost::encodeUInt64(frameIndex)); + emit responseReady(connectionId, response); + } + else + { + emit responseReady(connectionId, makeErrorResponse(type, requestId, error)); + } + return; + } + + if (type == driverhost::kCommandShutdown) + { + stopPreviewInternal(nullptr); + setState(State::ShuttingDown); + emit responseReady(connectionId, makeResponse(type, requestId, true)); + QTimer::singleShot(50, this, [this]() { emit shutdownRequested(); }); + return; + } + + emit responseReady(connectionId, + makeErrorResponse(type, + requestId, + QStringLiteral("Unknown control command"))); + } + + // Stop preview before the runtime thread exits + void MicroManagerDriverRuntime::stopForExit() + { + stopPreviewInternal(nullptr); + setState(State::ShuttingDown); + } + + // Check whether the runtime is currently previewing + bool MicroManagerDriverRuntime::previewRunning() const + { + return m_state == State::Previewing; + } + + // Update runtime state and publish state changes + void MicroManagerDriverRuntime::setState(State state, const QString& error) + { + const bool changed = (m_state != state); + m_state = state; + if (!error.isEmpty()) + { + m_lastError = error; + } + if (changed && (state == State::Previewing + || state == State::Idle + || state == State::Error + || state == State::ShuttingDown)) + { + emitPreviewStateEvent(); + } + if (!error.isEmpty()) + { + emitDriverHostErrorEvent(error); + } + } + + // Build a protocol response envelope + QJsonObject MicroManagerDriverRuntime::makeResponse(const QString& type, quint64 requestId, bool ok) const + { + QJsonObject response = + driverhost::makeEnvelope(driverhost::kMessageKindResponse, type, requestId); + response.insert(QStringLiteral("ok"), ok); + return response; + } + + // Build a protocol error response envelope + QJsonObject MicroManagerDriverRuntime::makeErrorResponse(const QString& type, + quint64 requestId, + const QString& error) const + { + QJsonObject response = makeResponse(type, requestId, false); + response.insert(QStringLiteral("error"), error); + return response; + } + + // Build a protocol event envelope + QJsonObject MicroManagerDriverRuntime::makeEvent(const QString& type) const + { + return driverhost::makeEnvelope(driverhost::kMessageKindEvent, type); + } + + // Publish current preview state to clients + void MicroManagerDriverRuntime::emitPreviewStateEvent() + { + QJsonObject event = makeEvent(driverhost::kEventPreviewState); + event.insert(QStringLiteral("running"), previewRunning()); + emit eventReady(event); + } + + // Publish a DriverHost error event to clients + void MicroManagerDriverRuntime::emitDriverHostErrorEvent(const QString& error) + { + QJsonObject event = makeEvent(driverhost::kEventDriverHostError); + event.insert(QStringLiteral("error"), error); + emit eventReady(event); + } + + // Publish the newest shared memory frame index + void MicroManagerDriverRuntime::emitFrameAvailableEvent(quint64 frameIndex) + { + QJsonObject event = makeEvent(driverhost::kEventFrameAvailable); + event.insert(QStringLiteral("frameIndex"), driverhost::encodeUInt64(frameIndex)); + emit eventReady(event); + } + + // Start continuous acquisition and polling + bool MicroManagerDriverRuntime::startPreviewInternal(QString* errorMessage) + { + if (!m_mmcore) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + if (m_state == State::ShuttingDown || m_state == State::Error) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("DriverHost is not in a runnable state"); + } + return false; + } + if (previewRunning()) + { + return true; + } + + try + { + while (m_mmcore->getRemainingImageCount() > 0) + { + m_mmcore->popNextImage(); + } + refreshSourceRoi(); + if (!ensureFrameLayout(m_mmcore->getImageWidth(), + m_mmcore->getImageHeight(), + m_mmcore->getBytesPerPixel())) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Unsupported frame format"); + } + return false; + } + m_mmcore->startContinuousSequenceAcquisition(0.0); + m_observedFrameIntervalMs = 0.0; + m_pendingFrameAdvance = 0; + m_frameIntervalTimer.restart(); + m_deliveryTimer.invalidate(); + m_timer->setInterval(pollingIntervalFor(m_exposureMs)); + if (m_timer && !m_timer->isActive()) + { + m_timer->start(); + } + setState(State::Previewing); + return true; + } + catch (const CMMError& error) + { + if (errorMessage) + { + *errorMessage = QString::fromStdString(error.getMsg()); + } + return false; + } + } + + // Stop continuous acquisition and polling + bool MicroManagerDriverRuntime::stopPreviewInternal(QString* errorMessage) + { + if (!m_mmcore) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + + try + { + if (m_mmcore->isSequenceRunning()) + { + m_mmcore->stopSequenceAcquisition(); + } + if (m_timer && m_timer->isActive()) + { + m_timer->stop(); + } + m_frameIntervalTimer.invalidate(); + m_deliveryTimer.invalidate(); + m_observedFrameIntervalMs = 0.0; + m_pendingFrameAdvance = 0; + if (m_state != State::ShuttingDown && m_state != State::Error) + { + setState(State::Idle); + } + return true; + } + catch (const CMMError& error) + { + if (errorMessage) + { + *errorMessage = QString::fromStdString(error.getMsg()); + } + return false; + } + } + + // Capture one frame for recording or API requests + bool MicroManagerDriverRuntime::captureEventFrameInternal(quint64& frameIndex, QString* errorMessage) + { + frameIndex = 0; + try + { + const void* pixels = nullptr; + if (previewRunning()) + { + QElapsedTimer waitTimer; + waitTimer.start(); + while (m_mmcore->getRemainingImageCount() <= 0 && waitTimer.elapsed() < 2000) + { + QThread::msleep(1); + } + if (m_mmcore->getRemainingImageCount() > 0) + { + pixels = m_mmcore->popNextImage(); + } + else if (errorMessage) + { + *errorMessage = QStringLiteral("No frame available from running sequence"); + } + } + else + { + m_mmcore->snapImage(); + pixels = m_mmcore->getImage(); + } + + if (!pixels) + { + if (errorMessage && errorMessage->isEmpty()) + { + *errorMessage = QStringLiteral("Empty image buffer"); + } + return false; + } + + const unsigned width = m_mmcore->getImageWidth(); + const unsigned height = m_mmcore->getImageHeight(); + const unsigned bytesPerPixel = m_mmcore->getBytesPerPixel(); + if (!ensureFrameLayout(width, height, bytesPerPixel)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Unsupported frame format"); + } + return false; + } + + const quint64 frameAdvance = m_pendingFrameAdvance + 1; + m_pendingFrameAdvance = 0; + if (!writeFrameToSharedMemory(pixels, frameAdvance, &frameIndex)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Shared memory unavailable"); + } + return false; + } + + if (m_frameDeliveryMode == FrameDeliveryMode::PreviewLatest) + { + m_deliveryTimer.restart(); + } + + emitFrameAvailableEvent(frameIndex); + return true; + } + catch (const CMMError& error) + { + if (errorMessage) + { + *errorMessage = QString::fromStdString(error.getMsg()); + } + return false; + } + } + + // Copy one camera frame into the shared memory ring buffer + bool MicroManagerDriverRuntime::writeFrameToSharedMemory(const void* pixels, + quint64 frameAdvance, + quint64* frameIndexOut) + { + if (!m_shm || !m_shm->isAttached()) + { + return false; + } + uchar* base = static_cast(m_shm->data()); + if (!base) + { + return false; + } + + const quint64 nextFrameIndex = m_frameIndex + (std::max)(quint64{1}, frameAdvance); + const int preferredSlotIndex = m_nextWriteSlot; + int slotIndex = -1; + uchar* ptr = nullptr; + for (int offset = 0; offset < kSharedFrameNumSlots; ++offset) + { + const int candidate = (preferredSlotIndex + offset) % kSharedFrameNumSlots; + uchar* candidatePtr = base + kSharedMemoryControlSize + + candidate * kSharedFrameSlotStride; + auto& stateValue = *reinterpret_cast(candidatePtr); + std::atomic_ref state(stateValue); + quint32 expected = state.load(std::memory_order_acquire); + while (expected == 0 || expected == 2) + { + if (state.compare_exchange_weak(expected, + 1, + std::memory_order_acq_rel, + std::memory_order_acquire)) + { + slotIndex = candidate; + ptr = candidatePtr; + break; + } + } + if (slotIndex >= 0) + { + break; + } + } + + if (slotIndex < 0 || !ptr) + { + m_frameIndex = nextFrameIndex; + if (frameIndexOut) + { + *frameIndexOut = m_frameIndex; + } + return false; + } + + auto* control = reinterpret_cast(base); + m_nextWriteSlot = (slotIndex + 1) % kSharedFrameNumSlots; + SharedFrameHeader header{}; + header.state = 1; + header.width = m_frameLayout.width; + header.height = m_frameLayout.height; + header.stride = m_frameLayout.stride; + header.pixelFormat = static_cast(m_frameLayout.format); + header.bitsPerSample = m_frameLayout.bitDepth; + header.channels = m_frameLayout.channels; + header.frameIndex = nextFrameIndex; + header.timestampNs = + static_cast(QDateTime::currentMSecsSinceEpoch()) * 1000000ull; + setSharedFrameSourceRoi(header, + m_sourceRoiX, + m_sourceRoiY, + m_sourceRoiWidth, + m_sourceRoiHeight); + memcpy(ptr + sizeof(header.state), + reinterpret_cast(&header) + sizeof(header.state), + sizeof(header) - sizeof(header.state)); + + uchar* dst = ptr + kSharedFrameHeaderSize; + memcpy(dst, pixels, static_cast(m_frameLayout.byteCount)); + + auto& stateValue = *reinterpret_cast(ptr); + std::atomic_ref(stateValue).store(2, std::memory_order_release); + std::atomic_ref(control->latestSlotIndex) + .store(static_cast(slotIndex), std::memory_order_release); + m_frameIndex = nextFrameIndex; + if (frameIndexOut) + { + *frameIndexOut = m_frameIndex; + } + + return true; + } + + // Drain camera frames into shared memory and publish the newest index + void MicroManagerDriverRuntime::pollAndWrite() + { + if (!m_mmcore) + { + return; + } + + try + { + long remaining = m_mmcore->getRemainingImageCount(); + + if (remaining <= 0) + { + return; + } + + quint64 newestFrameIndex = 0; + quint64 acquiredFrameCount = 0; + if (m_frameDeliveryMode != FrameDeliveryMode::AllFrames) + { + const bool previewRateLimited = + m_frameDeliveryMode == FrameDeliveryMode::PreviewLatest; + const bool publishFrame = !previewRateLimited + || !m_deliveryTimer.isValid() + || m_deliveryTimer.elapsed() >= kPreviewFrameDeliveryIntervalMs; + while (remaining-- > 0) + { + const void* pixels = m_mmcore->popNextImage(); + if (!pixels) + { + break; + } + ++acquiredFrameCount; + ++m_pendingFrameAdvance; + if (remaining > 0) + { + continue; + } + if (publishFrame && m_frameLayoutValid) + { + quint64 writtenFrameIndex = 0; + const bool written = writeFrameToSharedMemory( + pixels, + m_pendingFrameAdvance, + &writtenFrameIndex); + m_pendingFrameAdvance = 0; + if (previewRateLimited) + { + m_deliveryTimer.restart(); + } + if (written) + { + newestFrameIndex = writtenFrameIndex; + } + } + } + } + else + { + while (remaining-- > 0) + { + const void* pixels = m_mmcore->popNextImage(); + if (!pixels) + { + break; + } + ++acquiredFrameCount; + + if (!m_frameLayoutValid) + { + break; + } + + quint64 writtenFrameIndex = 0; + if (!writeFrameToSharedMemory(pixels, 1, &writtenFrameIndex)) + { + break; + } + newestFrameIndex = writtenFrameIndex; + } + } + + if (newestFrameIndex != 0) + { + emitFrameAvailableEvent(newestFrameIndex); + } + updatePollingInterval(acquiredFrameCount); + } + catch (const CMMError& error) + { + const QString message = + QStringLiteral("DriverHost capture error: %1") + .arg(QString::fromStdString(error.getMsg())); + setState(State::Error, message); + } + } + + // Adapts MMCore polling to the observed camera frame interval + void MicroManagerDriverRuntime::updatePollingInterval(quint64 frameCount) + { + if (frameCount == 0 || !m_frameIntervalTimer.isValid()) + { + return; + } + + const double elapsedMs = static_cast(m_frameIntervalTimer.nsecsElapsed()) / 1000000.0; + m_frameIntervalTimer.restart(); + const double measuredIntervalMs = elapsedMs / static_cast(frameCount); + if (!std::isfinite(measuredIntervalMs) || measuredIntervalMs <= 0.0) + { + return; + } + + m_observedFrameIntervalMs = m_observedFrameIntervalMs > 0.0 + ? 0.75 * m_observedFrameIntervalMs + + 0.25 * measuredIntervalMs + : measuredIntervalMs; + const int intervalMs = pollingIntervalFor(m_observedFrameIntervalMs); + if (m_timer->interval() != intervalMs) + { + m_timer->setInterval(intervalMs); + } + } + + // Validate and cache the current frame memory layout + bool MicroManagerDriverRuntime::ensureFrameLayout(unsigned width, unsigned height, unsigned bytesPerPixel) + { + const auto logOversized = [this](quint64 byteCount) + { + if (!m_loggedOversizedFrame) + { + qWarning().noquote() + << QString("[DriverHost %1] Frame payload (%2 bytes) exceeds shared memory slot capacity (%3 bytes)") + .arg(m_cameraId) + .arg(static_cast(byteCount)) + .arg(kSharedFrameMaxBytes); + m_loggedOversizedFrame = true; + } + }; + + if (bytesPerPixel != 1 && bytesPerPixel != 2) + { + if (!m_loggedUnsupportedFormat) + { + qWarning().noquote() + << QString("[DriverHost %1] Unsupported bytes-per-pixel (%2). Only Mono8/Mono16 are supported.") + .arg(m_cameraId) + .arg(bytesPerPixel); + m_loggedUnsupportedFormat = true; + } + return false; + } + m_loggedUnsupportedFormat = false; + + const bool geometryChanged = + !m_frameLayoutValid + || m_frameLayout.width != width + || m_frameLayout.height != height + || m_frameLayout.bytesPerPixel != bytesPerPixel; + + if (!geometryChanged) + { + if (m_frameLayout.byteCount == 0 + || m_frameLayout.byteCount > static_cast(kSharedFrameMaxBytes)) + { + logOversized(m_frameLayout.byteCount); + return false; + } + if (m_sourceRoiWidth <= 0 || m_sourceRoiHeight <= 0) + { + refreshSourceRoi(); + } + m_loggedOversizedFrame = false; + return true; + } + + m_frameLayoutValid = false; + + FrameLayout updated; + updated.width = width; + updated.height = height; + updated.bytesPerPixel = bytesPerPixel; + updated.format = + (bytesPerPixel == 2) ? SharedPixelFormat::Mono16 : SharedPixelFormat::Mono8; + updated.channels = 1; + const quint64 stride = static_cast(width) * bytesPerPixel; + if (stride == 0 || stride > (std::numeric_limits::max)()) + { + logOversized(stride); + return false; + } + updated.stride = static_cast(stride); + updated.byteCount = computeMaxFrameBytes(width, height, updated.format); + + if (updated.byteCount == 0 + || updated.byteCount > static_cast(kSharedFrameMaxBytes)) + { + logOversized(updated.byteCount); + return false; + } + + quint16 bitDepth = + (updated.format == SharedPixelFormat::Mono16) ? 16 : 8; + try + { + bitDepth = normalizedSharedBitDepth( + updated.format, + static_cast(m_mmcore->getImageBitDepth())); + } + catch (const CMMError&) + { + bitDepth = + (updated.format == SharedPixelFormat::Mono16) ? 16 : 8; + } + updated.bitDepth = normalizedSharedBitDepth(updated.format, bitDepth); + + m_frameLayout = updated; + m_frameLayoutValid = true; + refreshSourceRoi(); + m_loggedOversizedFrame = false; + return true; + } + + // Read the active camera ROI used by following frame metadata + void MicroManagerDriverRuntime::refreshSourceRoi() + { + int x = 0; + int y = 0; + int width = 0; + int height = 0; + try + { + m_mmcore->getROI(m_cameraId.toStdString().c_str(), x, y, width, height); + } + catch (const CMMError&) + { + width = static_cast(m_frameLayout.width); + height = static_cast(m_frameLayout.height); + } + + if (width <= 0 || height <= 0) + { + width = static_cast(m_frameLayout.width); + height = static_cast(m_frameLayout.height); + } + + m_sourceRoiX = x; + m_sourceRoiY = y; + m_sourceRoiWidth = width; + m_sourceRoiHeight = height; + } + + class ProviderDriverRuntime final : public DriverHostRuntime + { + public: + ProviderDriverRuntime(QString pluginPath, + QString providerId, + QString hostKey, + QJsonObject options, + QObject* parent = nullptr) + : DriverHostRuntime(parent) + , m_pluginPath(std::move(pluginPath)) + , m_providerId(std::move(providerId)) + , m_hostKey(std::move(hostKey)) + , m_options(std::move(options)) + { + } + + ~ProviderDriverRuntime() override + { + stopForExit(); + } + + bool initializeRuntime() override; + QString lastError() const override { return m_lastError; } + void publishHello() override; + void handleRequest(quint64 connectionId, + quint64 requestId, + const QString& type, + const QJsonObject& message) override; + void stopForExit() override; + + private: + enum class FrameDeliveryMode + { + PreviewLatest, + LatestOnly, + AllFrames + }; + + struct CameraTransport + { + QString deviceId; + QString shmKey; + std::unique_ptr shm; + std::atomic deliveryMode{FrameDeliveryMode::PreviewLatest}; + QMutex frameMutex; + QMutex writeMutex; + ImageFrame latestFrame; + bool latestDispatchQueued{false}; + ImageFrame previewFrame; + QTimer* previewTimer{nullptr}; + QElapsedTimer deliveryTimer; + quint64 frameIndex{0}; + int nextWriteSlot{0}; + bool previewRunning{false}; + }; + + QJsonObject makeResponse(const QString& type, quint64 requestId, bool ok) const; + QJsonObject makeErrorResponse(const QString& type, + quint64 requestId, + const QString& error) const; + QJsonObject makeEvent(const QString& type) const; + QJsonArray capabilities(const HardwareDeviceDescriptor& device) const; + bool supportsCapability(const HardwareDeviceDescriptor& device, + const QString& capability) const; + QString deviceKindName(HardwareDeviceKind kind) const; + QJsonObject deviceDescription(const HardwareDeviceDescriptor& device) const; + const HardwareDeviceDescriptor* requestedDevice(const QJsonObject& message) const; + std::shared_ptr cameraTransport(const QString& deviceId) const; + bool createSharedMemory(CameraTransport& transport); + void enqueueFrame(const ImageFrame& frame); + void dispatchLatestFrame(const QString& deviceId); + void publishAllFrame(const QString& deviceId, const ImageFrame& frame); + void publishFrame(const QString& deviceId, const ImageFrame& frame); + void flushPreviewFrame(const QString& deviceId); + bool writeFrame(CameraTransport& transport, + const ImageFrame& frame, + quint64* frameIndexOut = nullptr); + void emitPreviewStateEvent(const QString& deviceId, bool running); + + QString m_pluginPath; + QString m_providerId; + QString m_hostKey; + QJsonObject m_options; + QString m_lastError; + HardwareProviderDescriptor m_descriptor; + QList m_devices; + QHash m_devicesById; + QHash> m_cameraTransports; + mutable QReadWriteLock m_cameraTransportsLock; + std::unique_ptr m_pluginLoader; + HardwareProviderPtr m_provider; + CameraProvider* m_camera{nullptr}; + DevicePropertyProvider* m_properties{nullptr}; + StageProvider* m_stage{nullptr}; + ShutterProvider* m_shutter{nullptr}; + StateProvider* m_state{nullptr}; + ConfigurationProvider* m_configuration{nullptr}; + std::atomic_bool m_acceptFrames{false}; + }; + + bool ProviderDriverRuntime::initializeRuntime() + { + m_lastError.clear(); + m_pluginLoader = std::make_unique(m_pluginPath); + QObject* const instance = m_pluginLoader->instance(); + if (!instance) + { + m_lastError = m_pluginLoader->errorString(); + return false; + } + + auto* const factory = qobject_cast(instance); + if (!factory) + { + m_lastError = QStringLiteral("Module does not implement DriverHostProviderPlugin"); + return false; + } + if (factory->providerId() != m_providerId) + { + m_lastError = QStringLiteral("Provider module identity mismatch"); + return false; + } + + m_options.insert(QStringLiteral("providerId"), m_providerId); + m_provider = factory->createProvider(m_options, &m_lastError); + if (!m_provider) + { + if (m_lastError.isEmpty()) + { + m_lastError = QStringLiteral("Provider module returned no provider"); + } + return false; + } + m_descriptor = m_provider->descriptor(); + if (m_descriptor.id != m_providerId + || m_descriptor.id != m_descriptor.id.trimmed()) + { + m_lastError = QStringLiteral("Provider descriptor identity mismatch"); + return false; + } + + HardwareProvider* const provider = m_provider.get(); + m_camera = dynamic_cast(provider); + m_properties = dynamic_cast(provider); + m_stage = dynamic_cast(provider); + m_shutter = dynamic_cast(provider); + m_state = dynamic_cast(provider); + m_configuration = dynamic_cast(provider); + + QSet logicalIds; + int cameraIndex = 0; + for (HardwareDeviceDescriptor device : m_provider->devices()) + { + const QString logicalId = device.logicalId.trimmed(); + const bool capabilityAvailable = + (device.kind != HardwareDeviceKind::Camera || m_camera) + && ((device.kind != HardwareDeviceKind::XYStage + && device.kind != HardwareDeviceKind::ZStage) + || m_stage) + && (device.kind != HardwareDeviceKind::Shutter || m_shutter) + && (device.kind != HardwareDeviceKind::State || m_state); + if (logicalId.isEmpty() + || device.logicalId != logicalId + || device.providerId != m_providerId + || logicalIds.contains(logicalId) + || !capabilityAvailable) + { + m_lastError = QStringLiteral("Provider returned an invalid device catalog"); + return false; + } + logicalIds.insert(logicalId); + device.endpoint = HardwareEndpointKind::DriverHost; + m_devices.append(device); + m_devicesById.insert(logicalId, device); + + if (device.kind == HardwareDeviceKind::Camera) + { + auto transport = std::make_shared(); + transport->deviceId = logicalId; + transport->shmKey = driverhost::sharedMemoryKey(m_hostKey, cameraIndex++); + transport->previewTimer = new QTimer(this); + transport->previewTimer->setSingleShot(true); + transport->previewTimer->setTimerType(Qt::PreciseTimer); + connect(transport->previewTimer, &QTimer::timeout, this, + [this, logicalId]() { flushPreviewFrame(logicalId); }); + if (!createSharedMemory(*transport)) + { + return false; + } + m_cameraTransports.insert(logicalId, std::move(transport)); + } + } + + if (m_camera && !m_cameraTransports.isEmpty()) + { + m_acceptFrames.store(true, std::memory_order_release); + m_camera->setFrameSink([this](const ImageFrame& frame) { enqueueFrame(frame); }); + } + return true; + } + + QJsonObject ProviderDriverRuntime::makeResponse(const QString& type, + quint64 requestId, + bool ok) const + { + QJsonObject response = driverhost::makeEnvelope(driverhost::kMessageKindResponse, + type, + requestId); + response.insert(QStringLiteral("ok"), ok); + return response; + } + + QJsonObject ProviderDriverRuntime::makeErrorResponse(const QString& type, + quint64 requestId, + const QString& error) const + { + QJsonObject response = makeResponse(type, requestId, false); + response.insert(QStringLiteral("error"), error); + return response; + } + + QJsonObject ProviderDriverRuntime::makeEvent(const QString& type) const + { + QJsonObject event = driverhost::makeEnvelope(driverhost::kMessageKindEvent, type); + event.insert(driverhost::kProviderIdField, m_providerId); + return event; + } + + QJsonArray ProviderDriverRuntime::capabilities( + const HardwareDeviceDescriptor& device) const + { + QJsonArray result; + if (m_camera && device.kind == HardwareDeviceKind::Camera) + { + result.append(driverhost::kCapabilityCamera); + } + if (m_properties) result.append(driverhost::kCapabilityProperties); + if (m_stage + && (device.kind == HardwareDeviceKind::XYStage + || device.kind == HardwareDeviceKind::ZStage)) + { + result.append(driverhost::kCapabilityStage); + } + if (m_shutter && device.kind == HardwareDeviceKind::Shutter) + { + result.append(driverhost::kCapabilityShutter); + } + if (m_state && device.kind == HardwareDeviceKind::State) + { + result.append(driverhost::kCapabilityState); + } + if (m_configuration) result.append(driverhost::kCapabilityConfiguration); + return result; + } + + bool ProviderDriverRuntime::supportsCapability( + const HardwareDeviceDescriptor& device, + const QString& capability) const + { + return capabilities(device).contains(QJsonValue(capability)); + } + + QString ProviderDriverRuntime::deviceKindName(HardwareDeviceKind kind) const + { + switch (kind) + { + case HardwareDeviceKind::Camera: return QStringLiteral("Camera"); + case HardwareDeviceKind::XYStage: return QStringLiteral("XYStage"); + case HardwareDeviceKind::ZStage: return QStringLiteral("ZStage"); + case HardwareDeviceKind::Shutter: return QStringLiteral("Shutter"); + case HardwareDeviceKind::State: return QStringLiteral("State"); + case HardwareDeviceKind::Hub: return QStringLiteral("Hub"); + case HardwareDeviceKind::Serial: return QStringLiteral("Serial"); + case HardwareDeviceKind::Generic: return QStringLiteral("Generic"); + case HardwareDeviceKind::AutoFocus: return QStringLiteral("AutoFocus"); + case HardwareDeviceKind::ImageProcessor: return QStringLiteral("ImageProcessor"); + case HardwareDeviceKind::SignalIO: return QStringLiteral("SignalIO"); + case HardwareDeviceKind::Magnifier: return QStringLiteral("Magnifier"); + case HardwareDeviceKind::SLM: return QStringLiteral("SLM"); + case HardwareDeviceKind::Galvo: return QStringLiteral("Galvo"); + case HardwareDeviceKind::PressurePump: return QStringLiteral("PressurePump"); + case HardwareDeviceKind::VolumetricPump: return QStringLiteral("VolumetricPump"); + case HardwareDeviceKind::Unknown: break; + } + return QStringLiteral("Unknown"); + } + + QJsonObject ProviderDriverRuntime::deviceDescription( + const HardwareDeviceDescriptor& device) const + { + QJsonObject object; + object.insert(driverhost::kDeviceIdField, device.logicalId); + object.insert(driverhost::kProviderDeviceIdField, device.providerDeviceId); + object.insert(driverhost::kHardwareIdField, device.hardwareId); + object.insert(driverhost::kDeviceNameField, device.name); + object.insert(driverhost::kDeviceKindField, deviceKindName(device.kind)); + object.insert(driverhost::kDeviceStateField, static_cast(device.state)); + object.insert(driverhost::kDevicePropertiesField, + QJsonObject::fromVariantMap(device.properties)); + object.insert(driverhost::kCapabilitiesField, capabilities(device)); + if (const auto transport = cameraTransport(device.logicalId)) + { + object.insert(driverhost::kSharedMemoryKeyField, transport->shmKey); + } + return object; + } + + const HardwareDeviceDescriptor* ProviderDriverRuntime::requestedDevice( + const QJsonObject& message) const + { + const QString deviceId = message.value(driverhost::kDeviceIdField) + .toString().trimmed(); + const auto it = m_devicesById.constFind(deviceId); + return it == m_devicesById.cend() ? nullptr : &it.value(); + } + + std::shared_ptr + ProviderDriverRuntime::cameraTransport(const QString& deviceId) const + { + QReadLocker locker(&m_cameraTransportsLock); + return m_cameraTransports.value(deviceId.trimmed()); + } + + void ProviderDriverRuntime::publishHello() + { + QJsonObject event = makeEvent(driverhost::kEventHello); + event.insert(driverhost::kProviderIdField, m_providerId); + emit eventReady(event); + } + + bool ProviderDriverRuntime::createSharedMemory(CameraTransport& transport) + { + transport.shm = std::make_unique(); + transport.shm->setNativeKey(transport.shmKey); + const int totalBytes = + kSharedMemoryControlSize + kSharedFrameNumSlots * kSharedFrameSlotStride; + if (!transport.shm->create(totalBytes)) + { + if (transport.shm->attach()) + { + transport.shm->detach(); + } + if (!transport.shm->create(totalBytes)) + { + m_lastError = QStringLiteral("Cannot create shared memory '%1'") + .arg(transport.shmKey); + return false; + } + } + if (!transport.shm->lock()) + { + m_lastError = QStringLiteral("Cannot initialize shared memory '%1'") + .arg(transport.shmKey); + return false; + } + auto* const base = static_cast(transport.shm->data()); + const SharedMemoryControl control{}; + memcpy(base, &control, sizeof(control)); + for (int index = 0; index < kSharedFrameNumSlots; ++index) + { + const SharedFrameHeader header{}; + memcpy(base + kSharedMemoryControlSize + index * kSharedFrameSlotStride, + &header, + sizeof(header)); + } + transport.shm->unlock(); + return true; + } + + void ProviderDriverRuntime::enqueueFrame(const ImageFrame& frame) + { + if (!m_acceptFrames.load(std::memory_order_acquire)) + { + return; + } + const QString deviceId = frame.cameraId.trimmed(); + const auto transport = cameraTransport(deviceId); + if (!transport) + { + return; + } + if (transport->deliveryMode.load(std::memory_order_relaxed) + == FrameDeliveryMode::AllFrames) + { + publishAllFrame(deviceId, frame); + return; + } + + { + QMutexLocker locker(&transport->frameMutex); + transport->latestFrame = frame; + if (transport->latestDispatchQueued) + { + return; + } + transport->latestDispatchQueued = true; + } + QMetaObject::invokeMethod(this, + [this, deviceId]() { dispatchLatestFrame(deviceId); }, + Qt::QueuedConnection); + } + + void ProviderDriverRuntime::dispatchLatestFrame(const QString& deviceId) + { + const auto transport = cameraTransport(deviceId); + if (!transport) return; + ImageFrame frame; + { + QMutexLocker locker(&transport->frameMutex); + frame = transport->latestFrame; + transport->latestDispatchQueued = false; + } + publishFrame(deviceId, frame); + } + + void ProviderDriverRuntime::publishAllFrame(const QString& deviceId, + const ImageFrame& frame) + { + const auto transport = cameraTransport(deviceId); + if (!transport) return; + quint64 frameIndex = 0; + if (!writeFrame(*transport, frame, &frameIndex)) return; + + QJsonObject event = makeEvent(driverhost::kEventFrameAvailable); + event.insert(driverhost::kProviderIdField, m_providerId); + event.insert(driverhost::kDeviceIdField, deviceId); + event.insert(QStringLiteral("frameIndex"), driverhost::encodeUInt64(frameIndex)); + emit eventReady(event); + } + + void ProviderDriverRuntime::publishFrame(const QString& deviceId, + const ImageFrame& frame) + { + const auto transport = cameraTransport(deviceId); + if (!transport) return; + if (transport->deliveryMode.load(std::memory_order_relaxed) + == FrameDeliveryMode::PreviewLatest) + { + transport->previewFrame = frame; + const int remaining = transport->deliveryTimer.isValid() + ? kPreviewFrameDeliveryIntervalMs + - static_cast(transport->deliveryTimer.elapsed()) + : 0; + if (remaining > 0) + { + if (!transport->previewTimer->isActive()) + { + transport->previewTimer->start(remaining); + } + return; + } + } + + quint64 frameIndex = 0; + if (writeFrame(*transport, frame, &frameIndex)) + { + if (transport->deliveryMode.load(std::memory_order_relaxed) + == FrameDeliveryMode::PreviewLatest) + { + transport->deliveryTimer.restart(); + } + QJsonObject event = makeEvent(driverhost::kEventFrameAvailable); + event.insert(driverhost::kProviderIdField, m_providerId); + event.insert(driverhost::kDeviceIdField, deviceId); + event.insert(QStringLiteral("frameIndex"), driverhost::encodeUInt64(frameIndex)); + emit eventReady(event); + } + } + + void ProviderDriverRuntime::flushPreviewFrame(const QString& deviceId) + { + const auto transport = cameraTransport(deviceId); + if (!transport) return; + const ImageFrame frame = transport->previewFrame; + transport->previewFrame = {}; + quint64 frameIndex = 0; + if (writeFrame(*transport, frame, &frameIndex)) + { + transport->deliveryTimer.restart(); + QJsonObject event = makeEvent(driverhost::kEventFrameAvailable); + event.insert(driverhost::kProviderIdField, m_providerId); + event.insert(driverhost::kDeviceIdField, deviceId); + event.insert(QStringLiteral("frameIndex"), driverhost::encodeUInt64(frameIndex)); + emit eventReady(event); + } + } + + bool ProviderDriverRuntime::writeFrame(CameraTransport& transport, + const ImageFrame& frame, + quint64* frameIndexOut) + { + if (!transport.shm || !transport.shm->isAttached() || !frame.isValid() + || frame.cameraId.trimmed() != transport.deviceId + || frame.bytes.size() > kSharedFrameMaxBytes) + { + return false; + } + QMutexLocker writeLocker(&transport.writeMutex); + auto* const base = static_cast(transport.shm->data()); + if (!base) + { + return false; + } + + transport.frameIndex = (std::max)(transport.frameIndex + 1, frame.frameIndex); + const quint64 frameIndex = transport.frameIndex; + + int slotIndex = -1; + uchar* slot = nullptr; + for (int offset = 0; offset < kSharedFrameNumSlots; ++offset) + { + const int candidate = (transport.nextWriteSlot + offset) % kSharedFrameNumSlots; + uchar* const candidateSlot = base + kSharedMemoryControlSize + + candidate * kSharedFrameSlotStride; + auto& stateValue = *reinterpret_cast(candidateSlot); + std::atomic_ref state(stateValue); + quint32 expected = state.load(std::memory_order_acquire); + while (expected == 0 || expected == 2) + { + if (state.compare_exchange_weak(expected, + 1, + std::memory_order_acq_rel, + std::memory_order_acquire)) + { + slotIndex = candidate; + slot = candidateSlot; + break; + } + } + if (slot) + { + break; + } + } + if (!slot) + { + return false; + } + + transport.nextWriteSlot = (slotIndex + 1) % kSharedFrameNumSlots; + SharedFrameHeader header = frame.toSharedFrameHeader(); + header.state = 1; + header.frameIndex = frameIndex; + if (header.timestampNs == 0) + { + header.timestampNs = + static_cast(QDateTime::currentMSecsSinceEpoch()) * 1000000ull; + } + memcpy(slot + sizeof(header.state), + reinterpret_cast(&header) + sizeof(header.state), + sizeof(header) - sizeof(header.state)); + memcpy(slot + kSharedFrameHeaderSize, + frame.bytes.constData(), + static_cast(frame.bytes.size())); + + auto& stateValue = *reinterpret_cast(slot); + std::atomic_ref(stateValue).store(2, std::memory_order_release); + auto* const control = reinterpret_cast(base); + std::atomic_ref(control->latestSlotIndex) + .store(static_cast(slotIndex), std::memory_order_release); + if (frameIndexOut) + { + *frameIndexOut = frameIndex; + } + return true; + } + + void ProviderDriverRuntime::emitPreviewStateEvent(const QString& deviceId, bool running) + { + QJsonObject event = makeEvent(driverhost::kEventPreviewState); + event.insert(driverhost::kProviderIdField, m_providerId); + event.insert(driverhost::kDeviceIdField, deviceId); + event.insert(QStringLiteral("running"), running); + emit eventReady(event); + } + + void ProviderDriverRuntime::handleRequest(quint64 connectionId, + quint64 requestId, + const QString& type, + const QJsonObject& message) + { + const auto reply = [this, connectionId](const QJsonObject& response) + { + emit responseReady(connectionId, response); + }; + const auto unsupported = [this, requestId, &type, &reply](const QString& capability) + { + reply(makeErrorResponse(type, + requestId, + QStringLiteral("Provider does not support %1").arg(capability))); + }; + + if (type == driverhost::kCommandDescribe) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(driverhost::kProviderIdField, m_providerId); + response.insert(driverhost::kProviderNameField, m_descriptor.name); + response.insert(driverhost::kProviderVersionField, m_descriptor.version); + QJsonArray devices; + for (const HardwareDeviceDescriptor& device : m_devices) + { + devices.append(deviceDescription(device)); + } + response.insert(driverhost::kDevicesField, devices); + if (m_stage) + { + response.insert(driverhost::kDefaultXYStageField, m_stage->defaultXYStage()); + response.insert(driverhost::kDefaultZStageField, m_stage->defaultZStage()); + } + reply(response); + return; + } + if (type == driverhost::kCommandShutdown) + { + stopForExit(); + reply(makeResponse(type, requestId, true)); + QTimer::singleShot(50, this, [this]() { emit shutdownRequested(); }); + return; + } + if (!m_provider) + { + reply(makeErrorResponse(type, requestId, QStringLiteral("Provider is not initialized"))); + return; + } + + const bool configurationCommand = + type == driverhost::kCommandListConfigGroups + || type == driverhost::kCommandListConfigs + || type == driverhost::kCommandGetCurrentConfig + || type == driverhost::kCommandSetConfig; + const HardwareDeviceDescriptor* const device = + configurationCommand ? nullptr : requestedDevice(message); + if (!configurationCommand && !device) + { + reply(makeErrorResponse(type, requestId, QStringLiteral("Unknown provider device"))); + return; + } + const QString deviceId = device ? device->logicalId : QString{}; + + if (type == driverhost::kCommandSetFrameDeliveryMode) + { + if (!supportsCapability(*device, driverhost::kCapabilityCamera)) + { + unsupported(driverhost::kCapabilityCamera); + return; + } + const auto transport = cameraTransport(deviceId); + if (!transport) + { + reply(makeErrorResponse(type, requestId, + QStringLiteral("Camera transport is unavailable"))); + return; + } + const QString mode = message.value(QStringLiteral("mode")).toString(); + if (mode == driverhost::kFrameDeliveryModePreviewLatest) + { + transport->deliveryMode.store(FrameDeliveryMode::PreviewLatest, + std::memory_order_relaxed); + } + else if (mode == driverhost::kFrameDeliveryModeLatestOnly) + { + transport->deliveryMode.store(FrameDeliveryMode::LatestOnly, + std::memory_order_relaxed); + transport->previewTimer->stop(); + transport->previewFrame = {}; + } + else if (mode == driverhost::kFrameDeliveryModeAllFrames) + { + transport->deliveryMode.store(FrameDeliveryMode::AllFrames, + std::memory_order_relaxed); + transport->previewTimer->stop(); + transport->previewFrame = {}; + } + else + { + reply(makeErrorResponse(type, + requestId, + QStringLiteral("Unknown frame delivery mode"))); + return; + } + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("mode"), mode); + reply(response); + return; + } + + if (type == driverhost::kCommandStartPreview + || type == driverhost::kCommandStopPreview) + { + if (!supportsCapability(*device, driverhost::kCapabilityCamera)) + { + unsupported(driverhost::kCapabilityCamera); + return; + } + const bool start = type == driverhost::kCommandStartPreview; + const bool ok = start + ? m_camera->startPreviewFor(deviceId) + : m_camera->stopPreviewFor(deviceId); + if (!ok) + { + reply(makeErrorResponse(type, + requestId, + QStringLiteral("Provider rejected preview request"))); + return; + } + if (const auto transport = cameraTransport(deviceId)) + { + transport->previewRunning = start; + } + reply(makeResponse(type, requestId, true)); + emitPreviewStateEvent(deviceId, start); + return; + } + + if (type == driverhost::kCommandGetExposure) + { + if (!supportsCapability(*device, driverhost::kCapabilityCamera)) + { + unsupported(driverhost::kCapabilityCamera); + return; + } + double exposureMs = 0.0; + if (!m_camera->getExposure(deviceId, exposureMs)) + { + reply(makeErrorResponse(type, + requestId, + QStringLiteral("Provider failed to read exposure"))); + return; + } + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("exposureMs"), exposureMs); + reply(response); + return; + } + + if (type == driverhost::kCommandSetExposure) + { + if (!supportsCapability(*device, driverhost::kCapabilityCamera)) + { + unsupported(driverhost::kCapabilityCamera); + return; + } + const double exposureMs = message.value(QStringLiteral("value")).toDouble(-1.0); + double actualExposureMs = 0.0; + if (exposureMs <= 0.0 + || !m_camera->setExposure(deviceId, exposureMs) + || !m_camera->getExposure(deviceId, actualExposureMs)) + { + reply(makeErrorResponse(type, + requestId, + QStringLiteral("Provider rejected exposure"))); + return; + } + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("exposureMs"), actualExposureMs); + reply(response); + return; + } + + if (type == driverhost::kCommandSetRoi + || type == driverhost::kCommandClearRoi + || type == driverhost::kCommandGetRoi) + { + if (!supportsCapability(*device, driverhost::kCapabilityCamera)) + { + unsupported(driverhost::kCapabilityCamera); + return; + } + if (type == driverhost::kCommandSetRoi) + { + const bool ok = m_camera->setROI(deviceId, + message.value(QStringLiteral("x")).toInt(), + message.value(QStringLiteral("y")).toInt(), + message.value(QStringLiteral("width")).toInt(), + message.value(QStringLiteral("height")).toInt()); + reply(ok ? makeResponse(type, requestId, true) + : makeErrorResponse(type, + requestId, + QStringLiteral("Provider rejected ROI"))); + return; + } + if (type == driverhost::kCommandClearRoi) + { + const bool ok = m_camera->clearROI(deviceId); + reply(ok ? makeResponse(type, requestId, true) + : makeErrorResponse(type, + requestId, + QStringLiteral("Provider rejected ROI reset"))); + return; + } + + int x = 0; + int y = 0; + int width = 0; + int height = 0; + if (!m_camera->getROI(deviceId, x, y, width, height)) + { + reply(makeErrorResponse(type, + requestId, + QStringLiteral("Provider failed to read ROI"))); + return; + } + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("x"), x); + response.insert(QStringLiteral("y"), y); + response.insert(QStringLiteral("width"), width); + response.insert(QStringLiteral("height"), height); + reply(response); + return; + } + + if (type == driverhost::kCommandCaptureEvent) + { + if (!supportsCapability(*device, driverhost::kCapabilityCamera)) + { + unsupported(driverhost::kCapabilityCamera); + return; + } + ImageFrame frame; + quint64 frameIndex = 0; + const auto transport = cameraTransport(deviceId); + const int timeoutMs = (std::max)(1, + message.value(QStringLiteral("timeoutMs")).toInt(1500)); + if (!transport + || !m_camera->captureEventFrame(deviceId, frame, timeoutMs) + || !writeFrame(*transport, frame, &frameIndex)) + { + reply(makeErrorResponse(type, + requestId, + QStringLiteral("Provider failed to capture a frame"))); + return; + } + QJsonObject event = makeEvent(driverhost::kEventFrameAvailable); + event.insert(driverhost::kProviderIdField, m_providerId); + event.insert(driverhost::kDeviceIdField, deviceId); + event.insert(QStringLiteral("frameIndex"), driverhost::encodeUInt64(frameIndex)); + emit eventReady(event); + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("frameIndex"), driverhost::encodeUInt64(frameIndex)); + reply(response); + return; + } + + if (type == driverhost::kCommandListProperties + || type == driverhost::kCommandGetProperty + || type == driverhost::kCommandSetProperty) + { + if (!supportsCapability(*device, driverhost::kCapabilityProperties)) + { + unsupported(driverhost::kCapabilityProperties); + return; + } + if (type == driverhost::kCommandListProperties) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("properties"), + QJsonArray::fromStringList( + m_properties->listProperties(deviceId))); + reply(response); + return; + } + + const QString name = message.value(QStringLiteral("name")).toString(); + if (type == driverhost::kCommandSetProperty) + { + QString error; + const bool ok = m_properties->setProperty( + deviceId, + name, + message.value(QStringLiteral("value")).toString(), + &error); + reply(ok ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("value"), + m_properties->getProperty( + deviceId, + name, + message.value(QStringLiteral("fromCache")).toBool(false))); + response.insert(QStringLiteral("propertyType"), + m_properties->getPropertyType(deviceId, name)); + response.insert(QStringLiteral("readOnly"), + m_properties->isPropertyReadOnly(deviceId, name)); + response.insert(QStringLiteral("preInit"), + m_properties->isPropertyPreInit(deviceId, name)); + response.insert(QStringLiteral("allowedValues"), + QJsonArray::fromStringList( + m_properties->getAllowedPropertyValues(deviceId, name))); + const bool hasLimits = m_properties->hasPropertyLimits(deviceId, name); + response.insert(QStringLiteral("hasLimits"), hasLimits); + response.insert(QStringLiteral("lowerLimit"), + hasLimits + ? m_properties->getPropertyLowerLimit(deviceId, name) + : 0.0); + response.insert(QStringLiteral("upperLimit"), + hasLimits + ? m_properties->getPropertyUpperLimit(deviceId, name) + : 0.0); + reply(response); + return; + } + + if (type == driverhost::kCommandGetXYPosition + || type == driverhost::kCommandGetZPosition + || type == driverhost::kCommandSetRelativeXYPosition + || type == driverhost::kCommandSetRelativeZPosition + || type == driverhost::kCommandSetXYPosition + || type == driverhost::kCommandSetZPosition) + { + if (!supportsCapability(*device, driverhost::kCapabilityStage)) + { + unsupported(driverhost::kCapabilityStage); + return; + } + QString error; + if (type == driverhost::kCommandGetXYPosition) + { + double x = 0.0; + double y = 0.0; + if (!m_stage->getXYPosition(deviceId, x, y, &error)) + { + reply(makeErrorResponse(type, requestId, error)); + return; + } + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("x"), x); + response.insert(QStringLiteral("y"), y); + reply(response); + return; + } + if (type == driverhost::kCommandGetZPosition) + { + double z = 0.0; + if (!m_stage->getZPosition(deviceId, z, &error)) + { + reply(makeErrorResponse(type, requestId, error)); + return; + } + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("z"), z); + reply(response); + return; + } + + bool ok = false; + if (type == driverhost::kCommandSetRelativeXYPosition) + { + ok = m_stage->setRelativeXYPosition( + deviceId, + message.value(QStringLiteral("x")).toDouble(), + message.value(QStringLiteral("y")).toDouble(), + &error); + } + else if (type == driverhost::kCommandSetRelativeZPosition) + { + ok = m_stage->setRelativeZPosition( + deviceId, + message.value(QStringLiteral("z")).toDouble(), + &error); + } + else if (type == driverhost::kCommandSetXYPosition) + { + ok = m_stage->setXYPosition( + deviceId, + message.value(QStringLiteral("x")).toDouble(), + message.value(QStringLiteral("y")).toDouble(), + &error); + } + else + { + ok = m_stage->setZPosition( + deviceId, + message.value(QStringLiteral("z")).toDouble(), + &error); + } + reply(ok ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + if (type == driverhost::kCommandGetShutterOpen + || type == driverhost::kCommandSetShutterOpen) + { + if (!supportsCapability(*device, driverhost::kCapabilityShutter)) + { + unsupported(driverhost::kCapabilityShutter); + return; + } + QString error; + if (type == driverhost::kCommandGetShutterOpen) + { + bool open = false; + if (!m_shutter->isShutterOpen(deviceId, open, &error)) + { + reply(makeErrorResponse(type, requestId, error)); + return; + } + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("open"), open); + reply(response); + return; + } + const bool ok = m_shutter->setShutterOpen( + deviceId, + message.value(QStringLiteral("open")).toBool(), + &error); + reply(ok ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + if (type == driverhost::kCommandGetState + || type == driverhost::kCommandSetState + || type == driverhost::kCommandGetStateLabel) + { + if (!supportsCapability(*device, driverhost::kCapabilityState)) + { + unsupported(driverhost::kCapabilityState); + return; + } + const long requestedState = static_cast( + message.value(QStringLiteral("state")).toDouble()); + if (type == driverhost::kCommandGetStateLabel) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("label"), + m_state->stateLabel(deviceId, requestedState)); + reply(response); + return; + } + QString error; + if (type == driverhost::kCommandSetState) + { + const bool ok = m_state->setState(deviceId, requestedState, &error); + reply(ok ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + long state = 0; + if (!m_state->getState(deviceId, state, &error)) + { + reply(makeErrorResponse(type, requestId, error)); + return; + } + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("state"), static_cast(state)); + response.insert(QStringLiteral("label"), m_state->stateLabel(deviceId, state)); + reply(response); + return; + } + + if (type == driverhost::kCommandListConfigGroups + || type == driverhost::kCommandListConfigs + || type == driverhost::kCommandGetCurrentConfig + || type == driverhost::kCommandSetConfig) + { + if (!m_configuration) + { + unsupported(driverhost::kCapabilityConfiguration); + return; + } + if (type == driverhost::kCommandListConfigGroups) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("groups"), + QJsonArray::fromStringList( + m_configuration->availableConfigGroups())); + reply(response); + return; + } + const QString group = message.value(QStringLiteral("group")).toString(); + if (type == driverhost::kCommandListConfigs) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("configs"), + QJsonArray::fromStringList( + m_configuration->availableConfigs(group))); + reply(response); + return; + } + if (type == driverhost::kCommandGetCurrentConfig) + { + QJsonObject response = makeResponse(type, requestId, true); + response.insert(QStringLiteral("config"), + m_configuration->currentConfig(group)); + reply(response); + return; + } + QString error; + const bool ok = m_configuration->setConfig( + group, + message.value(QStringLiteral("config")).toString(), + &error); + reply(ok ? makeResponse(type, requestId, true) + : makeErrorResponse(type, requestId, error)); + return; + } + + reply(makeErrorResponse(type, requestId, QStringLiteral("Unknown control command"))); + } + + void ProviderDriverRuntime::stopForExit() + { + m_acceptFrames.store(false, std::memory_order_release); + for (const auto& transport : m_cameraTransports) + { + if (transport && transport->previewTimer) + { + transport->previewTimer->stop(); + } + } + if (m_camera) + { + for (const auto& transport : m_cameraTransports) + { + if (transport && transport->previewRunning) + { + m_camera->stopPreviewFor(transport->deviceId); + transport->previewRunning = false; + } + } + m_camera->setFrameSink({}); + } + for (const auto& transport : m_cameraTransports) + { + if (!transport) continue; + QMutexLocker locker(&transport->frameMutex); + transport->latestFrame = {}; + transport->latestDispatchQueued = false; + transport->previewFrame = {}; + } + m_camera = nullptr; + m_properties = nullptr; + m_stage = nullptr; + m_shutter = nullptr; + m_state = nullptr; + m_configuration = nullptr; + m_provider.reset(); + { + QWriteLocker locker(&m_cameraTransportsLock); + m_cameraTransports.clear(); + } + m_devicesById.clear(); + m_devices.clear(); + if (m_pluginLoader) + { + m_pluginLoader->unload(); + m_pluginLoader.reset(); + } + } + + class DriverHost final : public QObject + { + Q_OBJECT + + public: + // Create the control server wrapper for one isolated runtime + DriverHost(QString hostKey, + DriverHostRuntime* runtime, + QObject* parent = nullptr) + : QObject(parent) + , m_hostKey(std::move(hostKey)) + , m_serverName(driverhost::controlServerName(m_hostKey)) + , m_runtime(runtime) + { + qRegisterMetaType("QJsonObject"); + qRegisterMetaType("quint64"); + } + + // Stop the runtime thread before destruction + ~DriverHost() override + { + stopRuntime(); + } + + bool start(); + + private slots: + void onNewControlConnection(); + void onRuntimeResponse(quint64 connectionId, const QJsonObject& response); + void broadcastEvent(const QJsonObject& event); + void onConnectionClosed(quint64 connectionId); + + private: + void stopRuntime(); + + QString m_hostKey; + QString m_serverName; + + std::unique_ptr m_ctrlServer; + QThread m_runtimeThread; + DriverHostRuntime* m_runtime{nullptr}; + quint64 m_nextConnectionId{1}; + QHash m_connections; + }; + + // Start the runtime thread and local control server + bool DriverHost::start() + { + if (!m_runtime) + { + return false; + } + m_runtime->moveToThread(&m_runtimeThread); + connect(&m_runtimeThread, &QThread::finished, + m_runtime, &QObject::deleteLater); + connect(m_runtime, &DriverHostRuntime::responseReady, + this, &DriverHost::onRuntimeResponse); + connect(m_runtime, &DriverHostRuntime::eventReady, + this, &DriverHost::broadcastEvent); + connect(m_runtime, &DriverHostRuntime::shutdownRequested, this, + []() { QCoreApplication::quit(); }); + + m_runtimeThread.start(); + + bool initialized = false; + QString initError; + QMetaObject::invokeMethod( + m_runtime, + [this, &initialized, &initError]() + { + initialized = m_runtime->initializeRuntime(); + initError = m_runtime->lastError(); + }, + Qt::BlockingQueuedConnection); + + if (!initialized) + { + qCritical().noquote() + << QString("DriverHost init failed for '%1': %2") + .arg(m_hostKey, initError); + stopRuntime(); + return false; + } + + QLocalServer::removeServer(m_serverName); + m_ctrlServer = std::make_unique(this); + connect(m_ctrlServer.get(), &QLocalServer::newConnection, + this, &DriverHost::onNewControlConnection); + if (!m_ctrlServer->listen(m_serverName)) + { + qCritical().noquote() + << QString("DriverHost control server failed to listen on %1") + .arg(m_serverName); + stopRuntime(); + return false; + } + + qInfo().noquote() + << QString("DriverHost control server listening on %1").arg(m_serverName); + return true; + } + + // Accept pending local control connections + void DriverHost::onNewControlConnection() + { + while (m_ctrlServer && m_ctrlServer->hasPendingConnections()) + { + QLocalSocket* socket = m_ctrlServer->nextPendingConnection(); + if (!socket) + { + continue; + } + + const quint64 connectionId = m_nextConnectionId++; + auto* connection = new ControlConnection(connectionId, socket, this); + m_connections.insert(connectionId, connection); + + connect(connection, &ControlConnection::requestReceived, + m_runtime, &DriverHostRuntime::handleRequest, + Qt::QueuedConnection); + connect(connection, &ControlConnection::connectionClosed, + this, &DriverHost::onConnectionClosed); + + DriverHostRuntime* const runtime = m_runtime; + QMetaObject::invokeMethod(runtime, + [runtime]() { runtime->publishHello(); }, + Qt::QueuedConnection); + } + } + + // Route one runtime response back to its connection + void DriverHost::onRuntimeResponse(quint64 connectionId, const QJsonObject& response) + { + auto it = m_connections.find(connectionId); + if (it == m_connections.end() || !it.value()) + { + return; + } + it.value()->sendMessage(response); + } + + // Broadcast one runtime event to all clients + void DriverHost::broadcastEvent(const QJsonObject& event) + { + for (auto it = m_connections.begin(); it != m_connections.end(); ++it) + { + if (it.value()) + { + it.value()->sendMessage(event); + } + } + } + + // Remove a closed control connection + void DriverHost::onConnectionClosed(quint64 connectionId) + { + m_connections.remove(connectionId); + } + + // Stop the runtime worker thread cleanly + void DriverHost::stopRuntime() + { + if (!m_runtime) + { + return; + } + + DriverHostRuntime* const runtime = m_runtime; + QMetaObject::invokeMethod(runtime, + [runtime]() { runtime->stopForExit(); }, + Qt::BlockingQueuedConnection); + m_runtimeThread.quit(); + m_runtimeThread.wait(); + m_runtime = nullptr; + } +} // namespace scopeone::core::internal + +// Launch one provider in an isolated DriverHost process +int main(int argc, char* argv[]) +{ + QCoreApplication app(argc, argv); + + QCommandLineParser parser; + parser.setApplicationDescription("ScopeOne DriverHost"); + parser.addHelpOption(); + + QCommandLineOption optProvider(QStringLiteral("provider"), + QStringLiteral("Hardware provider ID"), + QStringLiteral("id")); + QCommandLineOption optDeviceId(QStringLiteral("deviceId"), + QStringLiteral("Provider device ID"), + QStringLiteral("id")); + QCommandLineOption optPlugin(QStringLiteral("plugin"), + QStringLiteral("Provider module path"), + QStringLiteral("path")); + QCommandLineOption optHostKey(QStringLiteral("hostKey"), + QStringLiteral("DriverHost instance key"), + QStringLiteral("key")); + QCommandLineOption optProviderOption(QStringLiteral("option"), + QStringLiteral("JSON provider options"), + QStringLiteral("json")); + QCommandLineOption optAdapter(QStringLiteral("adapter"), + QStringLiteral("MM adapter"), + QStringLiteral("adapter")); + QCommandLineOption optDevice(QStringLiteral("device"), + QStringLiteral("MM device"), + QStringLiteral("device")); + QCommandLineOption optShm(QStringLiteral("shm"), + QStringLiteral("Shared memory key"), + QStringLiteral("key")); + QCommandLineOption optExp(QStringLiteral("exposure"), + QStringLiteral("Exposure ms"), + QStringLiteral("ms")); + QCommandLineOption optPreInit(QStringLiteral("preinit"), + QStringLiteral("JSON-encoded pre-init property"), + QStringLiteral("json")); + QCommandLineOption optProperty(QStringLiteral("property"), + QStringLiteral("JSON-encoded initialized property"), + QStringLiteral("json")); + QCommandLineOption optAuto(QStringLiteral("autoPreview"), + QStringLiteral("Start preview immediately")); + + parser.addOption(optProvider); + parser.addOption(optDeviceId); + parser.addOption(optPlugin); + parser.addOption(optHostKey); + parser.addOption(optProviderOption); + parser.addOption(optAdapter); + parser.addOption(optDevice); + parser.addOption(optShm); + parser.addOption(optExp); + parser.addOption(optPreInit); + parser.addOption(optProperty); + parser.addOption(optAuto); + parser.process(app); + + if (!parser.isSet(optProvider)) + { + qCritical().noquote() + << "Missing required argument: --provider"; + return 2; + } + + const QString providerId = parser.value(optProvider).trimmed(); + const QString deviceId = parser.value(optDeviceId).trimmed(); + if (providerId.isEmpty()) + { + qCritical().noquote() << "Provider ID cannot be empty"; + return 2; + } + + const double exposureMs = parser.isSet(optExp) + ? parser.value(optExp).toDouble() + : 0.0; + scopeone::core::internal::DriverHostRuntime* runtime = nullptr; + if (providerId == QStringLiteral("micro-manager")) + { + if (deviceId.isEmpty() + || !parser.isSet(optAdapter) + || !parser.isSet(optDevice) + || !parser.isSet(optShm)) + { + qCritical().noquote() + << "Micro-Manager requires --deviceId, --adapter, --device and --shm"; + return 2; + } + runtime = new scopeone::core::internal::MicroManagerDriverRuntime( + providerId, + deviceId, + parser.value(optAdapter), + parser.value(optDevice), + parser.value(optShm), + parser.values(optPreInit), + parser.values(optProperty), + exposureMs, + parser.isSet(optAuto)); + } + else + { + const QString pluginPath = parser.value(optPlugin).trimmed(); + const QString hostKey = parser.value(optHostKey).trimmed(); + if (pluginPath.isEmpty() || hostKey.isEmpty()) + { + qCritical().noquote() << "External providers require --plugin and --hostKey"; + return 2; + } + + QJsonObject providerOptions; + for (const QString& encodedOptions : parser.values(optProviderOption)) + { + QJsonParseError parseError; + const QJsonDocument document = + QJsonDocument::fromJson(encodedOptions.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError || !document.isObject()) + { + qCritical().noquote() << QStringLiteral("Invalid --option JSON: %1") + .arg(parseError.errorString()); + return 2; + } + const QJsonObject object = document.object(); + for (auto it = object.constBegin(); it != object.constEnd(); ++it) + { + providerOptions.insert(it.key(), it.value()); + } + } + + runtime = new scopeone::core::internal::ProviderDriverRuntime( + pluginPath, + providerId, + hostKey, + providerOptions); + } + + const QString serverKey = providerId == QStringLiteral("micro-manager") + ? deviceId + : parser.value(optHostKey).trimmed(); + scopeone::core::internal::DriverHost driverHost(serverKey, runtime); + if (!driverHost.start()) + { + return 2; + } + + return app.exec(); +} + +#include "DriverHostMain.moc" diff --git a/ScopeOneCore/src/DriverHostProviderProxy.cpp b/ScopeOneCore/src/DriverHostProviderProxy.cpp new file mode 100644 index 0000000..6e7c3d1 --- /dev/null +++ b/ScopeOneCore/src/DriverHostProviderProxy.cpp @@ -0,0 +1,1606 @@ +#include "internal/DriverHostProviderProxy.h" + +#include "internal/CameraRuntimeControl.h" +#include "internal/DriverHostProtocol.h" +#include "scopeone/CameraProvider.h" +#include "scopeone/HardwareCapabilities.h" +#include "scopeone/SharedFrame.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scopeone::core::internal +{ + static_assert(std::atomic_ref::is_always_lock_free, + "Shared frame state requires lock-free 32-bit atomics"); + + namespace + { + constexpr int kControlReadyTimeoutMs = 15000; + constexpr int kControlRequestTimeoutMs = 10000; + + HardwareDeviceKind deviceKindFromName(const QString& name) + { + if (name == QStringLiteral("Camera")) return HardwareDeviceKind::Camera; + if (name == QStringLiteral("XYStage")) return HardwareDeviceKind::XYStage; + if (name == QStringLiteral("ZStage")) return HardwareDeviceKind::ZStage; + if (name == QStringLiteral("Shutter")) return HardwareDeviceKind::Shutter; + if (name == QStringLiteral("State")) return HardwareDeviceKind::State; + if (name == QStringLiteral("Hub")) return HardwareDeviceKind::Hub; + if (name == QStringLiteral("Serial")) return HardwareDeviceKind::Serial; + if (name == QStringLiteral("Generic")) return HardwareDeviceKind::Generic; + if (name == QStringLiteral("AutoFocus")) return HardwareDeviceKind::AutoFocus; + if (name == QStringLiteral("ImageProcessor")) return HardwareDeviceKind::ImageProcessor; + if (name == QStringLiteral("SignalIO")) return HardwareDeviceKind::SignalIO; + if (name == QStringLiteral("Magnifier")) return HardwareDeviceKind::Magnifier; + if (name == QStringLiteral("SLM")) return HardwareDeviceKind::SLM; + if (name == QStringLiteral("Galvo")) return HardwareDeviceKind::Galvo; + if (name == QStringLiteral("PressurePump")) return HardwareDeviceKind::PressurePump; + if (name == QStringLiteral("VolumetricPump")) return HardwareDeviceKind::VolumetricPump; + return HardwareDeviceKind::Unknown; + } + + bool responseSucceeded(const QJsonObject& response, QString* errorMessage) + { + if (response.value(QStringLiteral("ok")).toBool(false)) + { + if (errorMessage) errorMessage->clear(); + return true; + } + if (errorMessage) + { + *errorMessage = response.value(QStringLiteral("error")) + .toString(QStringLiteral("DriverHost request failed")); + } + return false; + } + + quint64 sharedFramePayloadSize(const SharedFrameHeader& header) + { + return static_cast(header.stride) * header.height; + } + + bool headerLooksSane(const SharedFrameHeader& header) + { + if (header.channels != 1 || header.width == 0 || header.height == 0 + || header.stride == 0) + { + return false; + } + const bool mono8 = + header.pixelFormat == static_cast(SharedPixelFormat::Mono8); + const bool mono16 = + header.pixelFormat == static_cast(SharedPixelFormat::Mono16); + if ((!mono8 && !mono16) + || (mono8 && header.bitsPerSample != 8) + || (mono16 && (header.bitsPerSample == 0 || header.bitsPerSample > 16))) + { + return false; + } + const quint32 bytesPerPixel = mono16 ? 2u : 1u; + const quint64 minimumStride = static_cast(header.width) * bytesPerPixel; + const quint64 payloadSize = sharedFramePayloadSize(header); + return header.width <= static_cast((std::numeric_limits::max)()) + && header.height <= static_cast((std::numeric_limits::max)()) + && header.stride <= static_cast((std::numeric_limits::max)()) + && header.stride >= minimumStride + && header.stride % bytesPerPixel == 0 + && payloadSize > 0 + && payloadSize <= static_cast(kSharedFrameMaxBytes); + } + + bool claimFrameSlot(uchar* slot, SharedFrameHeader& header) + { + auto& stateValue = *reinterpret_cast(slot); + std::atomic_ref state(stateValue); + quint32 expected = 2; + if (!state.compare_exchange_strong(expected, + 3, + std::memory_order_acq_rel, + std::memory_order_acquire)) + { + return false; + } + memcpy(&header, slot, sizeof(header)); + header.state = 2; + if (headerLooksSane(header)) + { + return true; + } + state.store(2, std::memory_order_release); + return false; + } + + void releaseFrameSlot(uchar* slot) + { + auto& stateValue = *reinterpret_cast(slot); + std::atomic_ref(stateValue).store(2, std::memory_order_release); + } + } + + class DriverHostProviderTransport final : public QObject + { + public: + using FrameHandler = std::function; + using PreviewHandler = std::function; + + DriverHostProviderTransport(QString providerId, + QString pluginPath, + QString hostKey, + QJsonObject options, + FrameHandler frameHandler, + PreviewHandler previewHandler) + : m_providerId(std::move(providerId)) + , m_pluginPath(std::move(pluginPath)) + , m_hostKey(std::move(hostKey)) + , m_options(std::move(options)) + , m_frameHandler(std::move(frameHandler)) + , m_previewHandler(std::move(previewHandler)) + { + } + + bool initialize(HardwareProviderDescriptor& descriptor, + QList& devices, + QString& defaultXYStage, + QString& defaultZStage, + QString* errorMessage) + { + m_socket = std::make_unique(); + connect(m_socket.get(), &QLocalSocket::readyRead, + this, [this]() { handleReadyRead(); }); + connect(m_socket.get(), &QLocalSocket::disconnected, + this, [this]() + { + m_lastError = QStringLiteral("DriverHost control connection closed"); + finishWaitingRequest(); + }); + const QString driverHostPath = QDir(QCoreApplication::applicationDirPath()) + .filePath(driverhost::kExecutableFileName); + if (!QFileInfo::exists(driverHostPath)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("DriverHost executable not found: %1") + .arg(driverHostPath); + } + return false; + } + if (!QFileInfo::exists(m_pluginPath)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Provider module not found: %1") + .arg(m_pluginPath); + } + return false; + } + + m_process = std::make_unique(); + m_process->setProcessChannelMode(QProcess::MergedChannels); + connect(m_process.get(), &QProcess::readyReadStandardOutput, + this, [this]() + { + const QString output = QString::fromUtf8( + m_process->readAllStandardOutput()).trimmed(); + if (!output.isEmpty()) + { + qInfo().noquote() + << QString("[DriverHost %1] %2").arg(m_providerId, output); + } + }); + connect(m_process.get(), &QProcess::finished, + this, [this](int, QProcess::ExitStatus) + { + m_lastError = QStringLiteral("DriverHost process exited"); + for (const QString& cameraId : m_cameraIds) + { + if (m_previewHandler) m_previewHandler(cameraId, false); + } + m_runningCameras.clear(); + finishWaitingRequest(); + }); + + QStringList arguments; + arguments << QStringLiteral("--provider") << m_providerId + << QStringLiteral("--plugin") << m_pluginPath + << QStringLiteral("--hostKey") << m_hostKey; + if (!m_options.isEmpty()) + { + arguments << QStringLiteral("--option") + << QString::fromUtf8( + QJsonDocument(m_options).toJson(QJsonDocument::Compact)); + } + m_process->setProgram(driverHostPath); + m_process->setArguments(arguments); + m_process->start(); + if (!m_process->waitForStarted(3000)) + { + if (errorMessage) *errorMessage = QStringLiteral("Failed to start DriverHost"); + return false; + } + + const QString serverName = driverhost::controlServerName(m_hostKey); + QElapsedTimer connectTimer; + connectTimer.start(); + while (connectTimer.elapsed() < kControlReadyTimeoutMs) + { + m_socket->abort(); + m_socket->connectToServer(serverName); + if (m_socket->waitForConnected(200)) + { + break; + } + if (m_process->state() == QProcess::NotRunning) + { + break; + } + } + if (m_socket->state() != QLocalSocket::ConnectedState) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("DriverHost control server did not become ready"); + } + cleanup(); + return false; + } + + QJsonObject request; + request.insert(driverhost::kMessageTypeField, driverhost::kCommandDescribe); + QJsonObject response; + if (!sendRequest(request, response, kControlRequestTimeoutMs, errorMessage) + || !responseSucceeded(response, errorMessage)) + { + cleanup(); + return false; + } + + descriptor.id = response.value(driverhost::kProviderIdField).toString(); + descriptor.name = response.value(driverhost::kProviderNameField).toString(); + descriptor.version = response.value(driverhost::kProviderVersionField).toString(); + if (descriptor.id != m_providerId) + { + if (errorMessage) *errorMessage = QStringLiteral("Provider identity mismatch"); + cleanup(); + return false; + } + + QSet logicalIds; + const QJsonArray deviceArray = response.value(driverhost::kDevicesField).toArray(); + for (const QJsonValue& value : deviceArray) + { + const QJsonObject object = value.toObject(); + HardwareDeviceDescriptor device; + device.logicalId = object.value(driverhost::kDeviceIdField).toString(); + device.providerId = descriptor.id; + device.providerDeviceId = + object.value(driverhost::kProviderDeviceIdField).toString(); + device.hardwareId = object.value(driverhost::kHardwareIdField).toString(); + device.name = object.value(driverhost::kDeviceNameField).toString(); + device.kind = deviceKindFromName( + object.value(driverhost::kDeviceKindField).toString()); + device.state = static_cast( + object.value(driverhost::kDeviceStateField) + .toInt(static_cast(HardwareDeviceState::Unknown))); + device.endpoint = HardwareEndpointKind::DriverHost; + device.properties = object.value(driverhost::kDevicePropertiesField) + .toObject().toVariantMap(); + if (device.logicalId.trimmed().isEmpty() + || device.logicalId != device.logicalId.trimmed() + || logicalIds.contains(device.logicalId)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("DriverHost returned an invalid device catalog"); + } + cleanup(); + return false; + } + logicalIds.insert(device.logicalId); + devices.append(device); + + if (device.kind == HardwareDeviceKind::Camera) + { + const QString shmKey = + object.value(driverhost::kSharedMemoryKeyField).toString(); + if (shmKey.isEmpty()) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Camera '%1' has no shared memory mapping") + .arg(device.logicalId); + } + cleanup(); + return false; + } + auto reader = std::make_shared(); + reader->cameraId = device.logicalId; + reader->shmKey = shmKey; + reader->shm = std::make_unique(); + reader->shm->setNativeKey(shmKey); + m_readers.insert(device.logicalId, std::move(reader)); + m_cameraIds.append(device.logicalId); + } + } + defaultXYStage = response.value(driverhost::kDefaultXYStageField).toString(); + defaultZStage = response.value(driverhost::kDefaultZStageField).toString(); + if (errorMessage) errorMessage->clear(); + return true; + } + + bool sendRequest(QJsonObject request, + QJsonObject& response, + int timeoutMs, + QString* errorMessage) + { + if (!m_socket + || m_socket->state() != QLocalSocket::ConnectedState + || m_waitingLoop) + { + if (errorMessage) + { + *errorMessage = m_waitingLoop + ? QStringLiteral("Nested DriverHost request") + : QStringLiteral("DriverHost is not connected"); + } + return false; + } + + m_waitingRequestId = m_nextRequestId++; + m_waitingResponse = {}; + m_lastError.clear(); + request.insert(driverhost::kEnvelopeKindField, driverhost::kMessageKindRequest); + request.insert(driverhost::kEnvelopeVersionField, + static_cast(driverhost::kProtocolVersion)); + request.insert(driverhost::kEnvelopeRequestIdField, + driverhost::encodeUInt64(m_waitingRequestId)); + + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + m_waitingLoop = &loop; + m_socket->write(driverhost::encodeMessage(request)); + m_socket->flush(); + timer.start((std::max)(1, timeoutMs)); + loop.exec(); + m_waitingLoop = nullptr; + + if (m_waitingResponse.isEmpty()) + { + if (errorMessage) + { + *errorMessage = m_lastError.isEmpty() + ? QStringLiteral("DriverHost request timed out") + : m_lastError; + } + m_waitingRequestId = 0; + return false; + } + response = m_waitingResponse; + m_waitingResponse = {}; + m_waitingRequestId = 0; + if (errorMessage) errorMessage->clear(); + return true; + } + + bool setFrameDeliveryMode(const QString& mode, QString* errorMessage) + { + const QString previousMode = m_deliveryMode; + QStringList changed; + for (const QString& cameraId : m_cameraIds) + { + QJsonObject request; + request.insert(driverhost::kMessageTypeField, + driverhost::kCommandSetFrameDeliveryMode); + request.insert(driverhost::kDeviceIdField, cameraId); + request.insert(QStringLiteral("mode"), mode); + QJsonObject response; + if (!sendRequest(request, response, kControlRequestTimeoutMs, errorMessage) + || !responseSucceeded(response, errorMessage)) + { + for (const QString& changedCameraId : changed) + { + QJsonObject rollback; + rollback.insert(driverhost::kMessageTypeField, + driverhost::kCommandSetFrameDeliveryMode); + rollback.insert(driverhost::kDeviceIdField, changedCameraId); + rollback.insert(QStringLiteral("mode"), previousMode); + QJsonObject ignored; + sendRequest(rollback, ignored, kControlRequestTimeoutMs, nullptr); + } + return false; + } + changed.append(cameraId); + } + m_deliveryMode = mode; + m_readAllFrames = mode == driverhost::kFrameDeliveryModeAllFrames; + return true; + } + + void setFrameDeliveryPaused(bool paused) + { + m_frameDeliveryPaused = paused; + if (!paused) + { + for (const QString& cameraId : m_runningCameras) + { + deliverAvailableFrames(cameraId, false); + } + } + } + + bool captureEventFrame(const QString& cameraId, + ImageFrame& frame, + int timeoutMs, + QString* errorMessage) + { + auto it = m_readers.find(cameraId); + if (it == m_readers.end() || !ensureSharedMemory(*it.value())) + { + if (errorMessage) *errorMessage = QStringLiteral("Camera shared memory is unavailable"); + return false; + } + QList discarded; + readLatest(*it.value(), discarded); + const quint64 previousIndex = it.value()->lastFrameIndex; + + QJsonObject request; + request.insert(driverhost::kMessageTypeField, driverhost::kCommandCaptureEvent); + request.insert(driverhost::kDeviceIdField, cameraId); + QJsonObject response; + const int waitMs = (std::max)(1, timeoutMs); + request.insert(QStringLiteral("timeoutMs"), waitMs); + if (!sendRequest(request, response, waitMs + 500, errorMessage) + || !responseSucceeded(response, errorMessage)) + { + return false; + } + const quint64 targetIndex = driverhost::decodeUInt64( + response.value(QStringLiteral("frameIndex"))); + + QElapsedTimer timer; + timer.start(); + while (timer.elapsed() <= waitMs) + { + QList frames; + readLatest(*it.value(), frames); + const ImageFrame candidate = !frames.isEmpty() + ? frames.constLast() + : it.value()->latestFrame; + if (candidate.isValid() + && candidate.frameIndex > previousIndex + && (targetIndex == 0 || candidate.frameIndex >= targetIndex)) + { + frame = candidate; + if (errorMessage) errorMessage->clear(); + return true; + } + QEventLoop loop; + QTimer::singleShot(1, &loop, &QEventLoop::quit); + loop.exec(); + } + if (errorMessage) *errorMessage = QStringLiteral("Timed out waiting for captured frame"); + return false; + } + + void cleanup() + { + if (m_cleanedUp) return; + m_cleanedUp = true; + if (m_socket && m_socket->state() == QLocalSocket::ConnectedState) + { + QJsonObject request; + request.insert(driverhost::kMessageTypeField, driverhost::kCommandShutdown); + QJsonObject response; + sendRequest(request, response, 800, nullptr); + m_socket->disconnectFromServer(); + } + m_readers.clear(); + m_socket.reset(); + if (m_process && m_process->state() != QProcess::NotRunning) + { + if (!m_process->waitForFinished(1000)) + { + m_process->terminate(); + if (!m_process->waitForFinished(1000)) + { + m_process->kill(); + m_process->waitForFinished(1000); + } + } + } + m_process.reset(); + } + + private: + struct Reader + { + QString cameraId; + QString shmKey; + std::unique_ptr shm; + quint64 lastFrameIndex{0}; + ImageFrame latestFrame; + }; + + void finishWaitingRequest() + { + if (m_waitingLoop) m_waitingLoop->quit(); + } + + void handleReadyRead() + { + if (!m_socket) return; + m_readBuffer += m_socket->readAll(); + while (true) + { + QJsonObject message; + QString error; + const driverhost::DecodeResult result = + driverhost::tryDecodeMessage(m_readBuffer, message, &error); + if (result == driverhost::DecodeResult::Incomplete) return; + if (result == driverhost::DecodeResult::Error) + { + m_lastError = error; + m_socket->abort(); + finishWaitingRequest(); + return; + } + if (message.value(driverhost::kEnvelopeVersionField).toInt(0) + != static_cast(driverhost::kProtocolVersion)) + { + m_lastError = QStringLiteral("DriverHost protocol version mismatch"); + m_socket->abort(); + finishWaitingRequest(); + return; + } + + const QString kind = message.value(driverhost::kEnvelopeKindField).toString(); + if (kind == driverhost::kMessageKindResponse) + { + const quint64 requestId = driverhost::decodeUInt64( + message.value(driverhost::kEnvelopeRequestIdField)); + if (requestId == m_waitingRequestId) + { + m_waitingResponse = message; + finishWaitingRequest(); + } + continue; + } + if (kind == driverhost::kMessageKindEvent) + { + handleEvent(message); + } + } + } + + void handleEvent(const QJsonObject& event) + { + if (event.value(driverhost::kProviderIdField).toString() != m_providerId) + { + return; + } + const QString type = event.value(driverhost::kMessageTypeField).toString(); + const QString deviceId = event.value(driverhost::kDeviceIdField).toString(); + if (type == driverhost::kEventFrameAvailable + && !m_frameDeliveryPaused + && m_runningCameras.contains(deviceId)) + { + deliverAvailableFrames(deviceId, m_readAllFrames); + } + else if (type == driverhost::kEventPreviewState) + { + const bool running = event.value(QStringLiteral("running")).toBool(); + if (running) m_runningCameras.insert(deviceId); + else m_runningCameras.remove(deviceId); + if (m_previewHandler) m_previewHandler(deviceId, running); + } + else if (type == driverhost::kEventDriverHostError) + { + qWarning().noquote() + << QString("DriverHost '%1' error: %2") + .arg(m_providerId, + event.value(QStringLiteral("error")).toString()); + } + } + + bool ensureSharedMemory(Reader& reader) + { + const int expectedSize = + kSharedMemoryControlSize + kSharedFrameNumSlots * kSharedFrameSlotStride; + if (reader.shm->isAttached()) + { + return reader.shm->size() >= expectedSize; + } + if (!reader.shm->attach(QSharedMemory::ReadWrite)) + { + return false; + } + if (reader.shm->size() >= expectedSize) + { + return true; + } + reader.shm->detach(); + return false; + } + + bool copyFrame(Reader& reader, + const SharedFrameHeader& header, + const uchar* pixels, + QList& frames) + { + QByteArray payload; + payload.resize(static_cast(sharedFramePayloadSize(header))); + memcpy(payload.data(), pixels, static_cast(payload.size())); + ImageFrame frame = ImageFrame::fromSharedFrame(reader.cameraId, header, payload); + if (!frame.isValid()) return false; + reader.latestFrame = frame; + frames.append(std::move(frame)); + return true; + } + + bool readLatest(Reader& reader, QList& frames) + { + if (!ensureSharedMemory(reader)) return false; + auto* base = static_cast(reader.shm->data()); + if (!base) return false; + + auto* control = reinterpret_cast(base); + SharedFrameHeader capturedHeader{}; + uchar* capturedSlot = nullptr; + auto claim = [&](quint32 index) + { + if (index >= kSharedFrameNumSlots) return false; + uchar* slot = base + kSharedMemoryControlSize + + static_cast(index) * kSharedFrameSlotStride; + SharedFrameHeader header{}; + if (!claimFrameSlot(slot, header)) return false; + if (header.frameIndex <= reader.lastFrameIndex) + { + releaseFrameSlot(slot); + return false; + } + capturedHeader = header; + capturedSlot = slot; + return true; + }; + + const quint32 latest = std::atomic_ref(control->latestSlotIndex) + .load(std::memory_order_acquire); + claim(latest); + if (!capturedSlot) + { + quint64 bestIndex = reader.lastFrameIndex; + for (int index = 0; index < kSharedFrameNumSlots; ++index) + { + uchar* slot = base + kSharedMemoryControlSize + + index * kSharedFrameSlotStride; + SharedFrameHeader header{}; + if (!claimFrameSlot(slot, header)) continue; + if (header.frameIndex > bestIndex) + { + if (capturedSlot) releaseFrameSlot(capturedSlot); + bestIndex = header.frameIndex; + capturedHeader = header; + capturedSlot = slot; + } + else + { + releaseFrameSlot(slot); + } + } + } + + const bool copied = capturedSlot + && copyFrame(reader, + capturedHeader, + capturedSlot + kSharedFrameHeaderSize, + frames); + if (capturedSlot) releaseFrameSlot(capturedSlot); + if (copied) reader.lastFrameIndex = capturedHeader.frameIndex; + return copied; + } + + bool readAll(Reader& reader, QList& frames) + { + if (!ensureSharedMemory(reader)) return false; + auto* base = static_cast(reader.shm->data()); + if (!base) return false; + + struct Claimed + { + uchar* slot{nullptr}; + SharedFrameHeader header{}; + }; + std::vector claimed; + claimed.reserve(kSharedFrameNumSlots); + for (int index = 0; index < kSharedFrameNumSlots; ++index) + { + uchar* slot = base + kSharedMemoryControlSize + + index * kSharedFrameSlotStride; + SharedFrameHeader header{}; + if (!claimFrameSlot(slot, header)) continue; + if (header.frameIndex > reader.lastFrameIndex) + { + claimed.push_back({slot, header}); + } + else + { + releaseFrameSlot(slot); + } + } + std::sort(claimed.begin(), claimed.end(), [](const Claimed& lhs, const Claimed& rhs) + { + return lhs.header.frameIndex < rhs.header.frameIndex; + }); + quint64 lastIndex = reader.lastFrameIndex; + for (const Claimed& item : claimed) + { + if (copyFrame(reader, + item.header, + item.slot + kSharedFrameHeaderSize, + frames)) + { + lastIndex = item.header.frameIndex; + } + releaseFrameSlot(item.slot); + } + reader.lastFrameIndex = lastIndex; + return !frames.isEmpty(); + } + + void deliverAvailableFrames(const QString& cameraId, bool allFrames) + { + auto it = m_readers.find(cameraId); + if (it == m_readers.end()) return; + QList frames; + if (allFrames) readAll(*it.value(), frames); + else readLatest(*it.value(), frames); + if (!m_frameHandler) return; + for (const ImageFrame& frame : frames) + { + m_frameHandler(frame); + } + } + + QString m_providerId; + QString m_pluginPath; + QString m_hostKey; + QJsonObject m_options; + FrameHandler m_frameHandler; + PreviewHandler m_previewHandler; + std::unique_ptr m_process; + std::unique_ptr m_socket; + QByteArray m_readBuffer; + QHash> m_readers; + QStringList m_cameraIds; + QSet m_runningCameras; + QString m_deliveryMode{driverhost::kFrameDeliveryModePreviewLatest}; + QEventLoop* m_waitingLoop{nullptr}; + QJsonObject m_waitingResponse; + QString m_lastError; + quint64 m_nextRequestId{1}; + quint64 m_waitingRequestId{0}; + bool m_frameDeliveryPaused{false}; + bool m_readAllFrames{false}; + bool m_cleanedUp{false}; + }; + + class DriverHostProviderProxy final : public QObject, + public HardwareProvider, + public CameraProvider, + public StageProvider, + public ShutterProvider, + public StateProvider, + public ConfigurationProvider, + public CameraRuntimeControl + { + public: + DriverHostProviderProxy(QString providerId, + QString pluginPath, + QJsonObject options) + : m_expectedProviderId(std::move(providerId)) + , m_pluginPath(std::move(pluginPath)) + , m_options(std::move(options)) + { + } + + ~DriverHostProviderProxy() override + { + if (m_worker && m_workerThread.isRunning()) + { + DriverHostProviderTransport* const worker = m_worker; + QMetaObject::invokeMethod(worker, + [worker]() { worker->cleanup(); }, + Qt::BlockingQueuedConnection); + m_workerThread.quit(); + m_workerThread.wait(); + } + m_worker = nullptr; + } + + bool initialize(QString* errorMessage) + { + const QString hostKey = QUuid::createUuid().toString(QUuid::WithoutBraces); + m_worker = new DriverHostProviderTransport( + m_expectedProviderId, + m_pluginPath, + hostKey, + m_options, + [this](const ImageFrame& frame) { deliverFrame(frame); }, + [this](const QString& cameraId, bool running) + { + QMutexLocker locker(&m_stateMutex); + if (running) m_runningCameras.insert(cameraId); + else m_runningCameras.remove(cameraId); + }); + m_worker->moveToThread(&m_workerThread); + connect(&m_workerThread, &QThread::finished, + m_worker, &QObject::deleteLater); + m_workerThread.setObjectName( + QStringLiteral("ScopeOneProvider_%1").arg(m_expectedProviderId)); + m_workerThread.start(); + + bool initialized = false; + QString error; + DriverHostProviderTransport* const worker = m_worker; + QMetaObject::invokeMethod( + worker, + [this, worker, &initialized, &error]() + { + initialized = worker->initialize(m_descriptor, + m_devices, + m_defaultXYStage, + m_defaultZStage, + &error); + }, + Qt::BlockingQueuedConnection); + if (!initialized) + { + if (errorMessage) *errorMessage = error; + QMetaObject::invokeMethod(worker, + [worker]() { worker->cleanup(); }, + Qt::BlockingQueuedConnection); + m_workerThread.quit(); + m_workerThread.wait(); + m_worker = nullptr; + return false; + } + if (errorMessage) errorMessage->clear(); + return true; + } + + HardwareProviderDescriptor descriptor() const override { return m_descriptor; } + QList devices() const override { return m_devices; } + + void setFrameSink(FrameSink sink) override + { + QMutexLocker locker(&m_sinkMutex); + m_frameSink = std::move(sink); + } + + bool startPreview() override + { + const QStringList ids = cameraIds(); + QStringList started; + for (const QString& cameraId : ids) + { + const bool wasRunning = isPreviewRunning(cameraId); + if (!startPreviewFor(cameraId)) + { + for (const QString& startedId : started) stopPreviewFor(startedId); + return false; + } + if (!wasRunning) started.append(cameraId); + } + return !ids.isEmpty(); + } + + bool stopPreview() override + { + bool ok = true; + const QStringList ids = cameraIds(); + for (const QString& cameraId : ids) ok = stopPreviewFor(cameraId) && ok; + return ok; + } + + bool startPreviewFor(const QString& cameraId) override + { + if (!isCamera(cameraId)) return false; + if (isPreviewRunning(cameraId)) return true; + QJsonObject response; + if (!request(driverhost::kCommandStartPreview, cameraId, {}, response, nullptr)) + { + return false; + } + QMutexLocker locker(&m_stateMutex); + m_runningCameras.insert(cameraId); + return true; + } + + bool stopPreviewFor(const QString& cameraId) override + { + if (!isCamera(cameraId)) return false; + if (!isPreviewRunning(cameraId)) return true; + QJsonObject response; + if (!request(driverhost::kCommandStopPreview, cameraId, {}, response, nullptr)) + { + return false; + } + QMutexLocker locker(&m_stateMutex); + m_runningCameras.remove(cameraId); + return true; + } + + bool isPreviewRunning(const QString& cameraId) const override + { + QMutexLocker locker(&m_stateMutex); + return m_runningCameras.contains(cameraId.trimmed()); + } + + bool getExposure(const QString& cameraIdOrAll, double& exposureMs) const override + { + const QStringList targets = targetCameras(cameraIdOrAll); + bool found = false; + double common = 0.0; + for (const QString& cameraId : targets) + { + QJsonObject response; + if (!request(driverhost::kCommandGetExposure, + cameraId, + {}, + response, + nullptr)) + { + return false; + } + const double value = response.value(QStringLiteral("exposureMs")).toDouble(); + if (found && !qFuzzyCompare(common + 1.0, value + 1.0)) return false; + common = value; + found = true; + } + if (found) exposureMs = common; + return found; + } + + bool setExposure(const QString& cameraIdOrAll, double exposureMs) override + { + const QStringList targets = targetCameras(cameraIdOrAll); + if (targets.isEmpty() || exposureMs <= 0.0) return false; + for (const QString& cameraId : targets) + { + QJsonObject fields; + fields.insert(QStringLiteral("value"), exposureMs); + QJsonObject response; + if (!request(driverhost::kCommandSetExposure, + cameraId, + fields, + response, + nullptr)) + { + return false; + } + } + return true; + } + + QStringList listProperties(const QString& deviceId) override + { + QJsonObject response; + if (!request(driverhost::kCommandListProperties, + deviceId, + {}, + response, + nullptr)) + { + return {}; + } + QStringList properties; + for (const QJsonValue& value : response.value(QStringLiteral("properties")).toArray()) + { + properties.append(value.toString()); + } + return properties; + } + + QString getProperty(const QString& deviceId, + const QString& name, + bool fromCache) override + { + QJsonObject response; + QJsonObject fields; + fields.insert(QStringLiteral("name"), name); + fields.insert(QStringLiteral("fromCache"), fromCache); + return request(driverhost::kCommandGetProperty, + deviceId, + fields, + response, + nullptr) + ? response.value(QStringLiteral("value")).toString() + : QString{}; + } + + bool setProperty(const QString& deviceId, + const QString& name, + const QString& value, + QString* errorMessage) override + { + QJsonObject fields; + fields.insert(QStringLiteral("name"), name); + fields.insert(QStringLiteral("value"), value); + QJsonObject response; + return request(driverhost::kCommandSetProperty, + deviceId, + fields, + response, + errorMessage); + } + + QString getPropertyType(const QString& deviceId, const QString& name) override + { + return propertyDetails(deviceId, name).value(QStringLiteral("propertyType")) + .toString(QStringLiteral("Unknown")); + } + + bool isPropertyReadOnly(const QString& deviceId, const QString& name) override + { + return propertyDetails(deviceId, name).value(QStringLiteral("readOnly")).toBool(true); + } + + bool isPropertyPreInit(const QString& deviceId, const QString& name) override + { + return propertyDetails(deviceId, name).value(QStringLiteral("preInit")).toBool(false); + } + + QStringList getAllowedPropertyValues(const QString& deviceId, + const QString& name) override + { + QStringList result; + for (const QJsonValue& value : + propertyDetails(deviceId, name).value(QStringLiteral("allowedValues")).toArray()) + { + result.append(value.toString()); + } + return result; + } + + bool hasPropertyLimits(const QString& deviceId, const QString& name) override + { + return propertyDetails(deviceId, name).value(QStringLiteral("hasLimits")).toBool(); + } + + double getPropertyLowerLimit(const QString& deviceId, const QString& name) override + { + return propertyDetails(deviceId, name).value(QStringLiteral("lowerLimit")).toDouble(); + } + + double getPropertyUpperLimit(const QString& deviceId, const QString& name) override + { + return propertyDetails(deviceId, name).value(QStringLiteral("upperLimit")).toDouble(); + } + + bool setROI(const QString& cameraId, int x, int y, int width, int height) override + { + QJsonObject fields; + fields.insert(QStringLiteral("x"), x); + fields.insert(QStringLiteral("y"), y); + fields.insert(QStringLiteral("width"), width); + fields.insert(QStringLiteral("height"), height); + QJsonObject response; + return request(driverhost::kCommandSetRoi, + cameraId, + fields, + response, + nullptr); + } + + bool clearROI(const QString& cameraId) override + { + QJsonObject response; + return request(driverhost::kCommandClearRoi, + cameraId, + {}, + response, + nullptr); + } + + bool getROI(const QString& cameraId, + int& x, + int& y, + int& width, + int& height) override + { + QJsonObject response; + if (!request(driverhost::kCommandGetRoi, + cameraId, + {}, + response, + nullptr)) + { + return false; + } + x = response.value(QStringLiteral("x")).toInt(); + y = response.value(QStringLiteral("y")).toInt(); + width = response.value(QStringLiteral("width")).toInt(); + height = response.value(QStringLiteral("height")).toInt(); + return true; + } + + bool captureEventFrame(const QString& cameraId, + ImageFrame& frame, + int timeoutMs) override + { + QMutexLocker requestLocker(&m_requestMutex); + if (!m_worker || !m_workerThread.isRunning()) return false; + bool ok = false; + DriverHostProviderTransport* const worker = m_worker; + const auto capture = [worker, &ok, &frame, cameraId, timeoutMs]() + { + ok = worker->captureEventFrame(cameraId, + frame, + (std::max)(1, timeoutMs), + nullptr); + }; + if (QThread::currentThread() == worker->thread()) capture(); + else QMetaObject::invokeMethod(worker, capture, Qt::BlockingQueuedConnection); + return ok; + } + + void setFrameDeliveryPaused(bool paused) override + { + if (!m_worker || !m_workerThread.isRunning()) return; + DriverHostProviderTransport* const worker = m_worker; + const auto update = [worker, paused]() + { + worker->setFrameDeliveryPaused(paused); + }; + if (QThread::currentThread() == worker->thread()) update(); + else QMetaObject::invokeMethod(worker, update, Qt::BlockingQueuedConnection); + } + + bool setRecordingFrameDeliveryEnabled(bool enabled) override + { + if (m_recordingDelivery == enabled) return true; + const QString mode = enabled + ? driverhost::kFrameDeliveryModeAllFrames + : m_highRateDelivery + ? driverhost::kFrameDeliveryModeLatestOnly + : driverhost::kFrameDeliveryModePreviewLatest; + if (!setDeliveryMode(mode)) return false; + m_recordingDelivery = enabled; + return true; + } + + bool setHighRateFrameDeliveryEnabled(bool enabled) override + { + if (m_highRateDelivery == enabled) return true; + if (!m_recordingDelivery) + { + const QString mode = enabled + ? driverhost::kFrameDeliveryModeLatestOnly + : driverhost::kFrameDeliveryModePreviewLatest; + if (!setDeliveryMode(mode)) return false; + } + m_highRateDelivery = enabled; + return true; + } + + bool isProcessingFrameTokenCurrent(const QString&, quint64) override { return false; } + void finishProcessingFrame(const QString&, quint64) override {} + + QString defaultXYStage() const override { return m_defaultXYStage; } + QString defaultZStage() const override { return m_defaultZStage; } + + bool getXYPosition(const QString& deviceId, + double& x, + double& y, + QString* errorMessage) const override + { + QJsonObject response; + if (!request(driverhost::kCommandGetXYPosition, + deviceId, + {}, + response, + errorMessage)) + { + return false; + } + x = response.value(QStringLiteral("x")).toDouble(); + y = response.value(QStringLiteral("y")).toDouble(); + return true; + } + + bool getZPosition(const QString& deviceId, + double& z, + QString* errorMessage) const override + { + QJsonObject response; + if (!request(driverhost::kCommandGetZPosition, + deviceId, + {}, + response, + errorMessage)) + { + return false; + } + z = response.value(QStringLiteral("z")).toDouble(); + return true; + } + + bool setRelativeXYPosition(const QString& deviceId, + double dx, + double dy, + QString* errorMessage) override + { + return stageWrite(driverhost::kCommandSetRelativeXYPosition, + deviceId, + dx, + dy, + errorMessage); + } + + bool setRelativeZPosition(const QString& deviceId, + double dz, + QString* errorMessage) override + { + QJsonObject fields; + fields.insert(QStringLiteral("z"), dz); + QJsonObject response; + return request(driverhost::kCommandSetRelativeZPosition, + deviceId, + fields, + response, + errorMessage); + } + + bool setXYPosition(const QString& deviceId, + double x, + double y, + QString* errorMessage) override + { + return stageWrite(driverhost::kCommandSetXYPosition, + deviceId, + x, + y, + errorMessage); + } + + bool setZPosition(const QString& deviceId, + double z, + QString* errorMessage) override + { + QJsonObject fields; + fields.insert(QStringLiteral("z"), z); + QJsonObject response; + return request(driverhost::kCommandSetZPosition, + deviceId, + fields, + response, + errorMessage); + } + + bool isShutterOpen(const QString& deviceId, + bool& open, + QString* errorMessage) const override + { + QJsonObject response; + if (!request(driverhost::kCommandGetShutterOpen, + deviceId, + {}, + response, + errorMessage)) + { + return false; + } + open = response.value(QStringLiteral("open")).toBool(); + return true; + } + + bool setShutterOpen(const QString& deviceId, + bool open, + QString* errorMessage) override + { + QJsonObject fields; + fields.insert(QStringLiteral("open"), open); + QJsonObject response; + return request(driverhost::kCommandSetShutterOpen, + deviceId, + fields, + response, + errorMessage); + } + + bool getState(const QString& deviceId, + long& state, + QString* errorMessage) const override + { + QJsonObject response; + if (!request(driverhost::kCommandGetState, + deviceId, + {}, + response, + errorMessage)) + { + return false; + } + state = static_cast(response.value(QStringLiteral("state")).toDouble()); + return true; + } + + bool setState(const QString& deviceId, + long state, + QString* errorMessage) override + { + QJsonObject fields; + fields.insert(QStringLiteral("state"), static_cast(state)); + QJsonObject response; + return request(driverhost::kCommandSetState, + deviceId, + fields, + response, + errorMessage); + } + + QString stateLabel(const QString& deviceId, long state) const override + { + QJsonObject fields; + fields.insert(QStringLiteral("state"), static_cast(state)); + QJsonObject response; + return request(driverhost::kCommandGetStateLabel, + deviceId, + fields, + response, + nullptr) + ? response.value(QStringLiteral("label")).toString() + : QString{}; + } + + QStringList availableConfigGroups() const override + { + QJsonObject response; + if (!request(driverhost::kCommandListConfigGroups, + {}, + {}, + response, + nullptr)) + { + return {}; + } + return jsonStringList(response.value(QStringLiteral("groups")).toArray()); + } + + QStringList availableConfigs(const QString& groupName) const override + { + QJsonObject fields; + fields.insert(QStringLiteral("group"), groupName); + QJsonObject response; + return request(driverhost::kCommandListConfigs, + {}, + fields, + response, + nullptr) + ? jsonStringList(response.value(QStringLiteral("configs")).toArray()) + : QStringList{}; + } + + QString currentConfig(const QString& groupName) const override + { + QJsonObject fields; + fields.insert(QStringLiteral("group"), groupName); + QJsonObject response; + return request(driverhost::kCommandGetCurrentConfig, + {}, + fields, + response, + nullptr) + ? response.value(QStringLiteral("config")).toString() + : QString{}; + } + + bool setConfig(const QString& groupName, + const QString& configName, + QString* errorMessage) override + { + QJsonObject fields; + fields.insert(QStringLiteral("group"), groupName); + fields.insert(QStringLiteral("config"), configName); + QJsonObject response; + return request(driverhost::kCommandSetConfig, + {}, + fields, + response, + errorMessage); + } + + private: + void deliverFrame(const ImageFrame& frame) + { + FrameSink sink; + { + QMutexLocker locker(&m_sinkMutex); + sink = m_frameSink; + } + if (sink) sink(frame); + } + + QStringList cameraIds() const + { + QStringList result; + for (const HardwareDeviceDescriptor& device : m_devices) + { + if (device.kind == HardwareDeviceKind::Camera) result.append(device.logicalId); + } + return result; + } + + bool isCamera(const QString& cameraId) const + { + const QString normalized = cameraId.trimmed(); + return std::any_of(m_devices.cbegin(), m_devices.cend(), + [&normalized](const HardwareDeviceDescriptor& device) + { + return device.kind == HardwareDeviceKind::Camera + && device.logicalId == normalized; + }); + } + + QStringList targetCameras(const QString& cameraIdOrAll) const + { + const QString target = cameraIdOrAll.trimmed(); + if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) + { + return cameraIds(); + } + return isCamera(target) ? QStringList{target} : QStringList{}; + } + + bool request(const QString& type, + const QString& deviceId, + const QJsonObject& fields, + QJsonObject& response, + QString* errorMessage) const + { + QMutexLocker requestLocker(&m_requestMutex); + if (!m_worker || !m_workerThread.isRunning()) + { + if (errorMessage) *errorMessage = QStringLiteral("DriverHost is not running"); + return false; + } + QJsonObject request = fields; + request.insert(driverhost::kMessageTypeField, type); + if (!deviceId.isEmpty()) + { + request.insert(driverhost::kDeviceIdField, deviceId.trimmed()); + } + bool transported = false; + QString transportError; + DriverHostProviderTransport* const worker = m_worker; + const auto send = [worker, request, &response, &transported, &transportError]() + { + transported = worker->sendRequest(request, + response, + kControlRequestTimeoutMs, + &transportError); + }; + if (QThread::currentThread() == worker->thread()) send(); + else QMetaObject::invokeMethod(worker, send, Qt::BlockingQueuedConnection); + if (!transported) + { + if (errorMessage) *errorMessage = transportError; + return false; + } + return responseSucceeded(response, errorMessage); + } + + QJsonObject propertyDetails(const QString& deviceId, const QString& name) + { + QJsonObject fields; + fields.insert(QStringLiteral("name"), name); + fields.insert(QStringLiteral("fromCache"), true); + QJsonObject response; + return request(driverhost::kCommandGetProperty, + deviceId, + fields, + response, + nullptr) + ? response + : QJsonObject{}; + } + + bool stageWrite(const QString& command, + const QString& deviceId, + double x, + double y, + QString* errorMessage) + { + QJsonObject fields; + fields.insert(QStringLiteral("x"), x); + fields.insert(QStringLiteral("y"), y); + QJsonObject response; + return request(command, deviceId, fields, response, errorMessage); + } + + bool setDeliveryMode(const QString& mode) + { + QMutexLocker requestLocker(&m_requestMutex); + if (!m_worker || !m_workerThread.isRunning()) return false; + bool ok = false; + DriverHostProviderTransport* const worker = m_worker; + const auto update = [worker, mode, &ok]() + { + ok = worker->setFrameDeliveryMode(mode, nullptr); + }; + if (QThread::currentThread() == worker->thread()) update(); + else QMetaObject::invokeMethod(worker, update, Qt::BlockingQueuedConnection); + return ok; + } + + static QStringList jsonStringList(const QJsonArray& array) + { + QStringList result; + for (const QJsonValue& value : array) result.append(value.toString()); + return result; + } + + QString m_expectedProviderId; + QString m_pluginPath; + QJsonObject m_options; + HardwareProviderDescriptor m_descriptor; + QList m_devices; + QString m_defaultXYStage; + QString m_defaultZStage; + QThread m_workerThread; + DriverHostProviderTransport* m_worker{nullptr}; + mutable QMutex m_requestMutex; + mutable QMutex m_sinkMutex; + FrameSink m_frameSink; + mutable QMutex m_stateMutex; + QSet m_runningCameras; + bool m_recordingDelivery{false}; + bool m_highRateDelivery{false}; + }; + + HardwareProviderPtr createDriverHostProviderProxy(const QString& providerId, + const QString& pluginPath, + const QJsonObject& options, + QString* errorMessage) + { + const QString normalizedProviderId = providerId.trimmed(); + const QString normalizedPluginPath = QFileInfo(pluginPath).absoluteFilePath(); + if (normalizedProviderId.isEmpty() || pluginPath.trimmed().isEmpty()) + { + if (errorMessage) *errorMessage = QStringLiteral("Provider ID and module path are required"); + return {}; + } + auto proxy = std::make_shared(normalizedProviderId, + normalizedPluginPath, + options); + if (!proxy->initialize(errorMessage)) return {}; + return proxy; + } +} diff --git a/ScopeOneCore/src/FrameBufferUtils.cpp b/ScopeOneCore/src/FrameBufferUtils.cpp index 56113df..17f7d3e 100644 --- a/ScopeOneCore/src/FrameBufferUtils.cpp +++ b/ScopeOneCore/src/FrameBufferUtils.cpp @@ -16,7 +16,6 @@ namespace scopeone::core::internal dst.cameraId = src.cameraId; dst.frameIndex = src.frameIndex; dst.timestampNs = src.timestampNs; - dst.clockStamp = src.clockStamp; dst.sourceRoiX = src.sourceRoiX; dst.sourceRoiY = src.sourceRoiY; dst.sourceRoiWidth = src.sourceRoiWidth; diff --git a/ScopeOneCore/src/FrameRouter.cpp b/ScopeOneCore/src/FrameRouter.cpp index 31b4446..04f6ae7 100644 --- a/ScopeOneCore/src/FrameRouter.cpp +++ b/ScopeOneCore/src/FrameRouter.cpp @@ -1,22 +1,14 @@ #include "internal/FrameRouter.h" -#include "scopeone/ClockService.h" - namespace scopeone::core::internal { - FrameRouter::FrameRouter(scopeone::core::ClockService* clockService, QObject* parent) + FrameRouter::FrameRouter(QObject* parent) : QObject(parent) - , m_clockService(clockService) { } void FrameRouter::publish(const scopeone::core::ImageFrame& frame) { - scopeone::core::ImageFrame routedFrame(frame); - if (!routedFrame.clockStamp.isValid() && m_clockService) - { - routedFrame.clockStamp = m_clockService->now(); - } - emit frameReady(routedFrame); + emit frameReady(frame); } } diff --git a/ScopeOneCore/src/HardwareProvider.cpp b/ScopeOneCore/src/HardwareProvider.cpp new file mode 100644 index 0000000..d1d1d02 --- /dev/null +++ b/ScopeOneCore/src/HardwareProvider.cpp @@ -0,0 +1,14 @@ +#include "scopeone/CameraProvider.h" +#include "scopeone/HardwareCapabilities.h" +#include "scopeone/HardwareProvider.h" + +namespace scopeone::core +{ + HardwareProvider::~HardwareProvider() = default; + DevicePropertyProvider::~DevicePropertyProvider() = default; + StageProvider::~StageProvider() = default; + ShutterProvider::~ShutterProvider() = default; + StateProvider::~StateProvider() = default; + ConfigurationProvider::~ConfigurationProvider() = default; + CameraProvider::~CameraProvider() = default; +} diff --git a/ScopeOneCore/src/HardwareRuntime.cpp b/ScopeOneCore/src/HardwareRuntime.cpp index 5314467..a039a02 100644 --- a/ScopeOneCore/src/HardwareRuntime.cpp +++ b/ScopeOneCore/src/HardwareRuntime.cpp @@ -15,79 +15,93 @@ namespace scopeone::core::internal void DeviceRegistry::clear() { - if (m_providers.isEmpty()) { - return; + QWriteLocker locker(&m_lock); + if (m_providers.isEmpty()) + { + return; + } + m_providers.clear(); } - m_providers.clear(); emit changed(); } - bool DeviceRegistry::registerProvider(const HardwareProviderPtr& provider) + bool DeviceRegistry::registerProvider( + const HardwareProviderPtr& provider, + const HardwareProviderDescriptor& descriptor, + const QList& devices) { if (!provider) { return false; } - const HardwareProviderDescriptor descriptor = provider->descriptor(); const QString providerId = descriptor.id.trimmed(); - if (providerId.isEmpty()) + if (providerId.isEmpty() || descriptor.id != providerId) { return false; } - const QList devices = provider->devices(); QSet logicalIds; for (const HardwareDeviceDescriptor& device : devices) { const QString logicalId = device.logicalId.trimmed(); if (logicalId.isEmpty() - || device.providerId.trimmed() != providerId + || device.logicalId != logicalId + || device.providerId != providerId || logicalIds.contains(logicalId)) { return false; } - const HardwareDeviceDescriptor existing = this->device(logicalId); - if (!existing.logicalId.isEmpty() && existing.providerId != providerId) - { - return false; - } logicalIds.insert(logicalId); } ProviderEntry entry; entry.provider = provider; + entry.descriptor = descriptor; entry.devices = devices; - m_providers.insert(providerId, std::move(entry)); + { + QWriteLocker locker(&m_lock); + for (auto providerIt = m_providers.cbegin(); + providerIt != m_providers.cend(); + ++providerIt) + { + if (providerIt.key() == providerId) + { + continue; + } + for (const HardwareDeviceDescriptor& existing : providerIt->devices) + { + if (logicalIds.contains(existing.logicalId)) + { + return false; + } + } + } + m_providers.insert(providerId, std::move(entry)); + } emit changed(); return true; } void DeviceRegistry::unregisterProvider(const QString& providerId) { - if (m_providers.remove(providerId.trimmed()) > 0) + bool removed = false; { - emit changed(); + QWriteLocker locker(&m_lock); + removed = m_providers.remove(providerId.trimmed()) > 0; } - } - - void DeviceRegistry::refreshProvider(const QString& providerId) - { - const QString normalizedId = providerId.trimmed(); - const auto it = m_providers.constFind(normalizedId); - if (it == m_providers.constEnd() || !it->provider) + if (removed) { - return; + emit changed(); } - const HardwareProviderPtr provider = it->provider; - registerProvider(provider); } QList DeviceRegistry::providers() const { + QReadLocker locker(&m_lock); QList result; result.reserve(m_providers.size()); for (auto it = m_providers.constBegin(); it != m_providers.constEnd(); ++it) { - result.append(it->provider->descriptor()); + result.append(it->descriptor); } std::sort(result.begin(), result.end(), [](const auto& lhs, const auto& rhs) { @@ -98,6 +112,7 @@ namespace scopeone::core::internal QList DeviceRegistry::devices() const { + QReadLocker locker(&m_lock); QList result; for (auto it = m_providers.constBegin(); it != m_providers.constEnd(); ++it) { @@ -113,6 +128,7 @@ namespace scopeone::core::internal HardwareDeviceDescriptor DeviceRegistry::device(const QString& logicalId) const { const QString normalizedId = logicalId.trimmed(); + QReadLocker locker(&m_lock); for (auto it = m_providers.constBegin(); it != m_providers.constEnd(); ++it) { for (const HardwareDeviceDescriptor& candidate : it->devices) @@ -128,20 +144,34 @@ namespace scopeone::core::internal HardwareProviderPtr DeviceRegistry::provider(const QString& providerId) const { + QReadLocker locker(&m_lock); const auto it = m_providers.constFind(providerId.trimmed()); return it == m_providers.constEnd() ? HardwareProviderPtr{} : it->provider; } HardwareProviderPtr DeviceRegistry::providerForDevice(const QString& logicalId) const { - const HardwareDeviceDescriptor descriptor = device(logicalId); - return descriptor.logicalId.isEmpty() ? HardwareProviderPtr{} : provider(descriptor.providerId); + const QString normalizedId = logicalId.trimmed(); + QReadLocker locker(&m_lock); + for (auto providerIt = m_providers.constBegin(); + providerIt != m_providers.constEnd(); + ++providerIt) + { + for (const HardwareDeviceDescriptor& device : providerIt->devices) + { + if (device.logicalId == normalizedId) + { + return providerIt->provider; + } + } + } + return {}; } HardwareRuntime::HardwareRuntime(QObject* parent) : QObject(parent) , m_registry(this) - , m_frameRouter(&m_clockService, this) + , m_frameRouter(this) , m_acquisitionEngine(&m_registry, this) { connect(&m_registry, &DeviceRegistry::changed, @@ -167,6 +197,58 @@ namespace scopeone::core::internal return dynamic_cast(provider.get()); } + DevicePropertyProvider* HardwareRuntime::propertyProviderForDevice( + const QString& logicalId) const + { + const HardwareProviderPtr provider = m_registry.providerForDevice(logicalId); + return dynamic_cast(provider.get()); + } + + StageProvider* HardwareRuntime::stageProviderForDevice(const QString& logicalId) const + { + const HardwareProviderPtr provider = m_registry.providerForDevice(logicalId); + return dynamic_cast(provider.get()); + } + + ShutterProvider* HardwareRuntime::shutterProviderForDevice(const QString& logicalId) const + { + const HardwareProviderPtr provider = m_registry.providerForDevice(logicalId); + return dynamic_cast(provider.get()); + } + + StateProvider* HardwareRuntime::stateProviderForDevice(const QString& logicalId) const + { + const HardwareProviderPtr provider = m_registry.providerForDevice(logicalId); + return dynamic_cast(provider.get()); + } + + ConfigurationProvider* HardwareRuntime::configurationProviderForGroup( + const QString& groupName) const + { + const QString normalizedGroup = groupName.trimmed(); + if (normalizedGroup.isEmpty()) + { + return nullptr; + } + ConfigurationProvider* match = nullptr; + for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + { + const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + auto* configurationProvider = dynamic_cast(provider.get()); + if (!configurationProvider + || !configurationProvider->availableConfigGroups().contains(normalizedGroup)) + { + continue; + } + if (match) + { + return nullptr; + } + match = configurationProvider; + } + return match; + } + QList HardwareRuntime::cameraProviders() const { QList result; @@ -222,8 +304,32 @@ namespace scopeone::core::internal CameraProvider* provider = cameraProviderForDevice(target); return provider && provider->getExposure(target, exposureMs); } - const QList providers = cameraProviders(); - return !providers.isEmpty() && providers.first()->getExposure(QStringLiteral("All"), exposureMs); + bool found = false; + double commonExposureMs = 0.0; + for (const HardwareDeviceDescriptor& device : m_registry.devices()) + { + if (device.kind != HardwareDeviceKind::Camera) + { + continue; + } + CameraProvider* provider = cameraProviderForDevice(device.logicalId); + double deviceExposureMs = 0.0; + if (!provider || !provider->getExposure(device.logicalId, deviceExposureMs)) + { + return false; + } + if (found && !qFuzzyCompare(commonExposureMs + 1.0, deviceExposureMs + 1.0)) + { + return false; + } + commonExposureMs = deviceExposureMs; + found = true; + } + if (found) + { + exposureMs = commonExposureMs; + } + return found; } bool HardwareRuntime::setExposure(const QString& cameraIdOrAll, double exposureMs) @@ -245,7 +351,7 @@ namespace scopeone::core::internal QStringList HardwareRuntime::listProperties(const QString& cameraId) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider ? provider->listProperties(cameraId) : QStringList{}; } @@ -253,7 +359,7 @@ namespace scopeone::core::internal const QString& name, bool fromCache) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider ? provider->getProperty(cameraId, name, fromCache) : QString{}; } @@ -262,50 +368,50 @@ namespace scopeone::core::internal const QString& value, QString* errorMessage) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider && provider->setProperty(cameraId, name, value, errorMessage); } QString HardwareRuntime::getPropertyType(const QString& cameraId, const QString& name) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider ? provider->getPropertyType(cameraId, name) : QStringLiteral("Unknown"); } bool HardwareRuntime::isPropertyReadOnly(const QString& cameraId, const QString& name) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return !provider || provider->isPropertyReadOnly(cameraId, name); } bool HardwareRuntime::isPropertyPreInit(const QString& cameraId, const QString& name) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider && provider->isPropertyPreInit(cameraId, name); } QStringList HardwareRuntime::getAllowedPropertyValues(const QString& cameraId, const QString& name) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider ? provider->getAllowedPropertyValues(cameraId, name) : QStringList{}; } bool HardwareRuntime::hasPropertyLimits(const QString& cameraId, const QString& name) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider && provider->hasPropertyLimits(cameraId, name); } double HardwareRuntime::getPropertyLowerLimit(const QString& cameraId, const QString& name) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider ? provider->getPropertyLowerLimit(cameraId, name) : 0.0; } double HardwareRuntime::getPropertyUpperLimit(const QString& cameraId, const QString& name) { - CameraProvider* provider = cameraProviderForDevice(cameraId); + DevicePropertyProvider* provider = propertyProviderForDevice(cameraId); return provider ? provider->getPropertyUpperLimit(cameraId, name) : 0.0; } @@ -343,135 +449,435 @@ namespace scopeone::core::internal return provider && provider->captureEventFrame(cameraId, frame, timeoutMs); } - void HardwareRuntime::clear() + void HardwareRuntime::setFrameDeliveryPaused(bool paused) { - m_acquisitionEngine.reset(); for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) { const HardwareProviderPtr provider = m_registry.provider(descriptor.id); - if (auto* cameraProvider = dynamic_cast(provider.get())) + if (auto* control = dynamic_cast(provider.get())) { - cameraProvider->stopPreview(); - cameraProvider->setFrameSink({}); + control->setFrameDeliveryPaused(paused); } } - m_registry.clear(); } - bool HardwareRuntime::registerProvider(const HardwareProviderPtr& provider) + bool HardwareRuntime::setRecordingFrameDeliveryEnabled(bool enabled) + { + QList changed; + for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + { + const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + if (auto* control = dynamic_cast(provider.get())) + { + if (!control->setRecordingFrameDeliveryEnabled(enabled)) + { + for (CameraRuntimeControl* previous : changed) + { + previous->setRecordingFrameDeliveryEnabled(!enabled); + } + return false; + } + changed.append(control); + } + } + return true; + } + + bool HardwareRuntime::setHighRateFrameDeliveryEnabled(bool enabled) + { + QList changed; + for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + { + const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + if (auto* control = dynamic_cast(provider.get())) + { + if (!control->setHighRateFrameDeliveryEnabled(enabled)) + { + for (CameraRuntimeControl* previous : changed) + { + previous->setHighRateFrameDeliveryEnabled(!enabled); + } + return false; + } + changed.append(control); + } + } + return true; + } + + bool HardwareRuntime::isProcessingFrameTokenCurrent(const QString& cameraId, quint64 token) + { + const HardwareProviderPtr provider = m_registry.providerForDevice(cameraId); + auto* control = dynamic_cast(provider.get()); + return control && control->isProcessingFrameTokenCurrent(cameraId, token); + } + + void HardwareRuntime::finishProcessingFrame(const QString& cameraId, quint64 token) + { + const HardwareProviderPtr provider = m_registry.providerForDevice(cameraId); + if (auto* control = dynamic_cast(provider.get())) + { + control->finishProcessingFrame(cameraId, token); + } + } + + QString HardwareRuntime::defaultXYStage() const + { + QString match; + for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + { + const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + if (auto* stageProvider = dynamic_cast(provider.get())) + { + const QString deviceId = stageProvider->defaultXYStage().trimmed(); + const HardwareDeviceDescriptor device = m_registry.device(deviceId); + if (device.providerId == descriptor.id + && device.kind == HardwareDeviceKind::XYStage) + { + if (!match.isEmpty() && match != deviceId) + { + return {}; + } + match = deviceId; + } + } + } + return match; + } + + QString HardwareRuntime::defaultZStage() const + { + QString match; + for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + { + const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + if (auto* stageProvider = dynamic_cast(provider.get())) + { + const QString deviceId = stageProvider->defaultZStage().trimmed(); + const HardwareDeviceDescriptor device = m_registry.device(deviceId); + if (device.providerId == descriptor.id + && device.kind == HardwareDeviceKind::ZStage) + { + if (!match.isEmpty() && match != deviceId) + { + return {}; + } + match = deviceId; + } + } + } + return match; + } + + bool HardwareRuntime::getXYPosition(const QString& deviceId, + double& x, + double& y, + QString* errorMessage) const { - if (!provider || provider->descriptor().id.trimmed().isEmpty()) + StageProvider* provider = stageProviderForDevice(deviceId); + if (!provider) { + if (errorMessage) + { + *errorMessage = QStringLiteral("Stage provider not available"); + } return false; } - const HardwareProviderPtr previous = m_registry.provider(provider->descriptor().id); - auto* cameraProvider = dynamic_cast(provider.get()); - if (cameraProvider) + return provider->getXYPosition(deviceId, x, y, errorMessage); + } + + bool HardwareRuntime::getZPosition(const QString& deviceId, + double& z, + QString* errorMessage) const + { + StageProvider* provider = stageProviderForDevice(deviceId); + if (!provider) { - cameraProvider->setFrameSink([this](const ImageFrame& frame) + if (errorMessage) { - m_frameRouter.publish(frame); - }); + *errorMessage = QStringLiteral("Stage provider not available"); + } + return false; } - const QList providerDevices = provider->devices(); - const bool providerHasCamera = std::any_of( - providerDevices.cbegin(), - providerDevices.cend(), - [](const HardwareDeviceDescriptor& device) + return provider->getZPosition(deviceId, z, errorMessage); + } + + bool HardwareRuntime::setRelativeXYPosition(const QString& deviceId, + double dx, + double dy, + QString* errorMessage) + { + StageProvider* provider = stageProviderForDevice(deviceId); + if (!provider) + { + if (errorMessage) { - return device.kind == HardwareDeviceKind::Camera; - }); - if (providerHasCamera) + *errorMessage = QStringLiteral("Stage provider not available"); + } + return false; + } + return provider->setRelativeXYPosition(deviceId, dx, dy, errorMessage); + } + + bool HardwareRuntime::setRelativeZPosition(const QString& deviceId, + double dz, + QString* errorMessage) + { + StageProvider* provider = stageProviderForDevice(deviceId); + if (!provider) { - m_acquisitionEngine.prepare(); + if (errorMessage) + { + *errorMessage = QStringLiteral("Stage provider not available"); + } + return false; } - if (!m_registry.registerProvider(provider)) + return provider->setRelativeZPosition(deviceId, dz, errorMessage); + } + + bool HardwareRuntime::setXYPosition(const QString& deviceId, + double x, + double y, + QString* errorMessage) + { + StageProvider* provider = stageProviderForDevice(deviceId); + if (!provider) { - if (cameraProvider && previous != provider) + if (errorMessage) { - cameraProvider->setFrameSink({}); + *errorMessage = QStringLiteral("Stage provider not available"); } - const QList registeredDevices = m_registry.devices(); - const bool hasRegisteredCamera = std::any_of( - registeredDevices.cbegin(), - registeredDevices.cend(), - [](const HardwareDeviceDescriptor& device) - { - return device.kind == HardwareDeviceKind::Camera; - }); - if (!hasRegisteredCamera) + return false; + } + return provider->setXYPosition(deviceId, x, y, errorMessage); + } + + bool HardwareRuntime::setZPosition(const QString& deviceId, + double z, + QString* errorMessage) + { + StageProvider* provider = stageProviderForDevice(deviceId); + if (!provider) + { + if (errorMessage) { - m_acquisitionEngine.reset(); + *errorMessage = QStringLiteral("Stage provider not available"); } return false; } - if (previous && previous != provider) + return provider->setZPosition(deviceId, z, errorMessage); + } + + bool HardwareRuntime::isShutterOpen(const QString& deviceId, + bool& open, + QString* errorMessage) const + { + ShutterProvider* provider = shutterProviderForDevice(deviceId); + if (!provider) { - if (auto* previousCameraProvider = dynamic_cast(previous.get())) + if (errorMessage) { - previousCameraProvider->stopPreview(); - previousCameraProvider->setFrameSink({}); + *errorMessage = QStringLiteral("Shutter provider not available"); } + return false; } - if (!providerHasCamera) + return provider->isShutterOpen(deviceId, open, errorMessage); + } + + bool HardwareRuntime::setShutterOpen(const QString& deviceId, + bool open, + QString* errorMessage) + { + ShutterProvider* provider = shutterProviderForDevice(deviceId); + if (!provider) { - const QList registeredDevices = m_registry.devices(); - const bool hasRegisteredCamera = std::any_of( - registeredDevices.cbegin(), - registeredDevices.cend(), - [](const HardwareDeviceDescriptor& device) - { - return device.kind == HardwareDeviceKind::Camera; - }); - if (!hasRegisteredCamera) + if (errorMessage) { - m_acquisitionEngine.reset(); + *errorMessage = QStringLiteral("Shutter provider not available"); } + return false; } - return true; + return provider->setShutterOpen(deviceId, open, errorMessage); } - void HardwareRuntime::unregisterProvider(const QString& providerId) + bool HardwareRuntime::getState(const QString& deviceId, + long& state, + QString* errorMessage) const { - const HardwareProviderPtr provider = m_registry.provider(providerId); - if (auto* cameraProvider = dynamic_cast(provider.get())) + StateProvider* provider = stateProviderForDevice(deviceId); + if (!provider) { - cameraProvider->stopPreview(); - cameraProvider->setFrameSink({}); + if (errorMessage) + { + *errorMessage = QStringLiteral("State provider not available"); + } + return false; } - m_registry.unregisterProvider(providerId); - const QList devices = m_registry.devices(); - const bool hasCamera = std::any_of( - devices.cbegin(), - devices.cend(), - [](const HardwareDeviceDescriptor& device) + return provider->getState(deviceId, state, errorMessage); + } + + bool HardwareRuntime::setState(const QString& deviceId, + long state, + QString* errorMessage) + { + StateProvider* provider = stateProviderForDevice(deviceId); + if (!provider) + { + if (errorMessage) { - return device.kind == HardwareDeviceKind::Camera; - }); - if (!hasCamera) + *errorMessage = QStringLiteral("State provider not available"); + } + return false; + } + return provider->setState(deviceId, state, errorMessage); + } + + QString HardwareRuntime::stateLabel(const QString& deviceId, long state) const + { + StateProvider* provider = stateProviderForDevice(deviceId); + return provider ? provider->stateLabel(deviceId, state) : QString{}; + } + + QStringList HardwareRuntime::availableConfigGroups() const + { + QHash groupCounts; + for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) { - m_acquisitionEngine.reset(); + const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + if (auto* configurationProvider = dynamic_cast(provider.get())) + { + QSet providerGroups; + for (const QString& group : configurationProvider->availableConfigGroups()) + { + const QString normalizedGroup = group.trimmed(); + if (!normalizedGroup.isEmpty()) + { + providerGroups.insert(normalizedGroup); + } + } + for (const QString& group : providerGroups) + { + ++groupCounts[group]; + } + } } + QStringList groups; + for (auto it = groupCounts.cbegin(); it != groupCounts.cend(); ++it) + { + if (it.value() == 1) + { + groups.append(it.key()); + } + } + std::sort(groups.begin(), groups.end()); + return groups; + } + + QStringList HardwareRuntime::availableConfigs(const QString& groupName) const + { + ConfigurationProvider* provider = configurationProviderForGroup(groupName); + return provider ? provider->availableConfigs(groupName) : QStringList{}; + } + + QString HardwareRuntime::currentConfig(const QString& groupName) const + { + ConfigurationProvider* provider = configurationProviderForGroup(groupName); + return provider ? provider->currentConfig(groupName) : QString{}; } - void HardwareRuntime::refreshProvider(const QString& providerId) + bool HardwareRuntime::setConfig(const QString& groupName, + const QString& configName, + QString* errorMessage) { - m_registry.refreshProvider(providerId); - const QList devices = m_registry.devices(); - const bool hasCamera = std::any_of( - devices.cbegin(), - devices.cend(), - [](const HardwareDeviceDescriptor& device) + ConfigurationProvider* provider = configurationProviderForGroup(groupName); + if (!provider) + { + if (errorMessage) { - return device.kind == HardwareDeviceKind::Camera; - }); - if (hasCamera) + *errorMessage = QStringLiteral("Configuration provider not available"); + } + return false; + } + return provider->setConfig(groupName, configName, errorMessage); + } + + void HardwareRuntime::clear() + { + for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + { + const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + if (auto* cameraProvider = dynamic_cast(provider.get())) + { + cameraProvider->stopPreview(); + cameraProvider->setFrameSink({}); + } + } + m_registry.clear(); + } + + bool HardwareRuntime::registerProvider(const HardwareProviderPtr& provider) + { + if (!provider) { - m_acquisitionEngine.prepare(); + return false; + } + const HardwareProviderDescriptor descriptor = provider->descriptor(); + if (descriptor.id.trimmed().isEmpty()) + { + return false; + } + const HardwareProviderPtr previous = m_registry.provider(descriptor.id); + if (previous && previous != provider) + { + return false; + } + auto* cameraProvider = dynamic_cast(provider.get()); + auto* stageProvider = dynamic_cast(provider.get()); + auto* shutterProvider = dynamic_cast(provider.get()); + auto* stateProvider = dynamic_cast(provider.get()); + const QList providerDevices = provider->devices(); + for (const HardwareDeviceDescriptor& device : providerDevices) + { + const bool supported = + (device.kind != HardwareDeviceKind::Camera || cameraProvider) + && ((device.kind != HardwareDeviceKind::XYStage + && device.kind != HardwareDeviceKind::ZStage) + || stageProvider) + && (device.kind != HardwareDeviceKind::Shutter || shutterProvider) + && (device.kind != HardwareDeviceKind::State || stateProvider); + if (!supported) + { + return false; + } + } + if (!m_registry.registerProvider(provider, descriptor, providerDevices)) + { + return false; + } + if (cameraProvider) + { + cameraProvider->setFrameSink([this](const ImageFrame& frame) + { + m_frameRouter.publish(frame); + }); } - else + return true; + } + + void HardwareRuntime::unregisterProvider(const QString& providerId) + { + const HardwareProviderPtr provider = m_registry.provider(providerId); + if (auto* cameraProvider = dynamic_cast(provider.get())) { - m_acquisitionEngine.reset(); + cameraProvider->stopPreview(); + cameraProvider->setFrameSink({}); } + m_registry.unregisterProvider(providerId); + } + + bool HardwareRuntime::refreshProvider(const QString& providerId) + { + const HardwareProviderPtr provider = m_registry.provider(providerId); + return provider && registerProvider(provider); } } diff --git a/ScopeOneCore/src/MDAManager.cpp b/ScopeOneCore/src/MDAManager.cpp index efbd1cf..526c491 100644 --- a/ScopeOneCore/src/MDAManager.cpp +++ b/ScopeOneCore/src/MDAManager.cpp @@ -1,15 +1,13 @@ #include "internal/MDAManager.h" #include "scopeone/CameraProvider.h" -#include "MMCore.h" -#include "scopeone/ClockService.h" +#include "scopeone/HardwareCapabilities.h" #include #include #include #include #include -#include #include #include @@ -24,9 +22,8 @@ namespace scopeone::core::internal } // Creates the MDA manager and registers queued signal types - MDAManager::MDAManager(std::shared_ptr core, QObject* parent) + MDAManager::MDAManager(QObject* parent) : QObject(parent) - , m_mmcore(std::move(core)) { qRegisterMetaType("scopeone::core::internal::MDAOutput"); m_threadPool.setMaxThreadCount(1); @@ -44,6 +41,11 @@ namespace scopeone::core::internal m_cameraProvider = cameraProvider; } + void MDAManager::setStageProvider(StageProvider* stageProvider) + { + m_stageProvider = stageProvider; + } + // Starts one immutable acquisition event sequence bool MDAManager::start(const QList& events, bool block) { @@ -89,10 +91,10 @@ namespace scopeone::core::internal // Executes a precomputed event sequence in order void MDAManager::runSequence(QList events) { - if (!m_mmcore) + if (!m_cameraProvider) { m_running.store(false); - emit sequenceError(QStringLiteral("MMCore not available")); + emit sequenceError(QStringLiteral("Camera provider not available")); return; } @@ -151,13 +153,8 @@ namespace scopeone::core::internal // Moves hardware into place before capture bool MDAManager::setupEvent(const AcquisitionEvent& event, QString* errorMessage) { - if (!m_mmcore) - { - if (errorMessage) *errorMessage = QStringLiteral("MMCore not available"); - return false; - } - - if (event.exposureMs > 0.0 && !setExposure(event.exposureMs, errorMessage)) + if (event.exposureMs > 0.0 + && !setExposure(event.cameraIds, event.exposureMs, errorMessage)) { return false; } @@ -170,122 +167,29 @@ namespace scopeone::core::internal return false; } - try - { - m_mmcore->waitForSystem(); - } - catch (const CMMError& e) - { - if (errorMessage) *errorMessage = QString::fromStdString(e.getMsg()); - return false; - } return true; } // Routes one event to the active capture implementation bool MDAManager::execEvent(const AcquisitionEvent& event, MDAOutput& output, QString* errorMessage) { - if (event.cameraIds.size() > 1) - { - if (!m_cameraProvider) - { - if (errorMessage) *errorMessage = QStringLiteral("Camera provider not available"); - return false; - } - return execEventMultiCamera(event, output, errorMessage); - } - return execEventSingleCamera(event, output, errorMessage); - } - - // Captures one event through the native MMCore camera path - bool MDAManager::execEventSingleCamera(const AcquisitionEvent& event, - MDAOutput& output, - QString* errorMessage) - { - if (!m_mmcore) + if (!m_cameraProvider) { - if (errorMessage) *errorMessage = QStringLiteral("MMCore not available"); + if (errorMessage) *errorMessage = QStringLiteral("Camera provider not available"); return false; } - - try + if (event.cameraIds.isEmpty()) { - m_mmcore->snapImage(); - - const unsigned width = m_mmcore->getImageWidth(); - const unsigned height = m_mmcore->getImageHeight(); - const unsigned bytesPerPixel = m_mmcore->getBytesPerPixel(); - if (bytesPerPixel != 1 && bytesPerPixel != 2) - { - if (errorMessage) *errorMessage = QStringLiteral("Unsupported pixel format"); - return false; - } - - const qint64 stride = static_cast(width) * bytesPerPixel; - const qint64 byteCount = stride * height; - if (width > static_cast((std::numeric_limits::max)()) - || height > static_cast((std::numeric_limits::max)()) - || stride > (std::numeric_limits::max)() - || byteCount <= 0 - || byteCount > (std::numeric_limits::max)()) - { - if (errorMessage) *errorMessage = QStringLiteral("Image frame is too large"); - return false; - } - - const unsigned char* ptr = static_cast(m_mmcore->getImage()); - if (!ptr) - { - if (errorMessage) *errorMessage = QStringLiteral("Empty image buffer"); - return false; - } - - ImageFrame frame; - frame.cameraId = event.cameraIds.isEmpty() ? QString() : event.cameraIds.first(); - frame.width = static_cast(width); - frame.height = static_cast(height); - frame.stride = static_cast(stride); - frame.pixelFormat = bytesPerPixel == 2 ? ImagePixelFormat::Mono16 : ImagePixelFormat::Mono8; - frame.bitsPerSample = ImageFrame::normalizedBitsPerSample( - frame.pixelFormat, - static_cast(m_mmcore->getImageBitDepth())); - frame.timestampNs = currentTimestampNs(); - frame.clockStamp = scopeone::core::ClockService{}.now(); - frame.sourceRoiWidth = frame.width; - frame.sourceRoiHeight = frame.height; - if (!frame.cameraId.isEmpty()) - { - try - { - m_mmcore->getROI(frame.cameraId.toStdString().c_str(), - frame.sourceRoiX, - frame.sourceRoiY, - frame.sourceRoiWidth, - frame.sourceRoiHeight); - } - catch (const CMMError&) - { - frame.sourceRoiX = 0; - frame.sourceRoiY = 0; - frame.sourceRoiWidth = frame.width; - frame.sourceRoiHeight = frame.height; - } - } - frame.bytes = QByteArray(reinterpret_cast(ptr), static_cast(byteCount)); - output.frames.insert(frame.cameraId, frame); - return true; - } - catch (const CMMError& e) - { - if (errorMessage) *errorMessage = QString::fromStdString(e.getMsg()); + if (errorMessage) *errorMessage = QStringLiteral("No camera selected for acquisition event"); return false; } + return captureCameras(event, output, errorMessage); } - // Captures one event across active camera processes - bool MDAManager::execEventMultiCamera(const AcquisitionEvent& event, - MDAOutput& output, - QString* errorMessage) + // Captures one event across all selected camera providers + bool MDAManager::captureCameras(const AcquisitionEvent& event, + MDAOutput& output, + QString* errorMessage) { const int captureTimeoutMs = static_cast((std::max)(1500.0, event.exposureMs * 4.0 + 500.0)); @@ -335,59 +239,61 @@ namespace scopeone::core::internal } // Applies exposure for the next event - bool MDAManager::setExposure(double exposureMs, QString* errorMessage) + bool MDAManager::setExposure(const QStringList& cameraIds, + double exposureMs, + QString* errorMessage) { - try + if (!m_cameraProvider || cameraIds.isEmpty()) { - m_mmcore->setExposure(exposureMs); - return true; + if (errorMessage) *errorMessage = QStringLiteral("Camera provider not available"); + return false; } - catch (const CMMError& e) + for (const QString& cameraId : cameraIds) { - if (errorMessage) *errorMessage = QString::fromStdString(e.getMsg()); - return false; + if (!m_cameraProvider->setExposure(cameraId, exposureMs)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Failed to set exposure for camera: %1") + .arg(cameraId); + } + return false; + } } + return true; } // Moves the active XY stage to an event position bool MDAManager::moveXY(double x, double y, QString* errorMessage) { - try + if (!m_stageProvider) { - const std::string stage = m_mmcore->getXYStageDevice(); - if (stage.empty()) - { - if (errorMessage) *errorMessage = QStringLiteral("No XY stage device configured"); - return false; - } - m_mmcore->setXYPosition(stage.c_str(), x, y); - return true; + if (errorMessage) *errorMessage = QStringLiteral("Stage provider not available"); + return false; } - catch (const CMMError& e) + const QString stage = m_stageProvider->defaultXYStage(); + if (stage.isEmpty()) { - if (errorMessage) *errorMessage = QString::fromStdString(e.getMsg()); + if (errorMessage) *errorMessage = QStringLiteral("No XY stage device configured"); return false; } + return m_stageProvider->setXYPosition(stage, x, y, errorMessage); } // Moves the active focus device to an event position bool MDAManager::moveZ(double z, QString* errorMessage) { - try + if (!m_stageProvider) { - const std::string focus = m_mmcore->getFocusDevice(); - if (focus.empty()) - { - if (errorMessage) *errorMessage = QStringLiteral("No focus device configured"); - return false; - } - m_mmcore->setPosition(focus.c_str(), z); - return true; + if (errorMessage) *errorMessage = QStringLiteral("Stage provider not available"); + return false; } - catch (const CMMError& e) + const QString focus = m_stageProvider->defaultZStage(); + if (focus.isEmpty()) { - if (errorMessage) *errorMessage = QString::fromStdString(e.getMsg()); + if (errorMessage) *errorMessage = QStringLiteral("No focus device configured"); return false; } + return m_stageProvider->setZPosition(focus, z, errorMessage); } } // namespace scopeone::core::internal diff --git a/ScopeOneCore/src/MMCoreManager.cpp b/ScopeOneCore/src/MMCoreManager.cpp index 6ee53f3..7915533 100644 --- a/ScopeOneCore/src/MMCoreManager.cpp +++ b/ScopeOneCore/src/MMCoreManager.cpp @@ -32,6 +32,26 @@ namespace scopeone::core::internal return scopeone::core::HardwareDeviceKind::State; case MM::HubDevice: return scopeone::core::HardwareDeviceKind::Hub; + case MM::SerialDevice: + return scopeone::core::HardwareDeviceKind::Serial; + case MM::GenericDevice: + return scopeone::core::HardwareDeviceKind::Generic; + case MM::AutoFocusDevice: + return scopeone::core::HardwareDeviceKind::AutoFocus; + case MM::ImageProcessorDevice: + return scopeone::core::HardwareDeviceKind::ImageProcessor; + case MM::SignalIODevice: + return scopeone::core::HardwareDeviceKind::SignalIO; + case MM::MagnifierDevice: + return scopeone::core::HardwareDeviceKind::Magnifier; + case MM::SLMDevice: + return scopeone::core::HardwareDeviceKind::SLM; + case MM::GalvoDevice: + return scopeone::core::HardwareDeviceKind::Galvo; + case MM::PressurePumpDevice: + return scopeone::core::HardwareDeviceKind::PressurePump; + case MM::VolumetricPumpDevice: + return scopeone::core::HardwareDeviceKind::VolumetricPump; default: return scopeone::core::HardwareDeviceKind::Unknown; } @@ -106,7 +126,7 @@ namespace scopeone::core::internal QHash> startupProperties; }; - // Reads camera property entries that must be replayed by agent processes + // Read camera property entries that must be replayed by DriverHost processes ConfigPropertyReplay configPropertyReplay(const QString& configPath) { ConfigPropertyReplay replay; @@ -491,7 +511,7 @@ namespace scopeone::core::internal { const bool started = result.useSingleCamera ? cameraManager.configureNativeCamera(m_mmcore, camera.label, camera.exposureMs) - : cameraManager.addAgentCamera(camera.label, + : cameraManager.addDriverHostCamera(camera.label, camera.adapter, camera.device, camera.preInitProperties, @@ -499,6 +519,14 @@ namespace scopeone::core::internal camera.exposureMs); if (!started) { + for (auto& device : result.devices) + { + if (device.logicalId == camera.label) + { + device.state = scopeone::core::HardwareDeviceState::Faulted; + break; + } + } ++result.failCount; result.failedDevices.append(camera.label); result.failedDevices.removeDuplicates(); @@ -506,6 +534,14 @@ namespace scopeone::core::internal return false; } + for (auto& device : result.devices) + { + if (device.logicalId == camera.label) + { + device.state = scopeone::core::HardwareDeviceState::Initialized; + break; + } + } result.cameraIds.append(camera.label); } return true; diff --git a/ScopeOneCore/src/MicroManagerProvider.cpp b/ScopeOneCore/src/MicroManagerProvider.cpp index 390eb66..fb2ae9c 100644 --- a/ScopeOneCore/src/MicroManagerProvider.cpp +++ b/ScopeOneCore/src/MicroManagerProvider.cpp @@ -1,11 +1,44 @@ #include "internal/MicroManagerProvider.h" +#include "MMCore.h" + +#include +#include + +#include #include +#include namespace scopeone::core::internal { - MicroManagerProvider::MicroManagerProvider(CameraProvider* cameraProvider) - : m_cameraProvider(cameraProvider) + namespace + { + QStringList toQStringList(const std::vector& values) + { + QStringList result; + result.reserve(static_cast(values.size())); + for (const std::string& value : values) + { + result.append(QString::fromStdString(value)); + } + return result; + } + + void setError(QString* errorMessage, const CMMError& error) + { + if (errorMessage) + { + *errorMessage = QString::fromStdString(error.getMsg()); + } + } + } + + MicroManagerProvider::MicroManagerProvider(std::shared_ptr core, + CameraProvider* cameraProvider, + CameraRuntimeControl* cameraRuntimeControl) + : m_core(std::move(core)) + , m_cameraProvider(cameraProvider) + , m_cameraRuntimeControl(cameraRuntimeControl) { } @@ -28,6 +61,17 @@ namespace scopeone::core::internal m_devices = devices; } + bool MicroManagerProvider::isCamera(const QString& deviceId) const + { + const QString normalizedId = deviceId.trimmed(); + return std::any_of(m_devices.cbegin(), m_devices.cend(), + [&normalizedId](const HardwareDeviceDescriptor& device) + { + return device.logicalId == normalizedId + && device.kind == HardwareDeviceKind::Camera; + }); + } + void MicroManagerProvider::setFrameSink(FrameSink sink) { if (m_cameraProvider) @@ -73,14 +117,52 @@ namespace scopeone::core::internal QStringList MicroManagerProvider::listProperties(const QString& cameraId) { - return m_cameraProvider ? m_cameraProvider->listProperties(cameraId) : QStringList{}; + if (isCamera(cameraId)) + { + return m_cameraProvider ? m_cameraProvider->listProperties(cameraId) : QStringList{}; + } + if (!m_core) + { + return {}; + } + try + { + return toQStringList( + m_core->getDevicePropertyNames(cameraId.trimmed().toStdString().c_str())); + } + catch (const CMMError&) + { + return {}; + } } QString MicroManagerProvider::getProperty(const QString& cameraId, const QString& name, bool fromCache) { - return m_cameraProvider ? m_cameraProvider->getProperty(cameraId, name, fromCache) : QString{}; + if (isCamera(cameraId)) + { + return m_cameraProvider + ? m_cameraProvider->getProperty(cameraId, name, fromCache) + : QString{}; + } + if (!m_core) + { + return {}; + } + try + { + const std::string device = cameraId.trimmed().toStdString(); + const std::string property = name.trimmed().toStdString(); + return QString::fromStdString( + fromCache + ? m_core->getPropertyFromCache(device.c_str(), property.c_str()) + : m_core->getProperty(device.c_str(), property.c_str())); + } + catch (const CMMError&) + { + return {}; + } } bool MicroManagerProvider::setProperty(const QString& cameraId, @@ -88,48 +170,195 @@ namespace scopeone::core::internal const QString& value, QString* errorMessage) { - return m_cameraProvider - && m_cameraProvider->setProperty(cameraId, name, value, errorMessage); + if (isCamera(cameraId)) + { + return m_cameraProvider + && m_cameraProvider->setProperty(cameraId, name, value, errorMessage); + } + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + const std::string device = cameraId.trimmed().toStdString(); + m_core->setProperty(device.c_str(), + name.trimmed().toStdString().c_str(), + value.toStdString().c_str()); + m_core->waitForDevice(device.c_str()); + m_core->updateSystemStateCache(); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } } QString MicroManagerProvider::getPropertyType(const QString& cameraId, const QString& name) { - return m_cameraProvider - ? m_cameraProvider->getPropertyType(cameraId, name) - : QStringLiteral("Unknown"); + if (isCamera(cameraId)) + { + return m_cameraProvider + ? m_cameraProvider->getPropertyType(cameraId, name) + : QStringLiteral("Unknown"); + } + if (!m_core) + { + return QStringLiteral("Unknown"); + } + try + { + const MM::PropertyType type = m_core->getPropertyType( + cameraId.trimmed().toStdString().c_str(), + name.trimmed().toStdString().c_str()); + switch (type) + { + case MM::String: return QStringLiteral("String"); + case MM::Float: return QStringLiteral("Float"); + case MM::Integer: return QStringLiteral("Integer"); + default: return QStringLiteral("Unknown"); + } + } + catch (const CMMError&) + { + return QStringLiteral("Unknown"); + } } bool MicroManagerProvider::isPropertyReadOnly(const QString& cameraId, const QString& name) { - return !m_cameraProvider || m_cameraProvider->isPropertyReadOnly(cameraId, name); + if (isCamera(cameraId)) + { + return !m_cameraProvider || m_cameraProvider->isPropertyReadOnly(cameraId, name); + } + if (!m_core) + { + return true; + } + try + { + return m_core->isPropertyReadOnly(cameraId.trimmed().toStdString().c_str(), + name.trimmed().toStdString().c_str()); + } + catch (const CMMError&) + { + return true; + } } bool MicroManagerProvider::isPropertyPreInit(const QString& cameraId, const QString& name) { - return m_cameraProvider && m_cameraProvider->isPropertyPreInit(cameraId, name); + if (isCamera(cameraId)) + { + return m_cameraProvider && m_cameraProvider->isPropertyPreInit(cameraId, name); + } + if (!m_core) + { + return false; + } + try + { + return m_core->isPropertyPreInit(cameraId.trimmed().toStdString().c_str(), + name.trimmed().toStdString().c_str()); + } + catch (const CMMError&) + { + return false; + } } QStringList MicroManagerProvider::getAllowedPropertyValues(const QString& cameraId, const QString& name) { - return m_cameraProvider - ? m_cameraProvider->getAllowedPropertyValues(cameraId, name) - : QStringList{}; + if (isCamera(cameraId)) + { + return m_cameraProvider + ? m_cameraProvider->getAllowedPropertyValues(cameraId, name) + : QStringList{}; + } + if (!m_core) + { + return {}; + } + try + { + return toQStringList(m_core->getAllowedPropertyValues( + cameraId.trimmed().toStdString().c_str(), + name.trimmed().toStdString().c_str())); + } + catch (const CMMError&) + { + return {}; + } } bool MicroManagerProvider::hasPropertyLimits(const QString& cameraId, const QString& name) { - return m_cameraProvider && m_cameraProvider->hasPropertyLimits(cameraId, name); + if (isCamera(cameraId)) + { + return m_cameraProvider && m_cameraProvider->hasPropertyLimits(cameraId, name); + } + if (!m_core) + { + return false; + } + try + { + return m_core->hasPropertyLimits(cameraId.trimmed().toStdString().c_str(), + name.trimmed().toStdString().c_str()); + } + catch (const CMMError&) + { + return false; + } } double MicroManagerProvider::getPropertyLowerLimit(const QString& cameraId, const QString& name) { - return m_cameraProvider ? m_cameraProvider->getPropertyLowerLimit(cameraId, name) : 0.0; + if (isCamera(cameraId)) + { + return m_cameraProvider ? m_cameraProvider->getPropertyLowerLimit(cameraId, name) : 0.0; + } + if (!m_core) + { + return 0.0; + } + try + { + return m_core->getPropertyLowerLimit(cameraId.trimmed().toStdString().c_str(), + name.trimmed().toStdString().c_str()); + } + catch (const CMMError&) + { + return 0.0; + } } double MicroManagerProvider::getPropertyUpperLimit(const QString& cameraId, const QString& name) { - return m_cameraProvider ? m_cameraProvider->getPropertyUpperLimit(cameraId, name) : 0.0; + if (isCamera(cameraId)) + { + return m_cameraProvider ? m_cameraProvider->getPropertyUpperLimit(cameraId, name) : 0.0; + } + if (!m_core) + { + return 0.0; + } + try + { + return m_core->getPropertyUpperLimit(cameraId.trimmed().toStdString().c_str(), + name.trimmed().toStdString().c_str()); + } + catch (const CMMError&) + { + return 0.0; + } } bool MicroManagerProvider::setROI(const QString& cameraId, @@ -161,4 +390,513 @@ namespace scopeone::core::internal { return m_cameraProvider && m_cameraProvider->captureEventFrame(cameraId, frame, timeoutMs); } + + void MicroManagerProvider::setFrameDeliveryPaused(bool paused) + { + if (m_cameraRuntimeControl) + { + m_cameraRuntimeControl->setFrameDeliveryPaused(paused); + } + } + + bool MicroManagerProvider::setRecordingFrameDeliveryEnabled(bool enabled) + { + return !m_cameraRuntimeControl + || m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(enabled); + } + + bool MicroManagerProvider::setHighRateFrameDeliveryEnabled(bool enabled) + { + return !m_cameraRuntimeControl + || m_cameraRuntimeControl->setHighRateFrameDeliveryEnabled(enabled); + } + + bool MicroManagerProvider::isProcessingFrameTokenCurrent(const QString& cameraId, + quint64 token) + { + return m_cameraRuntimeControl + && m_cameraRuntimeControl->isProcessingFrameTokenCurrent(cameraId, token); + } + + void MicroManagerProvider::finishProcessingFrame(const QString& cameraId, quint64 token) + { + if (m_cameraRuntimeControl) + { + m_cameraRuntimeControl->finishProcessingFrame(cameraId, token); + } + } + + QString MicroManagerProvider::defaultXYStage() const + { + if (!m_core) + { + return {}; + } + try + { + return QString::fromStdString(m_core->getXYStageDevice()); + } + catch (const CMMError&) + { + return {}; + } + } + + QString MicroManagerProvider::defaultZStage() const + { + if (!m_core) + { + return {}; + } + try + { + return QString::fromStdString(m_core->getFocusDevice()); + } + catch (const CMMError&) + { + return {}; + } + } + + bool MicroManagerProvider::getXYPosition(const QString& deviceId, + double& x, + double& y, + QString* errorMessage) const + { + x = 0.0; + y = 0.0; + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + m_core->getXYPosition(deviceId.trimmed().toStdString().c_str(), x, y); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::getZPosition(const QString& deviceId, + double& z, + QString* errorMessage) const + { + z = 0.0; + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + z = m_core->getPosition(deviceId.trimmed().toStdString().c_str()); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::setRelativeXYPosition(const QString& deviceId, + double dx, + double dy, + QString* errorMessage) + { + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + const std::string device = deviceId.trimmed().toStdString(); + m_core->setRelativeXYPosition(device.c_str(), dx, dy); + m_core->waitForDevice(device.c_str()); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::setRelativeZPosition(const QString& deviceId, + double dz, + QString* errorMessage) + { + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + const std::string device = deviceId.trimmed().toStdString(); + m_core->setRelativePosition(device.c_str(), dz); + m_core->waitForDevice(device.c_str()); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::setXYPosition(const QString& deviceId, + double x, + double y, + QString* errorMessage) + { + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + const std::string device = deviceId.trimmed().toStdString(); + m_core->setXYPosition(device.c_str(), x, y); + m_core->waitForDevice(device.c_str()); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::setZPosition(const QString& deviceId, + double z, + QString* errorMessage) + { + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + const std::string device = deviceId.trimmed().toStdString(); + m_core->setPosition(device.c_str(), z); + m_core->waitForDevice(device.c_str()); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::isShutterOpen(const QString& deviceId, + bool& open, + QString* errorMessage) const + { + open = false; + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + open = m_core->getShutterOpen(deviceId.trimmed().toStdString().c_str()); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::setShutterOpen(const QString& deviceId, + bool open, + QString* errorMessage) + { + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + const std::string device = deviceId.trimmed().toStdString(); + m_core->setShutterOpen(device.c_str(), open); + m_core->waitForDevice(device.c_str()); + m_core->updateSystemStateCache(); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::getState(const QString& deviceId, + long& state, + QString* errorMessage) const + { + state = 0; + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + state = m_core->getState(deviceId.trimmed().toStdString().c_str()); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + bool MicroManagerProvider::setState(const QString& deviceId, + long state, + QString* errorMessage) + { + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + try + { + const std::string device = deviceId.trimmed().toStdString(); + m_core->setState(device.c_str(), state); + m_core->waitForDevice(device.c_str()); + m_core->updateSystemStateCache(); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } + + QString MicroManagerProvider::stateLabel(const QString& deviceId, long state) const + { + if (!m_core || state < 0) + { + return {}; + } + try + { + const std::vector labels = + m_core->getStateLabels(deviceId.trimmed().toStdString().c_str()); + const size_t index = static_cast(state); + return index < labels.size() ? QString::fromStdString(labels[index]) : QString{}; + } + catch (const CMMError&) + { + return {}; + } + } + + QStringList MicroManagerProvider::availableConfigGroups() const + { + if (!m_core) + { + return {}; + } + try + { + return toQStringList(m_core->getAvailableConfigGroups()); + } + catch (const CMMError&) + { + return {}; + } + } + + QStringList MicroManagerProvider::availableConfigs(const QString& groupName) const + { + if (!m_core) + { + return {}; + } + try + { + return toQStringList( + m_core->getAvailableConfigs(groupName.trimmed().toStdString().c_str())); + } + catch (const CMMError&) + { + return {}; + } + } + + QString MicroManagerProvider::currentConfig(const QString& groupName) const + { + if (!m_core) + { + return {}; + } + try + { + const std::string group = groupName.trimmed().toStdString(); + const std::vector configs = m_core->getAvailableConfigs(group.c_str()); + QHash currentValues; + QSet failedProperties; + for (const std::string& config : configs) + { + const Configuration preset = m_core->getConfigData(group.c_str(), config.c_str()); + bool matches = true; + for (size_t index = 0; index < preset.size(); ++index) + { + const PropertySetting setting = preset.getSetting(index); + const QString device = QString::fromStdString(setting.getDeviceLabel()); + const QString property = QString::fromStdString(setting.getPropertyName()); + const QString key = device + QChar(0x1f) + property; + if (!currentValues.contains(key) && !failedProperties.contains(key)) + { + const QString value = const_cast(this)->getProperty( + device, property, false); + if (value.isNull()) + { + failedProperties.insert(key); + } + else + { + currentValues.insert(key, value); + } + } + if (failedProperties.contains(key) + || currentValues.value(key) + != QString::fromStdString(setting.getPropertyValue())) + { + matches = false; + break; + } + } + if (matches) + { + return QString::fromStdString(config); + } + } + } + catch (const CMMError&) + { + } + return {}; + } + + bool MicroManagerProvider::setConfig(const QString& groupName, + const QString& configName, + QString* errorMessage) + { + if (errorMessage) + { + errorMessage->clear(); + } + if (!m_core) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("MMCore not available"); + } + return false; + } + + try + { + const std::string group = groupName.trimmed().toStdString(); + const std::string config = configName.trimmed().toStdString(); + const Configuration preset = m_core->getConfigData(group.c_str(), config.c_str()); + std::vector pending; + pending.reserve(preset.size()); + for (size_t index = 0; index < preset.size(); ++index) + { + pending.push_back(preset.getSetting(index)); + } + + while (!pending.empty()) + { + std::vector failed; + QString failureDescription; + for (const PropertySetting& setting : pending) + { + const QString device = QString::fromStdString(setting.getDeviceLabel()); + const QString property = QString::fromStdString(setting.getPropertyName()); + const QString value = QString::fromStdString(setting.getPropertyValue()); + QString error; + if (!setProperty(device, property, value, &error)) + { + failed.push_back(setting); + failureDescription = QStringLiteral("%1.%2 = %3: %4") + .arg(device, property, value, error); + } + } + if (failed.empty()) + { + break; + } + if (failed.size() == pending.size()) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Failed to apply config preset %1 = %2 at %3") + .arg(groupName, configName, failureDescription); + } + return false; + } + pending = std::move(failed); + } + + m_core->waitForSystem(); + m_core->updateSystemStateCache(); + return true; + } + catch (const CMMError& error) + { + setError(errorMessage, error); + return false; + } + } } diff --git a/ScopeOneCore/src/NativeCameraBackend.cpp b/ScopeOneCore/src/NativeCameraBackend.cpp index 2bb519d..7f28cea 100644 --- a/ScopeOneCore/src/NativeCameraBackend.cpp +++ b/ScopeOneCore/src/NativeCameraBackend.cpp @@ -1,7 +1,6 @@ #include "internal/CameraBackend.h" #include "MMCore.h" -#include "scopeone/ClockService.h" #include #include @@ -280,7 +279,6 @@ namespace scopeone::core::internal frame.pixelFormat = m_configuration.pixelFormat; frame.frameIndex = frameIndex; frame.timestampNs = static_cast(QDateTime::currentMSecsSinceEpoch()) * 1000000ull; - frame.clockStamp = scopeone::core::ClockService{}.now(); frame.sourceRoiX = m_configuration.sourceRoiX; frame.sourceRoiY = m_configuration.sourceRoiY; frame.sourceRoiWidth = m_configuration.sourceRoiWidth; diff --git a/ScopeOneCore/src/RecordingManager.cpp b/ScopeOneCore/src/RecordingManager.cpp index 3ad1902..9fcbce3 100644 --- a/ScopeOneCore/src/RecordingManager.cpp +++ b/ScopeOneCore/src/RecordingManager.cpp @@ -1,7 +1,6 @@ #include "internal/RecordingManager.h" #include "scopeone/CameraProvider.h" -#include "MMCore.h" #include #include @@ -802,10 +801,6 @@ namespace scopeone::core::internal plan.cameraIds.append(trimmedCameraId); } } - if (plan.cameraIds.isEmpty() && m_mmcore) - { - plan.cameraIds << QStringLiteral("Camera"); - } if (plan.cameraIds.isEmpty()) { errorMessage = QStringLiteral("No cameras available for recording"); @@ -850,7 +845,7 @@ namespace scopeone::core::internal // Returns whether native preview recording uses the requested frame interval bool RecordingManager::planStreamsMda(const ExperimentPlan& plan) const { - return m_mmcore && plan.cameraIds.size() == 1 && !planUsesMda(plan); + return m_cameraProvider && plan.cameraIds.size() == 1 && !planUsesMda(plan); } // Resets counters and MDA state for a new capture plan @@ -1788,6 +1783,21 @@ namespace scopeone::core::internal return; } + const quint64 previousFrameIndex = m_captureState.lastFrameIndex.value(cameraId, 0); + if (!m_mdaState.usingMda + && !(m_mdaState.streamMda && m_mdaState.streamIntervalMs > 0.0) + && m_captureState.framesCapturedThisBurst.value(cameraId, 0) > 0 + && frame.frameIndex - previousFrameIndex > 1) + { + const quint64 droppedFrames = frame.frameIndex - previousFrameIndex - 1; + onFrameDeliveryFailed( + QStringLiteral("Recording frame delivery skipped %1 frame(s) for %2") + .arg(droppedFrames) + .arg(cameraId), + droppedFrames); + return; + } + const QString writerError = m_captureState.streamToDisk ? writerErrorSnapshot() : QString(); @@ -1961,9 +1971,9 @@ namespace scopeone::core::internal // Starts MDA driven recording capture bool RecordingManager::startMdaCapture(QString* errorMessage) { - if (!m_mmcore) + if (!m_cameraProvider) { - const QString message = QStringLiteral("MMCore not available for MDA"); + const QString message = QStringLiteral("Camera provider not available for MDA"); if (errorMessage) *errorMessage = message; qWarning().noquote() << message; return false; @@ -1975,9 +1985,9 @@ namespace scopeone::core::internal qWarning().noquote() << message; return false; } - if (m_captureState.activeCameraIds.size() > 1 && !m_cameraProvider) + if (planUsesMda(m_mdaState.plan) && !m_stageProvider) { - const QString message = QStringLiteral("Multi-camera MDA requires a camera provider"); + const QString message = QStringLiteral("Stage provider not available for MDA"); if (errorMessage) *errorMessage = message; qWarning().noquote() << message; return false; @@ -1985,7 +1995,7 @@ namespace scopeone::core::internal if (!m_mdaState.manager) { - m_mdaState.manager = new MDAManager(m_mmcore, this); + m_mdaState.manager = new MDAManager(this); } if (m_mdaState.manager->isRunning()) { @@ -1995,8 +2005,9 @@ namespace scopeone::core::internal return false; } m_mdaState.manager->setCameraProvider(m_cameraProvider); + m_mdaState.manager->setStageProvider(m_stageProvider); - if (m_captureState.activeCameraIds.size() > 1 && m_cameraRuntimeControl) + if (m_cameraRuntimeControl) { m_cameraRuntimeControl->setFrameDeliveryPaused(true); } diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 3b5e413..b1c7127 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -8,6 +8,7 @@ #include "internal/GaussianBlurModule.h" #include "internal/ImageProcessingFramework.h" #include "internal/FrameRouter.h" +#include "internal/DriverHostProviderProxy.h" #include "internal/HardwareRuntime.h" #include "internal/MMCoreManager.h" #include "internal/MicroManagerProvider.h" @@ -139,18 +140,6 @@ namespace } } - // Convert MMCore string vectors into Qt string lists - QStringList toQStringList(const std::vector& values) - { - QStringList out; - out.reserve(static_cast(values.size())); - for (const auto& value : values) - { - out.append(QString::fromStdString(value)); - } - return out; - } - // Build the cache key for raw and processed histogram layers QString histogramLayerKey(const QString& cameraId, bool processed) { @@ -955,19 +944,61 @@ namespace scopeone::core bool ScopeOneCore::registerHardwareProvider(const HardwareProviderPtr& provider) { + const QString providerId = provider ? provider->descriptor().id.trimmed() : QString{}; if (!provider - || provider->descriptor().id.trimmed().isEmpty() + || providerId.isEmpty() + || providerId == QStringLiteral("micro-manager") + || m_managers->hardwareRuntime->deviceRegistry()->provider(providerId) || m_configurationOperationRunning + || m_pendingStageCommands > 0 + || !m_managers->activeExperimentId.isEmpty() || isRecording()) { return false; } - const bool registered = m_managers->hardwareRuntime->registerProvider(provider); - if (registered) + return m_managers->hardwareRuntime->registerProvider(provider); + } + + bool ScopeOneCore::registerDriverHostProvider(const QString& providerId, + const QString& modulePath, + const QVariantMap& options, + QString* errorMessage) + { + if (errorMessage) errorMessage->clear(); + const QString normalizedId = providerId.trimmed(); + if (normalizedId.isEmpty() + || normalizedId == QStringLiteral("micro-manager") + || m_configurationOperationRunning + || m_pendingStageCommands > 0 + || !m_managers->activeExperimentId.isEmpty() + || isRecording() + || m_managers->hardwareRuntime->deviceRegistry()->provider(normalizedId)) { - synchronizeCameraIdsFromRegistry(); + if (errorMessage) + { + *errorMessage = QStringLiteral("Provider cannot be registered in the current state"); + } + return false; } - return registered; + + HardwareProviderPtr provider = internal::createDriverHostProviderProxy( + normalizedId, + modulePath, + QJsonObject::fromVariantMap(options), + errorMessage); + if (!provider) + { + return false; + } + if (!m_managers->hardwareRuntime->registerProvider(provider)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Provider device catalog conflicts with registered hardware"); + } + return false; + } + return true; } bool ScopeOneCore::unregisterHardwareProvider(const QString& providerId) @@ -976,6 +1007,8 @@ namespace scopeone::core if (normalizedId.isEmpty() || normalizedId == QStringLiteral("micro-manager") || m_configurationOperationRunning + || m_pendingStageCommands > 0 + || !m_managers->activeExperimentId.isEmpty() || isRecording()) { return false; @@ -985,7 +1018,6 @@ namespace scopeone::core return false; } m_managers->hardwareRuntime->unregisterProvider(normalizedId); - synchronizeCameraIdsFromRegistry(); return true; } @@ -1151,11 +1183,13 @@ namespace scopeone::core this, &ScopeOneCore::syncLineProfileFromScene); m_managers->mmcoreManager = new MMCoreManager(this); m_managers->cameraManager = new CameraManager(this); - m_managers->cameraRuntimeControl = m_managers->cameraManager; m_managers->microManagerProvider = - std::make_shared(m_managers->cameraManager); + std::make_shared(m_managers->mmcoreManager->getCore(), + m_managers->cameraManager, + m_managers->cameraManager); m_managers->hardwareRuntime = new HardwareRuntime(this); m_managers->cameraProvider = m_managers->hardwareRuntime; + m_managers->cameraRuntimeControl = m_managers->hardwareRuntime; connect(m_managers->hardwareRuntime, &HardwareRuntime::devicesChanged, this, [this]() { @@ -1169,8 +1203,8 @@ namespace scopeone::core m_managers->recordingWriterStatus.reset( m_managers->recordingManager->recordedMaxBytes()); m_managers->recordingManager->setCameraProvider(m_managers->cameraProvider); + m_managers->recordingManager->setStageProvider(m_managers->hardwareRuntime); m_managers->recordingManager->setCameraRuntimeControl(m_managers->cameraRuntimeControl); - m_managers->recordingManager->setMMCore(m_managers->mmcoreManager->getCore()); m_managers->recordingManager->setLatestFrameFetcher( [this](const QString& cameraId, ImageFrame& frame) { @@ -1197,6 +1231,7 @@ namespace scopeone::core { return; } + emit rawFramesAcquired(frame.cameraId, 1); submitProcessingFrame(frame); m_managers->recordingManager->onRawFramesReady(QList{frame}); }); @@ -1222,19 +1257,14 @@ namespace scopeone::core m_managers->recordingManager, &RecordingManager::onFrameDeliveryFailed); connect(m_managers->cameraManager, &CameraManager::previewStateChanged, this, &ScopeOneCore::previewStateChanged); - connect(m_managers->cameraManager, &CameraManager::agentControlServerListening, - this, &ScopeOneCore::agentControlServerListening); + connect(m_managers->cameraManager, &CameraManager::driverHostControlServerListening, + this, &ScopeOneCore::driverHostControlServerListening); connect(m_managers->recordingManager, &RecordingManager::mdaRawFrameReady, this, [this](const ImageFrame& frame) { - m_managers->hardwareRuntime->frameRouter()->publish(frame); - const HardwareDeviceDescriptor device = - m_managers->hardwareRuntime->deviceRegistry()->device(frame.cameraId); - if (device.providerId == QStringLiteral("micro-manager")) - { - submitProcessingFrame(frame); - } + handleIncomingRawFrame(frame); + submitProcessingFrame(frame); }, Qt::QueuedConnection); @@ -1319,7 +1349,7 @@ namespace scopeone::core this, &ScopeOneCore::handleProcessedFrame, Qt::DirectConnection); connect(m_managers->imageProcessingManager, &ImageProcessingManager::processingFrameFinished, - m_managers->cameraManager, &CameraManager::finishProcessingFrame, + m_managers->hardwareRuntime, &HardwareRuntime::finishProcessingFrame, Qt::DirectConnection); connect(m_managers->imageProcessingManager, &ImageProcessingManager::processingError, this, &ScopeOneCore::processingError); @@ -1365,32 +1395,6 @@ namespace scopeone::core return m_managers->mmcoreManager->getCore(); } - // Check whether a device is owned by the active camera backend - bool ScopeOneCore::isConfiguredCamera(const QString& deviceLabel) const - { - return m_cameraIds.contains(deviceLabel); - } - - // Check whether a device is a native MMCore camera - bool ScopeOneCore::isNativeCamera(const QString& deviceLabel) const - { - const QString device = deviceLabel.trimmed(); - if (m_configurationOperationRunning || device.isEmpty() || isConfiguredCamera(device)) - { - return false; - } - - auto handle = core(); - try - { - return handle->getDeviceType(device.toStdString().c_str()) == MM::CameraDevice; - } - catch (const CMMError&) - { - return false; - } - } - // Collect camera ids that currently have active previews QStringList ScopeOneCore::runningPreviewCameraIds() const { @@ -1414,7 +1418,10 @@ namespace scopeone::core { return overrideIt.value(); } - if (!m_managers->cameraManager->usesAgentBackend() + const HardwareDeviceDescriptor device = + m_managers->hardwareRuntime->deviceRegistry()->device(camera); + if (device.providerId == QStringLiteral("micro-manager") + && !m_managers->cameraManager->usesDriverHostBackend() && m_cameraIds.size() == 1 && m_cameraIds.first() == camera) { @@ -1475,17 +1482,21 @@ namespace scopeone::core } // Applies a completed device load to the frame graph and public state - void ScopeOneCore::applyLoadedConfiguration(const QString& configPath, + bool ScopeOneCore::applyLoadedConfiguration(const QString& configPath, const LoadConfigResult& result) { + m_managers->microManagerProvider->setDevices(result.devices); + if (!m_managers->hardwareRuntime->refreshProvider(QStringLiteral("micro-manager"))) + { + m_managers->microManagerProvider->setDevices({}); + m_managers->hardwareRuntime->refreshProvider(QStringLiteral("micro-manager")); + return false; + } m_configurationFailedDevices = result.failedDevices; m_configurationError.clear(); m_configurationState = result.failedDevices.isEmpty() ? ConfigurationState::Loaded : ConfigurationState::PartiallyLoaded; - m_managers->microManagerProvider->setDevices(result.devices); - m_managers->hardwareRuntime->refreshProvider(QStringLiteral("micro-manager")); - synchronizeCameraIdsFromRegistry(); const QFileInfo configFile(configPath); m_loadedConfigPath = configPath.trimmed().isEmpty() ? QString() @@ -1498,6 +1509,7 @@ namespace scopeone::core QCryptographicHash::hash(file.readAll(), QCryptographicHash::Sha256).toHex()); } emit hardwareConfigurationChanged(); + return true; } void ScopeOneCore::synchronizeCameraIdsFromRegistry() @@ -1572,6 +1584,7 @@ namespace scopeone::core // Apply the configured hardware shutdown state before releasing devices void ScopeOneCore::applySystemShutdownPreset() { + m_managers->hardwareRuntime->stopPreview(); auto handle = core(); try { @@ -1580,7 +1593,6 @@ namespace scopeone::core { return; } - m_managers->cameraManager->stopPreview(); setConfig(MM::g_CFGGroup_System, MM::g_CFGGroup_System_Shutdown); } catch (const CMMError& error) @@ -1599,10 +1611,9 @@ namespace scopeone::core const bool processingWasEnabled = isRealTimeProcessingEnabled(); m_managers->imageProcessingManager->enableRealTimeProcessing(false); const QStringList cameraIds = m_cameraIds; - m_managers->hardwareRuntime->acquisitionEngine()->reset(); + m_managers->hardwareRuntime->stopPreview(); if (shutdownCameraBackend) { - m_managers->cameraManager->stopPreview(); m_managers->cameraManager->shutdownNow(); } @@ -1624,7 +1635,6 @@ namespace scopeone::core m_imageSceneModel->reset(); m_managers->microManagerProvider->setDevices(QList{}); m_managers->hardwareRuntime->refreshProvider(QStringLiteral("micro-manager")); - synchronizeCameraIdsFromRegistry(); if (notify && processingWasEnabled) { emit processingSettingsChanged(); @@ -1720,8 +1730,27 @@ namespace scopeone::core watcher->deleteLater(); return; } + if (!applyLoadedConfiguration(path, result)) + { + const QString errorMessage = + QStringLiteral("Micro-Manager device catalog conflicts with registered hardware"); + m_managers->cameraManager->shutdown( + [this, result, errorMessage](const QString& shutdownError) + { + if (!shutdownError.isEmpty()) + { + finishConfigurationLoadFailure( + result, + QStringLiteral("%1; camera cleanup failed: %2") + .arg(errorMessage, shutdownError)); + return; + } + startConfigurationLoadCleanupTask(result, errorMessage); + }); + watcher->deleteLater(); + return; + } m_configurationOperationRunning = false; - applyLoadedConfiguration(path, result); emit configurationLoadFinished(true, result, {}); } else @@ -1895,7 +1924,15 @@ namespace scopeone::core { return false; } - return m_managers->hardwareRuntime->acquisitionEngine()->start(target); + const bool all = target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0; + const bool started = all + ? m_managers->hardwareRuntime->startPreview() + : m_managers->hardwareRuntime->startPreviewFor(target); + if (started) + { + emit previewStateChanged(true); + } + return started; } // Stop preview for one camera or the full camera set @@ -1906,7 +1943,15 @@ namespace scopeone::core { return false; } - return m_managers->hardwareRuntime->acquisitionEngine()->stop(target); + const bool all = target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0; + const bool stopped = all + ? m_managers->hardwareRuntime->stopPreview() + : m_managers->hardwareRuntime->stopPreviewFor(target); + if (stopped) + { + emit previewStateChanged(!runningPreviewCameraIds().isEmpty()); + } + return stopped; } // Submit exposure changes through the active camera manager @@ -3008,15 +3053,15 @@ namespace scopeone::core { return {}; } - auto handle = core(); - try - { - return toQStringList(handle->getLoadedDevicesOfType(MM::XYStageDevice)); - } - catch (const CMMError&) + QStringList devices; + for (const HardwareDeviceDescriptor& device : hardwareDevices()) { - return {}; + if (device.kind == HardwareDeviceKind::XYStage) + { + devices.append(device.logicalId); + } } + return devices; } QStringList ScopeOneCore::zStageDevices() const @@ -3025,15 +3070,15 @@ namespace scopeone::core { return {}; } - auto handle = core(); - try - { - return toQStringList(handle->getLoadedDevicesOfType(MM::StageDevice)); - } - catch (const CMMError&) + QStringList devices; + for (const HardwareDeviceDescriptor& device : hardwareDevices()) { - return {}; + if (device.kind == HardwareDeviceKind::ZStage) + { + devices.append(device.logicalId); + } } + return devices; } QString ScopeOneCore::currentXYStageDevice() const @@ -3042,15 +3087,7 @@ namespace scopeone::core { return {}; } - auto handle = core(); - try - { - return QString::fromStdString(handle->getXYStageDevice()); - } - catch (const CMMError&) - { - return {}; - } + return m_managers->hardwareRuntime->defaultXYStage(); } QString ScopeOneCore::currentFocusDevice() const @@ -3059,64 +3096,38 @@ namespace scopeone::core { return {}; } - auto handle = core(); - try - { - return QString::fromStdString(handle->getFocusDevice()); - } - catch (const CMMError&) - { - return {}; - } + return m_managers->hardwareRuntime->defaultZStage(); } - // Read the current XY stage position from MMCore + // Read the current XY stage position through its provider bool ScopeOneCore::readXYPosition(const QString& xyStageLabel, double& x, double& y) const { x = 0.0; y = 0.0; const QString label = xyStageLabel.trimmed(); - auto handle = core(); if (m_configurationOperationRunning || label.isEmpty()) { return false; } - try - { - handle->getXYPosition(label.toStdString().c_str(), x, y); - return true; - } - catch (const CMMError&) - { - return false; - } + return m_managers->hardwareRuntime->getXYPosition(label, x, y, nullptr); } - // Read the current Z stage position from MMCore + // Read the current Z stage position through its provider bool ScopeOneCore::readZPosition(const QString& zStageLabel, double& z) const { z = 0.0; const QString label = zStageLabel.trimmed(); - auto handle = core(); if (m_configurationOperationRunning || label.isEmpty()) { return false; } - try - { - z = handle->getPosition(label.toStdString().c_str()); - return true; - } - catch (const CMMError&) - { - return false; - } + return m_managers->hardwareRuntime->getZPosition(label, z, nullptr); } // Queues one stage command on the serialized hardware worker quint64 ScopeOneCore::queueStageMove( const QString& deviceLabel, - std::function command) + std::function command) { const QString label = deviceLabel.trimmed(); if (label.isEmpty() @@ -3144,23 +3155,12 @@ namespace scopeone::core watcher->deleteLater(); }); - const auto handle = core(); watcher->setFuture(QtConcurrent::run( m_hardwareThreadPool.get(), - [handle, label, command = std::move(command)]() + [command = std::move(command)]() { StageTaskResult task; - try - { - const std::string device = label.toStdString(); - command(*handle, device.c_str()); - handle->waitForDevice(device.c_str()); - task.success = true; - } - catch (const CMMError& error) - { - task.errorMessage = QString::fromStdString(error.getMsg()); - } + task.success = command(&task.errorMessage); return task; })); return commandId; @@ -3168,164 +3168,149 @@ namespace scopeone::core quint64 ScopeOneCore::moveXYRelative(const QString& xyStageLabel, double dx, double dy) { - return queueStageMove(xyStageLabel, [dx, dy](CMMCore& handle, const char* label) + HardwareRuntime* const runtime = m_managers->hardwareRuntime; + const QString device = xyStageLabel.trimmed(); + return queueStageMove(device, [runtime, device, dx, dy](QString* errorMessage) { - handle.setRelativeXYPosition(label, dx, dy); + return runtime->setRelativeXYPosition(device, dx, dy, errorMessage); }); } quint64 ScopeOneCore::moveZRelative(const QString& zStageLabel, double dz) { - return queueStageMove(zStageLabel, [dz](CMMCore& handle, const char* label) + HardwareRuntime* const runtime = m_managers->hardwareRuntime; + const QString device = zStageLabel.trimmed(); + return queueStageMove(device, [runtime, device, dz](QString* errorMessage) { - handle.setRelativePosition(label, dz); + return runtime->setRelativeZPosition(device, dz, errorMessage); }); } quint64 ScopeOneCore::moveXYTo(const QString& xyStageLabel, double x, double y) { - return queueStageMove(xyStageLabel, [x, y](CMMCore& handle, const char* label) + HardwareRuntime* const runtime = m_managers->hardwareRuntime; + const QString device = xyStageLabel.trimmed(); + return queueStageMove(device, [runtime, device, x, y](QString* errorMessage) { - handle.setXYPosition(label, x, y); + return runtime->setXYPosition(device, x, y, errorMessage); }); } quint64 ScopeOneCore::moveZTo(const QString& zStageLabel, double z) { - return queueStageMove(zStageLabel, [z](CMMCore& handle, const char* label) + HardwareRuntime* const runtime = m_managers->hardwareRuntime; + const QString device = zStageLabel.trimmed(); + return queueStageMove(device, [runtime, device, z](QString* errorMessage) { - handle.setPosition(label, z); + return runtime->setZPosition(device, z, errorMessage); }); } - // List available Micro Manager configuration groups - QStringList ScopeOneCore::availableConfigGroups() const + bool ScopeOneCore::readShutterOpen(const QString& shutterLabel, bool& open) const { - if (m_configurationOperationRunning) + open = false; + const QString device = shutterLabel.trimmed(); + return !m_configurationOperationRunning + && !device.isEmpty() + && m_managers->hardwareRuntime->isShutterOpen(device, open, nullptr); + } + + bool ScopeOneCore::setShutterOpen(const QString& shutterLabel, + bool open, + QString* errorMessage) + { + if (errorMessage) { - return {}; + errorMessage->clear(); } - auto handle = core(); - try + const QString device = shutterLabel.trimmed(); + if (m_configurationOperationRunning || device.isEmpty()) { - const auto groups = handle->getAvailableConfigGroups(); - QStringList result; - for (const auto& g : groups) + if (errorMessage) { - result.append(QString::fromStdString(g)); + *errorMessage = QStringLiteral("Invalid shutter target"); } - return result; + return false; } - catch (const CMMError&) + const bool ok = m_managers->hardwareRuntime->setShutterOpen( + device, open, errorMessage); + if (ok) { - return {}; + emit deviceStateChanged(); } + return ok; } - // List presets in a Micro Manager configuration group - QStringList ScopeOneCore::availableConfigs(const QString& configGroup) const + bool ScopeOneCore::readDeviceState(const QString& deviceLabel, long& state) const { - auto handle = core(); - if (m_configurationOperationRunning || configGroup.isEmpty()) + state = 0; + const QString device = deviceLabel.trimmed(); + return !m_configurationOperationRunning + && !device.isEmpty() + && m_managers->hardwareRuntime->getState(device, state, nullptr); + } + + bool ScopeOneCore::setDeviceState(const QString& deviceLabel, + long state, + QString* errorMessage) + { + if (errorMessage) { - return {}; + errorMessage->clear(); } - try + const QString device = deviceLabel.trimmed(); + if (m_configurationOperationRunning || device.isEmpty()) { - const auto configs = handle->getAvailableConfigs(configGroup.toStdString().c_str()); - QStringList result; - for (const auto& c : configs) + if (errorMessage) { - result.append(QString::fromStdString(c)); + *errorMessage = QStringLiteral("Invalid state device target"); } - return result; + return false; } - catch (const CMMError&) + const bool ok = m_managers->hardwareRuntime->setState(device, state, errorMessage); + if (ok) { - return {}; + emit deviceStateChanged(); } + return ok; } - // Read the current preset for a configuration group - QString ScopeOneCore::currentConfig(const QString& groupName) const + QString ScopeOneCore::deviceStateLabel(const QString& deviceLabel, long state) const { - auto handle = core(); - if (m_configurationOperationRunning || groupName.isEmpty()) + const QString device = deviceLabel.trimmed(); + return !m_configurationOperationRunning && !device.isEmpty() + ? m_managers->hardwareRuntime->stateLabel(device, state) + : QString{}; + } + + // List configuration groups exposed by registered providers + QStringList ScopeOneCore::availableConfigGroups() const + { + if (m_configurationOperationRunning) { return {}; } - try + return m_managers->hardwareRuntime->availableConfigGroups(); + } + + // List presets in a provider configuration group + QStringList ScopeOneCore::availableConfigs(const QString& configGroup) const + { + if (m_configurationOperationRunning || configGroup.isEmpty()) { - if (m_managers->cameraManager->usesAgentBackend()) - { - const std::string group = groupName.toStdString(); - const std::vector configs = handle->getAvailableConfigs(group.c_str()); - QHash currentValues; - QSet failedProperties; - for (const std::string& config : configs) - { - const Configuration preset = handle->getConfigData(group.c_str(), config.c_str()); - bool matches = true; - for (size_t index = 0; index < preset.size(); ++index) - { - const PropertySetting setting = preset.getSetting(index); - const QString device = QString::fromStdString(setting.getDeviceLabel()); - const QString property = QString::fromStdString(setting.getPropertyName()); - const QString key = device + QChar(0x1f) + property; - if (!currentValues.contains(key) && !failedProperties.contains(key)) - { - if (isConfiguredCamera(device)) - { - const QString value = m_managers->cameraProvider->getProperty( - device, property, false); - if (value.isNull()) - { - failedProperties.insert(key); - } - else - { - currentValues.insert(key, value); - } - } - else - { - try - { - currentValues.insert( - key, - QString::fromStdString( - handle->getProperty( - setting.getDeviceLabel().c_str(), - setting.getPropertyName().c_str()))); - } - catch (const CMMError&) - { - failedProperties.insert(key); - } - } - } - if (failedProperties.contains(key) - || currentValues.value(key) - != QString::fromStdString(setting.getPropertyValue())) - { - matches = false; - break; - } - } - if (matches) - { - return QString::fromStdString(config); - } - } - return {}; - } - return QString::fromStdString( - handle->getCurrentConfig(groupName.toStdString().c_str())); + return {}; } - catch (const CMMError&) + return m_managers->hardwareRuntime->availableConfigs(configGroup); + } + + // Read the current preset for a configuration group + QString ScopeOneCore::currentConfig(const QString& groupName) const + { + if (m_configurationOperationRunning || groupName.isEmpty()) { return {}; } + return m_managers->hardwareRuntime->currentConfig(groupName); } // Apply a configuration preset while camera previews are paused @@ -3337,7 +3322,6 @@ namespace scopeone::core { errorMessage->clear(); } - auto handle = core(); if (m_configurationOperationRunning || m_pendingStageCommands > 0 || groupName.isEmpty() @@ -3363,106 +3347,9 @@ namespace scopeone::core const QStringList runningPreviewIds = runningPreviewCameraIds(); const bool ok = withSuspendedPreviews(this, runningPreviewIds, [&]() { - try - { - const std::string group = groupName.toStdString(); - const std::string config = configName.toStdString(); - const bool agentMode = m_managers->cameraManager->usesAgentBackend(); - if (!agentMode) - { - handle->setConfig(group.c_str(), config.c_str()); - } - else - { - const Configuration preset = handle->getConfigData(group.c_str(), config.c_str()); - std::vector pending; - pending.reserve(preset.size()); - for (size_t index = 0; index < preset.size(); ++index) - { - pending.push_back(preset.getSetting(index)); - } - while (!pending.empty()) - { - std::vector failed; - QString failureDescription; - for (const PropertySetting& setting : pending) - { - const QString device = QString::fromStdString(setting.getDeviceLabel()); - const QString property = QString::fromStdString(setting.getPropertyName()); - const QString value = QString::fromStdString(setting.getPropertyValue()); - if (isConfiguredCamera(device)) - { - QString error; - if (!m_managers->cameraProvider->setProperty( - device, property, value, &error)) - { - failed.push_back(setting); - failureDescription = QString("%1.%2 = %3: %4") - .arg(device, property, value, error); - } - continue; - } - try - { - handle->setProperty(setting.getDeviceLabel().c_str(), - setting.getPropertyName().c_str(), - setting.getPropertyValue().c_str()); - } - catch (const CMMError& error) - { - failed.push_back(setting); - failureDescription = QString("%1.%2 = %3: %4") - .arg(device, - property, - value, - QString::fromStdString(error.getMsg())); - } - } - if (failed.empty()) - { - break; - } - if (failed.size() == pending.size()) - { - const QString message = - QString("Failed to apply config preset %1 = %2 at %3") - .arg(groupName, configName, failureDescription); - if (errorMessage) - { - *errorMessage = message; - } - qWarning().noquote() - << message; - return false; - } - pending = std::move(failed); - } - } - if (agentMode) - { - handle->waitForSystem(); - } - else - { - handle->waitForConfig(group.c_str(), config.c_str()); - } - handle->updateSystemStateCache(); - return true; - } - catch (const CMMError& error) - { - const QString message = QString("Failed to apply config preset %1 = %2: %3") - .arg(groupName, - configName, - QString::fromStdString(error.getMsg())); - if (errorMessage) - { - *errorMessage = message; - } - qWarning().noquote() - << message; - return false; - } + return m_managers->hardwareRuntime->setConfig(groupName, + configName, + errorMessage); }); if (ok) { @@ -3481,65 +3368,20 @@ namespace scopeone::core return false; } - QString resolvedTarget = target; - if (resolvedTarget.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) - { - if (m_cameraIds.isEmpty()) - { - return false; - } - resolvedTarget = m_cameraIds.first(); - } - if (m_cameraIds.contains(resolvedTarget) - && m_managers->cameraProvider->getExposure(resolvedTarget, exposureMs)) - { - return true; - } - - auto handle = core(); - try - { - if (target.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) - { - exposureMs = handle->getExposure(); - } - else - { - exposureMs = handle->getExposure(target.toStdString().c_str()); - } - return true; - } - catch (const CMMError&) - { - return false; - } + return m_managers->cameraProvider->getExposure(target, exposureMs); } - // Merge native MMCore devices with agent camera labels + // List devices from the unified registry QStringList ScopeOneCore::loadedDevices() const { if (m_configurationOperationRunning) { return {}; } - auto handle = core(); QStringList devices; - try + for (const HardwareDeviceDescriptor& device : hardwareDevices()) { - devices = toQStringList(handle->getLoadedDevices()); - } - catch (const CMMError&) - { - devices.clear(); - } - - // Agent cameras are not always loaded in the UI side MMCore instance - for (const QString& cameraId : m_cameraIds) - { - if (!devices.contains(cameraId)) - { - devices.append(cameraId); - } + devices.append(device.logicalId); } return devices; } @@ -3579,19 +3421,7 @@ namespace scopeone::core { return {}; } - if (isConfiguredCamera(device)) - { - return m_managers->cameraProvider->listProperties(device); - } - auto handle = core(); - try - { - return toQStringList(handle->getDevicePropertyNames(device.toStdString().c_str())); - } - catch (const CMMError&) - { - return {}; - } + return m_managers->hardwareRuntime->listProperties(device); } // Read a property value from hardware or cache @@ -3603,26 +3433,7 @@ namespace scopeone::core { return {}; } - if (isConfiguredCamera(device)) - { - return m_managers->cameraProvider->getProperty(device, property, fromCache); - } - auto handle = core(); - try - { - if (fromCache) - { - return QString::fromStdString( - handle->getPropertyFromCache(device.toStdString().c_str(), - property.toStdString().c_str())); - } - return QString::fromStdString( - handle->getProperty(device.toStdString().c_str(), property.toStdString().c_str())); - } - catch (const CMMError&) - { - return {}; - } + return m_managers->hardwareRuntime->getProperty(device, property, fromCache); } // Convert backend property types into UI strings @@ -3634,27 +3445,7 @@ namespace scopeone::core { return QStringLiteral("Unknown"); } - if (isConfiguredCamera(device)) - { - return m_managers->cameraProvider->getPropertyType(device, property); - } - auto handle = core(); - try - { - const MM::PropertyType type = handle->getPropertyType(device.toStdString().c_str(), - property.toStdString().c_str()); - switch (type) - { - case MM::String: return QStringLiteral("String"); - case MM::Float: return QStringLiteral("Float"); - case MM::Integer: return QStringLiteral("Integer"); - default: return QStringLiteral("Unknown"); - } - } - catch (const CMMError&) - { - return QStringLiteral("Unknown"); - } + return m_managers->hardwareRuntime->getPropertyType(device, property); } // Check whether a property can be edited @@ -3666,19 +3457,7 @@ namespace scopeone::core { return true; } - if (isConfiguredCamera(device)) - { - return m_managers->cameraProvider->isPropertyReadOnly(device, property); - } - auto handle = core(); - try - { - return handle->isPropertyReadOnly(device.toStdString().c_str(), property.toStdString().c_str()); - } - catch (const CMMError&) - { - return true; - } + return m_managers->hardwareRuntime->isPropertyReadOnly(device, property); } // Check whether a property must be set before initialization @@ -3690,19 +3469,7 @@ namespace scopeone::core { return false; } - if (isConfiguredCamera(device)) - { - return m_managers->cameraProvider->isPropertyPreInit(device, property); - } - auto handle = core(); - try - { - return handle->isPropertyPreInit(device.toStdString().c_str(), property.toStdString().c_str()); - } - catch (const CMMError&) - { - return false; - } + return m_managers->hardwareRuntime->isPropertyPreInit(device, property); } // Return allowed values for enumerated properties @@ -3714,20 +3481,7 @@ namespace scopeone::core { return {}; } - if (isConfiguredCamera(device)) - { - return m_managers->cameraProvider->getAllowedPropertyValues(device, property); - } - auto handle = core(); - try - { - return toQStringList( - handle->getAllowedPropertyValues(device.toStdString().c_str(), property.toStdString().c_str())); - } - catch (const CMMError&) - { - return {}; - } + return m_managers->hardwareRuntime->getAllowedPropertyValues(device, property); } // Return numeric limits for range constrained properties @@ -3745,32 +3499,13 @@ namespace scopeone::core { return false; } - if (isConfiguredCamera(device)) - { - if (!m_managers->cameraProvider->hasPropertyLimits(device, property)) - { - return false; - } - lower = m_managers->cameraProvider->getPropertyLowerLimit(device, property); - upper = m_managers->cameraProvider->getPropertyUpperLimit(device, property); - return true; - } - - auto handle = core(); - try - { - if (!handle->hasPropertyLimits(device.toStdString().c_str(), property.toStdString().c_str())) - { - return false; - } - lower = handle->getPropertyLowerLimit(device.toStdString().c_str(), property.toStdString().c_str()); - upper = handle->getPropertyUpperLimit(device.toStdString().c_str(), property.toStdString().c_str()); - return true; - } - catch (const CMMError&) + if (!m_managers->hardwareRuntime->hasPropertyLimits(device, property)) { return false; } + lower = m_managers->hardwareRuntime->getPropertyLowerLimit(device, property); + upper = m_managers->hardwareRuntime->getPropertyUpperLimit(device, property); + return true; } // Set a property and refresh backend state after the device accepts it @@ -3804,49 +3539,8 @@ namespace scopeone::core return false; } - if (isConfiguredCamera(device)) - { - QString cameraError; - if (!m_managers->cameraProvider->setProperty(device, property, value, &cameraError)) - { - if (errorMessage) - { - *errorMessage = cameraError.isEmpty() - ? QStringLiteral("Camera setProperty failed") - : cameraError; - } - return false; - } - emit deviceStateChanged(); - return true; - } - - auto handle = core(); - const bool isCamera = isNativeCamera(device); - const QStringList runningPreviewIds = isCamera ? runningPreviewCameraIds() : QStringList{}; - const auto applyProperty = [&]() -> bool - { - try - { - handle->setProperty(device.toStdString().c_str(), - property.toStdString().c_str(), - value.toStdString().c_str()); - handle->waitForDevice(device.toStdString().c_str()); - handle->updateSystemStateCache(); - return true; - } - catch (const CMMError& e) - { - if (errorMessage) - { - *errorMessage = QString::fromStdString(e.getMsg()); - } - return false; - } - }; - const bool ok = isCamera - ? withSuspendedPreviews(this, runningPreviewIds, applyProperty) - : applyProperty(); + const bool ok = m_managers->hardwareRuntime->setProperty( + device, property, value, errorMessage); if (ok) { emit deviceStateChanged(); diff --git a/ScopeOneCore/src/SimulatorProvider.cpp b/ScopeOneCore/src/SimulatorProvider.cpp index c5f043b..db4bcf3 100644 --- a/ScopeOneCore/src/SimulatorProvider.cpp +++ b/ScopeOneCore/src/SimulatorProvider.cpp @@ -1,8 +1,8 @@ #include "scopeone/SimulatorProvider.h" -#include "scopeone/ClockService.h" - #include +#include +#include #include #include #include @@ -10,9 +10,14 @@ namespace scopeone::core { - SimulatorProvider::SimulatorProvider(const QString& logicalCameraId, int width, int height) - : m_providerId(QStringLiteral("simulator.%1").arg( - QUuid::createUuid().toString(QUuid::WithoutBraces))) + SimulatorProvider::SimulatorProvider(const QString& logicalCameraId, + int width, + int height, + const QString& providerId) + : m_providerId(providerId.trimmed().isEmpty() + ? QStringLiteral("simulator.%1").arg( + QUuid::createUuid().toString(QUuid::WithoutBraces)) + : providerId.trimmed()) , m_cameraId(logicalCameraId.trimmed().isEmpty() ? QStringLiteral("camera.simulator") : logicalCameraId.trimmed()) @@ -24,10 +29,12 @@ namespace scopeone::core updateTimerInterval(); connect(&m_timer, &QTimer::timeout, this, [this]() { - if (m_frameSink) + FrameSink sink; { - m_frameSink(makeFrame()); + QMutexLocker locker(&m_mutex); + sink = m_frameSink; } + if (sink) sink(makeFrame()); }); } @@ -52,14 +59,15 @@ namespace scopeone::core void SimulatorProvider::setFrameSink(FrameSink sink) { + QMutexLocker locker(&m_mutex); m_frameSink = std::move(sink); } bool SimulatorProvider::startPreview() { - if (!m_frameSink) { - return false; + QMutexLocker locker(&m_mutex); + if (!m_frameSink) return false; } m_timer.start(); return true; @@ -92,6 +100,7 @@ namespace scopeone::core { return false; } + QMutexLocker locker(&m_mutex); exposureMs = m_exposureMs; return true; } @@ -102,8 +111,20 @@ namespace scopeone::core { return false; } - m_exposureMs = exposureMs; - updateTimerInterval(); + { + QMutexLocker locker(&m_mutex); + m_exposureMs = exposureMs; + } + if (QThread::currentThread() == thread()) + { + updateTimerInterval(); + } + else + { + QMetaObject::invokeMethod(this, + [this]() { updateTimerInterval(); }, + Qt::QueuedConnection); + } return true; } @@ -126,6 +147,7 @@ namespace scopeone::core } if (name == QStringLiteral("Exposure")) { + QMutexLocker locker(&m_mutex); return QString::number(m_exposureMs, 'g', 12); } if (name == QStringLiteral("SensorWidth")) @@ -220,6 +242,7 @@ namespace scopeone::core { return false; } + QMutexLocker locker(&m_mutex); m_roi = roi; return true; } @@ -230,6 +253,7 @@ namespace scopeone::core { return false; } + QMutexLocker locker(&m_mutex); m_roi = QRect(0, 0, m_sensorWidth, m_sensorHeight); return true; } @@ -244,6 +268,7 @@ namespace scopeone::core { return false; } + QMutexLocker locker(&m_mutex); x = m_roi.x(); y = m_roi.y(); width = m_roi.width(); @@ -272,6 +297,7 @@ namespace scopeone::core ImageFrame SimulatorProvider::makeFrame() { + QMutexLocker locker(&m_mutex); ImageFrame frame; frame.cameraId = m_cameraId; frame.width = m_roi.width(); @@ -281,7 +307,6 @@ namespace scopeone::core frame.pixelFormat = ImagePixelFormat::Mono8; frame.frameIndex = ++m_frameIndex; frame.timestampNs = static_cast(QDateTime::currentMSecsSinceEpoch()) * 1000000ull; - frame.clockStamp = ClockService{}.now(); frame.sourceRoiX = m_roi.x(); frame.sourceRoiY = m_roi.y(); frame.sourceRoiWidth = m_roi.width(); @@ -301,6 +326,11 @@ namespace scopeone::core void SimulatorProvider::updateTimerInterval() { - m_timer.setInterval((std::max)(1, static_cast(std::ceil(m_exposureMs)))); + double exposureMs = 0.0; + { + QMutexLocker locker(&m_mutex); + exposureMs = m_exposureMs; + } + m_timer.setInterval((std::max)(1, static_cast(std::ceil(exposureMs)))); } } diff --git a/ScopeOneCore/src/SimulatorProviderPlugin.cpp b/ScopeOneCore/src/SimulatorProviderPlugin.cpp new file mode 100644 index 0000000..423788c --- /dev/null +++ b/ScopeOneCore/src/SimulatorProviderPlugin.cpp @@ -0,0 +1,49 @@ +#include + +#include "scopeone/DriverHostProviderPlugin.h" +#include "scopeone/SimulatorProvider.h" + +namespace scopeone::core +{ + class SimulatorProviderPlugin final : public QObject, + public DriverHostProviderPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneDriverHostProviderPlugin_iid) + Q_INTERFACES(scopeone::core::DriverHostProviderPlugin) + + public: + QString providerId() const override + { + return QStringLiteral("simulator"); + } + + HardwareProviderPtr createProvider(const QJsonObject& options, + QString* errorMessage) override + { + if (errorMessage) + { + errorMessage->clear(); + } + const QString deviceId = options.value(QStringLiteral("cameraId")) + .toString(QStringLiteral("camera.simulator")) + .trimmed(); + const int width = options.value(QStringLiteral("width")).toInt(512); + const int height = options.value(QStringLiteral("height")).toInt(512); + if (deviceId.isEmpty() || width <= 0 || height <= 0) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Invalid simulator options"); + } + return {}; + } + return std::make_shared(deviceId, + width, + height, + providerId()); + } + }; +} + +#include "SimulatorProviderPlugin.moc" From a8e9aa5daa95d7fc3b9287caf783d431c572a2eb Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Fri, 21 Aug 2026 01:40:24 +0200 Subject: [PATCH 03/37] Add extensible processing and tool workflows --- CMakeLists.txt | 8 +- README.md | 15 + ScopeOneCore/CMakeLists.txt | 9 +- ScopeOneCore/README.md | 17 +- .../include/scopeone/CameraProvider.h | 2 + .../include/scopeone/ExperimentDocument.h | 13 +- .../include/scopeone/ProcessingPlugin.h | 84 ++ ScopeOneCore/include/scopeone/ScopeOneCore.h | 39 +- .../include/scopeone/SimulatorProvider.h | 2 + .../internal/BackgroundCalibrationModule.h | 2 +- ScopeOneCore/internal/CameraManager.h | 10 +- ScopeOneCore/internal/CameraRuntimeControl.h | 9 +- .../internal/DifferentialRollingModule.h | 2 +- ScopeOneCore/internal/FFTModule.h | 2 +- ScopeOneCore/internal/GaussianBlurModule.h | 2 +- ScopeOneCore/internal/HardwareRuntime.h | 14 +- ScopeOneCore/internal/MicroManagerProvider.h | 9 +- ScopeOneCore/internal/ProcessingModule.h | 31 +- .../internal/ProcessingModuleRegistry.h | 40 + ScopeOneCore/internal/SharedFrameRing.h | 70 ++ .../internal/SpatiotemporalBinningModule.h | 2 +- ScopeOneCore/python/scopeone/README.md | 10 +- .../python/scopeone/src/scopeone/client.py | 19 +- .../python/scopeone/src/scopeone/core.py | 10 +- ScopeOneCore/src/CameraManager.cpp | 17 +- ScopeOneCore/src/DriverHostCameraBackend.cpp | 100 +- ScopeOneCore/src/DriverHostProviderProxy.cpp | 120 +-- ScopeOneCore/src/ExperimentDocument.cpp | 103 +- ScopeOneCore/src/HardwareRuntime.cpp | 110 ++- ScopeOneCore/src/MicroManagerProvider.cpp | 22 +- ScopeOneCore/src/ProcessingModuleRegistry.cpp | 277 ++++++ ScopeOneCore/src/RecordingManager.cpp | 14 +- ScopeOneCore/src/ScopeOneCore.cpp | 410 ++++++-- ScopeOneCore/src/SimulatorProvider.cpp | 19 + ScopeOneCore/src/SimulatorProvider.json | 4 + ScopeOneCore/src/SimulatorProviderPlugin.cpp | 2 +- VERSION | 2 +- src/ImageProcessingWidget.cpp | 934 ++++++++---------- src/ImageProcessingWidget.h | 26 +- src/ImageToolsDialog.cpp | 54 +- src/ImageToolsDialog.h | 12 +- src/MainWindow.cpp | 199 ++-- src/MainWindow.h | 24 +- src/ScopeOneLocalApiServer.cpp | 117 +-- src/ScopeOneMcpServer.cpp | 12 +- src/ScopeOneToolPlugin.cpp | 217 ++++ src/ScopeOneToolPlugin.h | 86 ++ 47 files changed, 2105 insertions(+), 1197 deletions(-) create mode 100644 ScopeOneCore/include/scopeone/ProcessingPlugin.h create mode 100644 ScopeOneCore/internal/ProcessingModuleRegistry.h create mode 100644 ScopeOneCore/internal/SharedFrameRing.h create mode 100644 ScopeOneCore/src/ProcessingModuleRegistry.cpp create mode 100644 ScopeOneCore/src/SimulatorProvider.json create mode 100644 src/ScopeOneToolPlugin.cpp create mode 100644 src/ScopeOneToolPlugin.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cb66019..b66901f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ set(SCOPEONE_UI_SOURCES src/DevicePropertyWidget.cpp src/ConfigPresetWidget.cpp src/ScopeOneLocalApiServer.cpp + src/ScopeOneToolPlugin.cpp ${CMAKE_SOURCE_DIR}/resources/resources.qrc ) @@ -99,6 +100,7 @@ set(SCOPEONE_UI_HEADERS src/DevicePropertyWidget.h src/ConfigPresetWidget.h src/ScopeOneLocalApiServer.h + src/ScopeOneToolPlugin.h ) if (WIN32) @@ -200,14 +202,14 @@ if (SCOPEONE_CORE_RUNTIME_FILES) ) endif () -set(SCOPEONE_PROVIDER_DIR "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/providers") +set(SCOPEONE_PROVIDER_DIR "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/plugins/hardware") if (EXISTS "${SCOPEONE_PROVIDER_DIR}") add_custom_command(TARGET ScopeOne POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${SCOPEONE_PROVIDER_DIR}" - "$/providers" + "$/plugins/hardware" ) - install(DIRECTORY "${SCOPEONE_PROVIDER_DIR}/" DESTINATION providers) + install(DIRECTORY "${SCOPEONE_PROVIDER_DIR}/" DESTINATION plugins/hardware) endif () if (WIN32) diff --git a/README.md b/README.md index 4321523..b92bda2 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,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 discovers optional extensions beside the application executable: + +```text +plugins/ + hardware/ Native hardware providers loaded in isolated DriverHost processes + processing/ Image-processing modules loaded by ScopeOneCore + tools/ Optional desktop tools loaded by the Qt application +``` + +Micro-Manager remains the built-in hardware provider and continues to load its Device Adapters from `.cfg` files. Native devices that do not belong in Micro-Manager use the `HardwareProvider` plugin contract. Built-in and external processing modules share stable string IDs and parameter descriptors, so the Image Processing panel creates the same controls for both. Built-in desktop tools and external tool plugins likewise share the tool registry. + +The Image Processing panel can process all live cameras or one selected camera. It can also run the current pipeline once on an image layer or across every frame of a recorded Gallery session stack. Stack processing runs in the background, preserves temporal module state across the stack, supports cancellation, and creates a new Gallery session without changing live pipeline state. + **Windows Build Steps:** 1. Build and install `ScopeOneCore`: diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index f1db249..098b2e3 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -90,6 +90,7 @@ set(CORE_SOURCES src/ParticleAnalysis.cpp src/StageMosaicManager.cpp src/ImageProcessingFramework.cpp + src/ProcessingModuleRegistry.cpp src/SpatiotemporalBinningModule.cpp src/GaussianBlurModule.cpp src/FFTModule.cpp @@ -124,7 +125,10 @@ set(CORE_HEADERS internal/StageMosaicManager.h internal/ImageProcessingFramework.h internal/ProcessingModule.h + internal/ProcessingModuleRegistry.h + include/scopeone/ProcessingPlugin.h internal/DriverHostProtocol.h + internal/SharedFrameRing.h internal/DriverHostProviderProxy.h internal/SpatiotemporalBinningModule.h internal/GaussianBlurModule.h @@ -228,6 +232,7 @@ scopeone_add_runtime_copy_commands(ScopeOne_DriverHost $; + 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; diff --git a/ScopeOneCore/include/scopeone/ExperimentDocument.h b/ScopeOneCore/include/scopeone/ExperimentDocument.h index 6dc7e41..8f7e98f 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, @@ -84,7 +74,7 @@ namespace scopeone::core struct ProcessingModuleRecipe { - ProcessingModuleKind kind{ProcessingModuleKind::Unknown}; + QString moduleId; int schemaVersion{kProcessingModuleSchemaVersion}; QVariantMap parameters; }; @@ -265,7 +255,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/ProcessingPlugin.h b/ScopeOneCore/include/scopeone/ProcessingPlugin.h new file mode 100644 index 0000000..14aa0bc --- /dev/null +++ b/ScopeOneCore/include/scopeone/ProcessingPlugin.h @@ -0,0 +1,84 @@ +#pragma once + +#include "scopeone/ImageFrame.h" +#include "scopeone/scopeone_core_export.h" + +#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 ProcessingResult + { + ImageFrame frame; + QString error; + + bool succeeded() const { return error.isEmpty() && frame.isValid(); } + }; + + class SCOPEONE_CORE_EXPORT ProcessingModule + { + public: + virtual ~ProcessingModule() = default; + + 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; + }; + + 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/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 21c63b5..4bc2547 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include "scopeone/HardwareTypes.h" #include "scopeone/HardwareProvider.h" #include "scopeone/ImageFrame.h" +#include "scopeone/ProcessingPlugin.h" #include "scopeone/scopeone_core_export.h" class CMMCore; @@ -51,7 +53,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; @@ -450,17 +451,20 @@ 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; } + 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; } private: - ProcessingModuleKind m_kind{ProcessingModuleKind::Unknown}; + QString m_id; QString m_name; QVariantMap m_parameters; + ProcessingModuleDescriptor m_descriptor; }; class DevicePropertyInfo @@ -636,16 +640,23 @@ 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; QList processingModules() const; - bool addProcessingModule(ProcessingModuleKind kind); + bool addProcessingModule(const QString& moduleId); bool removeProcessingModule(int index); bool setProcessingModuleParameters(int index, const QVariantMap& parameters); bool resetProcessingModuleState(int index); + quint64 requestLayerProcessing(const QString& layerKey); + quint64 requestRecordingSessionStackProcessing(const QString& sessionId, + const QString& cameraId); + bool cancelProcessingRequest(quint64 requestId); void setRecordingMaxPendingWriteBytes(qint64 bytes); @@ -686,6 +697,9 @@ namespace scopeone::core 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, @@ -727,6 +741,15 @@ namespace scopeone::core void processingModulesChanged(); void processingModuleParametersChanged(int index); void processingSettingsChanged(); + void layerProcessingFinished(quint64 requestId, + const QString& sourceLayerKey, + 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 recordingProgressChanged(int phase, qint64 frameCurrent, @@ -750,6 +773,7 @@ namespace scopeone::core void recordingStopped(const std::shared_ptr& session); void recordingSessionSaveFinished(const std::shared_ptr& session); void recordingSessionClosed(const QString& sessionId); + void recordingSessionsChanged(); void recordingSessionFrameReady( quint64 requestId, const std::shared_ptr& session, @@ -872,12 +896,14 @@ 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}; QSet m_sessionsSaving; + QSet m_pendingProviderRegistrations; enum class ConfigurationState { Unloaded, @@ -894,6 +920,9 @@ 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}; }; } diff --git a/ScopeOneCore/include/scopeone/SimulatorProvider.h b/ScopeOneCore/include/scopeone/SimulatorProvider.h index 94ff509..95f5a7a 100644 --- a/ScopeOneCore/include/scopeone/SimulatorProvider.h +++ b/ScopeOneCore/include/scopeone/SimulatorProvider.h @@ -24,6 +24,7 @@ namespace scopeone::core 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; @@ -71,6 +72,7 @@ namespace scopeone::core double m_exposureMs{10.0}; quint64 m_frameIndex{0}; FrameSink m_frameSink; + PreviewStateSink m_previewStateSink; QTimer m_timer; mutable QMutex m_mutex; }; 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/CameraManager.h b/ScopeOneCore/internal/CameraManager.h index 7a622be..d279088 100644 --- a/ScopeOneCore/internal/CameraManager.h +++ b/ScopeOneCore/internal/CameraManager.h @@ -25,6 +25,7 @@ namespace scopeone::core::internal ~CameraManager() override; void setFrameSink(FrameSink sink) override; + void setPreviewStateSink(PreviewStateSink sink) override; bool configureNativeCamera(const std::shared_ptr& core, const QString& cameraId, @@ -44,9 +45,11 @@ namespace scopeone::core::internal bool startPreviewFor(const QString& cameraId) override; bool stopPreviewFor(const QString& cameraId) override; bool isPreviewRunning(const QString& cameraId) const override; - void setFrameDeliveryPaused(bool paused) override; - bool setRecordingFrameDeliveryEnabled(bool enabled) override; - bool setHighRateFrameDeliveryEnabled(bool enabled) 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; @@ -89,6 +92,7 @@ namespace scopeone::core::internal 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 index b104b2e..7eab518 100644 --- a/ScopeOneCore/internal/CameraRuntimeControl.h +++ b/ScopeOneCore/internal/CameraRuntimeControl.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace scopeone::core::internal @@ -10,9 +11,11 @@ namespace scopeone::core::internal public: virtual ~CameraRuntimeControl() = default; - virtual void setFrameDeliveryPaused(bool paused) = 0; - virtual bool setRecordingFrameDeliveryEnabled(bool enabled) = 0; - virtual bool setHighRateFrameDeliveryEnabled(bool enabled) = 0; + 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/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/FFTModule.h b/ScopeOneCore/internal/FFTModule.h index 534bc99..a5b4075 100644 --- a/ScopeOneCore/internal/FFTModule.h +++ b/ScopeOneCore/internal/FFTModule.h @@ -22,7 +22,7 @@ namespace scopeone::core::internal 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; 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 index f733c2a..952752a 100644 --- a/ScopeOneCore/internal/HardwareRuntime.h +++ b/ScopeOneCore/internal/HardwareRuntime.h @@ -63,6 +63,7 @@ namespace scopeone::core::internal explicit HardwareRuntime(QObject* parent = nullptr); void setFrameSink(FrameSink sink) override; + void setPreviewStateSink(PreviewStateSink sink) override; bool startPreview() override; bool stopPreview() override; bool startPreviewFor(const QString& cameraId) override; @@ -96,9 +97,11 @@ namespace scopeone::core::internal bool captureEventFrame(const QString& cameraId, ImageFrame& frame, int timeoutMs) override; - void setFrameDeliveryPaused(bool paused) override; - bool setRecordingFrameDeliveryEnabled(bool enabled) override; - bool setHighRateFrameDeliveryEnabled(bool enabled) 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; QString defaultXYStage() const override; @@ -151,9 +154,11 @@ namespace scopeone::core::internal bool registerProvider(const HardwareProviderPtr& provider); void unregisterProvider(const QString& providerId); bool refreshProvider(const QString& providerId); + bool stopPreviewForProvider(const QString& providerId); signals: void devicesChanged(); + void previewStateChanged(bool running); private: CameraProvider* cameraProviderForDevice(const QString& logicalId) const; @@ -163,10 +168,13 @@ namespace scopeone::core::internal StateProvider* stateProviderForDevice(const QString& logicalId) const; ConfigurationProvider* configurationProviderForGroup(const QString& groupName) const; QList cameraProviders() const; + QList> runtimeControlsFor( + const QStringList& cameraIds) const; DeviceRegistry m_registry; FrameRouter m_frameRouter; AcquisitionEngine m_acquisitionEngine; FrameSink m_frameSink; + PreviewStateSink m_previewStateSink; }; } diff --git a/ScopeOneCore/internal/MicroManagerProvider.h b/ScopeOneCore/internal/MicroManagerProvider.h index 7f10347..fd42de3 100644 --- a/ScopeOneCore/internal/MicroManagerProvider.h +++ b/ScopeOneCore/internal/MicroManagerProvider.h @@ -30,6 +30,7 @@ namespace scopeone::core::internal void setDevices(const QList& devices); void setFrameSink(FrameSink sink) override; + void setPreviewStateSink(PreviewStateSink sink) override; bool startPreview() override; bool stopPreview() override; bool startPreviewFor(const QString& cameraId) override; @@ -63,9 +64,11 @@ namespace scopeone::core::internal bool captureEventFrame(const QString& cameraId, scopeone::core::ImageFrame& frame, int timeoutMs) override; - void setFrameDeliveryPaused(bool paused) override; - bool setRecordingFrameDeliveryEnabled(bool enabled) override; - bool setHighRateFrameDeliveryEnabled(bool enabled) 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; QString defaultXYStage() const override; diff --git a/ScopeOneCore/internal/ProcessingModule.h b/ScopeOneCore/internal/ProcessingModule.h index a733e17..8d790c6 100644 --- a/ScopeOneCore/internal/ProcessingModule.h +++ b/ScopeOneCore/internal/ProcessingModule.h @@ -1,35 +1,10 @@ #pragma once -#include -#include -#include - -#include "scopeone/ExperimentDocument.h" +#include "scopeone/ProcessingPlugin.h" namespace scopeone::core::internal { using ImageFrame = scopeone::core::ImageFrame; - using ProcessingModuleKind = scopeone::core::ProcessingModuleKind; - - struct ProcessingResult - { - ImageFrame frame; - QString error; - - bool succeeded() const { return error.isEmpty() && frame.isValid(); } - }; - - class ProcessingModule - { - public: - virtual ~ProcessingModule() = default; - - virtual ProcessingModuleKind kind() const noexcept = 0; - virtual QString name() const = 0; - virtual QVariantMap parameters() const = 0; - virtual void setParameters(const QVariantMap& params) = 0; - virtual std::unique_ptr createRuntime() const = 0; - virtual bool resetState() { return false; } - virtual ProcessingResult process(const ImageFrame& frame, int processingBitDepth) = 0; - }; + using ProcessingModule = scopeone::core::ProcessingModule; + using ProcessingResult = scopeone::core::ProcessingResult; } diff --git a/ScopeOneCore/internal/ProcessingModuleRegistry.h b/ScopeOneCore/internal/ProcessingModuleRegistry.h new file mode 100644 index 0000000..7fd9198 --- /dev/null +++ b/ScopeOneCore/internal/ProcessingModuleRegistry.h @@ -0,0 +1,40 @@ +#pragma once + +#include "scopeone/ProcessingPlugin.h" + +#include +#include +#include +#include +#include + +class QPluginLoader; + +namespace scopeone::core::internal +{ + class ProcessingModuleRegistry + { + public: + using Factory = std::function()>; + + ProcessingModuleRegistry(); + ~ProcessingModuleRegistry(); + + bool registerModule(const ProcessingModuleDescriptor& descriptor, Factory factory); + QList descriptors() const; + ProcessingModuleDescriptor descriptor(const QString& moduleId) const; + std::unique_ptr create(const QString& moduleId) const; + QStringList loadPlugins(const QString& directoryPath); + + private: + struct Entry + { + ProcessingModuleDescriptor descriptor; + Factory factory; + }; + + QHash m_entries; + QStringList m_order; + std::vector> m_pluginLoaders; + }; +} diff --git a/ScopeOneCore/internal/SharedFrameRing.h b/ScopeOneCore/internal/SharedFrameRing.h new file mode 100644 index 0000000..2d00f8a --- /dev/null +++ b/ScopeOneCore/internal/SharedFrameRing.h @@ -0,0 +1,70 @@ +#pragma once + +#include "scopeone/SharedFrame.h" + +#include +#include +#include + +namespace scopeone::core::internal::sharedframe +{ + static_assert(std::atomic_ref::is_always_lock_free, + "Shared frame state requires lock-free 32-bit atomics"); + + inline quint64 payloadSize(const SharedFrameHeader& header) + { + return static_cast(header.stride) * header.height; + } + + inline bool headerLooksSane(const SharedFrameHeader& header) + { + if (header.channels != 1 || header.width == 0 || header.height == 0 + || header.stride == 0) + { + return false; + } + const bool mono8 = header.pixelFormat == static_cast(SharedPixelFormat::Mono8); + const bool mono16 = header.pixelFormat == static_cast(SharedPixelFormat::Mono16); + if ((!mono8 && !mono16) + || (mono8 && header.bitsPerSample != 8) + || (mono16 && (header.bitsPerSample == 0 || header.bitsPerSample > 16))) + { + return false; + } + const quint32 bytesPerPixel = mono16 ? 2u : 1u; + const quint64 minimumStride = static_cast(header.width) * bytesPerPixel; + const quint64 bytes = payloadSize(header); + return header.width <= static_cast((std::numeric_limits::max)()) + && header.height <= static_cast((std::numeric_limits::max)()) + && header.stride <= static_cast((std::numeric_limits::max)()) + && header.stride >= minimumStride + && header.stride % bytesPerPixel == 0 + && bytes > 0 + && bytes <= static_cast(kSharedFrameMaxBytes); + } + + inline bool claimSlot(uchar* slot, SharedFrameHeader& header) + { + auto& stateValue = *reinterpret_cast(slot); + std::atomic_ref state(stateValue); + quint32 expected = 2; + if (!state.compare_exchange_strong(expected, + 3, + std::memory_order_acq_rel, + std::memory_order_acquire)) + { + return false; + } + std::memcpy(&header, slot, sizeof(header)); + header.state = 2; + if (headerLooksSane(header)) return true; + state.store(2, std::memory_order_release); + return false; + } + + inline void releaseSlot(uchar* slot) + { + auto& stateValue = *reinterpret_cast(slot); + std::atomic_ref(stateValue).store(2, std::memory_order_release); + } +} diff --git a/ScopeOneCore/internal/SpatiotemporalBinningModule.h b/ScopeOneCore/internal/SpatiotemporalBinningModule.h index c1b21d9..f2e7fb5 100644 --- a/ScopeOneCore/internal/SpatiotemporalBinningModule.h +++ b/ScopeOneCore/internal/SpatiotemporalBinningModule.h @@ -19,7 +19,7 @@ namespace scopeone::core::internal Skip = 4 }; - ProcessingModuleKind kind() const noexcept override { return ProcessingModuleKind::SpatiotemporalBinning; } + QString id() const override { return QStringLiteral("spatiotemporal_binning"); } QString name() const override { return "Spatiotemporal Binning"; } QVariantMap parameters() const override; void setParameters(const QVariantMap& params) override; diff --git a/ScopeOneCore/python/scopeone/README.md b/ScopeOneCore/python/scopeone/README.md index 429301e..a79e1c5 100644 --- a/ScopeOneCore/python/scopeone/README.md +++ b/ScopeOneCore/python/scopeone/README.md @@ -163,8 +163,8 @@ A control connection is synchronous and processes one request at a time. Agent a - `ScopeOne.processing_state()` - `ScopeOne.processing_modules()` - `ScopeOne.set_processing_bit_depth(bit_depth)` -- `ScopeOne.set_realtime_processing(enabled)` -- `ScopeOne.start_processing()` +- `ScopeOne.set_realtime_processing(enabled, camera_id=None)` +- `ScopeOne.start_processing(camera_id=None)` - `ScopeOne.stop_processing()` - `ScopeOne.add_processing_module(kind, parameters=None)` - `ScopeOne.remove_processing_module(index)` @@ -286,9 +286,9 @@ ScopeOne uses one local control pipe for JSON commands and one shared-memory blo - `start_stage_mosaic`: fields `cameraId`, `xyStageId`, and optional `rows`, `columns`, `stepXUm`, `stepYUm`, `settleMs`, `returnToStart`, and `gallerySaveDir`; starts asynchronous mosaic acquisition and returns `status`. `gallerySaveDir` becomes the default directory if the resulting Gallery session is saved later. - `stage_mosaic_status`: response `status` with `state`, tile progress, message, and completed session ID. - `cancel_stage_mosaic`: cancels the running mosaic and returns its final `status`. -- `processing_modules`: response `bitDepth`, `realTime`, and `modules`. +- `processing_modules`: response `bitDepth`, `realTime`, `realTimeSource`, `modules`, and descriptor list `availableModules`. - `set_processing_bit_depth`: fields `bitDepth`, accepts `8` or `16`. -- `set_realtime_processing`: fields `enabled`. +- `set_realtime_processing`: fields `enabled` and optional `cameraId`; an empty camera ID selects all cameras. - `add_processing_module`: fields `kind`, optional `parameters`; response `index`. - `remove_processing_module`: fields `index`. - `set_processing_module_parameters`: fields `index`, `parameters`. @@ -332,7 +332,7 @@ For timed MDA with more than one time point, `order` must begin with `time` so e The initially created document is a complete editable Draft with in-memory recording enabled by default. Set `plan.streamToDisk`, `plan.saveDir`, and `plan.baseName` together for streamed output. Experiment documents are parsed strictly: every schema field is required, unknown fields and unsupported schema versions are rejected, and `start_experiment` accepts only Draft documents whose camera IDs are currently available. `start_experiment` is non-blocking; use the returned `ExperimentSession` or the direct status and cancel methods to control the run. Call `ExperimentSession.close()` after completion to release retained recording frames while keeping document status available. -Processing module editing follows the desktop UI rules: stop real-time processing before changing bit depth, adding/removing modules, updating module parameters, or resetting module state. `add_processing_module` accepts `fft`, `background_calibration`, `spatiotemporal_binning`, `gaussian_blur`, and `differential_rolling`. +Processing module editing follows the desktop UI rules: stop real-time processing before changing bit depth, adding/removing modules, updating module parameters, or resetting module state. Pass `add_processing_module` a stable module ID returned in `processing_modules.availableModules`; this includes modules supplied by installed processing plugins. ### Frame transfer diff --git a/ScopeOneCore/python/scopeone/src/scopeone/client.py b/ScopeOneCore/python/scopeone/src/scopeone/client.py index e42e3ef..b3e2b09 100644 --- a/ScopeOneCore/python/scopeone/src/scopeone/client.py +++ b/ScopeOneCore/python/scopeone/src/scopeone/client.py @@ -992,7 +992,9 @@ def processing_state(self) -> dict: return { "bitDepth": int(response.get("bitDepth", 0)), "realTime": bool(response.get("realTime", False)), + "realTimeSource": str(response.get("realTimeSource", "")), "modules": list(response.get("modules", [])), + "availableModules": list(response.get("availableModules", [])), } def processing_modules(self) -> list[dict]: @@ -1006,18 +1008,19 @@ def set_processing_bit_depth(self, bit_depth: int) -> None: } ) - def set_realtime_processing(self, enabled: bool) -> bool: - self._request( - { - "type": "set_realtime_processing", - "enabled": bool(enabled), - } - ) + def set_realtime_processing(self, enabled: bool, camera_id: str | None = None) -> bool: + request = { + "type": "set_realtime_processing", + "enabled": bool(enabled), + } + if camera_id is not None: + request["cameraId"] = str(camera_id) + self._request(request) return True def add_processing_module( self, - kind: str | int, + kind: str, parameters: dict | None = None, ) -> int: request = { diff --git a/ScopeOneCore/python/scopeone/src/scopeone/core.py b/ScopeOneCore/python/scopeone/src/scopeone/core.py index 32ac2ab..a5e904e 100644 --- a/ScopeOneCore/python/scopeone/src/scopeone/core.py +++ b/ScopeOneCore/python/scopeone/src/scopeone/core.py @@ -318,16 +318,16 @@ def processing_modules(self): def set_processing_bit_depth(self, bit_depth: int): self._client.set_processing_bit_depth(bit_depth) - def set_realtime_processing(self, enabled: bool): - return self._client.set_realtime_processing(enabled) + def set_realtime_processing(self, enabled: bool, camera_id: str | None = None): + return self._client.set_realtime_processing(enabled, camera_id) - def start_processing(self): - self.set_realtime_processing(True) + def start_processing(self, camera_id: str | None = None): + self.set_realtime_processing(True, camera_id) def stop_processing(self): self.set_realtime_processing(False) - def add_processing_module(self, kind: str | int, parameters: dict | None = None): + def add_processing_module(self, kind: str, parameters: dict | None = None): return self._client.add_processing_module(kind, parameters) def remove_processing_module(self, index: int): diff --git a/ScopeOneCore/src/CameraManager.cpp b/ScopeOneCore/src/CameraManager.cpp index 46b62d3..a17956f 100644 --- a/ScopeOneCore/src/CameraManager.cpp +++ b/ScopeOneCore/src/CameraManager.cpp @@ -34,6 +34,11 @@ namespace scopeone::core::internal m_frameSink = std::move(sink); } + void CameraManager::setPreviewStateSink(PreviewStateSink sink) + { + m_previewStateSink = std::move(sink); + } + // Selects one camera backend and forwards its runtime signals bool CameraManager::activateBackend(CameraBackend::Kind kind) { @@ -70,7 +75,11 @@ namespace scopeone::core::internal connect(m_backend.get(), &CameraBackend::frameDeliveryFailed, this, &CameraManager::frameDeliveryFailed); connect(m_backend.get(), &CameraBackend::previewStateChanged, - this, &CameraManager::previewStateChanged); + this, [this](bool running) + { + if (m_previewStateSink) m_previewStateSink(running); + emit previewStateChanged(running); + }); connect(m_backend.get(), &CameraBackend::driverHostControlServerListening, this, &CameraManager::driverHostControlServerListening); if (!m_backend->setHighRateFrameDeliveryEnabled(m_highRateFrameDeliveryEnabled) @@ -215,7 +224,7 @@ namespace scopeone::core::internal return !normalizedId.isEmpty() && m_backend && m_backend->isPreviewRunning(normalizedId); } - void CameraManager::setFrameDeliveryPaused(bool paused) + void CameraManager::setFrameDeliveryPaused(const QStringList&, bool paused) { if (m_backend) { @@ -224,7 +233,7 @@ namespace scopeone::core::internal } // Switches the active backend between preview and lossless recording delivery - bool CameraManager::setRecordingFrameDeliveryEnabled(bool enabled) + bool CameraManager::setRecordingFrameDeliveryEnabled(const QStringList&, bool enabled) { const bool ok = !m_backend || m_backend->setRecordingFrameDeliveryEnabled(enabled); if (ok || !enabled) @@ -235,7 +244,7 @@ namespace scopeone::core::internal } // Switches processing delivery independently of preview display rate - bool CameraManager::setHighRateFrameDeliveryEnabled(bool enabled) + bool CameraManager::setHighRateFrameDeliveryEnabled(const QStringList&, bool enabled) { const bool ok = !m_backend || m_backend->setHighRateFrameDeliveryEnabled(enabled); if (ok) diff --git a/ScopeOneCore/src/DriverHostCameraBackend.cpp b/ScopeOneCore/src/DriverHostCameraBackend.cpp index 3c8f2ab..b0f3100 100644 --- a/ScopeOneCore/src/DriverHostCameraBackend.cpp +++ b/ScopeOneCore/src/DriverHostCameraBackend.cpp @@ -1,5 +1,6 @@ #include "internal/CameraBackend.h" #include "internal/DriverHostProtocol.h" +#include "internal/SharedFrameRing.h" #include "scopeone/SharedFrame.h" #include @@ -30,9 +31,6 @@ namespace scopeone::core::internal { - static_assert(std::atomic_ref::is_always_lock_free, - "Shared frame state requires lock-free 32-bit atomics"); - using scopeone::core::ImageFrame; using scopeone::core::SharedFrameHeader; using scopeone::core::SharedMemoryControl; @@ -891,82 +889,6 @@ namespace scopeone::core::internal DriverHostFrameWorker* m_frameWorker{nullptr}; }; - // Returns payload byte count from a shared frame header - static quint64 sharedFramePayloadSize(const SharedFrameHeader& header) - { - return static_cast(header.stride) * header.height; - } - - // Validates one shared frame header before reading pixels - static bool headerLooksSane(const SharedFrameHeader& header) - { - if (header.channels != 1) return false; - if (header.width == 0 || header.height == 0) return false; - if (header.stride == 0) return false; - if (header.pixelFormat != static_cast(SharedPixelFormat::Mono8) && - header.pixelFormat != static_cast(SharedPixelFormat::Mono16)) - { - return false; - } - - const quint32 bytesPerPixel = - (header.pixelFormat == static_cast(SharedPixelFormat::Mono16)) ? 2u : 1u; - if (header.pixelFormat == static_cast(SharedPixelFormat::Mono8) - && header.bitsPerSample != 8) - { - return false; - } - if (header.pixelFormat == static_cast(SharedPixelFormat::Mono16) - && (header.bitsPerSample == 0 || header.bitsPerSample > 16)) - { - return false; - } - const quint64 minimumStride = static_cast(header.width) * bytesPerPixel; - if (minimumStride > static_cast((std::numeric_limits::max)())) return false; - if (header.width > static_cast((std::numeric_limits::max)())) return false; - if (header.height > static_cast((std::numeric_limits::max)())) return false; - if (header.stride > static_cast((std::numeric_limits::max)())) return false; - if (header.stride < minimumStride) return false; - if ((header.stride % bytesPerPixel) != 0) return false; - - const quint64 rawSize = sharedFramePayloadSize(header); - if (rawSize == 0 || rawSize > static_cast(kSharedFrameMaxBytes)) return false; - - return true; - } - - // Claims one ready ring slot so the producer cannot overwrite it while copying - static bool claimFrameSlot(uchar* slotPtr, SharedFrameHeader& header) - { - auto& stateValue = *reinterpret_cast(slotPtr); - std::atomic_ref state(stateValue); - quint32 expected = 2; - if (!state.compare_exchange_strong(expected, - 3, - std::memory_order_acq_rel, - std::memory_order_acquire)) - { - return false; - } - - memcpy(&header, slotPtr, sizeof(header)); - header.state = 2; - if (headerLooksSane(header)) - { - return true; - } - - state.store(2, std::memory_order_release); - return false; - } - - // Releases one claimed ring slot back to the producer - static void releaseFrameSlot(uchar* slotPtr) - { - auto& stateValue = *reinterpret_cast(slotPtr); - std::atomic_ref(stateValue).store(2, std::memory_order_release); - } - DriverHostCameraBackend::DriverHostCameraBackend(ProcessingFrameGate& processingFrameGate) : CameraBackend(processingFrameGate) { @@ -1492,7 +1414,7 @@ namespace scopeone::core::internal const uchar* pixelData, QList& frames) { - const quint64 rawSize = sharedFramePayloadSize(header); + const quint64 rawSize = sharedframe::payloadSize(header); QByteArray payload; payload.resize(static_cast(rawSize)); memcpy(payload.data(), pixelData, static_cast(rawSize)); @@ -1531,10 +1453,10 @@ namespace scopeone::core::internal if (idx >= kSharedFrameNumSlots) return false; uchar* ptr = base + baseOffset + idx * slotStride; SharedFrameHeader header{}; - if (!claimFrameSlot(ptr, header)) return false; + if (!sharedframe::claimSlot(ptr, header)) return false; if (header.frameIndex <= slot.lastFrameIndex) { - releaseFrameSlot(ptr); + sharedframe::releaseSlot(ptr); return false; } capturedHeader = header; @@ -1553,12 +1475,12 @@ namespace scopeone::core::internal { uchar* ptr = base + baseOffset + i * slotStride; SharedFrameHeader header{}; - if (!claimFrameSlot(ptr, header)) continue; + if (!sharedframe::claimSlot(ptr, header)) continue; if (header.frameIndex > bestIndex) { if (capturedSlot) { - releaseFrameSlot(capturedSlot); + sharedframe::releaseSlot(capturedSlot); } bestIndex = header.frameIndex; capturedHeader = header; @@ -1566,7 +1488,7 @@ namespace scopeone::core::internal } else { - releaseFrameSlot(ptr); + sharedframe::releaseSlot(ptr); } } } @@ -1578,7 +1500,7 @@ namespace scopeone::core::internal frames); if (capturedSlot) { - releaseFrameSlot(capturedSlot); + sharedframe::releaseSlot(capturedSlot); } if (ok) { @@ -1617,14 +1539,14 @@ namespace scopeone::core::internal { uchar* ptr = base + baseOffset + i * slotStride; SharedFrameHeader header{}; - if (!claimFrameSlot(ptr, header)) continue; + if (!sharedframe::claimSlot(ptr, header)) continue; if (header.frameIndex > lastIndex) { claimedSlots.push_back({ptr, header}); } else { - releaseFrameSlot(ptr); + sharedframe::releaseSlot(ptr); } } @@ -1649,7 +1571,7 @@ namespace scopeone::core::internal { maxIndex = claimed.header.frameIndex; } - releaseFrameSlot(claimed.ptr); + sharedframe::releaseSlot(claimed.ptr); } if (frames.isEmpty()) { diff --git a/ScopeOneCore/src/DriverHostProviderProxy.cpp b/ScopeOneCore/src/DriverHostProviderProxy.cpp index 6e7c3d1..e2459a8 100644 --- a/ScopeOneCore/src/DriverHostProviderProxy.cpp +++ b/ScopeOneCore/src/DriverHostProviderProxy.cpp @@ -2,6 +2,7 @@ #include "internal/CameraRuntimeControl.h" #include "internal/DriverHostProtocol.h" +#include "internal/SharedFrameRing.h" #include "scopeone/CameraProvider.h" #include "scopeone/HardwareCapabilities.h" #include "scopeone/SharedFrame.h" @@ -36,9 +37,6 @@ namespace scopeone::core::internal { - static_assert(std::atomic_ref::is_always_lock_free, - "Shared frame state requires lock-free 32-bit atomics"); - namespace { constexpr int kControlReadyTimeoutMs = 15000; @@ -80,67 +78,6 @@ namespace scopeone::core::internal return false; } - quint64 sharedFramePayloadSize(const SharedFrameHeader& header) - { - return static_cast(header.stride) * header.height; - } - - bool headerLooksSane(const SharedFrameHeader& header) - { - if (header.channels != 1 || header.width == 0 || header.height == 0 - || header.stride == 0) - { - return false; - } - const bool mono8 = - header.pixelFormat == static_cast(SharedPixelFormat::Mono8); - const bool mono16 = - header.pixelFormat == static_cast(SharedPixelFormat::Mono16); - if ((!mono8 && !mono16) - || (mono8 && header.bitsPerSample != 8) - || (mono16 && (header.bitsPerSample == 0 || header.bitsPerSample > 16))) - { - return false; - } - const quint32 bytesPerPixel = mono16 ? 2u : 1u; - const quint64 minimumStride = static_cast(header.width) * bytesPerPixel; - const quint64 payloadSize = sharedFramePayloadSize(header); - return header.width <= static_cast((std::numeric_limits::max)()) - && header.height <= static_cast((std::numeric_limits::max)()) - && header.stride <= static_cast((std::numeric_limits::max)()) - && header.stride >= minimumStride - && header.stride % bytesPerPixel == 0 - && payloadSize > 0 - && payloadSize <= static_cast(kSharedFrameMaxBytes); - } - - bool claimFrameSlot(uchar* slot, SharedFrameHeader& header) - { - auto& stateValue = *reinterpret_cast(slot); - std::atomic_ref state(stateValue); - quint32 expected = 2; - if (!state.compare_exchange_strong(expected, - 3, - std::memory_order_acq_rel, - std::memory_order_acquire)) - { - return false; - } - memcpy(&header, slot, sizeof(header)); - header.state = 2; - if (headerLooksSane(header)) - { - return true; - } - state.store(2, std::memory_order_release); - return false; - } - - void releaseFrameSlot(uchar* slot) - { - auto& stateValue = *reinterpret_cast(slot); - std::atomic_ref(stateValue).store(2, std::memory_order_release); - } } class DriverHostProviderTransport final : public QObject @@ -655,7 +592,7 @@ namespace scopeone::core::internal QList& frames) { QByteArray payload; - payload.resize(static_cast(sharedFramePayloadSize(header))); + payload.resize(static_cast(sharedframe::payloadSize(header))); memcpy(payload.data(), pixels, static_cast(payload.size())); ImageFrame frame = ImageFrame::fromSharedFrame(reader.cameraId, header, payload); if (!frame.isValid()) return false; @@ -679,10 +616,10 @@ namespace scopeone::core::internal uchar* slot = base + kSharedMemoryControlSize + static_cast(index) * kSharedFrameSlotStride; SharedFrameHeader header{}; - if (!claimFrameSlot(slot, header)) return false; + if (!sharedframe::claimSlot(slot, header)) return false; if (header.frameIndex <= reader.lastFrameIndex) { - releaseFrameSlot(slot); + sharedframe::releaseSlot(slot); return false; } capturedHeader = header; @@ -701,17 +638,17 @@ namespace scopeone::core::internal uchar* slot = base + kSharedMemoryControlSize + index * kSharedFrameSlotStride; SharedFrameHeader header{}; - if (!claimFrameSlot(slot, header)) continue; + if (!sharedframe::claimSlot(slot, header)) continue; if (header.frameIndex > bestIndex) { - if (capturedSlot) releaseFrameSlot(capturedSlot); + if (capturedSlot) sharedframe::releaseSlot(capturedSlot); bestIndex = header.frameIndex; capturedHeader = header; capturedSlot = slot; } else { - releaseFrameSlot(slot); + sharedframe::releaseSlot(slot); } } } @@ -721,7 +658,7 @@ namespace scopeone::core::internal capturedHeader, capturedSlot + kSharedFrameHeaderSize, frames); - if (capturedSlot) releaseFrameSlot(capturedSlot); + if (capturedSlot) sharedframe::releaseSlot(capturedSlot); if (copied) reader.lastFrameIndex = capturedHeader.frameIndex; return copied; } @@ -744,14 +681,14 @@ namespace scopeone::core::internal uchar* slot = base + kSharedMemoryControlSize + index * kSharedFrameSlotStride; SharedFrameHeader header{}; - if (!claimFrameSlot(slot, header)) continue; + if (!sharedframe::claimSlot(slot, header)) continue; if (header.frameIndex > reader.lastFrameIndex) { claimed.push_back({slot, header}); } else { - releaseFrameSlot(slot); + sharedframe::releaseSlot(slot); } } std::sort(claimed.begin(), claimed.end(), [](const Claimed& lhs, const Claimed& rhs) @@ -768,7 +705,7 @@ namespace scopeone::core::internal { lastIndex = item.header.frameIndex; } - releaseFrameSlot(item.slot); + sharedframe::releaseSlot(item.slot); } reader.lastFrameIndex = lastIndex; return !frames.isEmpty(); @@ -853,12 +790,22 @@ namespace scopeone::core::internal hostKey, m_options, [this](const ImageFrame& frame) { deliverFrame(frame); }, - [this](const QString& cameraId, bool running) - { - QMutexLocker locker(&m_stateMutex); - if (running) m_runningCameras.insert(cameraId); - else m_runningCameras.remove(cameraId); - }); + [this](const QString& cameraId, bool running) + { + bool anyRunning = false; + { + QMutexLocker locker(&m_stateMutex); + if (running) m_runningCameras.insert(cameraId); + else m_runningCameras.remove(cameraId); + anyRunning = !m_runningCameras.isEmpty(); + } + PreviewStateSink sink; + { + QMutexLocker locker(&m_sinkMutex); + sink = m_previewStateSink; + } + if (sink) sink(anyRunning); + }); m_worker->moveToThread(&m_workerThread); connect(&m_workerThread, &QThread::finished, m_worker, &QObject::deleteLater); @@ -904,6 +851,12 @@ namespace scopeone::core::internal m_frameSink = std::move(sink); } + void setPreviewStateSink(PreviewStateSink sink) override + { + QMutexLocker locker(&m_sinkMutex); + m_previewStateSink = std::move(sink); + } + bool startPreview() override { const QStringList ids = cameraIds(); @@ -1171,7 +1124,7 @@ namespace scopeone::core::internal return ok; } - void setFrameDeliveryPaused(bool paused) override + void setFrameDeliveryPaused(const QStringList&, bool paused) override { if (!m_worker || !m_workerThread.isRunning()) return; DriverHostProviderTransport* const worker = m_worker; @@ -1183,7 +1136,7 @@ namespace scopeone::core::internal else QMetaObject::invokeMethod(worker, update, Qt::BlockingQueuedConnection); } - bool setRecordingFrameDeliveryEnabled(bool enabled) override + bool setRecordingFrameDeliveryEnabled(const QStringList&, bool enabled) override { if (m_recordingDelivery == enabled) return true; const QString mode = enabled @@ -1196,7 +1149,7 @@ namespace scopeone::core::internal return true; } - bool setHighRateFrameDeliveryEnabled(bool enabled) override + bool setHighRateFrameDeliveryEnabled(const QStringList&, bool enabled) override { if (m_highRateDelivery == enabled) return true; if (!m_recordingDelivery) @@ -1579,6 +1532,7 @@ namespace scopeone::core::internal mutable QMutex m_requestMutex; mutable QMutex m_sinkMutex; FrameSink m_frameSink; + PreviewStateSink m_previewStateSink; mutable QMutex m_stateMutex; QSet m_runningCameras; bool m_recordingDelivery{false}; diff --git a/ScopeOneCore/src/ExperimentDocument.cpp b/ScopeOneCore/src/ExperimentDocument.cpp index 8fbc0ca..a9b018b 100644 --- a/ScopeOneCore/src/ExperimentDocument.cpp +++ b/ScopeOneCore/src/ExperimentDocument.cpp @@ -563,7 +563,7 @@ namespace scopeone::core return result; } - QVariantMap processingParametersFromJson(ProcessingModuleKind kind, + QVariantMap processingParametersFromJson(const QString& moduleId, const QJsonObject& object) { QVariantMap result = variantMapFromJson(object); @@ -589,36 +589,36 @@ namespace scopeone::core } }; - switch (kind) + if (moduleId == QStringLiteral("fft")) { - case ProcessingModuleKind::FFT: normalizeDouble(QStringLiteral("min_feature_size")); normalizeDouble(QStringLiteral("max_feature_size")); normalizeInt(QStringLiteral("filter_kind")); normalizeInt(QStringLiteral("output_mode")); - break; - case ProcessingModuleKind::BackgroundCalibration: + } + else if (moduleId == QStringLiteral("background_calibration")) + { normalizeInt(QStringLiteral("calibration_frames")); normalizeInt(QStringLiteral("operation")); normalizeInt(QStringLiteral("method")); normalizeInt(QStringLiteral("mode")); - break; - case ProcessingModuleKind::SpatiotemporalBinning: + } + else if (moduleId == QStringLiteral("spatiotemporal_binning")) + { normalizeInt(QStringLiteral("spatial_bin_x")); normalizeInt(QStringLiteral("spatial_bin_y")); normalizeInt(QStringLiteral("temporal_bin")); normalizeInt(QStringLiteral("spatial_mode")); normalizeInt(QStringLiteral("temporal_mode")); - break; - case ProcessingModuleKind::GaussianBlur: + } + else if (moduleId == QStringLiteral("gaussian_blur")) + { normalizeInt(QStringLiteral("kernel_size")); normalizeDouble(QStringLiteral("sigma")); - break; - case ProcessingModuleKind::DifferentialRolling: + } + else if (moduleId == QStringLiteral("differential_rolling")) + { normalizeInt(QStringLiteral("batch_size")); - break; - case ProcessingModuleKind::Unknown: - break; } return result; } @@ -698,39 +698,29 @@ namespace scopeone::core return false; } - bool parseProcessingModuleKind(const QString& name, ProcessingModuleKind& kind) + QString processingModuleIdFromDocument(const QString& name) { if (name == QStringLiteral("FFT")) { - kind = ProcessingModuleKind::FFT; - return true; + return QStringLiteral("fft"); } if (name == QStringLiteral("BackgroundCalibration")) { - kind = ProcessingModuleKind::BackgroundCalibration; - return true; + return QStringLiteral("background_calibration"); } if (name == QStringLiteral("SpatiotemporalBinning")) { - kind = ProcessingModuleKind::SpatiotemporalBinning; - return true; + return QStringLiteral("spatiotemporal_binning"); } if (name == QStringLiteral("GaussianBlur")) { - kind = ProcessingModuleKind::GaussianBlur; - return true; + return QStringLiteral("gaussian_blur"); } if (name == QStringLiteral("DifferentialRolling")) { - kind = ProcessingModuleKind::DifferentialRolling; - return true; - } - if (name == QStringLiteral("Unknown")) - { - kind = ProcessingModuleKind::Unknown; - return true; + return QStringLiteral("differential_rolling"); } - return false; + return name == QStringLiteral("Unknown") ? QString{} : name.trimmed(); } bool parseExperimentRunState(const QString& name, ExperimentRunState& state) @@ -1100,24 +1090,15 @@ namespace scopeone::core const QString modulePath = elementPath(memberPath(memberPath(path, QStringLiteral("processing")), QStringLiteral("modules")), index); - if (module.schemaVersion != kProcessingModuleSchemaVersion) + if (module.schemaVersion <= 0) { return fail(errorMessage, - QStringLiteral("%1.schemaVersion %2 is unsupported; expected %3") + QStringLiteral("%1.schemaVersion must be positive; got %2") .arg(modulePath) - .arg(module.schemaVersion) - .arg(kProcessingModuleSchemaVersion)); + .arg(module.schemaVersion)); } - switch (module.kind) + if (module.moduleId.trimmed().isEmpty()) { - case ProcessingModuleKind::FFT: - case ProcessingModuleKind::BackgroundCalibration: - case ProcessingModuleKind::SpatiotemporalBinning: - case ProcessingModuleKind::GaussianBlur: - case ProcessingModuleKind::DifferentialRolling: - break; - case ProcessingModuleKind::Unknown: - default: return fail(errorMessage, QStringLiteral("%1.kind must name a supported processing module").arg(modulePath)); } @@ -1621,7 +1602,7 @@ namespace scopeone::core for (const ProcessingModuleRecipe& module : plan.processing.modules) { QJsonObject moduleObject; - moduleObject.insert(QStringLiteral("kind"), processingModuleKindName(module.kind)); + moduleObject.insert(QStringLiteral("kind"), module.moduleId); moduleObject.insert(QStringLiteral("schemaVersion"), module.schemaVersion); moduleObject.insert(QStringLiteral("parameters"), variantMapToJson(module.parameters)); modules.append(canonicalJsonObject(moduleObject)); @@ -2098,21 +2079,21 @@ namespace scopeone::core { return false; } - if (!parseProcessingModuleKind(kindName, module.kind)) + module.moduleId = processingModuleIdFromDocument(kindName); + if (module.moduleId.isEmpty()) { return fail(errorMessage, QStringLiteral("%1.kind has unknown processing module name '%2'") .arg(modulePath, kindName)); } - if (module.schemaVersion != kProcessingModuleSchemaVersion) + if (module.schemaVersion <= 0) { return fail(errorMessage, - QStringLiteral("%1.schemaVersion %2 is unsupported; expected %3") + QStringLiteral("%1.schemaVersion must be positive; got %2") .arg(modulePath) - .arg(module.schemaVersion) - .arg(kProcessingModuleSchemaVersion)); + .arg(module.schemaVersion)); } - module.parameters = processingParametersFromJson(module.kind, parameters); + module.parameters = processingParametersFromJson(module.moduleId, parameters); parsed.processing.modules.append(module); } @@ -3172,26 +3153,6 @@ namespace scopeone::core return QStringLiteral("Unknown"); } - QString processingModuleKindName(ProcessingModuleKind kind) - { - switch (kind) - { - case ProcessingModuleKind::FFT: - return QStringLiteral("FFT"); - case ProcessingModuleKind::BackgroundCalibration: - return QStringLiteral("BackgroundCalibration"); - case ProcessingModuleKind::SpatiotemporalBinning: - return QStringLiteral("SpatiotemporalBinning"); - case ProcessingModuleKind::GaussianBlur: - return QStringLiteral("GaussianBlur"); - case ProcessingModuleKind::DifferentialRolling: - return QStringLiteral("DifferentialRolling"); - case ProcessingModuleKind::Unknown: - return QStringLiteral("Unknown"); - } - return QStringLiteral("Unknown"); - } - QString experimentRunStateName(ExperimentRunState state) { switch (state) diff --git a/ScopeOneCore/src/HardwareRuntime.cpp b/ScopeOneCore/src/HardwareRuntime.cpp index a039a02..a69702b 100644 --- a/ScopeOneCore/src/HardwareRuntime.cpp +++ b/ScopeOneCore/src/HardwareRuntime.cpp @@ -3,7 +3,9 @@ #include "scopeone/CameraProvider.h" #include +#include #include +#include #include namespace scopeone::core::internal @@ -86,7 +88,7 @@ namespace scopeone::core::internal bool removed = false; { QWriteLocker locker(&m_lock); - removed = m_providers.remove(providerId.trimmed()) > 0; + removed = m_providers.remove(providerId.trimmed()); } if (removed) { @@ -191,6 +193,11 @@ namespace scopeone::core::internal m_frameSink = std::move(sink); } + void HardwareRuntime::setPreviewStateSink(PreviewStateSink sink) + { + m_previewStateSink = std::move(sink); + } + CameraProvider* HardwareRuntime::cameraProviderForDevice(const QString& logicalId) const { const HardwareProviderPtr provider = m_registry.providerForDevice(logicalId); @@ -449,58 +456,74 @@ namespace scopeone::core::internal return provider && provider->captureEventFrame(cameraId, frame, timeoutMs); } - void HardwareRuntime::setFrameDeliveryPaused(bool paused) + QList> HardwareRuntime::runtimeControlsFor( + const QStringList& cameraIds) const { - for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + QHash grouped; + for (const QString& cameraId : cameraIds) { - const HardwareProviderPtr provider = m_registry.provider(descriptor.id); + const QString normalizedId = cameraId.trimmed(); + const HardwareProviderPtr provider = m_registry.providerForDevice(normalizedId); if (auto* control = dynamic_cast(provider.get())) { - control->setFrameDeliveryPaused(paused); + QStringList& providerCameraIds = grouped[control]; + if (!providerCameraIds.contains(normalizedId)) + { + providerCameraIds.append(normalizedId); + } } } + QList> result; + result.reserve(grouped.size()); + for (auto it = grouped.cbegin(); it != grouped.cend(); ++it) + { + result.append({it.key(), it.value()}); + } + return result; } - bool HardwareRuntime::setRecordingFrameDeliveryEnabled(bool enabled) + void HardwareRuntime::setFrameDeliveryPaused(const QStringList& cameraIds, bool paused) { - QList changed; - for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + for (const auto& [control, providerCameraIds] : runtimeControlsFor(cameraIds)) { - const HardwareProviderPtr provider = m_registry.provider(descriptor.id); - if (auto* control = dynamic_cast(provider.get())) + control->setFrameDeliveryPaused(providerCameraIds, paused); + } + } + + bool HardwareRuntime::setRecordingFrameDeliveryEnabled(const QStringList& cameraIds, + bool enabled) + { + QList> changed; + for (const auto& entry : runtimeControlsFor(cameraIds)) + { + if (!entry.first->setRecordingFrameDeliveryEnabled(entry.second, enabled)) { - if (!control->setRecordingFrameDeliveryEnabled(enabled)) + for (const auto& previous : changed) { - for (CameraRuntimeControl* previous : changed) - { - previous->setRecordingFrameDeliveryEnabled(!enabled); - } - return false; + previous.first->setRecordingFrameDeliveryEnabled(previous.second, !enabled); } - changed.append(control); + return false; } + changed.append(entry); } return true; } - bool HardwareRuntime::setHighRateFrameDeliveryEnabled(bool enabled) + bool HardwareRuntime::setHighRateFrameDeliveryEnabled(const QStringList& cameraIds, + bool enabled) { - QList changed; - for (const HardwareProviderDescriptor& descriptor : m_registry.providers()) + QList> changed; + for (const auto& entry : runtimeControlsFor(cameraIds)) { - const HardwareProviderPtr provider = m_registry.provider(descriptor.id); - if (auto* control = dynamic_cast(provider.get())) + if (!entry.first->setHighRateFrameDeliveryEnabled(entry.second, enabled)) { - if (!control->setHighRateFrameDeliveryEnabled(enabled)) + for (const auto& previous : changed) { - for (CameraRuntimeControl* previous : changed) - { - previous->setHighRateFrameDeliveryEnabled(!enabled); - } - return false; + previous.first->setHighRateFrameDeliveryEnabled(previous.second, !enabled); } - changed.append(control); + return false; } + changed.append(entry); } return true; } @@ -810,6 +833,7 @@ namespace scopeone::core::internal { cameraProvider->stopPreview(); cameraProvider->setFrameSink({}); + cameraProvider->setPreviewStateSink({}); } } m_registry.clear(); @@ -860,6 +884,26 @@ namespace scopeone::core::internal { m_frameRouter.publish(frame); }); + cameraProvider->setPreviewStateSink([this](bool) + { + const auto publishState = [this]() + { + bool running = false; + for (const HardwareDeviceDescriptor& device : m_registry.devices()) + { + if (device.kind == HardwareDeviceKind::Camera + && isPreviewRunning(device.logicalId)) + { + running = true; + break; + } + } + if (m_previewStateSink) m_previewStateSink(running); + emit previewStateChanged(running); + }; + if (QThread::currentThread() == thread()) publishState(); + else QMetaObject::invokeMethod(this, publishState, Qt::QueuedConnection); + }); } return true; } @@ -871,6 +915,7 @@ namespace scopeone::core::internal { cameraProvider->stopPreview(); cameraProvider->setFrameSink({}); + cameraProvider->setPreviewStateSink({}); } m_registry.unregisterProvider(providerId); } @@ -880,4 +925,11 @@ namespace scopeone::core::internal const HardwareProviderPtr provider = m_registry.provider(providerId); return provider && registerProvider(provider); } + + bool HardwareRuntime::stopPreviewForProvider(const QString& providerId) + { + const HardwareProviderPtr provider = m_registry.provider(providerId); + auto* cameraProvider = dynamic_cast(provider.get()); + return cameraProvider && cameraProvider->stopPreview(); + } } diff --git a/ScopeOneCore/src/MicroManagerProvider.cpp b/ScopeOneCore/src/MicroManagerProvider.cpp index fb2ae9c..0b8fdda 100644 --- a/ScopeOneCore/src/MicroManagerProvider.cpp +++ b/ScopeOneCore/src/MicroManagerProvider.cpp @@ -80,6 +80,14 @@ namespace scopeone::core::internal } } + void MicroManagerProvider::setPreviewStateSink(PreviewStateSink sink) + { + if (m_cameraProvider) + { + m_cameraProvider->setPreviewStateSink(std::move(sink)); + } + } + bool MicroManagerProvider::startPreview() { return m_cameraProvider && m_cameraProvider->startPreview(); @@ -391,24 +399,26 @@ namespace scopeone::core::internal return m_cameraProvider && m_cameraProvider->captureEventFrame(cameraId, frame, timeoutMs); } - void MicroManagerProvider::setFrameDeliveryPaused(bool paused) + void MicroManagerProvider::setFrameDeliveryPaused(const QStringList& cameraIds, bool paused) { if (m_cameraRuntimeControl) { - m_cameraRuntimeControl->setFrameDeliveryPaused(paused); + m_cameraRuntimeControl->setFrameDeliveryPaused(cameraIds, paused); } } - bool MicroManagerProvider::setRecordingFrameDeliveryEnabled(bool enabled) + bool MicroManagerProvider::setRecordingFrameDeliveryEnabled(const QStringList& cameraIds, + bool enabled) { return !m_cameraRuntimeControl - || m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(enabled); + || m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(cameraIds, enabled); } - bool MicroManagerProvider::setHighRateFrameDeliveryEnabled(bool enabled) + bool MicroManagerProvider::setHighRateFrameDeliveryEnabled(const QStringList& cameraIds, + bool enabled) { return !m_cameraRuntimeControl - || m_cameraRuntimeControl->setHighRateFrameDeliveryEnabled(enabled); + || m_cameraRuntimeControl->setHighRateFrameDeliveryEnabled(cameraIds, enabled); } bool MicroManagerProvider::isProcessingFrameTokenCurrent(const QString& cameraId, diff --git a/ScopeOneCore/src/ProcessingModuleRegistry.cpp b/ScopeOneCore/src/ProcessingModuleRegistry.cpp new file mode 100644 index 0000000..d0018e4 --- /dev/null +++ b/ScopeOneCore/src/ProcessingModuleRegistry.cpp @@ -0,0 +1,277 @@ +#include "internal/ProcessingModuleRegistry.h" + +#include "internal/BackgroundCalibrationModule.h" +#include "internal/DifferentialRollingModule.h" +#include "internal/FFTModule.h" +#include "internal/GaussianBlurModule.h" +#include "internal/SpatiotemporalBinningModule.h" + +#include +#include +#include +#include +#include + +namespace scopeone::core::internal +{ + namespace + { + bool validDescriptor(const ProcessingModuleDescriptor& descriptor) + { + QSet parameterKeys; + for (const ProcessingParameterDescriptor& parameter : descriptor.parameters) + { + const QString key = parameter.key.trimmed(); + if (key.isEmpty() + || parameter.name.trimmed().isEmpty() + || parameterKeys.contains(key)) + { + return false; + } + parameterKeys.insert(key); + } + return !descriptor.id.trimmed().isEmpty() + && !descriptor.name.trimmed().isEmpty(); + } + + ProcessingParameterDescriptor integerParameter(const char* key, + const char* name, + int value, + int minimum, + int maximum, + int step = 1) + { + return {QString::fromLatin1(key), + QString::fromLatin1(name), + ProcessingParameterType::Integer, + value, + minimum, + maximum, + step}; + } + + ProcessingParameterDescriptor realParameter(const char* key, + const char* name, + double value, + double minimum, + double maximum, + double step, + int decimals) + { + ProcessingParameterDescriptor descriptor{ + QString::fromLatin1(key), + QString::fromLatin1(name), + ProcessingParameterType::Real, + value, + minimum, + maximum, + step}; + descriptor.decimals = decimals; + return descriptor; + } + + ProcessingParameterDescriptor booleanParameter(const char* key, + const char* name, + bool value) + { + return {QString::fromLatin1(key), + QString::fromLatin1(name), + ProcessingParameterType::Boolean, + value}; + } + + ProcessingParameterDescriptor choiceParameter( + const char* key, + const char* name, + int value, + std::initializer_list choices) + { + ProcessingParameterDescriptor descriptor{ + QString::fromLatin1(key), + QString::fromLatin1(name), + ProcessingParameterType::Choice, + value}; + int index = 0; + for (const char* choice : choices) + { + descriptor.choices.append({QString::fromLatin1(choice), index++}); + } + return descriptor; + } + + template + bool registerBuiltIn(ProcessingModuleRegistry& registry, + ProcessingModuleDescriptor descriptor) + { + return registry.registerModule(descriptor, []() + { + return std::make_unique(); + }); + } + + void registerBuiltIns(ProcessingModuleRegistry& registry) + { + registerBuiltIn( + registry, + {QStringLiteral("spatiotemporal_binning"), + QStringLiteral("Spatiotemporal Binning"), + 1, + {integerParameter("spatial_bin_x", "Spatial X", 1, 1, 64), + integerParameter("spatial_bin_y", "Spatial Y", 1, 1, 64), + integerParameter("temporal_bin", "Temporal", 1, 1, 256), + choiceParameter("spatial_mode", "Spatial mode", 0, + {"Mean", "Sum", "Minimum", "Maximum", "Skip"}), + choiceParameter("temporal_mode", "Temporal mode", 0, + {"Mean", "Sum", "Minimum", "Maximum", "Skip"})}, + true}); + + registerBuiltIn( + registry, + {QStringLiteral("gaussian_blur"), + QStringLiteral("Gaussian Blur"), + 1, + {integerParameter("kernel_size", "Kernel size", 3, 1, 99, 2), + realParameter("sigma", "Sigma", 0.0, 0.0, 100.0, 0.1, 2)}}); + + registerBuiltIn( + registry, + {QStringLiteral("fft"), + QStringLiteral("FFT"), + 1, + {choiceParameter("output_mode", "Output", 2, + {"FFT Spectrum", "Bandpass FFT Spectrum", "Bandpass IFFT Image"}), + realParameter("min_feature_size", "Min feature size", 2.0, 0.0, 1000.0, 0.1, 2), + realParameter("max_feature_size", "Max feature size", 10.0, 0.0, 1000.0, 0.1, 2), + choiceParameter("filter_kind", "Filter kind", 0, {"Smooth", "Hard"})}}); + + registerBuiltIn( + registry, + {QStringLiteral("differential_rolling"), + QStringLiteral("Differential Rolling"), + 1, + {integerParameter("batch_size", "Batch size", 16, 1, 256), + booleanParameter("normalize", "Normalize by batch 1", true)}, + true}); + + registerBuiltIn( + registry, + {QStringLiteral("background_calibration"), + QStringLiteral("Background Calibration"), + 1, + {integerParameter("calibration_frames", "Frames", 101, 3, 1001, 2), + choiceParameter("mode", "Mode", 0, {"Snapshot", "Running"}), + choiceParameter("method", "Method", 0, + {"Median", "Mean", "Maximum", "Minimum"}), + choiceParameter("operation", "Operation", 0, + {"Subtract", "Add", "Multiply", "Divide"})}, + true}); + } + } + + ProcessingModuleRegistry::ProcessingModuleRegistry() + { + registerBuiltIns(*this); + } + + ProcessingModuleRegistry::~ProcessingModuleRegistry() = default; + + bool ProcessingModuleRegistry::registerModule(const ProcessingModuleDescriptor& descriptor, + Factory factory) + { + const QString id = descriptor.id.trimmed(); + if (!validDescriptor(descriptor) || !factory || m_entries.contains(id)) + { + return false; + } + + ProcessingModuleDescriptor normalized = descriptor; + normalized.id = id; + m_entries.insert(id, {std::move(normalized), std::move(factory)}); + m_order.append(id); + return true; + } + + QList ProcessingModuleRegistry::descriptors() const + { + QList result; + result.reserve(m_order.size()); + for (const QString& id : m_order) + { + result.append(m_entries.value(id).descriptor); + } + return result; + } + + ProcessingModuleDescriptor ProcessingModuleRegistry::descriptor(const QString& moduleId) const + { + return m_entries.value(moduleId.trimmed()).descriptor; + } + + std::unique_ptr ProcessingModuleRegistry::create(const QString& moduleId) const + { + const auto it = m_entries.constFind(moduleId.trimmed()); + if (it == m_entries.constEnd()) + { + return {}; + } + std::unique_ptr module = it->factory(); + return module && module->id() == it->descriptor.id ? std::move(module) : nullptr; + } + + QStringList ProcessingModuleRegistry::loadPlugins(const QString& directoryPath) + { + QStringList errors; + const QDir directory(directoryPath); + for (const QFileInfo& file : directory.entryInfoList(QDir::Files, QDir::Name)) + { + if (!QLibrary::isLibrary(file.absoluteFilePath())) + { + continue; + } + + auto loader = std::make_unique(file.absoluteFilePath()); + QObject* instance = loader->instance(); + auto* plugin = qobject_cast(instance); + if (!plugin) + { + errors.append(QStringLiteral("%1: %2") + .arg(file.fileName(), loader->errorString())); + continue; + } + + const QList pluginDescriptors = plugin->processingModules(); + QSet pluginIds; + bool valid = !pluginDescriptors.isEmpty(); + for (const ProcessingModuleDescriptor& descriptor : pluginDescriptors) + { + const QString id = descriptor.id.trimmed(); + if (!validDescriptor(descriptor) + || pluginIds.contains(id) + || m_entries.contains(id)) + { + valid = false; + break; + } + pluginIds.insert(id); + } + if (!valid) + { + errors.append(QStringLiteral("%1: invalid or duplicate processing module id") + .arg(file.fileName())); + loader->unload(); + continue; + } + + for (const ProcessingModuleDescriptor& descriptor : pluginDescriptors) + { + const QString id = descriptor.id.trimmed(); + registerModule(descriptor, [plugin, id]() + { + return plugin->createProcessingModule(id); + }); + } + m_pluginLoaders.push_back(std::move(loader)); + } + return errors; + } +} diff --git a/ScopeOneCore/src/RecordingManager.cpp b/ScopeOneCore/src/RecordingManager.cpp index 9fcbce3..8098693 100644 --- a/ScopeOneCore/src/RecordingManager.cpp +++ b/ScopeOneCore/src/RecordingManager.cpp @@ -1325,7 +1325,8 @@ namespace scopeone::core::internal { primeLastFrameIndices(); if (m_cameraRuntimeControl - && !m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(true)) + && !m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled( + m_captureState.activeCameraIds, true)) { if (plan.streamToDisk) { @@ -1343,7 +1344,8 @@ namespace scopeone::core::internal { if (m_cameraRuntimeControl) { - m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(false); + m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled( + m_captureState.activeCameraIds, false); } const QString writerError = writerErrorSnapshot(); qWarning().noquote() << (writerError.isEmpty() @@ -1398,6 +1400,7 @@ namespace scopeone::core::internal if (!m_captureState.isRecording) return; const bool streamToDisk = m_captureState.streamToDisk; + const QStringList activeCameraIds = m_captureState.activeCameraIds; auto session = m_activeSession; m_completionPending = true; if (streamToDisk) @@ -1411,7 +1414,7 @@ namespace scopeone::core::internal if (m_cameraRuntimeControl) { - m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(false); + m_cameraRuntimeControl->setRecordingFrameDeliveryEnabled(activeCameraIds, false); } if (m_mdaState.usingMda && m_mdaState.manager && m_mdaState.manager->isRunning()) { @@ -1419,7 +1422,7 @@ namespace scopeone::core::internal } if (m_cameraRuntimeControl) { - m_cameraRuntimeControl->setFrameDeliveryPaused(false); + m_cameraRuntimeControl->setFrameDeliveryPaused(activeCameraIds, false); } qInfo().noquote() << "Recording stopped"; @@ -2009,7 +2012,8 @@ namespace scopeone::core::internal if (m_cameraRuntimeControl) { - m_cameraRuntimeControl->setFrameDeliveryPaused(true); + m_cameraRuntimeControl->setFrameDeliveryPaused( + m_captureState.activeCameraIds, true); } m_mdaState.cameraId = m_captureState.activeCameraIds.first(); diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index b1c7127..7f2fa72 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -1,12 +1,9 @@ #include "scopeone/ScopeOneCore.h" #include "scopeone/ImageSceneModel.h" -#include "internal/BackgroundCalibrationModule.h" #include "internal/AcquisitionEngine.h" -#include "internal/DifferentialRollingModule.h" -#include "internal/FFTModule.h" -#include "internal/GaussianBlurModule.h" #include "internal/ImageProcessingFramework.h" +#include "internal/ProcessingModuleRegistry.h" #include "internal/FrameRouter.h" #include "internal/DriverHostProviderProxy.h" #include "internal/HardwareRuntime.h" @@ -15,7 +12,6 @@ #include "internal/CameraManager.h" #include "internal/ParticleAnalysis.h" #include "internal/RecordingManager.h" -#include "internal/SpatiotemporalBinningModule.h" #include "internal/StageMosaicManager.h" #include "MMCore.h" #include @@ -29,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +58,20 @@ namespace constexpr bool kFrameRateDiagnosticsEnabled = false; constexpr int kFrameRateDiagnosticIntervalMs = 3000; + struct ProviderRegistrationResult + { + scopeone::core::HardwareProviderPtr provider; + QString errorMessage; + }; + + struct OfflineProcessingResult + { + QList frames; + scopeone::core::ExperimentPlan plan; + QString errorMessage; + bool canceled{false}; + }; + // Convert a histogram bin to its lower source value int histogramBinLowerValue(int binIndex, int maxValue) { @@ -170,7 +181,7 @@ namespace { const auto& leftModule = left.modules.at(index); const auto& rightModule = right.modules.at(index); - if (leftModule.kind != rightModule.kind + if (leftModule.moduleId != rightModule.moduleId || leftModule.schemaVersion != rightModule.schemaVersion || leftModule.parameters != rightModule.parameters) { @@ -438,39 +449,18 @@ namespace namespace scopeone::core { - using scopeone::core::internal::BackgroundCalibrationModule; - using scopeone::core::internal::FFTModule; - using scopeone::core::internal::GaussianBlurModule; using scopeone::core::internal::ImageProcessingManager; + using scopeone::core::internal::HardwareRuntime; + using scopeone::core::internal::FrameRouter; using scopeone::core::internal::MMCoreManager; using scopeone::core::internal::CameraManager; + using scopeone::core::internal::CameraRuntimeControl; + using scopeone::core::internal::MicroManagerProvider; using scopeone::core::internal::ProcessingModule; using scopeone::core::internal::ProcessingPipelineDefinition; using scopeone::core::internal::RecordingManager; - using scopeone::core::internal::DifferentialRollingModule; - using scopeone::core::internal::SpatiotemporalBinningModule; using scopeone::core::internal::StageMosaicManager; - static std::unique_ptr createProcessingModule(ProcessingModuleKind kind) - { - switch (kind) - { - case ProcessingModuleKind::FFT: - return std::make_unique(); - case ProcessingModuleKind::BackgroundCalibration: - return std::make_unique(); - case ProcessingModuleKind::SpatiotemporalBinning: - return std::make_unique(); - case ProcessingModuleKind::GaussianBlur: - return std::make_unique(); - case ProcessingModuleKind::DifferentialRolling: - return std::make_unique(); - case ProcessingModuleKind::Unknown: - return {}; - } - return {}; - } - static bool equalCanonicalParameter(const QVariant& actual, const QVariant& expected) { return actual.metaType().id() == expected.metaType().id() @@ -913,6 +903,7 @@ namespace scopeone::core CameraManager* cameraManager{nullptr}; RecordingManager* recordingManager{nullptr}; ImageProcessingManager* imageProcessingManager{nullptr}; + std::unique_ptr processingModuleRegistry; StageMosaicManager* stageMosaicManager{nullptr}; QHash experiments; QHash> sessions; @@ -972,6 +963,7 @@ namespace scopeone::core || m_pendingStageCommands > 0 || !m_managers->activeExperimentId.isEmpty() || isRecording() + || m_pendingProviderRegistrations.contains(normalizedId) || m_managers->hardwareRuntime->deviceRegistry()->provider(normalizedId)) { if (errorMessage) @@ -981,23 +973,36 @@ namespace scopeone::core return false; } - HardwareProviderPtr provider = internal::createDriverHostProviderProxy( - normalizedId, - modulePath, - QJsonObject::fromVariantMap(options), - errorMessage); - if (!provider) - { - return false; - } - if (!m_managers->hardwareRuntime->registerProvider(provider)) - { - if (errorMessage) + m_pendingProviderRegistrations.insert(normalizedId); + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, + this, [this, watcher, normalizedId]() + { + ProviderRegistrationResult result = watcher->result(); + m_pendingProviderRegistrations.remove(normalizedId); + bool success = static_cast(result.provider); + if (success + && !m_managers->hardwareRuntime->registerProvider(result.provider)) + { + success = false; + result.errorMessage = QStringLiteral( + "Provider device catalog conflicts with registered hardware"); + } + emit hardwareProviderRegistrationFinished(normalizedId, + success, + result.errorMessage); + watcher->deleteLater(); + }); + const QJsonObject providerOptions = QJsonObject::fromVariantMap(options); + watcher->setFuture(QtConcurrent::run( + m_hardwareThreadPool.get(), + [normalizedId, modulePath, providerOptions]() { - *errorMessage = QStringLiteral("Provider device catalog conflicts with registered hardware"); - } - return false; - } + ProviderRegistrationResult result; + result.provider = internal::createDriverHostProviderProxy( + normalizedId, modulePath, providerOptions, &result.errorMessage); + return result; + })); return true; } @@ -1009,7 +1014,8 @@ namespace scopeone::core || m_configurationOperationRunning || m_pendingStageCommands > 0 || !m_managers->activeExperimentId.isEmpty() - || isRecording()) + || isRecording() + || m_pendingProviderRegistrations.contains(normalizedId)) { return false; } @@ -1122,6 +1128,14 @@ namespace scopeone::core qRegisterMetaType( "scopeone::core::ScopeOneCore::ParticleDetectionResult"); qRegisterMetaType("scopeone::core::ImageFrame"); + m_managers->processingModuleRegistry = + std::make_unique(); + const QStringList processingPluginErrors = m_managers->processingModuleRegistry->loadPlugins( + QDir(QCoreApplication::applicationDirPath()).filePath(QStringLiteral("plugins/processing"))); + for (const QString& error : processingPluginErrors) + { + qWarning().noquote() << QStringLiteral("Failed to load processing plugin %1").arg(error); + } m_histogramThreadPool = std::make_unique(); m_histogramThreadPool->setMaxThreadCount(1); m_hardwareThreadPool = std::make_unique(); @@ -1130,6 +1144,8 @@ namespace scopeone::core m_analysisThreadPool->setMaxThreadCount(1); m_sessionFrameThreadPool = std::make_unique(); m_sessionFrameThreadPool->setMaxThreadCount(1); + m_offlineProcessingThreadPool = std::make_unique(); + m_offlineProcessingThreadPool->setMaxThreadCount(1); m_previewFlushTimer = new QTimer(this); m_previewFlushTimer->setSingleShot(true); m_previewFlushTimer->setTimerType(Qt::PreciseTimer); @@ -1196,6 +1212,8 @@ namespace scopeone::core synchronizeCameraIdsFromRegistry(); emit hardwareDevicesChanged(); }); + connect(m_managers->hardwareRuntime, &HardwareRuntime::previewStateChanged, + this, &ScopeOneCore::previewStateChanged); m_managers->hardwareRuntime->registerProvider(m_managers->microManagerProvider); m_managers->recordingManager = new RecordingManager(this); m_managers->imageProcessingManager = new ImageProcessingManager(this); @@ -1255,8 +1273,6 @@ namespace scopeone::core m_managers->recordingManager, &RecordingManager::onRawFramesReady); connect(m_managers->cameraManager, &CameraManager::frameDeliveryFailed, m_managers->recordingManager, &RecordingManager::onFrameDeliveryFailed); - connect(m_managers->cameraManager, &CameraManager::previewStateChanged, - this, &ScopeOneCore::previewStateChanged); connect(m_managers->cameraManager, &CameraManager::driverHostControlServerListening, this, &ScopeOneCore::driverHostControlServerListening); @@ -1353,6 +1369,27 @@ namespace scopeone::core Qt::DirectConnection); connect(m_managers->imageProcessingManager, &ImageProcessingManager::processingError, this, &ScopeOneCore::processingError); + + const QDir hardwarePlugins( + QDir(QCoreApplication::applicationDirPath()).filePath(QStringLiteral("plugins/hardware"))); + for (const QFileInfo& file : hardwarePlugins.entryInfoList(QDir::Files, QDir::Name)) + { + QPluginLoader loader(file.absoluteFilePath()); + const QJsonObject metadata = loader.metaData().value(QStringLiteral("MetaData")).toObject(); + if (!metadata.value(QStringLiteral("autoLoad")).toBool()) + { + continue; + } + const QString providerId = metadata.value(QStringLiteral("providerId")).toString().trimmed(); + QString errorMessage; + if (providerId.isEmpty() + || !registerDriverHostProvider(providerId, file.absoluteFilePath(), {}, &errorMessage)) + { + qWarning().noquote() + << QStringLiteral("Failed to discover hardware plugin %1: %2") + .arg(file.fileName(), errorMessage); + } + } } // Release loaded devices before the facade is destroyed @@ -1361,11 +1398,18 @@ namespace scopeone::core m_hardwareThreadPool->waitForDone(); m_analysisThreadPool->waitForDone(); m_sessionFrameThreadPool->waitForDone(); + for (const auto& token : std::as_const(m_processingRequestCancelTokens)) + { + token->store(true); + } + m_offlineProcessingThreadPool->waitForDone(); m_managers->recordingManager->shutdown(); m_pendingStageCommands = 0; m_configurationOperationRunning = false; unloadConfigurationForShutdown(); m_histogramThreadPool->waitForDone(); + delete m_managers->imageProcessingManager; + m_managers->imageProcessingManager = nullptr; } // Return the public configuration lifecycle state @@ -1462,7 +1506,6 @@ namespace scopeone::core { return false; } - QStringList normalizedPaths; for (const QString& path : paths) { @@ -1508,7 +1551,6 @@ namespace scopeone::core m_loadedConfigSha256 = QString::fromLatin1( QCryptographicHash::hash(file.readAll(), QCryptographicHash::Sha256).toHex()); } - emit hardwareConfigurationChanged(); return true; } @@ -1538,6 +1580,12 @@ namespace scopeone::core } } m_cameraIds = nextCameraIds; + if (!m_realTimeProcessingSource.isEmpty() + && !m_cameraIds.contains(m_realTimeProcessingSource)) + { + m_realTimeProcessingSource.clear(); + emit processingSettingsChanged(); + } for (const QString& cameraId : m_cameraIds) { ensureSceneLayer(rawLayerKey(cameraId), @@ -1584,7 +1632,7 @@ namespace scopeone::core // Apply the configured hardware shutdown state before releasing devices void ScopeOneCore::applySystemShutdownPreset() { - m_managers->hardwareRuntime->stopPreview(); + m_managers->hardwareRuntime->stopPreviewForProvider(QStringLiteral("micro-manager")); auto handle = core(); try { @@ -1593,7 +1641,8 @@ namespace scopeone::core { return; } - setConfig(MM::g_CFGGroup_System, MM::g_CFGGroup_System_Shutdown); + m_managers->microManagerProvider->setConfig( + MM::g_CFGGroup_System, MM::g_CFGGroup_System_Shutdown, nullptr); } catch (const CMMError& error) { @@ -1611,7 +1660,7 @@ namespace scopeone::core const bool processingWasEnabled = isRealTimeProcessingEnabled(); m_managers->imageProcessingManager->enableRealTimeProcessing(false); const QStringList cameraIds = m_cameraIds; - m_managers->hardwareRuntime->stopPreview(); + m_managers->hardwareRuntime->stopPreviewForProvider(QStringLiteral("micro-manager")); if (shutdownCameraBackend) { m_managers->cameraManager->shutdownNow(); @@ -1664,6 +1713,11 @@ namespace scopeone::core m_configurationError = QStringLiteral("Another configuration operation is running"); return false; } + if (!m_pendingProviderRegistrations.isEmpty()) + { + m_configurationError = QStringLiteral("A hardware provider is still loading"); + return false; + } if (m_pendingStageCommands > 0) { m_configurationError = QStringLiteral("A stage command is running"); @@ -1751,6 +1805,7 @@ namespace scopeone::core return; } m_configurationOperationRunning = false; + emit hardwareConfigurationChanged(); emit configurationLoadFinished(true, result, {}); } else @@ -1838,6 +1893,11 @@ namespace scopeone::core m_configurationError = QStringLiteral("Another configuration operation is running"); return false; } + if (!m_pendingProviderRegistrations.isEmpty()) + { + m_configurationError = QStringLiteral("A hardware provider is still loading"); + return false; + } if (m_pendingStageCommands > 0) { m_configurationError = QStringLiteral("A stage command is running"); @@ -2202,7 +2262,9 @@ namespace scopeone::core void ScopeOneCore::submitProcessingFrame(const ImageFrame& frame, quint64 processingToken) { if (!frame.isValid() - || !m_managers->imageProcessingManager->isRealTimeProcessingEnabled()) + || !m_managers->imageProcessingManager->isRealTimeProcessingEnabled() + || (!m_realTimeProcessingSource.isEmpty() + && frame.cameraId != m_realTimeProcessingSource)) { return; } @@ -3572,7 +3634,8 @@ namespace scopeone::core } if (m_managers->imageProcessingManager->isRealTimeProcessingEnabled() == enabled) { - if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled(enabled)) + if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled( + m_cameraIds, enabled)) { return false; } @@ -3585,7 +3648,8 @@ namespace scopeone::core if (enabled) { m_managers->imageProcessingManager->enableRealTimeProcessing(true); - if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled(true)) + if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled( + m_cameraIds, true)) { m_managers->imageProcessingManager->enableRealTimeProcessing(false); return false; @@ -3593,7 +3657,8 @@ namespace scopeone::core } else { - if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled(false)) + if (!m_managers->cameraRuntimeControl->setHighRateFrameDeliveryEnabled( + m_cameraIds, false)) { return false; } @@ -3630,6 +3695,28 @@ namespace scopeone::core return true; } + QString ScopeOneCore::realTimeProcessingSource() const + { + return m_realTimeProcessingSource; + } + + bool ScopeOneCore::setRealTimeProcessingSource(const QString& cameraId) + { + const QString source = cameraId.trimmed(); + if (isRealTimeProcessingEnabled() + || (!source.isEmpty() && !m_cameraIds.contains(source))) + { + return false; + } + if (m_realTimeProcessingSource == source) + { + return true; + } + m_realTimeProcessingSource = source; + emit processingSettingsChanged(); + return true; + } + // Captures the ordered processing pipeline as a replayable recipe ProcessingRecipe ScopeOneCore::processingRecipe() const { @@ -3640,8 +3727,8 @@ namespace scopeone::core for (const ProcessingModuleInfo& module : modules) { ProcessingModuleRecipe entry; - entry.kind = module.kind(); - entry.schemaVersion = kProcessingModuleSchemaVersion; + entry.moduleId = module.id(); + entry.schemaVersion = module.descriptor().schemaVersion; entry.parameters = module.parameters(); recipe.modules.append(std::move(entry)); } @@ -3668,25 +3755,37 @@ namespace scopeone::core modules.reserve(static_cast(recipe.modules.size())); for (const ProcessingModuleRecipe& entry : recipe.modules) { - if (entry.schemaVersion != kProcessingModuleSchemaVersion) + const ProcessingModuleDescriptor descriptor = + m_managers->processingModuleRegistry->descriptor(entry.moduleId); + if (descriptor.id.isEmpty()) { if (errorMessage) { - *errorMessage = QStringLiteral("Unsupported processing module schema version: %1") - .arg(entry.schemaVersion); + *errorMessage = QStringLiteral("Unsupported processing module: %1") + .arg(entry.moduleId); } return false; } - std::unique_ptr module = createProcessingModule(entry.kind); - if (!module) + if (entry.schemaVersion != descriptor.schemaVersion) { if (errorMessage) { - *errorMessage = QStringLiteral("Unsupported processing module: %1") - .arg(processingModuleKindName(entry.kind)); + *errorMessage = QStringLiteral( + "Unsupported schema version %1 for processing module %2; expected %3") + .arg(entry.schemaVersion) + .arg(entry.moduleId) + .arg(descriptor.schemaVersion); } return false; } + std::unique_ptr module = + m_managers->processingModuleRegistry->create(entry.moduleId); + if (!module) + { + if (errorMessage) *errorMessage = QStringLiteral("Failed to create processing module: %1") + .arg(entry.moduleId); + return false; + } module->setParameters(entry.parameters); if (!equalCanonicalParameters(module->parameters(), entry.parameters)) { @@ -3756,6 +3855,12 @@ namespace scopeone::core return m_managers->imageProcessingManager->processFrameThrough(endModuleIndex, frame); } + // Lists every registered processing module type + QList ScopeOneCore::availableProcessingModules() const + { + return m_managers->processingModuleRegistry->descriptors(); + } + // Export processing module descriptions for the UI QList ScopeOneCore::processingModules() const { @@ -3763,26 +3868,28 @@ namespace scopeone::core ProcessingPipelineDefinition& definition = m_managers->imageProcessingManager->definition(); out.reserve(definition.moduleCount()); - definition.forEachModule([&out](const ProcessingModule* module) + definition.forEachModule([this, &out](const ProcessingModule* module) { ProcessingModuleInfo info; - info.setKind(module->kind()); + info.setId(module->id()); info.setName(module->name()); info.setParameters(module->parameters()); + info.setDescriptor(m_managers->processingModuleRegistry->descriptor(module->id())); out.append(std::move(info)); }); return out; } // Add a processing module to the editable pipeline - bool ScopeOneCore::addProcessingModule(ProcessingModuleKind kind) + bool ScopeOneCore::addProcessingModule(const QString& moduleId) { if (isRealTimeProcessingEnabled()) { return false; } ProcessingPipelineDefinition& definition = m_managers->imageProcessingManager->definition(); - std::unique_ptr module = createProcessingModule(kind); + std::unique_ptr module = + m_managers->processingModuleRegistry->create(moduleId); if (!module) return false; definition.addModule(std::move(module)); @@ -3829,7 +3936,7 @@ namespace scopeone::core return false; } m_managers->imageProcessingManager->clearRuntimePipelines(); - emit processingModulesChanged(); + emit processingModuleParametersChanged(index); return true; } @@ -3858,6 +3965,161 @@ namespace scopeone::core return true; } + // Process one current graph layer without blocking the UI thread + quint64 ScopeOneCore::requestLayerProcessing(const QString& layerKey) + { + const QString sourceLayerKey = layerKey.trimmed(); + const ImageFrame frame = graphFrame(sourceLayerKey); + if (!frame.isValid() || processingModules().isEmpty()) + { + return 0; + } + + const quint64 requestId = ++m_nextProcessingRequestId; + auto cancelToken = std::make_shared(false); + m_processingRequestCancelTokens.insert(requestId, cancelToken); + auto pipeline = m_managers->imageProcessingManager->definition().createRuntime(); + const int bitDepth = static_cast(processingBitDepth()); + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, + this, [this, watcher, requestId, sourceLayerKey, cancelToken]() + { + ProcessingResult result = watcher->result(); + m_processingRequestCancelTokens.remove(requestId); + emit layerProcessingFinished( + requestId, + sourceLayerKey, + cancelToken->load() ? ImageFrame{} : result.frame, + cancelToken->load() ? QStringLiteral("Processing canceled") : result.error); + watcher->deleteLater(); + }); + watcher->setFuture(QtConcurrent::run( + m_offlineProcessingThreadPool.get(), + [pipeline = std::move(pipeline), frame, bitDepth, cancelToken]() + { + if (cancelToken->load()) + { + return ProcessingResult{{}, QStringLiteral("Processing canceled")}; + } + return pipeline->process(frame, bitDepth); + })); + return requestId; + } + + // Process a complete recorded stack with one isolated stateful runtime + quint64 ScopeOneCore::requestRecordingSessionStackProcessing(const QString& sessionId, + const QString& cameraId) + { + const auto sourceSession = recordingSession(sessionId); + const QString sourceCameraId = cameraId.trimmed(); + const qint64 frameCount = sourceSession + ? sourceSession->recordedFrameCount(sourceCameraId) + : 0; + if (!sourceSession + || sourceCameraId.isEmpty() + || frameCount <= 0 + || frameCount > (std::numeric_limits::max)() + || processingModules().isEmpty()) + { + return 0; + } + + const quint64 requestId = ++m_nextProcessingRequestId; + auto cancelToken = std::make_shared(false); + m_processingRequestCancelTokens.insert(requestId, cancelToken); + auto pipeline = m_managers->imageProcessingManager->definition().createRuntime(); + const int bitDepth = static_cast(processingBitDepth()); + const ProcessingRecipe recipe = processingRecipe(); + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, + this, [this, watcher, requestId, cancelToken]() + { + OfflineProcessingResult result = watcher->result(); + m_processingRequestCancelTokens.remove(requestId); + result.canceled = result.canceled || cancelToken->load(); + std::shared_ptr outputSession; + if (!result.canceled && result.errorMessage.isEmpty()) + { + result.plan.experimentId = QUuid::createUuid().toString(QUuid::WithoutBraces); + result.plan.streamToDisk = false; + result.plan.saveDir.clear(); + result.plan.baseName += QStringLiteral("_processed"); + outputSession = createFrameSession(result.frames, result.plan); + if (!outputSession) + { + result.errorMessage = QStringLiteral("Failed to create processed stack"); + } + else + { + outputSession->setCapturePlan(result.plan); + } + } + if (result.canceled) + { + result.errorMessage = QStringLiteral("Processing canceled"); + } + emit stackProcessingFinished(requestId, outputSession, result.errorMessage); + watcher->deleteLater(); + }); + watcher->setFuture(QtConcurrent::run( + m_offlineProcessingThreadPool.get(), + [this, requestId, sourceSession, sourceCameraId, frameCount, bitDepth, recipe, + pipeline = std::move(pipeline), cancelToken]() + { + OfflineProcessingResult result; + result.plan = sourceSession->capturePlan(); + result.plan.cameraIds = {sourceCameraId}; + result.plan.processing = recipe; + result.frames.reserve(static_cast(frameCount)); + QElapsedTimer progressTimer; + progressTimer.start(); + for (int index = 0; index < static_cast(frameCount); ++index) + { + if (cancelToken->load()) + { + result.canceled = true; + break; + } + const ImageFrame frame = sourceSession->imageFrameAt(sourceCameraId, index); + if (!frame.isValid()) + { + result.errorMessage = QStringLiteral("Failed to read stack frame %1") + .arg(index + 1); + break; + } + ProcessingResult processed = pipeline->process(frame, bitDepth); + if (!processed.succeeded()) + { + result.errorMessage = processed.error; + break; + } + result.frames.append(std::move(processed.frame)); + if (progressTimer.elapsed() >= 100 || index + 1 == frameCount) + { + const qint64 completed = index + 1; + QMetaObject::invokeMethod(this, [this, requestId, completed, frameCount]() + { + emit stackProcessingProgress(requestId, completed, frameCount); + }); + progressTimer.restart(); + } + } + return result; + })); + return requestId; + } + + bool ScopeOneCore::cancelProcessingRequest(quint64 requestId) + { + const auto token = m_processingRequestCancelTokens.value(requestId); + if (!token) + { + return false; + } + token->store(true); + return true; + } + void ScopeOneCore::setRecordingMaxPendingWriteBytes(qint64 bytes) { m_managers->recordingManager->setRecordedMaxBytes(bytes); @@ -4222,6 +4484,7 @@ namespace scopeone::core } m_managers->sessions.remove(id); emit recordingSessionClosed(id); + emit recordingSessionsChanged(); return true; } @@ -4239,6 +4502,7 @@ namespace scopeone::core } m_managers->sessions.insert(experimentId, session); m_managers->experiments.insert(experimentId, session->experimentDocument()); + emit recordingSessionsChanged(); } // Finalize the shared experiment state before notifying API and UI clients diff --git a/ScopeOneCore/src/SimulatorProvider.cpp b/ScopeOneCore/src/SimulatorProvider.cpp index db4bcf3..b441aad 100644 --- a/ScopeOneCore/src/SimulatorProvider.cpp +++ b/ScopeOneCore/src/SimulatorProvider.cpp @@ -63,6 +63,12 @@ namespace scopeone::core m_frameSink = std::move(sink); } + void SimulatorProvider::setPreviewStateSink(PreviewStateSink sink) + { + QMutexLocker locker(&m_mutex); + m_previewStateSink = std::move(sink); + } + bool SimulatorProvider::startPreview() { { @@ -70,12 +76,25 @@ namespace scopeone::core if (!m_frameSink) return false; } m_timer.start(); + PreviewStateSink sink; + { + QMutexLocker locker(&m_mutex); + sink = m_previewStateSink; + } + if (sink) sink(true); return true; } bool SimulatorProvider::stopPreview() { + const bool wasRunning = m_timer.isActive(); m_timer.stop(); + PreviewStateSink sink; + { + QMutexLocker locker(&m_mutex); + sink = m_previewStateSink; + } + if (wasRunning && sink) sink(false); return true; } diff --git a/ScopeOneCore/src/SimulatorProvider.json b/ScopeOneCore/src/SimulatorProvider.json new file mode 100644 index 0000000..a82e981 --- /dev/null +++ b/ScopeOneCore/src/SimulatorProvider.json @@ -0,0 +1,4 @@ +{ + "providerId": "scopeone.simulator", + "autoLoad": false +} diff --git a/ScopeOneCore/src/SimulatorProviderPlugin.cpp b/ScopeOneCore/src/SimulatorProviderPlugin.cpp index 423788c..c670ffd 100644 --- a/ScopeOneCore/src/SimulatorProviderPlugin.cpp +++ b/ScopeOneCore/src/SimulatorProviderPlugin.cpp @@ -9,7 +9,7 @@ namespace scopeone::core public DriverHostProviderPlugin { Q_OBJECT - Q_PLUGIN_METADATA(IID ScopeOneDriverHostProviderPlugin_iid) + Q_PLUGIN_METADATA(IID ScopeOneDriverHostProviderPlugin_iid FILE "SimulatorProvider.json") Q_INTERFACES(scopeone::core::DriverHostProviderPlugin) public: diff --git a/VERSION b/VERSION index 0f52923..11ca4c2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.803 +1.5.021 diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index 9ed2d6f..5623388 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -1,647 +1,435 @@ #include "ImageProcessingWidget.h" #include "scopeone/ScopeOneCore.h" +#include "scopeone/ImageSceneModel.h" #include #include #include -#include #include -#include +#include #include #include #include #include #include #include +#include #include #include #include #include #include -#include +#include namespace scopeone::ui { namespace { + using ProcessingModuleDescriptor = scopeone::core::ProcessingModuleDescriptor; using ProcessingModuleInfo = scopeone::core::ScopeOneCore::ProcessingModuleInfo; - using ProcessingModuleKind = scopeone::core::ScopeOneCore::ProcessingModuleKind; + using ProcessingParameterDescriptor = scopeone::core::ProcessingParameterDescriptor; + using ProcessingParameterType = scopeone::core::ProcessingParameterType; - void configureParameterSpinBox(QAbstractSpinBox* spinBox) + void configureSpinBox(QAbstractSpinBox* spinBox) { spinBox->setKeyboardTracking(false); spinBox->setCorrectionMode(QAbstractSpinBox::CorrectToNearestValue); } - class ProcessingModuleConfigWidgetBase : public QWidget + class ModuleConfigWidget final : public QWidget { public: - ProcessingModuleConfigWidgetBase(scopeone::core::ScopeOneCore* core, - int moduleIndex, - QWidget* parent = nullptr) - : QWidget(parent) - , m_scopeonecore(core) - , m_moduleIndex(moduleIndex) + ModuleConfigWidget(scopeone::core::ScopeOneCore* core, + int moduleIndex, + const ProcessingModuleInfo& info, + QWidget* parent) + : QWidget(parent), m_core(core), m_moduleIndex(moduleIndex) { - if (!core) + auto* layout = new QVBoxLayout(this); + auto* group = new QGroupBox(info.name(), this); + auto* form = new QFormLayout(group); + for (const ProcessingParameterDescriptor& parameter : info.descriptor().parameters) { - qFatal("ProcessingModuleConfigWidgetBase requires ScopeOneCore"); + QWidget* editor = createEditor(parameter, + info.parameters().value(parameter.key, + parameter.defaultValue), + group); + m_editors.insert(parameter.key, editor); + m_descriptors.insert(parameter.key, parameter); + form->addRow(parameter.name + QLatin1Char(':'), editor); } - } - - protected: - bool applyParameters(const QVariantMap& parameters) - { - return m_scopeonecore->setProcessingModuleParameters(m_moduleIndex, parameters); - } - - bool resetModule() - { - return m_scopeonecore->resetProcessingModuleState(m_moduleIndex); - } - - scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; - int m_moduleIndex{-1}; - }; - - class FFTModuleConfigWidget : public ProcessingModuleConfigWidgetBase - { - public: - FFTModuleConfigWidget(scopeone::core::ScopeOneCore* core, - int moduleIndex, - const ProcessingModuleInfo& info, - QWidget* parent = nullptr) - : ProcessingModuleConfigWidgetBase(core, moduleIndex, parent) - { - auto* layout = new QVBoxLayout(this); - auto* group = new QGroupBox("FFT Settings", this); - auto* groupLayout = new QGridLayout(group); - - groupLayout->addWidget(new QLabel("Output:", group), 0, 0); - m_outputModeCombo = new QComboBox(group); - m_outputModeCombo->addItem("FFT Spectrum", 0); - m_outputModeCombo->addItem("Bandpass FFT Spectrum", 1); - m_outputModeCombo->addItem("Bandpass IFFT Image", 2); - groupLayout->addWidget(m_outputModeCombo, 0, 1); - - groupLayout->addWidget(new QLabel("Min feature size:", group), 1, 0); - m_minFeatureSizeSpin = new QDoubleSpinBox(group); - m_minFeatureSizeSpin->setRange(0.0, 1000.0); - m_minFeatureSizeSpin->setDecimals(2); - configureParameterSpinBox(m_minFeatureSizeSpin); - groupLayout->addWidget(m_minFeatureSizeSpin, 1, 1); - - groupLayout->addWidget(new QLabel("Max feature size:", group), 2, 0); - m_maxFeatureSizeSpin = new QDoubleSpinBox(group); - m_maxFeatureSizeSpin->setRange(0.0, 1000.0); - m_maxFeatureSizeSpin->setDecimals(2); - configureParameterSpinBox(m_maxFeatureSizeSpin); - groupLayout->addWidget(m_maxFeatureSizeSpin, 2, 1); - - groupLayout->addWidget(new QLabel("Filter kind:", group), 3, 0); - m_filterKindCombo = new QComboBox(group); - m_filterKindCombo->addItem("Smooth", 0); - m_filterKindCombo->addItem("Hard", 1); - groupLayout->addWidget(m_filterKindCombo, 3, 1); - layout->addWidget(group); - layout->addStretch(); - - const QVariantMap params = info.parameters(); - const int outputModeIndex = m_outputModeCombo->findData(params.value("output_mode").toInt()); - m_outputModeCombo->setCurrentIndex(outputModeIndex); - m_minFeatureSizeSpin->setValue(params.value("min_feature_size").toDouble()); - m_maxFeatureSizeSpin->setValue(params.value("max_feature_size").toDouble()); - const int filterIndex = m_filterKindCombo->findData(params.value("filter_kind").toInt()); - m_filterKindCombo->setCurrentIndex(filterIndex); - - connect(m_outputModeCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, [this]() { apply(); }); - connect(m_minFeatureSizeSpin, QOverload::of(&QDoubleSpinBox::valueChanged), - this, [this]() { apply(); }); - connect(m_maxFeatureSizeSpin, QOverload::of(&QDoubleSpinBox::valueChanged), - this, [this]() { apply(); }); - connect(m_filterKindCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, [this]() { apply(); }); - updateFilterControls(); - } - - private: - void apply() - { - QVariantMap params; - params["output_mode"] = m_outputModeCombo->currentData().toInt(); - params["min_feature_size"] = m_minFeatureSizeSpin->value(); - params["max_feature_size"] = m_maxFeatureSizeSpin->value(); - params["filter_kind"] = m_filterKindCombo->currentData().toInt(); - updateFilterControls(); - applyParameters(params); - } - - void updateFilterControls() - { - const bool usesBandpass = m_outputModeCombo->currentData().toInt() != 0; - m_minFeatureSizeSpin->setEnabled(usesBandpass); - m_maxFeatureSizeSpin->setEnabled(usesBandpass); - m_filterKindCombo->setEnabled(usesBandpass); - } - - QComboBox* m_outputModeCombo{nullptr}; - QDoubleSpinBox* m_minFeatureSizeSpin{nullptr}; - QDoubleSpinBox* m_maxFeatureSizeSpin{nullptr}; - QComboBox* m_filterKindCombo{nullptr}; - }; - class SpatiotemporalBinningModuleConfigWidget : public ProcessingModuleConfigWidgetBase - { - public: - SpatiotemporalBinningModuleConfigWidget(scopeone::core::ScopeOneCore* core, - int moduleIndex, - const ProcessingModuleInfo& info, - QWidget* parent = nullptr) - : ProcessingModuleConfigWidgetBase(core, moduleIndex, parent) - { - auto* layout = new QVBoxLayout(this); - auto* group = new QGroupBox("Spatiotemporal Binning Settings", this); - auto* groupLayout = new QGridLayout(group); - - groupLayout->addWidget(new QLabel("Spatial X:", group), 0, 0); - m_spatialBinXSpin = new QSpinBox(group); - m_spatialBinXSpin->setRange(1, 64); - configureParameterSpinBox(m_spatialBinXSpin); - groupLayout->addWidget(m_spatialBinXSpin, 0, 1); - - groupLayout->addWidget(new QLabel("Spatial Y:", group), 1, 0); - m_spatialBinYSpin = new QSpinBox(group); - m_spatialBinYSpin->setRange(1, 64); - configureParameterSpinBox(m_spatialBinYSpin); - groupLayout->addWidget(m_spatialBinYSpin, 1, 1); - - groupLayout->addWidget(new QLabel("Temporal:", group), 2, 0); - m_temporalBinSpin = new QSpinBox(group); - m_temporalBinSpin->setRange(1, 256); - configureParameterSpinBox(m_temporalBinSpin); - groupLayout->addWidget(m_temporalBinSpin, 2, 1); - - groupLayout->addWidget(new QLabel("Spatial mode:", group), 3, 0); - m_spatialModeCombo = new QComboBox(group); - m_spatialModeCombo->addItem("Mean", 0); - m_spatialModeCombo->addItem("Sum", 1); - m_spatialModeCombo->addItem("Minimum", 2); - m_spatialModeCombo->addItem("Maximum", 3); - m_spatialModeCombo->addItem("Skip", 4); - groupLayout->addWidget(m_spatialModeCombo, 3, 1); - - groupLayout->addWidget(new QLabel("Temporal mode:", group), 4, 0); - m_temporalModeCombo = new QComboBox(group); - for (int i = 0; i < m_spatialModeCombo->count(); ++i) + if (info.descriptor().resettable) { - m_temporalModeCombo->addItem(m_spatialModeCombo->itemText(i), - m_spatialModeCombo->itemData(i)); + auto* resetButton = new QPushButton(tr("Reset State"), this); + connect(resetButton, &QPushButton::clicked, this, [this]() + { + m_core->resetProcessingModuleState(m_moduleIndex); + }); + layout->addWidget(resetButton); } - groupLayout->addWidget(m_temporalModeCombo, 4, 1); - - layout->addWidget(group); layout->addStretch(); - - const QVariantMap params = info.parameters(); - m_spatialBinXSpin->setValue(params.value("spatial_bin_x").toInt()); - m_spatialBinYSpin->setValue(params.value("spatial_bin_y").toInt()); - m_temporalBinSpin->setValue(params.value("temporal_bin").toInt()); - - const int spatialModeIndex = m_spatialModeCombo->findData(params.value("spatial_mode").toInt()); - m_spatialModeCombo->setCurrentIndex(spatialModeIndex); - const int temporalModeIndex = m_temporalModeCombo->findData(params.value("temporal_mode").toInt()); - m_temporalModeCombo->setCurrentIndex(temporalModeIndex); - - connect(m_spatialBinXSpin, QOverload::of(&QSpinBox::valueChanged), - this, [this]() { apply(); }); - connect(m_spatialBinYSpin, QOverload::of(&QSpinBox::valueChanged), - this, [this]() { apply(); }); - connect(m_temporalBinSpin, QOverload::of(&QSpinBox::valueChanged), - this, [this]() { apply(); }); - connect(m_spatialModeCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, [this]() { apply(); }); - connect(m_temporalModeCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, [this]() { apply(); }); } - private: - void apply() + void setParameters(const QVariantMap& parameters) { - QVariantMap params; - params["spatial_bin_x"] = m_spatialBinXSpin->value(); - params["spatial_bin_y"] = m_spatialBinYSpin->value(); - params["temporal_bin"] = m_temporalBinSpin->value(); - params["spatial_mode"] = m_spatialModeCombo->currentData().toInt(); - params["temporal_mode"] = m_temporalModeCombo->currentData().toInt(); - applyParameters(params); - } - - QSpinBox* m_spatialBinXSpin{nullptr}; - QSpinBox* m_spatialBinYSpin{nullptr}; - QSpinBox* m_temporalBinSpin{nullptr}; - QComboBox* m_spatialModeCombo{nullptr}; - QComboBox* m_temporalModeCombo{nullptr}; - }; - - class GaussianBlurModuleConfigWidget : public ProcessingModuleConfigWidgetBase - { - public: - GaussianBlurModuleConfigWidget(scopeone::core::ScopeOneCore* core, - int moduleIndex, - const ProcessingModuleInfo& info, - QWidget* parent = nullptr) - : ProcessingModuleConfigWidgetBase(core, moduleIndex, parent) - { - auto* layout = new QVBoxLayout(this); - auto* group = new QGroupBox("Gaussian Blur Settings", this); - auto* groupLayout = new QGridLayout(group); - - groupLayout->addWidget(new QLabel("Kernel size:", group), 0, 0); - m_kernelSizeSpin = new QSpinBox(group); - m_kernelSizeSpin->setRange(1, 99); - m_kernelSizeSpin->setSingleStep(2); - configureParameterSpinBox(m_kernelSizeSpin); - groupLayout->addWidget(m_kernelSizeSpin, 0, 1); - - groupLayout->addWidget(new QLabel("Sigma:", group), 1, 0); - m_sigmaSpin = new QDoubleSpinBox(group); - m_sigmaSpin->setRange(0.0, 100.0); - m_sigmaSpin->setDecimals(2); - configureParameterSpinBox(m_sigmaSpin); - groupLayout->addWidget(m_sigmaSpin, 1, 1); - - layout->addWidget(group); - layout->addStretch(); - - const QVariantMap params = info.parameters(); - m_kernelSizeSpin->setValue(params.value("kernel_size").toInt()); - m_sigmaSpin->setValue(params.value("sigma").toDouble()); - - connect(m_kernelSizeSpin, QOverload::of(&QSpinBox::valueChanged), - this, [this]() { apply(); }); - connect(m_sigmaSpin, QOverload::of(&QDoubleSpinBox::valueChanged), - this, [this]() { apply(); }); + for (auto it = m_descriptors.constBegin(); it != m_descriptors.constEnd(); ++it) + { + QWidget* editor = m_editors.value(it.key()); + const QVariant value = parameters.value(it.key(), it->defaultValue); + const QSignalBlocker blocker(editor); + switch (it->type) + { + case ProcessingParameterType::Integer: + qobject_cast(editor)->setValue(value.toInt()); + break; + case ProcessingParameterType::Real: + qobject_cast(editor)->setValue(value.toDouble()); + break; + case ProcessingParameterType::Boolean: + qobject_cast(editor)->setChecked(value.toBool()); + break; + case ProcessingParameterType::Choice: + { + auto* combo = qobject_cast(editor); + combo->setCurrentIndex(combo->findData(value)); + break; + } + } + } } private: - void apply() - { - QVariantMap params; - params["kernel_size"] = m_kernelSizeSpin->value(); - params["sigma"] = m_sigmaSpin->value(); - applyParameters(params); - } - - QSpinBox* m_kernelSizeSpin{nullptr}; - QDoubleSpinBox* m_sigmaSpin{nullptr}; - }; - - class DifferentialRollingModuleConfigWidget : public ProcessingModuleConfigWidgetBase - { - public: - DifferentialRollingModuleConfigWidget(scopeone::core::ScopeOneCore* core, - int moduleIndex, - const ProcessingModuleInfo& info, - QWidget* parent = nullptr) - : ProcessingModuleConfigWidgetBase(core, moduleIndex, parent) + QWidget* createEditor(const ProcessingParameterDescriptor& descriptor, + const QVariant& value, + QWidget* parent) { - auto* layout = new QVBoxLayout(this); - auto* group = new QGroupBox("Differential Rolling Settings", this); - auto* groupLayout = new QGridLayout(group); - groupLayout->addWidget(new QLabel("Batch size:", group), 0, 0); - - m_batchSizeSpin = new QSpinBox(group); - m_batchSizeSpin->setRange(1, 256); - configureParameterSpinBox(m_batchSizeSpin); - const QVariantMap params = info.parameters(); - m_batchSizeSpin->setValue(params.value("batch_size").toInt()); - groupLayout->addWidget(m_batchSizeSpin, 0, 1); - - m_normalizeCheck = new QCheckBox("Normalize by batch_1", group); - m_normalizeCheck->setChecked(params.value("normalize").toBool()); - groupLayout->addWidget(m_normalizeCheck, 1, 0, 1, 2); - - layout->addWidget(group); - layout->addWidget(new QLabel("Preview is zero-centered grayscale around mid-gray.", this)); - - m_resetButton = new QPushButton("Reset Buffer", this); - connect(m_batchSizeSpin, QOverload::of(&QSpinBox::valueChanged), this, [this]() + switch (descriptor.type) { - apply(); - }); - connect(m_normalizeCheck, &QCheckBox::toggled, this, [this]() + case ProcessingParameterType::Integer: { - apply(); - }); - connect(m_resetButton, &QPushButton::clicked, this, [this]() + auto* editor = new QSpinBox(parent); + editor->setRange(descriptor.minimum.toInt(), descriptor.maximum.toInt()); + editor->setSingleStep(qMax(1, descriptor.step.toInt())); + editor->setValue(value.toInt()); + configureSpinBox(editor); + connect(editor, QOverload::of(&QSpinBox::valueChanged), + this, [this]() { apply(); }); + return editor; + } + case ProcessingParameterType::Real: { - resetModule(); - }); - layout->addWidget(m_resetButton); - layout->addStretch(); - } - - private: - void apply() - { - QVariantMap params; - params["batch_size"] = m_batchSizeSpin->value(); - params["normalize"] = m_normalizeCheck->isChecked(); - applyParameters(params); - } - - QSpinBox* m_batchSizeSpin{nullptr}; - QCheckBox* m_normalizeCheck{nullptr}; - QPushButton* m_resetButton{nullptr}; - }; - - class BackgroundCalibrationModuleConfigWidget : public ProcessingModuleConfigWidgetBase - { - public: - BackgroundCalibrationModuleConfigWidget(scopeone::core::ScopeOneCore* core, - int moduleIndex, - const ProcessingModuleInfo& info, - QWidget* parent = nullptr) - : ProcessingModuleConfigWidgetBase(core, moduleIndex, parent) - { - auto* layout = new QVBoxLayout(this); - auto* group = new QGroupBox("Background Calibration Settings", this); - auto* groupLayout = new QGridLayout(group); - - groupLayout->addWidget(new QLabel("Frames:", group), 0, 0); - m_calibrationFramesSpin = new QSpinBox(group); - m_calibrationFramesSpin->setRange(3, 1001); - m_calibrationFramesSpin->setSingleStep(2); - configureParameterSpinBox(m_calibrationFramesSpin); - groupLayout->addWidget(m_calibrationFramesSpin, 0, 1); - - groupLayout->addWidget(new QLabel("Mode:", group), 1, 0); - m_modeCombo = new QComboBox(group); - m_modeCombo->addItem("Snapshot", 0); - m_modeCombo->addItem("Running", 1); - groupLayout->addWidget(m_modeCombo, 1, 1); - - groupLayout->addWidget(new QLabel("Method:", group), 2, 0); - m_methodCombo = new QComboBox(group); - m_methodCombo->addItem("Median", 0); - m_methodCombo->addItem("Mean", 1); - m_methodCombo->addItem("Maximum", 2); - m_methodCombo->addItem("Minimum", 3); - groupLayout->addWidget(m_methodCombo, 2, 1); - - groupLayout->addWidget(new QLabel("Operation:", group), 3, 0); - m_operationCombo = new QComboBox(group); - m_operationCombo->addItem("Subtract", 0); - m_operationCombo->addItem("Add", 1); - m_operationCombo->addItem("Multiply", 2); - m_operationCombo->addItem("Divide", 3); - groupLayout->addWidget(m_operationCombo, 3, 1); - - layout->addWidget(group); - - m_resetButton = new QPushButton("Reset Background", this); - layout->addWidget(m_resetButton); - layout->addStretch(); - - const QVariantMap params = info.parameters(); - int frames = params.value("calibration_frames").toInt(); - if (frames < 3) + auto* editor = new QDoubleSpinBox(parent); + editor->setRange(descriptor.minimum.toDouble(), descriptor.maximum.toDouble()); + editor->setSingleStep(descriptor.step.toDouble()); + editor->setDecimals(descriptor.decimals); + editor->setValue(value.toDouble()); + configureSpinBox(editor); + connect(editor, QOverload::of(&QDoubleSpinBox::valueChanged), + this, [this]() { apply(); }); + return editor; + } + case ProcessingParameterType::Boolean: { - frames = 3; + auto* editor = new QCheckBox(parent); + editor->setChecked(value.toBool()); + connect(editor, &QCheckBox::toggled, this, [this]() { apply(); }); + return editor; } - if ((frames % 2) == 0) + case ProcessingParameterType::Choice: { - ++frames; + auto* editor = new QComboBox(parent); + for (const auto& choice : descriptor.choices) + { + editor->addItem(choice.name, choice.value); + } + editor->setCurrentIndex(editor->findData(value)); + connect(editor, QOverload::of(&QComboBox::currentIndexChanged), + this, [this]() { apply(); }); + return editor; + } } - m_calibrationFramesSpin->setValue(frames); - const int modeIndex = m_modeCombo->findData(params.value("mode").toInt()); - m_modeCombo->setCurrentIndex(modeIndex); - const int methodIndex = m_methodCombo->findData(params.value("method").toInt()); - m_methodCombo->setCurrentIndex(methodIndex); - const int operationIndex = m_operationCombo->findData(params.value("operation").toInt()); - m_operationCombo->setCurrentIndex(operationIndex); - - connect(m_calibrationFramesSpin, QOverload::of(&QSpinBox::valueChanged), - this, [this]() { apply(); }); - connect(m_modeCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, [this]() { apply(); }); - connect(m_methodCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, [this]() { apply(); }); - connect(m_operationCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, [this]() { apply(); }); - connect(m_resetButton, &QPushButton::clicked, this, [this]() + return new QWidget(parent); + } + + QVariant editorValue(const QString& key) const + { + QWidget* editor = m_editors.value(key); + switch (m_descriptors.value(key).type) { - resetModule(); - }); + case ProcessingParameterType::Integer: + return qobject_cast(editor)->value(); + case ProcessingParameterType::Real: + return qobject_cast(editor)->value(); + case ProcessingParameterType::Boolean: + return qobject_cast(editor)->isChecked(); + case ProcessingParameterType::Choice: + return qobject_cast(editor)->currentData(); + } + return {}; } - private: void apply() { - QVariantMap params; - params["calibration_frames"] = m_calibrationFramesSpin->value(); - params["mode"] = m_modeCombo->currentData().toInt(); - params["method"] = m_methodCombo->currentData().toInt(); - params["operation"] = m_operationCombo->currentData().toInt(); - applyParameters(params); + QVariantMap parameters; + for (auto it = m_descriptors.constBegin(); it != m_descriptors.constEnd(); ++it) + { + parameters.insert(it.key(), editorValue(it.key())); + } + m_core->setProcessingModuleParameters(m_moduleIndex, parameters); } - QSpinBox* m_calibrationFramesSpin{nullptr}; - QComboBox* m_modeCombo{nullptr}; - QComboBox* m_methodCombo{nullptr}; - QComboBox* m_operationCombo{nullptr}; - QPushButton* m_resetButton{nullptr}; + scopeone::core::ScopeOneCore* m_core; + int m_moduleIndex; + QHash m_editors; + QHash m_descriptors; }; - - QWidget* createConfigWidget(scopeone::core::ScopeOneCore* core, - int moduleIndex, - const ProcessingModuleInfo& info, - QWidget* parent) - { - switch (info.kind()) - { - case ProcessingModuleKind::FFT: - return new FFTModuleConfigWidget(core, moduleIndex, info, parent); - case ProcessingModuleKind::SpatiotemporalBinning: - return new SpatiotemporalBinningModuleConfigWidget(core, moduleIndex, info, parent); - case ProcessingModuleKind::GaussianBlur: - return new GaussianBlurModuleConfigWidget(core, moduleIndex, info, parent); - case ProcessingModuleKind::DifferentialRolling: - return new DifferentialRollingModuleConfigWidget(core, moduleIndex, info, parent); - case ProcessingModuleKind::BackgroundCalibration: - return new BackgroundCalibrationModuleConfigWidget(core, moduleIndex, info, parent); - case ProcessingModuleKind::Unknown: - break; - } - qFatal("ImageProcessingWidget received an unsupported processing module kind"); - return nullptr; - } - } // namespace + } ImageProcessingWidget::ImageProcessingWidget(scopeone::core::ScopeOneCore* core, QWidget* parent) - : QWidget(parent) - , m_scopeonecore(core) + : QWidget(parent), m_scopeonecore(core) { if (!core) { qFatal("ImageProcessingWidget requires ScopeOneCore"); } - m_processingRunning = m_scopeonecore->isRealTimeProcessingEnabled(); + m_processingRunning = core->isRealTimeProcessingEnabled(); + setupUI(); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::processingModulesChanged, + connect(core, &scopeone::core::ScopeOneCore::processingModulesChanged, this, [this]() { updateModuleList(); updateConfigWidget(); updateRunButtons(); }); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::processingModuleParametersChanged, + connect(core, &scopeone::core::ScopeOneCore::processingModuleParametersChanged, this, [this](int moduleIndex) { if (moduleIndex == m_moduleList->currentRow()) { - updateConfigWidget(); + const auto modules = m_scopeonecore->processingModules(); + if (moduleIndex >= 0 && moduleIndex < modules.size()) + { + static_cast(m_configStack->currentWidget()) + ->setParameters(modules.at(moduleIndex).parameters()); + } } }); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::processingSettingsChanged, + connect(core, &scopeone::core::ScopeOneCore::processingSettingsChanged, this, [this]() { updateProcessingSettings(); updateRunButtons(); syncProcessingState(); }); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::processingError, + connect(core, &scopeone::core::ScopeOneCore::processingError, this, [](const QString& error) { - qWarning().noquote() << QString("Processing error: %1").arg(error); + qWarning().noquote() << QStringLiteral("Processing error: %1").arg(error); + }); + connect(core, &scopeone::core::ScopeOneCore::hardwareDevicesChanged, + this, &ImageProcessingWidget::refreshSources); + connect(core, &scopeone::core::ScopeOneCore::recordingSessionsChanged, + this, &ImageProcessingWidget::refreshSources); + connect(core->imageSceneModel(), &scopeone::core::ImageSceneModel::layersChanged, + this, &ImageProcessingWidget::refreshSources); + connect(core, &scopeone::core::ScopeOneCore::layerProcessingFinished, + this, [this](quint64 requestId, + const QString& sourceLayerKey, + const scopeone::core::ImageFrame& frame, + const QString& error) + { + if (requestId != m_offlineProcessingRequestId) + { + return; + } + finishOfflineProcessing(); + if (!error.isEmpty() || !frame.isValid()) + { + QMessageBox::warning(this, tr("Processing Failed"), error); + return; + } + const QString sourceId = QStringLiteral("processed:%1") + .arg(scopeone::core::ScopeOneCore::sourceIdFromLayerKey( + sourceLayerKey)); + const auto output = m_scopeonecore->publishStaticFrame( + sourceId, frame, tr("Processed Image")); + if (output.isValid()) + { + emit processedLayerReady(scopeone::core::ScopeOneCore::staticLayerKey(sourceId)); + } + }); + connect(core, &scopeone::core::ScopeOneCore::stackProcessingProgress, + this, [this](quint64 requestId, qint64 completed, qint64 total) + { + if (requestId == m_offlineProcessingRequestId) + { + m_processingProgress->setMaximum(static_cast(total)); + m_processingProgress->setValue(static_cast(completed)); + } + }); + connect(core, &scopeone::core::ScopeOneCore::stackProcessingFinished, + this, [this](quint64 requestId, + const std::shared_ptr& session, + const QString& error) + { + if (requestId != m_offlineProcessingRequestId) + { + return; + } + finishOfflineProcessing(); + if (!error.isEmpty() || !session) + { + QMessageBox::warning(this, tr("Processing Failed"), error); + return; + } + emit processedStackReady(session); + refreshSources(); }); - setupUI(); updateProcessingSettings(); + refreshSources(); updateModuleList(); updateConfigWidget(); updateRunButtons(); } - // Builds the image processing widget layout void ImageProcessingWidget::setupUI() { auto* mainLayout = new QVBoxLayout(this); auto* splitter = new QSplitter(Qt::Vertical, this); - setupRunControls(); + setupSourceControls(); setupModuleList(); setupModuleConfig(); - auto* topWidget = new QWidget(this); auto* topLayout = new QVBoxLayout(topWidget); topLayout->addWidget(m_runControlsWidget); + topLayout->addWidget(m_sourceControlsWidget); topLayout->addWidget(m_moduleList->parentWidget()); - splitter->addWidget(topWidget); splitter->addWidget(m_configStack->parentWidget()); splitter->setStretchFactor(0, 2); splitter->setStretchFactor(1, 3); - mainLayout->addWidget(splitter); } - // Builds processing start stop controls + void ImageProcessingWidget::setupSourceControls() + { + m_sourceControlsWidget = new QGroupBox(tr("Processing Source"), this); + auto* layout = new QVBoxLayout(m_sourceControlsWidget); + m_sourceCombo = new QComboBox(m_sourceControlsWidget); + layout->addWidget(m_sourceCombo); + auto* buttons = new QHBoxLayout; + m_processImageButton = new QPushButton(tr("Process Image"), m_sourceControlsWidget); + m_processStackButton = new QPushButton(tr("Process Stack"), m_sourceControlsWidget); + m_cancelProcessingButton = new QPushButton(tr("Cancel"), m_sourceControlsWidget); + m_cancelProcessingButton->setEnabled(false); + connect(m_processImageButton, &QPushButton::clicked, + this, &ImageProcessingWidget::onProcessImage); + connect(m_processStackButton, &QPushButton::clicked, + this, &ImageProcessingWidget::onProcessStack); + connect(m_cancelProcessingButton, &QPushButton::clicked, + this, &ImageProcessingWidget::onCancelProcessing); + connect(m_sourceCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, [this]() { updateRunButtons(); }); + buttons->addWidget(m_processImageButton); + buttons->addWidget(m_processStackButton); + buttons->addWidget(m_cancelProcessingButton); + layout->addLayout(buttons); + m_processingProgress = new QProgressBar(m_sourceControlsWidget); + m_processingProgress->setVisible(false); + layout->addWidget(m_processingProgress); + } + void ImageProcessingWidget::setupRunControls() { m_runControlsWidget = new QWidget(this); auto* layout = new QHBoxLayout(m_runControlsWidget); - m_startButton = new QPushButton("Start Processing", m_runControlsWidget); - m_stopButton = new QPushButton("Stop Processing", m_runControlsWidget); + m_startButton = new QPushButton(tr("Start Processing"), m_runControlsWidget); + m_stopButton = new QPushButton(tr("Stop Processing"), m_runControlsWidget); m_processingBitDepthCombo = new QComboBox(m_runControlsWidget); + m_liveSourceCombo = new QComboBox(m_runControlsWidget); m_processingBitDepthCombo->addItem( - "8-bit", static_cast(scopeone::core::ScopeOneCore::ProcessingBitDepth::Bit8)); + tr("8-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit8)); m_processingBitDepthCombo->addItem( - "16-bit", static_cast(scopeone::core::ScopeOneCore::ProcessingBitDepth::Bit16)); + tr("16-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit16)); connect(m_startButton, &QPushButton::clicked, this, &ImageProcessingWidget::onStartProcessing); connect(m_stopButton, &QPushButton::clicked, this, &ImageProcessingWidget::onStopProcessing); connect(m_processingBitDepthCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &ImageProcessingWidget::onProcessingBitDepthChanged); - + connect(m_liveSourceCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, [this]() + { + if (!m_scopeonecore->setRealTimeProcessingSource( + m_liveSourceCombo->currentData().toString())) + { + updateProcessingSettings(); + } + }); layout->addWidget(m_startButton); layout->addWidget(m_stopButton); layout->addWidget(m_processingBitDepthCombo); + layout->addWidget(m_liveSourceCombo); layout->addStretch(); } - // Builds the module list and add remove controls void ImageProcessingWidget::setupModuleList() { - auto* group = new QGroupBox("Processing Modules", this); + auto* group = new QGroupBox(tr("Processing Modules"), this); auto* layout = new QVBoxLayout(group); - m_moduleList = new QListWidget(group); connect(m_moduleList, &QListWidget::currentRowChanged, this, &ImageProcessingWidget::onModuleSelectionChanged); layout->addWidget(m_moduleList); - - auto* controlsLayout = new QHBoxLayout(); + auto* controls = new QHBoxLayout; m_moduleTypeCombo = new QComboBox(group); - m_moduleTypeCombo->addItem("Spatiotemporal Binning", - static_cast(ProcessingModuleKind::SpatiotemporalBinning)); - m_moduleTypeCombo->addItem("Gaussian Blur", static_cast(ProcessingModuleKind::GaussianBlur)); - m_moduleTypeCombo->addItem("FFT", static_cast(ProcessingModuleKind::FFT)); - m_moduleTypeCombo->addItem("Differential Rolling", - static_cast(ProcessingModuleKind::DifferentialRolling)); - m_moduleTypeCombo->addItem("Background Calibration", - static_cast(ProcessingModuleKind::BackgroundCalibration)); - controlsLayout->addWidget(m_moduleTypeCombo); - - m_addModuleButton = new QPushButton("Add", group); + for (const ProcessingModuleDescriptor& descriptor : m_scopeonecore->availableProcessingModules()) + { + m_moduleTypeCombo->addItem(descriptor.name, descriptor.id); + } + controls->addWidget(m_moduleTypeCombo); + m_addModuleButton = new QPushButton(tr("Add"), group); + m_removeModuleButton = new QPushButton(tr("Remove"), group); connect(m_addModuleButton, &QPushButton::clicked, this, &ImageProcessingWidget::onAddModuleClicked); - controlsLayout->addWidget(m_addModuleButton); - - m_removeModuleButton = new QPushButton("Remove", group); connect(m_removeModuleButton, &QPushButton::clicked, this, &ImageProcessingWidget::onRemoveModuleClicked); - controlsLayout->addWidget(m_removeModuleButton); - - layout->addLayout(controlsLayout); + controls->addWidget(m_addModuleButton); + controls->addWidget(m_removeModuleButton); + layout->addLayout(controls); } - // Builds the module configuration stack void ImageProcessingWidget::setupModuleConfig() { - auto* group = new QGroupBox("Module Configuration", this); + auto* group = new QGroupBox(tr("Module Configuration"), this); auto* layout = new QVBoxLayout(group); - m_configStack = new QStackedWidget(group); m_emptyConfigWidget = new QWidget(m_configStack); auto* emptyLayout = new QVBoxLayout(m_emptyConfigWidget); - emptyLayout->addWidget(new QLabel("Select a module to configure", m_emptyConfigWidget)); + emptyLayout->addWidget(new QLabel(tr("Select a module to configure"), m_emptyConfigWidget)); emptyLayout->addStretch(); - m_configStack->addWidget(m_emptyConfigWidget); layout->addWidget(m_configStack); } - // Rebuilds the visible module list from core state void ImageProcessingWidget::updateModuleList() { const int currentRow = m_moduleList->currentRow(); - const QList modules = m_scopeonecore->processingModules(); - - const QSignalBlocker moduleListBlocker(m_moduleList); + const auto modules = m_scopeonecore->processingModules(); + const QSignalBlocker blocker(m_moduleList); m_moduleList->clear(); - for (const ProcessingModuleInfo& info : modules) + for (const ProcessingModuleInfo& module : modules) { - m_moduleList->addItem(info.name()); + m_moduleList->addItem(module.name()); } if (!modules.isEmpty()) { - const int nextRow = qBound(0, currentRow, m_moduleList->count() - 1); - m_moduleList->setCurrentRow(nextRow); + m_moduleList->setCurrentRow(qBound(0, currentRow, modules.size() - 1)); } } - // Rebuilds the editor for the selected module void ImageProcessingWidget::updateConfigWidget() { while (m_configStack->count() > 1) @@ -651,32 +439,38 @@ namespace scopeone::ui widget->deleteLater(); } m_configStack->setCurrentWidget(m_emptyConfigWidget); - - const int currentRow = m_moduleList->currentRow(); - const QList modules = m_scopeonecore->processingModules(); - if (currentRow < 0 || currentRow >= modules.size()) + const int row = m_moduleList->currentRow(); + const auto modules = m_scopeonecore->processingModules(); + if (row < 0 || row >= modules.size()) { return; } - - QWidget* configWidget = createConfigWidget(m_scopeonecore, currentRow, modules.at(currentRow), m_configStack); - m_configStack->addWidget(configWidget); - m_configStack->setCurrentWidget(configWidget); + auto* editor = new ModuleConfigWidget(m_scopeonecore, row, modules.at(row), m_configStack); + m_configStack->addWidget(editor); + m_configStack->setCurrentWidget(editor); } - // Updates processing controls from running state void ImageProcessingWidget::updateRunButtons() { const bool running = m_scopeonecore->isRealTimeProcessingEnabled(); const bool hasModules = !m_scopeonecore->processingModules().isEmpty(); - m_startButton->setEnabled(!running && hasModules); + const bool idle = m_offlineProcessingRequestId == 0; + m_startButton->setEnabled(!running && idle && hasModules); m_stopButton->setEnabled(running); - m_processingBitDepthCombo->setEnabled(!running); - m_moduleList->parentWidget()->setEnabled(!running); - m_configStack->parentWidget()->setEnabled(!running); + m_processingBitDepthCombo->setEnabled(!running && idle); + m_liveSourceCombo->setEnabled(!running && idle); + m_moduleList->parentWidget()->setEnabled(!running && idle); + m_configStack->parentWidget()->setEnabled(!running && idle); + const QString sourceType = m_sourceCombo->currentData(Qt::UserRole).toString(); + const bool hasPipeline = !m_scopeonecore->processingModules().isEmpty(); + m_sourceCombo->setEnabled(!running && idle); + m_processImageButton->setEnabled(!running && idle && hasPipeline + && sourceType == QStringLiteral("layer")); + m_processStackButton->setEnabled(!running && idle && hasPipeline + && sourceType == QStringLiteral("stack")); + m_cancelProcessingButton->setEnabled(!idle); } - // Emits user facing processing state changes from the shared core state void ImageProcessingWidget::syncProcessingState() { const bool running = m_scopeonecore->isRealTimeProcessingEnabled(); @@ -695,94 +489,180 @@ namespace scopeone::ui } } - // Syncs processing settings from core state void ImageProcessingWidget::updateProcessingSettings() { - const auto currentBitDepth = static_cast(m_scopeonecore->processingBitDepth()); - const int index = m_processingBitDepthCombo->findData(currentBitDepth); - if (index != m_processingBitDepthCombo->currentIndex()) + const int value = static_cast(m_scopeonecore->processingBitDepth()); + const QSignalBlocker blocker(m_processingBitDepthCombo); + m_processingBitDepthCombo->setCurrentIndex(m_processingBitDepthCombo->findData(value)); + const QSignalBlocker sourceBlocker(m_liveSourceCombo); + const int sourceIndex = m_liveSourceCombo->findData( + m_scopeonecore->realTimeProcessingSource()); + m_liveSourceCombo->setCurrentIndex(qMax(0, sourceIndex)); + } + + void ImageProcessingWidget::refreshSources() + { + const QString currentType = m_sourceCombo->currentData(Qt::UserRole).toString(); + const QString currentFirst = m_sourceCombo->currentData(Qt::UserRole + 1).toString(); + const QString currentSecond = m_sourceCombo->currentData(Qt::UserRole + 2).toString(); + const QSignalBlocker blocker(m_sourceCombo); + const QString liveSource = m_scopeonecore->realTimeProcessingSource(); + { + const QSignalBlocker liveBlocker(m_liveSourceCombo); + m_liveSourceCombo->clear(); + m_liveSourceCombo->addItem(tr("Live: All Cameras"), QString{}); + for (const QString& cameraId : m_scopeonecore->cameraIds()) + { + m_liveSourceCombo->addItem(tr("Live: %1").arg(cameraId), cameraId); + } + const int liveIndex = m_liveSourceCombo->findData(liveSource); + m_liveSourceCombo->setCurrentIndex(qMax(0, liveIndex)); + } + m_sourceCombo->clear(); + + for (const QString& layerKey : m_scopeonecore->imageSceneModel()->layerIds()) { - const QSignalBlocker bitDepthBlocker(m_processingBitDepthCombo); - m_processingBitDepthCombo->setCurrentIndex(index); + scopeone::core::DocumentLayer layer; + if (m_scopeonecore->imageSceneModel()->findLayer(layerKey, layer)) + { + m_sourceCombo->addItem(layer.name.isEmpty() ? layerKey : layer.name, + QStringLiteral("layer")); + const int index = m_sourceCombo->count() - 1; + m_sourceCombo->setItemData(index, layerKey, Qt::UserRole + 1); + } } + for (const QString& sessionId : m_scopeonecore->recordingSessionIds()) + { + const auto session = m_scopeonecore->recordingSession(sessionId); + if (!session) + { + continue; + } + for (const QString& cameraId : session->recordedCameraIds()) + { + if (session->recordedFrameCount(cameraId) <= 0) + { + continue; + } + m_sourceCombo->addItem( + tr("Stack: %1 / %2 (%3 frames)") + .arg(sessionId, cameraId) + .arg(session->recordedFrameCount(cameraId)), + QStringLiteral("stack")); + const int index = m_sourceCombo->count() - 1; + m_sourceCombo->setItemData(index, sessionId, Qt::UserRole + 1); + m_sourceCombo->setItemData(index, cameraId, Qt::UserRole + 2); + } + } + for (int index = 0; index < m_sourceCombo->count(); ++index) + { + if (m_sourceCombo->itemData(index, Qt::UserRole).toString() == currentType + && m_sourceCombo->itemData(index, Qt::UserRole + 1).toString() == currentFirst + && m_sourceCombo->itemData(index, Qt::UserRole + 2).toString() == currentSecond) + { + m_sourceCombo->setCurrentIndex(index); + break; + } + } + updateRunButtons(); } - // Adds the selected processing module - void ImageProcessingWidget::onAddModuleClicked() + void ImageProcessingWidget::onProcessImage() { - const auto kind = static_cast(m_moduleTypeCombo->currentData().toInt()); - if (!m_scopeonecore->addProcessingModule(kind)) + m_offlineProcessingRequestId = m_scopeonecore->requestLayerProcessing( + m_sourceCombo->currentData(Qt::UserRole + 1).toString()); + if (m_offlineProcessingRequestId == 0) { - QMessageBox::warning(this, "Warning", "Failed to add processing module"); + QMessageBox::warning(this, tr("Processing Failed"), tr("No image is available")); return; } - - updateModuleList(); - m_moduleList->setCurrentRow(m_moduleList->count() - 1); + m_processingProgress->setRange(0, 0); + m_processingProgress->setVisible(true); updateRunButtons(); } - // Removes the selected processing module - void ImageProcessingWidget::onRemoveModuleClicked() + void ImageProcessingWidget::onProcessStack() { - const int currentRow = m_moduleList->currentRow(); - if (currentRow < 0) + m_offlineProcessingRequestId = m_scopeonecore->requestRecordingSessionStackProcessing( + m_sourceCombo->currentData(Qt::UserRole + 1).toString(), + m_sourceCombo->currentData(Qt::UserRole + 2).toString()); + if (m_offlineProcessingRequestId == 0) { - QMessageBox::information(this, "Information", "Please select a module to remove"); + QMessageBox::warning(this, tr("Processing Failed"), tr("No stack is available")); return; } + m_processingProgress->setRange(0, 0); + m_processingProgress->setVisible(true); + updateRunButtons(); + } + + void ImageProcessingWidget::onCancelProcessing() + { + m_scopeonecore->cancelProcessingRequest(m_offlineProcessingRequestId); + } - if (!m_scopeonecore->removeProcessingModule(currentRow)) + void ImageProcessingWidget::finishOfflineProcessing() + { + m_offlineProcessingRequestId = 0; + m_processingProgress->setVisible(false); + updateRunButtons(); + } + + void ImageProcessingWidget::onAddModuleClicked() + { + if (!m_scopeonecore->addProcessingModule(m_moduleTypeCombo->currentData().toString())) { - QMessageBox::warning(this, "Warning", "Failed to remove processing module"); + QMessageBox::warning(this, tr("Warning"), tr("Failed to add processing module")); return; } + updateModuleList(); + m_moduleList->setCurrentRow(m_moduleList->count() - 1); + } + void ImageProcessingWidget::onRemoveModuleClicked() + { + const int row = m_moduleList->currentRow(); + if (row < 0 || !m_scopeonecore->removeProcessingModule(row)) + { + QMessageBox::warning(this, tr("Warning"), tr("Failed to remove processing module")); + return; + } updateModuleList(); updateConfigWidget(); - updateRunButtons(); } - // Updates configuration when module selection changes void ImageProcessingWidget::onModuleSelectionChanged() { updateConfigWidget(); } - // Applies the selected processing bit depth void ImageProcessingWidget::onProcessingBitDepthChanged() { - const auto bitDepth = static_cast( + const auto depth = static_cast( m_processingBitDepthCombo->currentData().toInt()); - if (!m_scopeonecore->setProcessingBitDepth(bitDepth)) + if (!m_scopeonecore->setProcessingBitDepth(depth)) { - QMessageBox::warning(this, "Warning", "Failed to update processing bit depth"); updateProcessingSettings(); } } - // Starts real time image processing void ImageProcessingWidget::onStartProcessing() { if (!m_scopeonecore->setRealTimeProcessingEnabled(true)) { - QMessageBox::information(this, "Information", "Please add a processing module first"); - updateRunButtons(); + QMessageBox::information(this, tr("Information"), tr("Please add a processing module first")); return; } updateRunButtons(); qInfo().noquote() << "Processing started"; } - // Stops real time image processing void ImageProcessingWidget::onStopProcessing() { - if (!m_scopeonecore->setRealTimeProcessingEnabled(false)) + if (m_scopeonecore->setRealTimeProcessingEnabled(false)) { - QMessageBox::warning(this, "Warning", "Failed to stop image processing"); - return; + updateRunButtons(); + qInfo().noquote() << "Processing stopped"; } - updateRunButtons(); - qInfo().noquote() << "Processing stopped"; } -} // namespace scopeone::ui +} diff --git a/src/ImageProcessingWidget.h b/src/ImageProcessingWidget.h index 3b391ca..4a47117 100644 --- a/src/ImageProcessingWidget.h +++ b/src/ImageProcessingWidget.h @@ -1,17 +1,16 @@ #pragma once +#include "scopeone/ScopeOneCore.h" + #include +#include class QListWidget; class QComboBox; class QPushButton; +class QProgressBar; class QStackedWidget; -namespace scopeone::core -{ - class ScopeOneCore; -} - namespace scopeone::ui { class ImageProcessingWidget : public QWidget @@ -25,6 +24,9 @@ namespace scopeone::ui signals: void processingStarted(); void processingStopped(); + void processedLayerReady(const QString& layerKey); + void processedStackReady( + const std::shared_ptr& session); private: void onAddModuleClicked(); @@ -33,9 +35,13 @@ namespace scopeone::ui void onProcessingBitDepthChanged(); void onStartProcessing(); void onStopProcessing(); + void onProcessImage(); + void onProcessStack(); + void onCancelProcessing(); void setupUI(); void setupRunControls(); + void setupSourceControls(); void setupModuleList(); void setupModuleConfig(); void updateProcessingSettings(); @@ -43,18 +49,28 @@ namespace scopeone::ui void updateConfigWidget(); void updateRunButtons(); void syncProcessingState(); + void refreshSources(); + void finishOfflineProcessing(); scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; bool m_processingRunning{false}; QWidget* m_runControlsWidget{nullptr}; + QWidget* m_sourceControlsWidget{nullptr}; + QComboBox* m_sourceCombo{nullptr}; QPushButton* m_startButton{nullptr}; QPushButton* m_stopButton{nullptr}; + QPushButton* m_processImageButton{nullptr}; + QPushButton* m_processStackButton{nullptr}; + QPushButton* m_cancelProcessingButton{nullptr}; + QProgressBar* m_processingProgress{nullptr}; QComboBox* m_processingBitDepthCombo{nullptr}; + QComboBox* m_liveSourceCombo{nullptr}; QListWidget* m_moduleList{nullptr}; QPushButton* m_addModuleButton{nullptr}; QPushButton* m_removeModuleButton{nullptr}; QComboBox* m_moduleTypeCombo{nullptr}; QStackedWidget* m_configStack{nullptr}; QWidget* m_emptyConfigWidget{nullptr}; + quint64 m_offlineProcessingRequestId{0}; }; } diff --git a/src/ImageToolsDialog.cpp b/src/ImageToolsDialog.cpp index 244259b..928271c 100644 --- a/src/ImageToolsDialog.cpp +++ b/src/ImageToolsDialog.cpp @@ -1,6 +1,6 @@ #include "ImageToolsDialog.h" -#include "PreviewWidget.h" +#include "ScopeOneToolPlugin.h" #include "scopeone/ImageSceneModel.h" #include "scopeone/ScopeOneCore.h" @@ -114,18 +114,12 @@ namespace scopeone::ui } // Create a stage driven mosaic tool - StageMosaicDialog::StageMosaicDialog(scopeone::core::ScopeOneCore* core, - PreviewWidget* previewWidget, + StageMosaicDialog::StageMosaicDialog(ScopeOneToolContext& context, QWidget* parent) : QDialog(parent) - , m_core(core) - , m_previewWidget(previewWidget) + , m_core(&context.core()) + , m_context(context) { - if (!core || !previewWidget) - { - qFatal("StageMosaicDialog requires ScopeOneCore and PreviewWidget"); - } - setWindowTitle(tr("Stage Mosaic")); setupUI(); refreshDevices(); @@ -135,11 +129,28 @@ namespace scopeone::ui m_statusLabel->setText(message); }); connect(m_core, &ScopeOneCore::stageMosaicFinished, - this, [this](const std::shared_ptr&, - const QString&, + this, [this](const std::shared_ptr& session, + const QString& message, bool) { setMosaicRunning(false); + if (!session) + { + m_context.showToolStatus(message, 8000); + return; + } + m_context.presentSession( + session, tr("Stage Mosaic %1").arg(session->cameraIds().value(0))); + m_core->removeStaticFrame(QStringLiteral("stage_mosaic")); + }); + connect(m_core, &ScopeOneCore::stageMosaicFrameUpdated, + this, [this](const scopeone::core::ImageFrame&) + { + const QString layerKey = ScopeOneCore::staticLayerKey( + QStringLiteral("stage_mosaic")); + m_core->imageSceneModel()->setLayerColormap(layerKey, QStringLiteral("Gray")); + m_core->imageSceneModel()->setLayerBlending(layerKey, QStringLiteral("Opaque")); + m_context.showLayers({layerKey}); }); const ScopeOneCore::StageMosaicStatus status = m_core->stageMosaicStatus(); if (status.state == ScopeOneCore::StageMosaicState::Running) @@ -293,9 +304,7 @@ namespace scopeone::ui } setMosaicRunning(true); m_statusLabel->setText(tr("Starting mosaic capture")); - m_core->imageSceneModel()->setVisibleLayers( - {scopeone::core::ScopeOneCore::rawLayerKey(m_activeCameraId)}); - m_previewWidget->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::Overlay); + m_context.showLayers({scopeone::core::ScopeOneCore::rawLayerKey(m_activeCameraId)}); } // Request cancellation after the current stage move @@ -319,18 +328,12 @@ namespace scopeone::ui } // Create a particle detection tool - ParticleDetectionDialog::ParticleDetectionDialog(scopeone::core::ScopeOneCore* core, - PreviewWidget* previewWidget, + ParticleDetectionDialog::ParticleDetectionDialog(ScopeOneToolContext& context, QWidget* parent) : QDialog(parent) - , m_core(core) - , m_previewWidget(previewWidget) + , m_core(&context.core()) + , m_context(context) { - if (!core || !previewWidget) - { - qFatal("ParticleDetectionDialog requires ScopeOneCore and PreviewWidget"); - } - setWindowTitle(tr("Particle Detection")); setupUI(); connect(m_core, &ScopeOneCore::particleDetectionFinished, @@ -534,9 +537,8 @@ namespace scopeone::ui m_core->imageSceneModel()->setLayerBlending(maskLayer, QStringLiteral("Additive")); m_core->imageSceneModel()->setLayerVisible( scopeone::core::ScopeOneCore::rawLayerKey(cameraId), true); - m_core->imageSceneModel()->setVisibleLayers( + m_context.showLayers( {scopeone::core::ScopeOneCore::rawLayerKey(cameraId), maskLayer}); - m_previewWidget->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::Overlay); m_statusLabel->setText( result.truncated ? tr("Detected at least %1 particle(s)").arg(result.particles.size()) diff --git a/src/ImageToolsDialog.h b/src/ImageToolsDialog.h index f4cefb7..9c8ed41 100644 --- a/src/ImageToolsDialog.h +++ b/src/ImageToolsDialog.h @@ -15,7 +15,7 @@ class QTimer; namespace scopeone::ui { - class PreviewWidget; + class ScopeOneToolContext; class CameraScaleDialog : public QDialog { @@ -40,8 +40,7 @@ namespace scopeone::ui Q_OBJECT public: - StageMosaicDialog(scopeone::core::ScopeOneCore* core, - PreviewWidget* previewWidget, + StageMosaicDialog(ScopeOneToolContext& context, QWidget* parent = nullptr); void reject() override; @@ -56,7 +55,7 @@ namespace scopeone::ui QString selectedStageId() const; scopeone::core::ScopeOneCore* m_core{nullptr}; - PreviewWidget* m_previewWidget{nullptr}; + ScopeOneToolContext& m_context; QComboBox* m_cameraCombo{nullptr}; QComboBox* m_stageCombo{nullptr}; QSpinBox* m_rowsSpinBox{nullptr}; @@ -76,8 +75,7 @@ namespace scopeone::ui Q_OBJECT public: - ParticleDetectionDialog(scopeone::core::ScopeOneCore* core, - PreviewWidget* previewWidget, + ParticleDetectionDialog(ScopeOneToolContext& context, QWidget* parent = nullptr); void reject() override; @@ -95,7 +93,7 @@ namespace scopeone::ui QString selectedCameraId() const; scopeone::core::ScopeOneCore* m_core{nullptr}; - PreviewWidget* m_previewWidget{nullptr}; + ScopeOneToolContext& m_context; QComboBox* m_cameraCombo{nullptr}; QSpinBox* m_thresholdSpinBox{nullptr}; QSpinBox* m_minAreaSpinBox{nullptr}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 2477015..13b4b47 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -287,17 +287,7 @@ namespace scopeone::ui m_propertyBrowser->setEnabled(!configurationRunning); m_configPresetWidget->setEnabled(!configurationRunning); m_deviceControlWidget->setEnabled(!configurationRunning); - m_scaleAction->setEnabled(!configurationRunning && !m_scopeonecore->cameraIds().isEmpty()); - m_stageMosaicAction->setEnabled(!configurationRunning); - m_particleDetectionAction->setEnabled(!configurationRunning); - if (m_stageMosaicDialog) - { - m_stageMosaicDialog->setEnabled(!configurationRunning); - } - if (m_particleDetectionDialog) - { - m_particleDetectionDialog->setEnabled(!configurationRunning); - } + m_toolRegistry->setEnabled(!configurationRunning); const QStringList cameraIds = m_scopeonecore->cameraIds(); if (cameraIds.isEmpty()) @@ -325,6 +315,15 @@ namespace scopeone::ui } } }); + connect(m_scopeonecore, &scopeone::core::ScopeOneCore::hardwareDevicesChanged, + this, [this]() + { + if (m_scopeonecore->configurationOperationRunning()) return; + const QStringList cameraIds = m_scopeonecore->cameraIds(); + if (cameraIds.isEmpty()) applyNoCameraState(); + else applyLoadedCameraState(cameraIds); + refreshDevicePanels(false); + }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::configurationLoadFinished, this, [this](bool success, @@ -634,6 +633,17 @@ namespace scopeone::ui } m_imageSceneModel->setVisibleLayers(visibleLayerKeys); }); + connect(m_imageProcessingWidget, &ImageProcessingWidget::processedLayerReady, + this, [this](const QString& layerKey) + { + showLayers({layerKey}); + showStatusMessage(tr("Processed image added to preview"), 5000); + }); + connect(m_imageProcessingWidget, &ImageProcessingWidget::processedStackReady, + this, [this](const std::shared_ptr& session) + { + presentSession(session, tr("Processed Stack")); + }); connect(m_exitAction, &QAction::triggered, this, &QWidget::close); connect(m_fullScreenAction, &QAction::toggled, @@ -645,53 +655,9 @@ namespace scopeone::ui this, &MainWindow::loadConfigurationFromDialog); connect(m_unloadConfigurationAction, &QAction::triggered, this, &MainWindow::unloadConfigurationWithConfirmation); - connect(m_stageMosaicAction, &QAction::triggered, - this, &MainWindow::openStageMosaicTool); - connect(m_particleDetectionAction, &QAction::triggered, - this, &MainWindow::openParticleDetectionTool); - connect(m_scaleAction, &QAction::triggered, - this, &MainWindow::openScaleDialog); connect(m_settingsAction, &QAction::triggered, this, &MainWindow::openSettingsDialog); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::stageMosaicFrameUpdated, - this, [this](const scopeone::core::ImageFrame&) - { - const QString layerKey = - scopeone::core::ScopeOneCore::staticLayerKey(QStringLiteral("stage_mosaic")); - m_imageSceneModel->setLayerColormap(layerKey, QStringLiteral("Gray")); - m_imageSceneModel->setLayerBlending(layerKey, QStringLiteral("Opaque")); - m_imageSceneModel->setVisibleLayers({layerKey}); - m_previewWidget->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::Overlay); - }); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::stageMosaicFinished, - this, - [this](const std::shared_ptr& session, - const QString& message, - bool) - { - if (!session) - { - showStatusMessage(message, 8000); - return; - } - const QString title = tr("Stage Mosaic %1").arg(session->cameraIds().value(0)); - m_imageGalleryWidget->addSession(session, title); - m_scopeonecore->removeStaticFrame(QStringLiteral("stage_mosaic")); - registerGallerySessionFrameControls(session, 0); - for (const QString& cameraId : session->recordedCameraIds()) - { - if (session->recordedFrameCount(cameraId) > 0) - { - updateGalleryLayerFrame( - scopeone::core::ScopeOneCore::staticLayerKey( - gallerySessionLayerId(session, cameraId)), - 0); - } - } - showStatusMessage(tr("Mosaic added to Gallery"), 5000); - }); - connect(m_recordingWidget, &RecordingWidget::gallerySessionCaptured, this, [this](const std::shared_ptr& session) @@ -824,6 +790,7 @@ namespace scopeone::ui { m_previewWidget = new PreviewWidget(m_imageSceneModel, this); setCentralWidget(m_previewWidget); + setupTools(); setupStatusBar(); setupDeviceControl(); @@ -837,6 +804,85 @@ namespace scopeone::ui updateDockWidgetMenu(); } + // Register built in tools and discover external tool plugins + void MainWindow::setupTools() + { + m_toolRegistry = std::make_unique(*this); + m_toolRegistry->registerTool( + {QStringLiteral("scopeone.scale"), tr("&Scale..."), {}, ToolWindowMode::Modal, true}, + [](ScopeOneToolContext& context, QWidget* parent) + { + return new CameraScaleDialog(&context.core(), parent); + }); + m_toolRegistry->registerTool( + {QStringLiteral("scopeone.stage_mosaic"), tr("Stage &Mosaic..."), {}, + ToolWindowMode::ModelessSingleton}, + [](ScopeOneToolContext& context, QWidget* parent) + { + return new StageMosaicDialog(context, parent); + }); + m_toolRegistry->registerTool( + {QStringLiteral("scopeone.particle_detection"), tr("&Particle Detection..."), {}, + ToolWindowMode::ModelessSingleton}, + [](ScopeOneToolContext& context, QWidget* parent) + { + return new ParticleDetectionDialog(context, parent); + }); + + const QString pluginDirectory = QDir(QCoreApplication::applicationDirPath()) + .filePath(QStringLiteral("plugins/tools")); + for (const QString& error : m_toolRegistry->loadPlugins(pluginDirectory)) + { + qWarning().noquote() << QStringLiteral("Failed to load tool plugin %1").arg(error); + } + } + + scopeone::core::ScopeOneCore& MainWindow::core() const + { + return *m_scopeonecore; + } + + QString MainWindow::currentLayerKey() const + { + return m_deviceControlWidget->currentLayerKey(); + } + + void MainWindow::showLayers(const QStringList& layerKeys, bool sideBySide) + { + m_imageSceneModel->setVisibleLayers(layerKeys); + m_previewWidget->setLayerLayoutMode( + sideBySide ? PreviewWidget::LayerLayoutMode::SideBySide + : PreviewWidget::LayerLayoutMode::Overlay); + } + + void MainWindow::showToolStatus(const QString& message, int timeoutMs) + { + showStatusMessage(message, timeoutMs); + } + + void MainWindow::presentSession( + const std::shared_ptr& session, + const QString& title) + { + if (!session) + { + return; + } + m_imageGalleryWidget->addSession(session, title); + registerGallerySessionFrameControls(session, 0); + for (const QString& cameraId : session->recordedCameraIds()) + { + if (session->recordedFrameCount(cameraId) > 0) + { + updateGalleryLayerFrame( + scopeone::core::ScopeOneCore::staticLayerKey( + gallerySessionLayerId(session, cameraId)), + 0); + } + } + showStatusMessage(tr("Images added to Gallery"), 5000); + } + // Create the shared status strip for transient and persistent state void MainWindow::setupStatusBar() { @@ -898,10 +944,7 @@ namespace scopeone::ui m_dockWidgetsMenu = m_viewMenu->addMenu(tr("&Dock Widgets")); m_toolsMenu = menuBar()->addMenu(tr("&Tools")); - m_scaleAction = m_toolsMenu->addAction(tr("&Scale...")); - m_scaleAction->setEnabled(!m_scopeonecore->cameraIds().isEmpty()); - m_stageMosaicAction = m_toolsMenu->addAction(tr("Stage &Mosaic...")); - m_particleDetectionAction = m_toolsMenu->addAction(tr("&Particle Detection...")); + m_toolRegistry->populateMenu(m_toolsMenu, this); m_toolsMenu->addSeparator(); m_settingsAction = m_toolsMenu->addAction(tr("&Settings...")); @@ -1144,6 +1187,7 @@ namespace scopeone::ui } m_recordingWidget->setAvailableCameras(cameraIds); + m_toolRegistry->updateActions(); } // Clear preview and panel state when no camera is available @@ -1162,6 +1206,7 @@ namespace scopeone::ui m_inspectWidget->clearCrossSectionProfile(); m_recordingWidget->setAvailableCameras({}); + m_toolRegistry->updateActions(); } // Refresh panels that mirror device state @@ -1511,42 +1556,6 @@ namespace scopeone::ui 5000); } - // Edit the global per camera image scale - void MainWindow::openScaleDialog() - { - CameraScaleDialog dialog(m_scopeonecore, this); - dialog.exec(); - } - - // Open the stage driven image mosaic tool - void MainWindow::openStageMosaicTool() - { - if (!m_stageMosaicDialog) - { - auto* dialog = new StageMosaicDialog(m_scopeonecore, m_previewWidget, this); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setModal(false); - m_stageMosaicDialog = dialog; - } - m_stageMosaicDialog->show(); - m_stageMosaicDialog->raise(); - m_stageMosaicDialog->activateWindow(); - } - - // Open the OpenCV particle detection tool - void MainWindow::openParticleDetectionTool() - { - if (!m_particleDetectionDialog) - { - m_particleDetectionDialog = new ParticleDetectionDialog(m_scopeonecore, m_previewWidget, this); - m_particleDetectionDialog->setAttribute(Qt::WA_DeleteOnClose); - m_particleDetectionDialog->setModal(false); - } - m_particleDetectionDialog->show(); - m_particleDetectionDialog->raise(); - m_particleDetectionDialog->activateWindow(); - } - // Display image coordinates and pixel value under the cursor void MainWindow::handlePreviewMousePosition(const QPoint& pos) { diff --git a/src/MainWindow.h b/src/MainWindow.h index 17020d3..0830d4b 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -1,6 +1,7 @@ #pragma once #include "scopeone/ScopeOneCore.h" +#include "ScopeOneToolPlugin.h" #include #include @@ -35,10 +36,7 @@ namespace scopeone::ui class ConsoleWidget; class DeviceControlWidget; class RecordingWidget; - class StageMosaicDialog; - class ParticleDetectionDialog; - - class MainWindow : public QMainWindow + class MainWindow : public QMainWindow, public ScopeOneToolContext { Q_OBJECT @@ -46,6 +44,14 @@ namespace scopeone::ui explicit MainWindow(scopeone::core::ScopeOneCore* core, QWidget* parent = nullptr); ~MainWindow() override = default; + scopeone::core::ScopeOneCore& core() const override; + QString currentLayerKey() const override; + void showLayers(const QStringList& layerKeys, bool sideBySide = false) override; + void showToolStatus(const QString& message, int timeoutMs = 5000) override; + void presentSession( + const std::shared_ptr& session, + const QString& title) override; + protected: void closeEvent(QCloseEvent* event) override; @@ -53,6 +59,7 @@ namespace scopeone::ui void setupUI(); void setupSignalWiring(); void setupStatusBar(); + void setupTools(); void setupMenuBar(); void setupDeviceControl(); @@ -73,9 +80,6 @@ namespace scopeone::ui void applyStoredApplicationSettings(); void logStartupSummary(); void openSettingsDialog(); - void openScaleDialog(); - void openStageMosaicTool(); - void openParticleDetectionTool(); void connectPropertyPanels(); void showStatusMessage(const QString& message, int timeoutMs = 0); void setCursorStatus(const QString& text); @@ -173,16 +177,12 @@ namespace scopeone::ui QAction* m_loadConfigurationAction{nullptr}; QAction* m_unloadConfigurationAction{nullptr}; QAction* m_settingsAction{nullptr}; - QAction* m_scaleAction{nullptr}; - QAction* m_stageMosaicAction{nullptr}; - QAction* m_particleDetectionAction{nullptr}; QAction* m_aboutAction{nullptr}; QAction* m_aboutQtAction{nullptr}; QPointer m_loadConfigProgress; QPointer m_closeSaveProgress; - QPointer m_stageMosaicDialog; - QPointer m_particleDetectionDialog; + std::unique_ptr m_toolRegistry; QLabel* m_statusMessageLabel{nullptr}; QLabel* m_statusCursorLabel{nullptr}; QLabel* m_statusPreviewLabel{nullptr}; diff --git a/src/ScopeOneLocalApiServer.cpp b/src/ScopeOneLocalApiServer.cpp index cd1068d..d74ffe3 100644 --- a/src/ScopeOneLocalApiServer.cpp +++ b/src/ScopeOneLocalApiServer.cpp @@ -189,71 +189,9 @@ namespace scopeone::ui return object; } - QString processingModuleApiKindName(scopeone::core::ScopeOneCore::ProcessingModuleKind kind) + QString processingModuleIdFromJson(const QJsonValue& value) { - using ProcessingModuleKind = scopeone::core::ScopeOneCore::ProcessingModuleKind; - switch (kind) - { - case ProcessingModuleKind::FFT: - return QStringLiteral("fft"); - case ProcessingModuleKind::BackgroundCalibration: - return QStringLiteral("background_calibration"); - case ProcessingModuleKind::SpatiotemporalBinning: - return QStringLiteral("spatiotemporal_binning"); - case ProcessingModuleKind::GaussianBlur: - return QStringLiteral("gaussian_blur"); - case ProcessingModuleKind::DifferentialRolling: - return QStringLiteral("differential_rolling"); - case ProcessingModuleKind::Unknown: - return QStringLiteral("unknown"); - } - return QStringLiteral("unknown"); - } - - scopeone::core::ScopeOneCore::ProcessingModuleKind processingModuleKindFromJson(const QJsonValue& value) - { - using ProcessingModuleKind = scopeone::core::ScopeOneCore::ProcessingModuleKind; - if (value.isDouble()) - { - const int kind = value.toInt(static_cast(ProcessingModuleKind::Unknown)); - switch (static_cast(kind)) - { - case ProcessingModuleKind::FFT: - case ProcessingModuleKind::BackgroundCalibration: - case ProcessingModuleKind::SpatiotemporalBinning: - case ProcessingModuleKind::GaussianBlur: - case ProcessingModuleKind::DifferentialRolling: - return static_cast(kind); - case ProcessingModuleKind::Unknown: - return ProcessingModuleKind::Unknown; - } - } - - QString name = value.toString().trimmed().toLower(); - name.remove(QLatin1Char('_')); - name.remove(QLatin1Char('-')); - name.remove(QLatin1Char(' ')); - if (name == QStringLiteral("fft")) - { - return ProcessingModuleKind::FFT; - } - if (name == QStringLiteral("backgroundcalibration") || name == QStringLiteral("background")) - { - return ProcessingModuleKind::BackgroundCalibration; - } - if (name == QStringLiteral("spatiotemporalbinning") || name == QStringLiteral("binning")) - { - return ProcessingModuleKind::SpatiotemporalBinning; - } - if (name == QStringLiteral("gaussianblur") || name == QStringLiteral("blur")) - { - return ProcessingModuleKind::GaussianBlur; - } - if (name == QStringLiteral("differentialrolling") || name == QStringLiteral("rolling")) - { - return ProcessingModuleKind::DifferentialRolling; - } - return ProcessingModuleKind::Unknown; + return value.isString() ? value.toString().trimmed() : QString{}; } QJsonObject processingModuleToJson( @@ -262,13 +200,35 @@ namespace scopeone::ui { QJsonObject object; object.insert(QStringLiteral("index"), index); - object.insert(QStringLiteral("kind"), processingModuleApiKindName(info.kind())); - object.insert(QStringLiteral("kindValue"), static_cast(info.kind())); + object.insert(QStringLiteral("kind"), info.id()); object.insert(QStringLiteral("name"), info.name()); object.insert(QStringLiteral("parameters"), QJsonObject::fromVariantMap(info.parameters())); return object; } + QJsonObject processingModuleDescriptorToJson( + const scopeone::core::ProcessingModuleDescriptor& descriptor) + { + QJsonObject object; + object.insert(QStringLiteral("id"), descriptor.id); + object.insert(QStringLiteral("name"), descriptor.name); + object.insert(QStringLiteral("schemaVersion"), descriptor.schemaVersion); + QJsonArray parameters; + for (const auto& parameter : descriptor.parameters) + { + QJsonObject item; + item.insert(QStringLiteral("key"), parameter.key); + item.insert(QStringLiteral("name"), parameter.name); + item.insert(QStringLiteral("type"), static_cast(parameter.type)); + item.insert(QStringLiteral("default"), QJsonValue::fromVariant(parameter.defaultValue)); + item.insert(QStringLiteral("minimum"), QJsonValue::fromVariant(parameter.minimum)); + item.insert(QStringLiteral("maximum"), QJsonValue::fromVariant(parameter.maximum)); + parameters.append(item); + } + object.insert(QStringLiteral("parameters"), parameters); + return object; + } + QJsonArray processingModulesToJson( const QList& modules) { @@ -3458,7 +3418,15 @@ namespace scopeone::ui static_cast(m_scopeonecore->processingBitDepth())); response.insert(QStringLiteral("realTime"), m_scopeonecore->isRealTimeProcessingEnabled()); + response.insert(QStringLiteral("realTimeSource"), + m_scopeonecore->realTimeProcessingSource()); response.insert(QStringLiteral("modules"), processingModulesToJson(modules)); + QJsonArray availableModules; + for (const auto& descriptor : m_scopeonecore->availableProcessingModules()) + { + availableModules.append(processingModuleDescriptorToJson(descriptor)); + } + response.insert(QStringLiteral("availableModules"), availableModules); return response; } @@ -3491,6 +3459,15 @@ namespace scopeone::ui if (type == QStringLiteral("set_realtime_processing")) { const bool enabled = request.value(QStringLiteral("enabled")).toBool(false); + if (enabled && request.contains(QStringLiteral("cameraId")) + && !m_scopeonecore->setRealTimeProcessingSource( + request.value(QStringLiteral("cameraId")).toString())) + { + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + QStringLiteral("Unknown processing source or processing is already running")); + return response; + } if (!m_scopeonecore->setRealTimeProcessingEnabled(enabled)) { QJsonObject response = makeResponse(type, false); @@ -3502,19 +3479,21 @@ namespace scopeone::ui } QJsonObject response = makeResponse(type, true); response.insert(QStringLiteral("realTime"), m_scopeonecore->isRealTimeProcessingEnabled()); + response.insert(QStringLiteral("realTimeSource"), + m_scopeonecore->realTimeProcessingSource()); return response; } if (type == QStringLiteral("add_processing_module")) { - const auto kind = processingModuleKindFromJson(request.value(QStringLiteral("kind"))); + const QString moduleId = processingModuleIdFromJson(request.value(QStringLiteral("kind"))); QJsonObject response = makeResponse(type, false); - if (kind == scopeone::core::ScopeOneCore::ProcessingModuleKind::Unknown) + if (moduleId.isEmpty()) { response.insert(QStringLiteral("error"), QStringLiteral("Missing or unknown processing module kind")); return response; } - if (!m_scopeonecore->addProcessingModule(kind)) + if (!m_scopeonecore->addProcessingModule(moduleId)) { response.insert(QStringLiteral("error"), m_scopeonecore->isRealTimeProcessingEnabled() diff --git a/src/ScopeOneMcpServer.cpp b/src/ScopeOneMcpServer.cpp index 4364686..fb153e5 100644 --- a/src/ScopeOneMcpServer.cpp +++ b/src/ScopeOneMcpServer.cpp @@ -845,7 +845,10 @@ namespace QStringLiteral("Enable or disable real-time image processing"), inputProperties({ {QStringLiteral("enabled"), - inputProperty(QStringLiteral("boolean"), QStringLiteral("Real-time processing state"))} + inputProperty(QStringLiteral("boolean"), QStringLiteral("Real-time processing state"))}, + {QStringLiteral("cameraId"), + inputProperty(QStringLiteral("string"), + QStringLiteral("Optional camera source, empty selects all cameras"))} }), {QStringLiteral("enabled")}, ToolAccess::StateChanging), @@ -854,11 +857,8 @@ namespace QStringLiteral("Append a module to the image-processing pipeline"), inputProperties({ {QStringLiteral("kind"), - withEnum( - inputProperty(QStringLiteral("string"), QStringLiteral("Processing module kind")), - {QStringLiteral("fft"), QStringLiteral("background_calibration"), - QStringLiteral("spatiotemporal_binning"), QStringLiteral("gaussian_blur"), - QStringLiteral("differential_rolling")})}, + inputProperty(QStringLiteral("string"), + QStringLiteral("Processing module ID from processing_modules"))}, {QStringLiteral("parameters"), inputProperty(QStringLiteral("object"), QStringLiteral("Initial module parameters"))} }), diff --git a/src/ScopeOneToolPlugin.cpp b/src/ScopeOneToolPlugin.cpp new file mode 100644 index 0000000..2bf64f8 --- /dev/null +++ b/src/ScopeOneToolPlugin.cpp @@ -0,0 +1,217 @@ +#include "ScopeOneToolPlugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scopeone::ui +{ + struct ToolRegistry::Entry + { + ToolDescriptor descriptor; + Factory factory; + QAction* action{nullptr}; + QPointer instance; + }; + + ToolRegistry::ToolRegistry(ScopeOneToolContext& context) + : m_context(context) + { + } + + ToolRegistry::~ToolRegistry() + { + for (const auto& entry : m_entries) + { + delete entry->instance; + } + } + + bool ToolRegistry::registerTool(const ToolDescriptor& descriptor, Factory factory) + { + const QString id = descriptor.id.trimmed(); + for (const auto& entry : m_entries) + { + if (entry->descriptor.id == id) + { + return false; + } + } + if (id.isEmpty() || descriptor.name.trimmed().isEmpty() || !factory) + { + return false; + } + + auto entry = std::make_unique(); + entry->descriptor = descriptor; + entry->descriptor.id = id; + entry->factory = std::move(factory); + m_entries.push_back(std::move(entry)); + return true; + } + + QStringList ToolRegistry::loadPlugins(const QString& directoryPath) + { + QStringList errors; + const QDir directory(directoryPath); + for (const QFileInfo& file : directory.entryInfoList(QDir::Files, QDir::Name)) + { + if (!QLibrary::isLibrary(file.absoluteFilePath())) + { + continue; + } + auto loader = std::make_unique(file.absoluteFilePath()); + auto* plugin = qobject_cast(loader->instance()); + if (!plugin) + { + errors.append(QStringLiteral("%1: %2").arg(file.fileName(), loader->errorString())); + continue; + } + + const QList descriptors = plugin->tools(); + QSet ids; + bool valid = !descriptors.isEmpty(); + for (const ToolDescriptor& descriptor : descriptors) + { + const QString id = descriptor.id.trimmed(); + if (id.isEmpty() + || descriptor.name.trimmed().isEmpty() + || ids.contains(id)) + { + valid = false; + break; + } + ids.insert(id); + } + if (!valid) + { + errors.append(QStringLiteral("%1: invalid or duplicate tool id").arg(file.fileName())); + loader->unload(); + continue; + } + + for (const ToolDescriptor& descriptor : descriptors) + { + for (const auto& entry : m_entries) + { + if (entry->descriptor.id == descriptor.id.trimmed()) + { + valid = false; + break; + } + } + } + if (!valid) + { + errors.append(QStringLiteral("%1: tool id conflicts with an existing tool") + .arg(file.fileName())); + loader->unload(); + continue; + } + + for (const ToolDescriptor& descriptor : descriptors) + { + const QString id = descriptor.id.trimmed(); + registerTool(descriptor, [plugin, id](ScopeOneToolContext& context, + QWidget* parent) + { + return plugin->createTool(id, context, parent); + }); + } + m_pluginLoaders.push_back(std::move(loader)); + } + return errors; + } + + void ToolRegistry::populateMenu(QMenu* menu, QWidget* parent) + { + QHash categories; + for (const auto& entry : m_entries) + { + QMenu* target = menu; + const QString category = entry->descriptor.category.trimmed(); + if (!category.isEmpty()) + { + target = categories.value(category); + if (!target) + { + target = menu->addMenu(category); + categories.insert(category, target); + } + } + entry->action = target->addAction(entry->descriptor.name); + const QString id = entry->descriptor.id; + QObject::connect(entry->action, &QAction::triggered, parent, + [this, id, parent]() { openTool(id, parent); }); + } + updateActions(); + } + + void ToolRegistry::updateActions() + { + const bool hasCamera = !m_context.core().cameraIds().isEmpty(); + for (const auto& entry : m_entries) + { + if (entry->action) + { + entry->action->setEnabled( + m_enabled && (!entry->descriptor.requiresCamera || hasCamera)); + } + } + } + + void ToolRegistry::setEnabled(bool enabled) + { + m_enabled = enabled; + for (const auto& entry : m_entries) + { + if (entry->instance) + { + entry->instance->setEnabled(enabled); + } + } + updateActions(); + } + + void ToolRegistry::openTool(const QString& toolId, QWidget* parent) + { + for (const auto& entry : m_entries) + { + if (entry->descriptor.id != toolId) + { + continue; + } + if (entry->descriptor.windowMode == ToolWindowMode::ModelessSingleton && entry->instance) + { + entry->instance->show(); + entry->instance->raise(); + entry->instance->activateWindow(); + return; + } + + QWidget* tool = entry->factory(m_context, parent); + if (!tool) + { + return; + } + if (entry->descriptor.windowMode == ToolWindowMode::Modal) + { + if (auto* dialog = qobject_cast(tool)) + { + dialog->exec(); + } + delete tool; + return; + } + tool->setAttribute(Qt::WA_DeleteOnClose); + entry->instance = tool; + tool->show(); + } + } +} diff --git a/src/ScopeOneToolPlugin.h b/src/ScopeOneToolPlugin.h new file mode 100644 index 0000000..e0eb938 --- /dev/null +++ b/src/ScopeOneToolPlugin.h @@ -0,0 +1,86 @@ +#pragma once + +#include "scopeone/ScopeOneCore.h" + +#include +#include +#include +#include +#include +#include +#include + +class QAction; +class QMenu; +class QPluginLoader; +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 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; + }; + + class ToolRegistry + { + public: + using Factory = std::function; + + explicit ToolRegistry(ScopeOneToolContext& context); + ~ToolRegistry(); + + bool registerTool(const ToolDescriptor& descriptor, Factory factory); + QStringList loadPlugins(const QString& directoryPath); + void populateMenu(QMenu* menu, QWidget* parent); + void setEnabled(bool enabled); + void updateActions(); + + private: + struct Entry; + void openTool(const QString& toolId, QWidget* parent); + + ScopeOneToolContext& m_context; + std::vector> m_entries; + std::vector> m_pluginLoaders; + bool m_enabled{true}; + }; +} + +#define ScopeOneToolPlugin_iid "org.scopeone.ToolPlugin/1.0" +Q_DECLARE_INTERFACE(scopeone::ui::ScopeOneToolPlugin, ScopeOneToolPlugin_iid) From fd6bc015acb7626598b52aeab395e48e42c7faca Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Mon, 24 Aug 2026 09:24:28 +0200 Subject: [PATCH 04/37] Unify image workspace workflows --- CMakeLists.txt | 2 + README.md | 4 +- ScopeOneCore/README.md | 2 +- ScopeOneCore/include/scopeone/ScopeOneCore.h | 26 +- ScopeOneCore/internal/RecordingManager.h | 4 +- ScopeOneCore/python/scopeone/README.md | 16 +- .../python/scopeone/src/scopeone/client.py | 83 + .../python/scopeone/src/scopeone/core.py | 42 + ScopeOneCore/src/RecordingManager.cpp | 40 +- ScopeOneCore/src/ScopeOneCore.cpp | 166 +- VERSION | 2 +- src/DeviceControlWidget.cpp | 621 ++++--- src/DeviceControlWidget.h | 55 +- src/ImageProcessingWidget.cpp | 314 ++-- src/ImageProcessingWidget.h | 26 +- src/ImageWorkspace.cpp | 1588 +++++++++++++++++ src/ImageWorkspace.h | 179 ++ src/InspectWidget.cpp | 464 +++-- src/InspectWidget.h | 37 +- src/MainWindow.cpp | 435 ++--- src/MainWindow.h | 38 +- src/PreviewWidget.cpp | 19 +- src/PreviewWidget.h | 3 +- src/ScopeOneLocalApiServer.cpp | 379 +++- src/ScopeOneLocalApiServer.h | 3 + src/ScopeOneMcpServer.cpp | 73 +- 26 files changed, 3533 insertions(+), 1088 deletions(-) create mode 100644 src/ImageWorkspace.cpp create mode 100644 src/ImageWorkspace.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b66901f..b362896 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ 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 @@ -90,6 +91,7 @@ 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 diff --git a/README.md b/README.md index b92bda2..899aefa 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ plugins/ Micro-Manager remains the built-in hardware provider and continues to load its Device Adapters from `.cfg` files. Native devices that do not belong in Micro-Manager use the `HardwareProvider` plugin contract. Built-in and external processing modules share stable string IDs and parameter descriptors, so the Image Processing panel creates the same controls for both. Built-in desktop tools and external tool plugins likewise share the tool registry. -The Image Processing panel can process all live cameras or one selected camera. It can also run the current pipeline once on an image layer or across every frame of a recorded Gallery session stack. Stack processing runs in the background, preserves temporal module state across the stack, supports cancellation, and creates a new Gallery session without changing live pipeline state. +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:** @@ -245,7 +245,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/README.md b/ScopeOneCore/README.md index 8e3fbd4..36fe1d6 100644 --- a/ScopeOneCore/README.md +++ b/ScopeOneCore/README.md @@ -128,7 +128,7 @@ Hardware and processing contracts are installed public Core APIs. Desktop tool p `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(...)`. `requestLayerProcessing(...)` applies an isolated pipeline to one current image layer. `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. +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/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 4bc2547..cddffc2 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -582,6 +582,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, @@ -653,7 +659,8 @@ namespace scopeone::core bool removeProcessingModule(int index); bool setProcessingModuleParameters(int index, const QVariantMap& parameters); bool resetProcessingModuleState(int index); - quint64 requestLayerProcessing(const QString& layerKey); + quint64 requestImageProcessing(const ImageFrame& frame, + const QString& sourceId = QString()); quint64 requestRecordingSessionStackProcessing(const QString& sessionId, const QString& cameraId); bool cancelProcessingRequest(quint64 requestId); @@ -685,6 +692,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, @@ -741,8 +752,8 @@ namespace scopeone::core void processingModulesChanged(); void processingModuleParametersChanged(int index); void processingSettingsChanged(); - void layerProcessingFinished(quint64 requestId, - const QString& sourceLayerKey, + void imageProcessingFinished(quint64 requestId, + const QString& sourceId, const ImageFrame& frame, const QString& errorMessage); void stackProcessingProgress(quint64 requestId, qint64 completed, qint64 total); @@ -772,6 +783,11 @@ 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( @@ -868,6 +884,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 { diff --git a/ScopeOneCore/internal/RecordingManager.h b/ScopeOneCore/internal/RecordingManager.h index 9285da4..39dfa24 100644 --- a/ScopeOneCore/internal/RecordingManager.h +++ b/ScopeOneCore/internal/RecordingManager.h @@ -65,7 +65,9 @@ namespace scopeone::core::internal void onRawFramesReady(const QList& frames); void onFrameDeliveryFailed(const QString& errorMessage, quint64 droppedFrames); - static QString saveSessionToDisk(const std::shared_ptr& session); + static QString saveSessionToDisk( + const std::shared_ptr& session, + const std::shared_ptr& sourceSession = {}); signals: void mdaRawFrameReady(const scopeone::core::ImageFrame& frame); diff --git a/ScopeOneCore/python/scopeone/README.md b/ScopeOneCore/python/scopeone/README.md index a79e1c5..60f731a 100644 --- a/ScopeOneCore/python/scopeone/README.md +++ b/ScopeOneCore/python/scopeone/README.md @@ -109,6 +109,12 @@ A control connection is synchronous and processes one request at a time. Agent a - `ScopeOne.loaded_devices()` - `ScopeOne.start_preview(camera="All")` - `ScopeOne.stop_preview(camera="All")` +- `ScopeOne.image_windows()` +- `ScopeOne.open_image_window(session_id, title=None, camera_id=None)` +- `ScopeOne.activate_image_window(document_id)` +- `ScopeOne.close_image_window(document_id=None)` +- `ScopeOne.process_image_window(document_id=None, complete_stack=False)` +- `ScopeOne.save_image_window(save_dir, base_name, document_id=None, format="ome-tiff", compression=False, compression_level=6)` - `ScopeOne.list_layers()` - `ScopeOne.get_layer_histogram(layer_key)` - `ScopeOne.get_pixel_value(layer_key, x, y)` @@ -235,11 +241,17 @@ ScopeOne uses one local control pipe for JSON commands and one shared-memory blo - `loaded_devices`: response `devices`. - `start_preview`: fields `camera`, accepts a camera id or `"All"`. - `stop_preview`: fields `camera`, accepts a camera id or `"All"`. -- `list_layers`: response `layers`. +- `image_windows`: response `activeDocumentId` and `documents`; each document contains its ID, title, session, camera, current frame, frame count, readiness, and active state. +- `open_image_window`: fields `sessionId`, optional `title` and `cameraId`; opens matching retained session data, waits for the first frame, and returns `documentIds` and `activeDocumentId`. +- `activate_image_window`: field `documentId`; activates the window and returns `document`. +- `close_image_window`: optional field `documentId`; closes the selected or active window. +- `process_image_window`: optional fields `documentId` and `completeStack`; asynchronously processes the selected or active window and returns the new `document`. +- `save_image_window`: fields `saveDir`, `baseName`, `format` (`ome-tiff`, `ome-zarr`, `tiff` or `binary`), `compression`, and `compressionLevel`, plus optional `documentId`; asynchronously saves the selected or active window and returns `documentId` and `message`. +- `list_layers`: response `layers`; layer display, histogram, pixel, profile, and markup operations target the active image viewer. - `get_layer_histogram`: fields `layerKey`; response `histogram` with summary statistics and 256 `bins`. - `get_pixel_value`: fields `layerKey`, `x`, `y`; response `value`. - `get_line_profile`: fields `layerKey`, `x1`, `y1`, `x2`, `y2`; response `values`. -- `detect_particles`: fields `layerKey`, `threshold`, `minArea`, `maxArea`, optional `maxParticles`, `exportMask`, and `publishMask`; response `particleCount`, effective thresholds, truncation state, particle measurements, optional shared-memory `mask` metadata, and optional `maskLayerKey`. +- `detect_particles`: fields `layerKey`, `threshold`, `minArea`, `maxArea`, optional `maxParticles`, `exportMask`, and `publishMask`; response `particleCount`, effective thresholds, truncation state, particle measurements, optional shared-memory `mask` metadata, and either `maskLayerKey` for Live or `maskDocumentId` for a static image window. - `layer_options`: response `layouts`, `colormaps`, `blendingModes`. - `set_layer_layout`: fields `layout`, accepts `side_by_side` or `overlay`. - `set_visible_layers`: fields `layerKeys`; response `visibleLayers`. diff --git a/ScopeOneCore/python/scopeone/src/scopeone/client.py b/ScopeOneCore/python/scopeone/src/scopeone/client.py index b3e2b09..5835342 100644 --- a/ScopeOneCore/python/scopeone/src/scopeone/client.py +++ b/ScopeOneCore/python/scopeone/src/scopeone/client.py @@ -412,6 +412,87 @@ def stop_preview(self, camera: str = "All") -> bool: self._request({"type": "stop_preview", "camera": camera}) return True + def image_windows(self) -> dict: + response = self._request({"type": "image_windows"}) + return { + "activeDocumentId": str(response.get("activeDocumentId", "")), + "documents": list(response.get("documents", [])), + } + + def open_image_window( + self, + session_id: str, + title: str | None = None, + camera_id: str | None = None, + ) -> dict: + request = { + "type": "open_image_window", + "sessionId": session_id, + } + if title is not None: + request["title"] = title + if camera_id is not None: + request["cameraId"] = camera_id + response = self._request(request) + return { + "documentIds": list(response.get("documentIds", [])), + "activeDocumentId": str(response.get("activeDocumentId", "")), + } + + def activate_image_window(self, document_id: str) -> dict: + response = self._request( + { + "type": "activate_image_window", + "documentId": document_id, + } + ) + return dict(response["document"]) + + def close_image_window(self, document_id: str | None = None) -> None: + request = {"type": "close_image_window"} + if document_id is not None: + request["documentId"] = document_id + self._request(request) + + def process_image_window( + self, + document_id: str | None = None, + complete_stack: bool = False, + ) -> dict: + request = { + "type": "process_image_window", + "completeStack": bool(complete_stack), + } + if document_id is not None: + request["documentId"] = document_id + response = self._request(request) + return dict(response["document"]) + + def save_image_window( + self, + save_dir: str, + base_name: str, + document_id: str | None = None, + format: str = "ome-tiff", + compression: bool = False, + compression_level: int = 6, + ) -> dict: + request = { + "type": "save_image_window", + "saveDir": save_dir, + "baseName": base_name, + "format": format, + "compression": bool(compression), + "compressionLevel": int(compression_level), + } + if document_id is not None: + request["documentId"] = document_id + response = self._request(request) + return { + "documentId": str(response["documentId"]), + "message": str(response.get("message", "")), + } + def list_layers(self) -> list[dict]: response = self._request({"type": "list_layers"}) return list(response.get("layers", [])) @@ -489,6 +570,8 @@ def detect_particles( } if "maskLayerKey" in response: result["maskLayerKey"] = str(response["maskLayerKey"]) + if "maskDocumentId" in response: + result["maskDocumentId"] = str(response["maskDocumentId"]) if "mask" in response: result["mask"] = self._frame_result_from_mapping_response(dict(response["mask"])) return result diff --git a/ScopeOneCore/python/scopeone/src/scopeone/core.py b/ScopeOneCore/python/scopeone/src/scopeone/core.py index a5e904e..6593335 100644 --- a/ScopeOneCore/python/scopeone/src/scopeone/core.py +++ b/ScopeOneCore/python/scopeone/src/scopeone/core.py @@ -54,6 +54,48 @@ def start_preview(self, camera: str = "All"): def stop_preview(self, camera: str = "All"): return self._client.stop_preview(camera) + def image_windows(self): + return self._client.image_windows() + + def open_image_window( + self, + session_id: str, + title: str | None = None, + camera_id: str | None = None, + ): + return self._client.open_image_window(session_id, title, camera_id) + + def activate_image_window(self, document_id: str): + return self._client.activate_image_window(document_id) + + def close_image_window(self, document_id: str | None = None): + self._client.close_image_window(document_id) + + def process_image_window( + self, + document_id: str | None = None, + complete_stack: bool = False, + ): + return self._client.process_image_window(document_id, complete_stack) + + def save_image_window( + self, + save_dir: str, + base_name: str, + document_id: str | None = None, + format: str = "ome-tiff", + compression: bool = False, + compression_level: int = 6, + ): + return self._client.save_image_window( + save_dir, + base_name, + document_id, + format, + compression, + compression_level, + ) + def list_layers(self): return self._client.list_layers() diff --git a/ScopeOneCore/src/RecordingManager.cpp b/ScopeOneCore/src/RecordingManager.cpp index 8098693..4c0773b 100644 --- a/ScopeOneCore/src/RecordingManager.cpp +++ b/ScopeOneCore/src/RecordingManager.cpp @@ -2217,12 +2217,15 @@ namespace scopeone::core::internal } // Saves buffered sessions and preserves direct writer outputs - QString RecordingManager::saveSessionToDisk(const std::shared_ptr& session) + QString RecordingManager::saveSessionToDisk( + const std::shared_ptr& session, + const std::shared_ptr& sourceSession) { if (!session) { return QStringLiteral("Error: Missing recording session"); } + const auto inputSession = sourceSession ? sourceSession : session->cloneForSave(); ExperimentPlan capturePlan = session->capturePlan(); if (capturePlan.cameraIds.isEmpty()) { @@ -2242,21 +2245,8 @@ namespace scopeone::core::internal return updateSessionResult(*session, QStringLiteral("Error: %1").arg(planError), false); } - if (!session->hasAnyFrames()) + if (!inputSession->hasAnyFrames() && !inputSession->hasRecordedOutput()) { - if (!session->saveMessage().isEmpty()) - { - return session->saveMessage(); - } - if (session->streamedToDisk() && session->hasRecordedOutput()) - { - return updateSessionResult( - *session, - saveSuccessMessage( - QStringLiteral("Success: Recording was already saved during acquisition"), - savedSessionOutputDir(*session)), - true); - } return updateSessionResult(*session, QStringLiteral("Error: No frames captured"), false); } @@ -2288,8 +2278,8 @@ namespace scopeone::core::internal QHash completedOutputs; for (const QString& cameraId : session->cameraIds()) { - const int frameCount = session->frameCount(cameraId); - if (frameCount <= 0) + const qint64 frameCount = inputSession->recordedFrameCount(cameraId); + if (frameCount <= 0 || frameCount > (std::numeric_limits::max)()) { continue; } @@ -2299,7 +2289,7 @@ namespace scopeone::core::internal tiffOpts.useDeflate = capturePlan.enableCompression; tiffOpts.zipQuality = capturePlan.compressionLevel; - const ImageFrame firstImageFrame = session->imageFrameAt(cameraId, 0); + const ImageFrame firstImageFrame = inputSession->imageFrameAt(cameraId, 0); if (!firstImageFrame.isValid()) { continue; @@ -2308,7 +2298,7 @@ namespace scopeone::core::internal if (capturePlan.format == RecordingFormat::OmeTiff || capturePlan.format == RecordingFormat::OmeZarr) { - for (const AcquisitionEventRecord& record : session->experimentDocument().events) + for (const AcquisitionEventRecord& record : inputSession->experimentDocument().events) { if (record.succeeded && record.frames.contains(cameraId)) { @@ -2331,13 +2321,13 @@ namespace scopeone::core::internal capturePlan, physicalPixelSizeUm(firstImageFrame.width, firstImageFrame.sourceRoiWidth, - session->cameraPixelSizeUm(cameraId)), + inputSession->cameraPixelSizeUm(cameraId)), physicalPixelSizeUm(firstImageFrame.height, firstImageFrame.sourceRoiHeight, - session->cameraPixelSizeUm(cameraId)), - session->experimentDocument().startedTimestampNs, + inputSession->cameraPixelSizeUm(cameraId)), + inputSession->experimentDocument().startedTimestampNs, cameraId, - session->experimentDocument().deviceProperties + inputSession->experimentDocument().deviceProperties .value(cameraId).toObject(), tiffOpts)) { @@ -2345,9 +2335,9 @@ namespace scopeone::core::internal return failSave(errorMessage); } int saved = 0; - for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex) + for (int frameIndex = 0; frameIndex < static_cast(frameCount); ++frameIndex) { - const ImageFrame imageFrame = session->imageFrameAt(cameraId, frameIndex); + const ImageFrame imageFrame = inputSession->imageFrameAt(cameraId, frameIndex); if (!imageFrame.isValid()) { continue; diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 7f2fa72..f25da4d 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -2846,7 +2846,18 @@ namespace scopeone::core int maxParticles) { const QString key = layerKey.trimmed(); - const ImageFrame frame = graphFrame(key); + return detectParticles(graphFrame(key), key, + threshold, minArea, maxArea, maxParticles); + } + + quint64 ScopeOneCore::detectParticles(const ImageFrame& frame, + const QString& resultLayerKey, + int threshold, + int minArea, + int maxArea, + int maxParticles) + { + const QString key = resultLayerKey.trimmed(); if (key.isEmpty() || !frame.isValid()) { return 0; @@ -3965,11 +3976,10 @@ namespace scopeone::core return true; } - // Process one current graph layer without blocking the UI thread - quint64 ScopeOneCore::requestLayerProcessing(const QString& layerKey) + // Process one image without coupling the task to a UI layer + quint64 ScopeOneCore::requestImageProcessing(const ImageFrame& frame, + const QString& sourceId) { - const QString sourceLayerKey = layerKey.trimmed(); - const ImageFrame frame = graphFrame(sourceLayerKey); if (!frame.isValid() || processingModules().isEmpty()) { return 0; @@ -3982,13 +3992,13 @@ namespace scopeone::core const int bitDepth = static_cast(processingBitDepth()); auto* watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, - this, [this, watcher, requestId, sourceLayerKey, cancelToken]() + this, [this, watcher, requestId, sourceId, cancelToken]() { ProcessingResult result = watcher->result(); m_processingRequestCancelTokens.remove(requestId); - emit layerProcessingFinished( + emit imageProcessingFinished( requestId, - sourceLayerKey, + sourceId, cancelToken->load() ? ImageFrame{} : result.frame, cancelToken->load() ? QStringLiteral("Processing canceled") : result.error); watcher->deleteLater(); @@ -4603,6 +4613,48 @@ namespace scopeone::core return true; } + // Queue one session copy for background writing + bool ScopeOneCore::queueRecordingSessionSave( + const std::shared_ptr& sourceSession, + const std::shared_ptr& saveSession, + const QString& cameraId) + { + if (!sourceSession || !saveSession || m_sessionsSaving.contains(sourceSession.get())) + { + return false; + } + saveSession->m_frames.clear(); + saveSession->clearOutputFiles(); + saveSession->m_manifest.output.streamedToDisk = false; + m_sessionsSaving.insert(sourceSession.get()); + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, + this, [this, watcher, sourceSession, saveSession, cameraId]() + { + m_sessionsSaving.remove(sourceSession.get()); + if (cameraId.isEmpty()) + { + sourceSession->applySaveStateFrom(*saveSession); + registerRecordingSession(sourceSession); + emit recordingSessionSaveFinished(sourceSession); + } + else + { + emit recordingSessionCameraSaveFinished( + sourceSession, + cameraId, + saveSession->isSaved(), + saveSession->saveMessage()); + } + watcher->deleteLater(); + }); + watcher->setFuture(QtConcurrent::run([saveSession, sourceSession]() + { + return RecordingManager::saveSessionToDisk(saveSession, sourceSession); + })); + return true; + } + // Save a completed session on a worker thread bool ScopeOneCore::saveRecordingSession(const std::shared_ptr& session) { @@ -4610,50 +4662,59 @@ namespace scopeone::core { return false; } - if (m_sessionsSaving.contains(session.get())) + auto saveSession = session->cloneForSave(); + ExperimentPlan plan = saveSession->capturePlan(); + if (plan.metadataFileName.trimmed().isEmpty()) { - return false; + plan.metadataFileName = recordingMetadataFileName(plan.baseName); + saveSession->setCapturePlan(plan); } + return queueRecordingSessionSave(session, saveSession); + } - ExperimentPlan capturePlan = session->capturePlan(); - if (capturePlan.metadataFileName.trimmed().isEmpty()) + // Apply output options to a detached session copy before writing + bool ScopeOneCore::saveRecordingSession( + const std::shared_ptr& session, + const RecordingSaveOptions& saveOptions) + { + if (!session) { - capturePlan.metadataFileName = recordingMetadataFileName(capturePlan.baseName); - session->setCapturePlan(capturePlan); + return false; } - const std::shared_ptr saveSession = session->cloneForSave(); - m_sessionsSaving.insert(session.get()); - auto* watcher = new QFutureWatcher(this); - connect(watcher, &QFutureWatcher::finished, - this, - [this, watcher, session, saveSession]() + auto saveSession = session->cloneForSave(); + ExperimentPlan plan = saveSession->capturePlan(); + plan.format = saveOptions.format; + plan.enableCompression = saveOptions.enableCompression; + plan.compressionLevel = saveOptions.compressionLevel; + if (!saveOptions.saveDir.trimmed().isEmpty()) { - session->applySaveStateFrom(*saveSession); - registerRecordingSession(session); - m_sessionsSaving.remove(session.get()); - emit recordingSessionSaveFinished(session); - watcher->deleteLater(); - }); - - const auto future = QtConcurrent::run([saveSession]() + plan.saveDir = saveOptions.saveDir; + } + if (!saveOptions.baseName.trimmed().isEmpty()) { - return RecordingManager::saveSessionToDisk(saveSession); - }); - watcher->setFuture(future); - return true; + plan.baseName = saveOptions.baseName; + } + plan.metadataFileName = recordingMetadataFileName(plan.baseName); + saveSession->setCapturePlan(plan); + return queueRecordingSessionSave(session, saveSession); } - // Updates save options before dispatching the asynchronous writer - bool ScopeOneCore::saveRecordingSession( + // Save only the camera stack represented by one image document + bool ScopeOneCore::saveRecordingSessionCamera( const std::shared_ptr& session, - const RecordingSaveOptions& saveOptions) + const QString& cameraId, + const RecordingSaveOptions& saveOptions, + const ExperimentDocument* presentation) { - if (!session || m_sessionsSaving.contains(session.get())) + const QString sourceCameraId = cameraId.trimmed(); + if (!session || sourceCameraId.isEmpty() + || session->recordedFrameCount(sourceCameraId) <= 0) { return false; } - - ExperimentPlan plan = session->capturePlan(); + auto saveSession = session->cloneForSave(); + ExperimentPlan plan = saveSession->capturePlan(); + plan.cameraIds = {sourceCameraId}; plan.format = saveOptions.format; plan.enableCompression = saveOptions.enableCompression; plan.compressionLevel = saveOptions.compressionLevel; @@ -4665,8 +4726,33 @@ namespace scopeone::core { plan.baseName = saveOptions.baseName; } - session->setCapturePlan(plan); - return saveRecordingSession(session); + plan.metadataFileName = recordingMetadataFileName(plan.baseName); + saveSession->setCapturePlan(plan); + QList cameraEvents; + for (const AcquisitionEventRecord& record : saveSession->m_manifest.events) + { + const auto frame = record.frames.constFind(sourceCameraId); + if (frame == record.frames.constEnd()) + { + continue; + } + AcquisitionEventRecord cameraRecord = record; + cameraRecord.event.cameraIds = {sourceCameraId}; + cameraRecord.frames = {{sourceCameraId, frame.value()}}; + cameraEvents.append(std::move(cameraRecord)); + } + saveSession->m_manifest.events = std::move(cameraEvents); + if (presentation) + { + filterRecordingPresentation( + *presentation, saveSession->m_manifest.layers, saveSession->m_manifest.markups); + } + else + { + saveSession->m_manifest.layers.clear(); + saveSession->m_manifest.markups.clear(); + } + return queueRecordingSessionSave(session, saveSession, sourceCameraId); } // Reads one recording frame on the serialized session IO worker diff --git a/VERSION b/VERSION index 11ca4c2..8d6ccce 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.021 +1.5.023 diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 40e9db3..ca7ac34 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -1,4 +1,5 @@ #include "DeviceControlWidget.h" +#include "ImageWorkspace.h" #include "scopeone/ImageSceneModel.h" #include "scopeone/ScopeOneCore.h" #include "PreviewWidget.h" @@ -20,10 +21,10 @@ #include #include #include -#include #include #include #include +#include #include #include @@ -32,20 +33,6 @@ namespace scopeone::ui { namespace { - // Converts a preview layout mode to combo index - int layerLayoutComboIndex(PreviewWidget::LayerLayoutMode mode) - { - return mode == PreviewWidget::LayerLayoutMode::Overlay ? 1 : 0; - } - - // Converts a combo index to preview layout mode - PreviewWidget::LayerLayoutMode layerLayoutModeFromComboIndex(int index) - { - return index == 1 - ? PreviewWidget::LayerLayoutMode::Overlay - : PreviewWidget::LayerLayoutMode::SideBySide; - } - // Formats exposure with compact decimal precision QString formatExposureMs(double exposureMs) { @@ -120,30 +107,88 @@ namespace scopeone::ui // Builds the device control layout void DeviceControlWidget::setupUI() { - QVBoxLayout* mainLayout = new QVBoxLayout(this); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0, 0, 0, 0); - - QScrollArea* scrollArea = new QScrollArea(); - scrollArea->setWidgetResizable(true); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setFrameShape(QFrame::NoFrame); - - QWidget* contentContainer = new QWidget(); - QVBoxLayout* contentLayout = new QVBoxLayout(contentContainer); - contentLayout->setSpacing(5); - contentLayout->setContentsMargins(5, 5, 5, 5); + m_imageControlsWidget = new QScrollArea(this); + m_imageControlsWidget->setWidgetResizable(true); + m_imageControlsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_imageControlsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_imageControlsWidget->setFrameShape(QFrame::NoFrame); + auto* imageContainer = new QWidget(m_imageControlsWidget); + auto* imageLayout = new QVBoxLayout(imageContainer); + imageLayout->setSpacing(5); + imageLayout->setContentsMargins(5, 5, 5, 5); + imageLayout->addWidget(createPreviewControlsGroup()); + imageLayout->addStretch(); + m_imageControlsWidget->setWidget(imageContainer); + + m_hardwareControlsWidget = new QScrollArea(this); + m_hardwareControlsWidget->setWidgetResizable(true); + m_hardwareControlsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_hardwareControlsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_hardwareControlsWidget->setFrameShape(QFrame::NoFrame); + auto* hardwareContainer = new QWidget(m_hardwareControlsWidget); + auto* hardwareLayout = new QVBoxLayout(hardwareContainer); + hardwareLayout->setSpacing(5); + hardwareLayout->setContentsMargins(5, 5, 5, 5); + m_hardwareContextLabel = new QLabel( + QStringLiteral("Hardware controls are available in Live"), hardwareContainer); + m_hardwareContextLabel->setWordWrap(true); + hardwareLayout->addWidget(m_hardwareContextLabel); + hardwareLayout->addWidget(createControlGroup()); + auto* stageToggle = new QToolButton(hardwareContainer); + stageToggle->setText(QStringLiteral("Stage")); + stageToggle->setCheckable(true); + stageToggle->setChecked(false); + stageToggle->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + stageToggle->setArrowType(Qt::RightArrow); + auto* stageGroup = createStageGroup(); + stageGroup->setVisible(false); + connect(stageToggle, &QToolButton::toggled, + hardwareContainer, [stageToggle, stageGroup](bool expanded) + { + stageToggle->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow); + stageGroup->setVisible(expanded); + }); + hardwareLayout->addWidget(stageToggle); + hardwareLayout->addWidget(stageGroup); + hardwareLayout->addStretch(); + m_hardwareControlsWidget->setWidget(hardwareContainer); + } - contentLayout->addWidget(createPreviewControlsGroup()); + void DeviceControlWidget::setImageWorkspace(ImageWorkspace* workspace) + { + if (!workspace) + { + qFatal("DeviceControlWidget requires ImageWorkspace"); + } + m_workspace = workspace; + connect(m_workspace, &ImageWorkspace::activeLayerChanged, + this, [this](const QString&) + { + syncLayerSelection(); + refreshPreviewLayerSettings(); + updateControlsState(); + if (m_liveViewerContext) + { + syncControlTargetToSelectedRawLayer(); + } + }); + syncLayerSelection(); + } - contentLayout->addWidget(createControlGroup()); - contentLayout->addWidget(createStageGroup()); + QWidget* DeviceControlWidget::imageControlsWidget() const + { + return m_imageControlsWidget; + } - contentLayout->addStretch(); + QWidget* DeviceControlWidget::hardwareControlsWidget() const + { + return m_hardwareControlsWidget; + } - scrollArea->setWidget(contentContainer); - mainLayout->addWidget(scrollArea); + void DeviceControlWidget::setControlsEnabled(bool enabled) + { + m_imageControlsWidget->setEnabled(enabled); + m_hardwareControlsWidget->setEnabled(enabled); } // Connects the preview widget to control panel state @@ -153,7 +198,16 @@ namespace scopeone::ui { qFatal("DeviceControlWidget requires PreviewWidget"); } + if (m_previewWidget) + { + disconnect(m_previewWidget, nullptr, this, nullptr); + } + if (m_sceneModel) + { + disconnect(m_sceneModel, nullptr, this, nullptr); + } m_previewWidget = preview; + m_sceneModel = preview->sceneModel(); { QSignalBlocker blocker(m_layerColormapComboBox); @@ -175,35 +229,25 @@ namespace scopeone::ui { applyPreviewVisibility(layerKeys, false); }); - connect(m_previewWidget, &PreviewWidget::layerLayoutModeChanged, - this, [this](PreviewWidget::LayerLayoutMode mode) - { - syncPreviewLayerLayoutCombo(layerLayoutComboIndex(mode)); - }); connect(m_previewWidget, &PreviewWidget::layerInfoTextChanged, this, &DeviceControlWidget::onPreviewLayerInfoTextChanged); - connect(m_previewWidget, &PreviewWidget::zoomLevelChanged, this, - [this](int value) - { - const QSignalBlocker blocker(m_zoomSpinBox); - m_zoomSpinBox->setValue(value); - }); - connect(m_previewWidget, &PreviewWidget::fitToWindowChanged, this, - [this](bool enabled) + connect(m_sceneModel, &scopeone::core::ImageSceneModel::layerDisplayChanged, + this, [this](const QString& layerKey) { - const QSignalBlocker blocker(m_fitToWindowCheckBox); - m_fitToWindowCheckBox->setChecked(enabled); - updatePreviewZoomControls(); + if (layerKey == currentLayerKey()) + { + refreshPreviewLayerSettings(); + } }); - connect(m_scopeonecore->imageSceneModel(), &scopeone::core::ImageSceneModel::layerDisplayChanged, - this, [this](const QString& layerKey) + connect(m_sceneModel, &scopeone::core::ImageSceneModel::layerAutoStretchChanged, + this, [this](const QString& layerKey, bool) { - if (layerKey == m_selectedLayerKey) + if (layerKey == currentLayerKey()) { refreshPreviewLayerSettings(); } }); - connect(m_scopeonecore->imageSceneModel(), + connect(m_sceneModel, &scopeone::core::ImageSceneModel::sourceDisplayTransformChanged, this, [this](const QString& sourceId) { @@ -216,40 +260,18 @@ namespace scopeone::ui onPreviewAvailableCameraIdsChanged(m_previewWidget->availableCameraIds()); onPreviewAvailableLayerKeysChanged(m_previewWidget->availableLayerKeys()); applyPreviewVisibility(m_previewWidget->visibleLayerKeys(), false); - syncPreviewLayerLayoutCombo(layerLayoutComboIndex(m_previewWidget->layerLayoutMode())); onPreviewLayerInfoTextChanged(m_previewWidget->layerInfoSummaryText()); - - QSignalBlocker zoomBlocker(m_zoomSpinBox); - m_zoomSpinBox->setValue(m_previewWidget->zoomPercent()); - QSignalBlocker fitBlocker(m_fitToWindowCheckBox); - m_fitToWindowCheckBox->setChecked(m_previewWidget->isFitToWindow()); - updatePreviewZoomControls(); } - // Builds preview zoom layer and alignment controls + // Builds layer and alignment controls QWidget* DeviceControlWidget::createPreviewControlsGroup() { - m_previewControlsGroup = new QGroupBox("Preview Controls", this); + m_previewControlsGroup = new QGroupBox("Layers", this); QGridLayout* controlLayout = new QGridLayout(m_previewControlsGroup); controlLayout->setHorizontalSpacing(6); controlLayout->setVerticalSpacing(4); controlLayout->setContentsMargins(6, 6, 6, 6); - m_zoomLabel = new QLabel("View Zoom:", this); - m_zoomSpinBox = new QSpinBox(this); - m_zoomSpinBox->setRange(10, 500); - m_zoomSpinBox->setValue(100); - m_zoomSpinBox->setSuffix("%"); - m_zoomSpinBox->setFixedWidth(58); - m_zoomSpinBox->setKeyboardTracking(false); - - m_fitToWindowCheckBox = new QCheckBox("Fit to Window", this); - m_fitToWindowCheckBox->setChecked(true); - - m_layerLayoutCombo = new QComboBox(this); - m_layerLayoutCombo->addItem("Side-by-side"); - m_layerLayoutCombo->addItem("Overlay"); - m_layerTable = new QTableWidget(this); m_layerTable->setColumnCount(3); m_layerTable->setHorizontalHeaderLabels(QStringList{ @@ -297,99 +319,108 @@ namespace scopeone::ui m_layerColormapComboBox = new QComboBox(m_layerSettingsGroup); m_layerBlendingComboBox = new QComboBox(m_layerSettingsGroup); - - m_layerFrameLabel = new QLabel(QStringLiteral("Frame:"), m_layerSettingsGroup); - m_layerFrameSlider = new QSlider(Qt::Horizontal, m_layerSettingsGroup); - m_layerFrameSlider->setRange(0, 0); - m_layerFrameValueLabel = new QLabel(QStringLiteral("1 / 1"), m_layerSettingsGroup); - m_layerFrameValueLabel->setMinimumWidth(46); - m_layerFrameValueLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + m_layerAutoButton = new QPushButton(QStringLiteral("Auto"), m_layerSettingsGroup); + m_layerAutoButton->setToolTip(QStringLiteral("Set display levels from the current image")); + m_layerFullRangeButton = new QPushButton(QStringLiteral("Full Range"), m_layerSettingsGroup); + m_layerFullRangeButton->setToolTip(QStringLiteral("Show the complete intensity range")); + m_layerAutoStretchCheckBox = new QCheckBox(QStringLiteral("Continuous Auto"), m_layerSettingsGroup); + m_layerAutoStretchCheckBox->setToolTip( + QStringLiteral("Update display levels continuously as images arrive")); layerSettingsLayout->addWidget(m_selectedLayerLabel, 0, 0, 1, 6); - layerSettingsLayout->addWidget(m_layerFrameLabel, 1, 0); - layerSettingsLayout->addWidget(m_layerFrameSlider, 1, 1, 1, 4); - layerSettingsLayout->addWidget(m_layerFrameValueLabel, 1, 5); - layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Order:"), m_layerSettingsGroup), 2, 0); - layerSettingsLayout->addWidget(m_layerMoveUpButton, 2, 1, Qt::AlignLeft); - layerSettingsLayout->addWidget(m_layerMoveDownButton, 2, 2, Qt::AlignLeft); - layerSettingsLayout->addWidget(m_layerRemoveButton, 2, 3, Qt::AlignLeft); - layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Opacity:"), m_layerSettingsGroup), 3, 0); - layerSettingsLayout->addWidget(m_layerOpacitySpinBox, 3, 1, Qt::AlignLeft); - layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Gamma:"), m_layerSettingsGroup), 3, 2); - layerSettingsLayout->addWidget(m_layerGammaSpinBox, 3, 3, Qt::AlignLeft); - layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Color:"), m_layerSettingsGroup), 4, 0); - layerSettingsLayout->addWidget(m_layerColormapComboBox, 4, 1); - layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Blend:"), m_layerSettingsGroup), 4, 2); - layerSettingsLayout->addWidget(m_layerBlendingComboBox, 4, 3, 1, 3); - - m_alignXLabel = new QLabel("X offset:", m_layerSettingsGroup); - m_alignXSpinBox = new QSpinBox(m_layerSettingsGroup); + layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Order:"), m_layerSettingsGroup), 1, 0); + layerSettingsLayout->addWidget(m_layerMoveUpButton, 1, 1, Qt::AlignLeft); + layerSettingsLayout->addWidget(m_layerMoveDownButton, 1, 2, Qt::AlignLeft); + layerSettingsLayout->addWidget(m_layerRemoveButton, 1, 3, Qt::AlignLeft); + layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Opacity:"), m_layerSettingsGroup), 2, 0); + layerSettingsLayout->addWidget(m_layerOpacitySpinBox, 2, 1, Qt::AlignLeft); + layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Gamma:"), m_layerSettingsGroup), 2, 2); + layerSettingsLayout->addWidget(m_layerGammaSpinBox, 2, 3, Qt::AlignLeft); + layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Color:"), m_layerSettingsGroup), 3, 0); + layerSettingsLayout->addWidget(m_layerColormapComboBox, 3, 1); + layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Blend:"), m_layerSettingsGroup), 3, 2); + layerSettingsLayout->addWidget(m_layerBlendingComboBox, 3, 3, 1, 3); + layerSettingsLayout->addWidget(m_layerAutoButton, 4, 0, 1, 2); + layerSettingsLayout->addWidget(m_layerFullRangeButton, 4, 2, 1, 2); + layerSettingsLayout->addWidget(m_layerAutoStretchCheckBox, 4, 4, 1, 2); + + auto* transformGroup = new QGroupBox(QStringLiteral("Transform"), this); + auto* transformLayout = new QGridLayout(transformGroup); + transformLayout->setContentsMargins(6, 6, 6, 6); + transformGroup->setCheckable(true); + transformGroup->setChecked(false); + m_alignXLabel = new QLabel("X offset:", transformGroup); + m_alignXSpinBox = new QSpinBox(transformGroup); m_alignXSpinBox->setRange(-1000, 1000); m_alignXSpinBox->setValue(0); m_alignXSpinBox->setFixedWidth(64); m_alignXSpinBox->setKeyboardTracking(false); - m_alignYLabel = new QLabel("Y offset:", m_layerSettingsGroup); - m_alignYSpinBox = new QSpinBox(m_layerSettingsGroup); + m_alignYLabel = new QLabel("Y offset:", transformGroup); + m_alignYSpinBox = new QSpinBox(transformGroup); m_alignYSpinBox->setRange(-1000, 1000); m_alignYSpinBox->setValue(0); m_alignYSpinBox->setFixedWidth(64); m_alignYSpinBox->setKeyboardTracking(false); - m_alignZoomLabel = new QLabel("Scale:", m_layerSettingsGroup); - m_alignZoomSpinBox = new QSpinBox(m_layerSettingsGroup); + m_alignZoomLabel = new QLabel("Scale:", transformGroup); + m_alignZoomSpinBox = new QSpinBox(transformGroup); m_alignZoomSpinBox->setRange(10, 500); m_alignZoomSpinBox->setValue(100); m_alignZoomSpinBox->setFixedWidth(58); m_alignZoomSpinBox->setToolTip("Source camera display scale percent"); m_alignZoomSpinBox->setKeyboardTracking(false); - m_alignFlipXCheckBox = new QCheckBox("Flip X", m_layerSettingsGroup); - m_alignFlipYCheckBox = new QCheckBox("Flip Y", m_layerSettingsGroup); - m_alignResetButton = new QPushButton("Reset", m_layerSettingsGroup); + m_alignFlipXCheckBox = new QCheckBox("Flip X", transformGroup); + m_alignFlipYCheckBox = new QCheckBox("Flip Y", transformGroup); + m_alignResetButton = new QPushButton("Reset", transformGroup); m_alignResetButton->setMaximumWidth(50); m_alignResetButton->setToolTip("Reset offset and flip"); - // Display transforms are edited from the selected layer but stored per source camera for now - layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Transform:"), m_layerSettingsGroup), 5, 0, 1, 6); - layerSettingsLayout->addWidget(m_alignXLabel, 6, 0); - layerSettingsLayout->addWidget(m_alignXSpinBox, 6, 1, Qt::AlignLeft); - layerSettingsLayout->addWidget(m_alignYLabel, 6, 2); - layerSettingsLayout->addWidget(m_alignYSpinBox, 6, 3, Qt::AlignLeft); - layerSettingsLayout->addWidget(m_alignZoomLabel, 7, 0); - layerSettingsLayout->addWidget(m_alignZoomSpinBox, 7, 1, Qt::AlignLeft); - layerSettingsLayout->addWidget(m_alignFlipXCheckBox, 7, 2, Qt::AlignLeft); - layerSettingsLayout->addWidget(m_alignFlipYCheckBox, 7, 3, Qt::AlignLeft); - layerSettingsLayout->addWidget(m_alignResetButton, 7, 4, Qt::AlignLeft); - layerSettingsLayout->setColumnStretch(5, 1); - - m_zoomLabel->setMinimumWidth(60); + const QList transformWidgets{ + m_alignXLabel, + m_alignXSpinBox, + m_alignYLabel, + m_alignYSpinBox, + m_alignZoomLabel, + m_alignZoomSpinBox, + m_alignFlipXCheckBox, + m_alignFlipYCheckBox, + m_alignResetButton, + }; + connect(transformGroup, &QGroupBox::toggled, + transformGroup, [transformWidgets](bool expanded) + { + for (QWidget* widget : transformWidgets) + { + widget->setVisible(expanded); + } + }); + for (QWidget* widget : transformWidgets) + { + widget->setVisible(false); + } + + transformLayout->addWidget(m_alignXLabel, 0, 0); + transformLayout->addWidget(m_alignXSpinBox, 0, 1, Qt::AlignLeft); + transformLayout->addWidget(m_alignYLabel, 0, 2); + transformLayout->addWidget(m_alignYSpinBox, 0, 3, Qt::AlignLeft); + transformLayout->addWidget(m_alignZoomLabel, 1, 0); + transformLayout->addWidget(m_alignZoomSpinBox, 1, 1, Qt::AlignLeft); + transformLayout->addWidget(m_alignFlipXCheckBox, 1, 2, Qt::AlignLeft); + transformLayout->addWidget(m_alignFlipYCheckBox, 1, 3, Qt::AlignLeft); + transformLayout->addWidget(m_alignResetButton, 1, 4, Qt::AlignLeft); + transformLayout->setColumnStretch(5, 1); + m_alignXLabel->setMinimumWidth(20); m_alignYLabel->setMinimumWidth(20); m_alignZoomLabel->setMinimumWidth(60); + controlLayout->addWidget(m_layerTable, 0, 0, 1, 6); + controlLayout->addWidget(m_layerSettingsGroup, 1, 0, 1, 6); + controlLayout->addWidget(transformGroup, 2, 0, 1, 6); - QLabel* layoutLabel = new QLabel("Layout:", this); - layoutLabel->setMinimumWidth(60); - - controlLayout->addWidget(m_zoomLabel, 0, 0); - controlLayout->addWidget(m_zoomSpinBox, 0, 1, Qt::AlignLeft); - controlLayout->addWidget(m_fitToWindowCheckBox, 0, 2, 1, 2); - controlLayout->addWidget(layoutLabel, 0, 4); - controlLayout->addWidget(m_layerLayoutCombo, 0, 5); - - controlLayout->addWidget(m_layerTable, 1, 0, 1, 6); - controlLayout->addWidget(m_layerSettingsGroup, 2, 0, 1, 6); - - controlLayout->setColumnStretch(1, 1); - controlLayout->setColumnStretch(3, 1); controlLayout->setColumnStretch(5, 1); - connect(m_zoomSpinBox, QOverload::of(&QSpinBox::valueChanged), - this, &DeviceControlWidget::onPreviewZoomSpinBoxChanged); - connect(m_fitToWindowCheckBox, &QCheckBox::toggled, - this, &DeviceControlWidget::onPreviewFitToWindowToggled); - connect(m_layerLayoutCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, &DeviceControlWidget::onPreviewLayerLayoutComboChanged); connect(m_layerTable, &QTableWidget::currentCellChanged, this, &DeviceControlWidget::onPreviewLayerSelectionChanged); connect(m_layerMoveUpButton, &QPushButton::clicked, @@ -406,9 +437,12 @@ namespace scopeone::ui this, &DeviceControlWidget::onPreviewLayerColormapChanged); connect(m_layerBlendingComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeviceControlWidget::onPreviewLayerBlendingChanged); - connect(m_layerFrameSlider, &QSlider::valueChanged, - this, &DeviceControlWidget::onPreviewLayerFrameSliderChanged); - + connect(m_layerAutoButton, &QPushButton::clicked, + this, &DeviceControlWidget::onPreviewLayerAutoClicked); + connect(m_layerFullRangeButton, &QPushButton::clicked, + this, &DeviceControlWidget::onPreviewLayerFullRangeClicked); + connect(m_layerAutoStretchCheckBox, &QCheckBox::toggled, + this, &DeviceControlWidget::onPreviewLayerAutoStretchToggled); const auto applySourceTransform = [this]() { const QString sourceId = selectedLayerSourceId(); @@ -422,7 +456,7 @@ namespace scopeone::ui transform.zoomPercent = m_alignZoomSpinBox->value(); transform.flipX = m_alignFlipXCheckBox->isChecked(); transform.flipY = m_alignFlipYCheckBox->isChecked(); - m_scopeonecore->imageSceneModel()->setSourceDisplayTransform(sourceId, transform); + m_sceneModel->setSourceDisplayTransform(sourceId, transform); }; connect(m_alignXSpinBox, QOverload::of(&QSpinBox::valueChanged), this, [applySourceTransform](int) { applySourceTransform(); }); @@ -440,33 +474,17 @@ namespace scopeone::ui resetSelectedLayerTransform(); }); - updatePreviewZoomControls(); refreshPreviewLayerSettings(); return m_previewControlsGroup; } - // Updates zoom controls from fit to window state - void DeviceControlWidget::updatePreviewZoomControls() - { - m_zoomSpinBox->setEnabled(!m_fitToWindowCheckBox->isChecked()); - } - // Rebuilds the preview layer table void DeviceControlWidget::rebuildPreviewLayerTable(const QStringList& layerKeys) { - const QString previousLayerKey = m_selectedLayerKey; + const QString previousLayerKey = currentLayerKey(); QSignalBlocker tableBlocker(m_layerTable); m_layerRows.clear(); m_layerTable->setRowCount(layerKeys.size()); - for (const QString& layerKey : m_layerFrameCounts.keys()) - { - if (!layerKeys.contains(layerKey)) - { - m_layerFrameCounts.remove(layerKey); - m_layerFrameIndices.remove(layerKey); - } - } - int row = 0; const auto addLayerRow = [&](const QString& layerKey) { @@ -505,19 +523,17 @@ namespace scopeone::ui if (selectedRow >= 0) { m_layerTable->setCurrentCell(selectedRow, 1); - m_selectedLayerKey = layerKeys.at(selectedRow); - } - else - { - m_selectedLayerKey.clear(); } - refreshPreviewLayerSettings(); - if (m_selectedLayerKey != previousLayerKey) + const QString selectedLayerKey = selectedRow >= 0 + ? layerKeys.at(selectedRow) + : QString{}; + if (m_workspace && selectedLayerKey != previousLayerKey) { - emit currentLayerChanged(m_selectedLayerKey); - syncControlTargetToSelectedRawLayer(); + m_workspace->setActiveLayerKey(selectedLayerKey); } + refreshPreviewLayerSettings(); + syncControlTargetToSelectedRawLayer(); } // Refreshes per layer size and frame rate text in the layer table @@ -548,11 +564,11 @@ namespace scopeone::ui if (notifyPreview) { - m_scopeonecore->imageSceneModel()->setVisibleLayers(layerKeys); + m_sceneModel->setVisibleLayers(layerKeys); } - const QString previousLayerKey = m_selectedLayerKey; - if (!layerKeys.isEmpty() && !visibleLayerKeySet.contains(m_selectedLayerKey)) + const QString selectedLayerKey = currentLayerKey(); + if (!layerKeys.isEmpty() && !visibleLayerKeySet.contains(selectedLayerKey)) { const QString nextLayerKey = layerKeys.first(); for (int row = 0; row < m_layerTable->rowCount(); ++row) @@ -562,37 +578,34 @@ namespace scopeone::ui { QSignalBlocker tableBlocker(m_layerTable); m_layerTable->setCurrentCell(row, 1); - m_selectedLayerKey = nextLayerKey; + if (m_workspace) + { + m_workspace->setActiveLayerKey(nextLayerKey); + } break; } } } refreshPreviewLayerSettings(); - if (m_selectedLayerKey != previousLayerKey) - { - emit currentLayerChanged(m_selectedLayerKey); - } } // Updates the settings editor for the selected preview layer void DeviceControlWidget::refreshPreviewLayerSettings() { - const bool hasLayer = !m_selectedLayerKey.isEmpty() - && m_layerRows.contains(m_selectedLayerKey); + const QString layerKey = currentLayerKey(); + const bool hasLayer = !layerKey.isEmpty() && m_layerRows.contains(layerKey); m_layerSettingsGroup->setEnabled(hasLayer); if (!hasLayer) { m_selectedLayerLabel->setText(QStringLiteral("No layer selected")); - refreshLayerFrameControl(); return; } - m_selectedLayerLabel->setText(m_previewWidget->layerName(m_selectedLayerKey)); - refreshLayerFrameControl(); + m_selectedLayerLabel->setText(m_previewWidget->layerName(layerKey)); scopeone::core::DocumentLayer layer; - m_scopeonecore->imageSceneModel()->findLayer(m_selectedLayerKey, layer); + m_sceneModel->findLayer(layerKey, layer); { QSignalBlocker blocker(m_layerOpacitySpinBox); m_layerOpacitySpinBox->setValue(layer.display.opacityPercent); @@ -611,16 +624,23 @@ namespace scopeone::ui const int index = m_layerBlendingComboBox->findText(layer.display.blending); m_layerBlendingComboBox->setCurrentIndex(index); } + { + QSignalBlocker blocker(m_layerAutoStretchCheckBox); + m_layerAutoStretchCheckBox->setChecked( + m_workspace->layerAutoStretchEnabled(layerKey)); + } const int row = m_layerTable->currentRow(); m_layerMoveUpButton->setEnabled(row > 0); m_layerMoveDownButton->setEnabled(row < m_layerTable->rowCount() - 1); - m_layerRemoveButton->setEnabled(scopeone::core::ScopeOneCore::isStaticLayerKey(m_selectedLayerKey)); + m_layerRemoveButton->setEnabled( + m_sceneModel == m_scopeonecore->imageSceneModel() + && scopeone::core::ScopeOneCore::isStaticLayerKey(layerKey)); m_layerOpacitySpinBox->setEnabled(m_layerBlendingComboBox->currentText() != QStringLiteral("Opaque")); const QString sourceId = selectedLayerSourceId(); const scopeone::core::ImageSceneModel::SourceDisplayTransform transform = - m_scopeonecore->imageSceneModel()->sourceDisplayTransform(sourceId); + m_sceneModel->sourceDisplayTransform(sourceId); { QSignalBlocker blocker(m_alignXSpinBox); m_alignXSpinBox->setValue(transform.offsetX); @@ -643,27 +663,33 @@ namespace scopeone::ui } } - // Updates the frame slider for stack backed gallery layers - void DeviceControlWidget::refreshLayerFrameControl() + QString DeviceControlWidget::selectedLayerSourceId() const { - const int frameCount = m_layerFrameCounts.value(m_selectedLayerKey, 1); - const int frameIndex = qBound(0, m_layerFrameIndices.value(m_selectedLayerKey, 0), qMax(0, frameCount - 1)); - const bool visible = frameCount > 1; - - m_layerFrameLabel->setVisible(visible); - m_layerFrameSlider->setVisible(visible); - m_layerFrameValueLabel->setVisible(visible); - m_layerFrameSlider->setEnabled(visible); - - QSignalBlocker blocker(m_layerFrameSlider); - m_layerFrameSlider->setRange(0, qMax(0, frameCount - 1)); - m_layerFrameSlider->setValue(frameIndex); - m_layerFrameValueLabel->setText(QStringLiteral("%1 / %2").arg(frameIndex + 1).arg(qMax(1, frameCount))); + return scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey()); } - QString DeviceControlWidget::selectedLayerSourceId() const + QString DeviceControlWidget::currentLayerKey() const { - return scopeone::core::ScopeOneCore::sourceIdFromLayerKey(m_selectedLayerKey); + return m_workspace ? m_workspace->activeLayerKey() : QString{}; + } + + void DeviceControlWidget::syncLayerSelection() + { + const QString layerKey = currentLayerKey(); + for (int row = 0; row < m_layerTable->rowCount(); ++row) + { + QTableWidgetItem* item = m_layerTable->item(row, 1); + if (item && item->data(Qt::UserRole).toString() == layerKey) + { + const QSignalBlocker blocker(m_layerTable); + m_layerTable->setCurrentCell(row, 1); + refreshPreviewLayerSettings(); + return; + } + } + const QSignalBlocker blocker(m_layerTable); + m_layerTable->clearSelection(); + refreshPreviewLayerSettings(); } // Refreshes selected layer transform values when live cameras change @@ -679,33 +705,11 @@ namespace scopeone::ui updateControlsState(); } - void DeviceControlWidget::syncPreviewLayerLayoutCombo(int index) - { - QSignalBlocker blocker(m_layerLayoutCombo); - m_layerLayoutCombo->setCurrentIndex(index); - } - void DeviceControlWidget::onPreviewLayerInfoTextChanged(const QString&) { refreshPreviewLayerInfoText(); } - void DeviceControlWidget::onPreviewZoomSpinBoxChanged(int value) - { - m_previewWidget->setZoomPercent(value); - } - - void DeviceControlWidget::onPreviewFitToWindowToggled(bool enabled) - { - m_previewWidget->setFitToWindow(enabled); - updatePreviewZoomControls(); - } - - void DeviceControlWidget::onPreviewLayerLayoutComboChanged(int index) - { - m_previewWidget->setLayerLayoutMode(layerLayoutModeFromComboIndex(index)); - } - void DeviceControlWidget::onPreviewLayerVisibleToggled(bool) { auto* checkBox = qobject_cast(sender()); @@ -719,84 +723,79 @@ namespace scopeone::ui break; } } - m_scopeonecore->imageSceneModel()->setLayerVisible(layerKey, checkBox->isChecked()); + m_sceneModel->setLayerVisible(layerKey, checkBox->isChecked()); } void DeviceControlWidget::onPreviewLayerOpacityChanged(int value) { - m_scopeonecore->imageSceneModel()->setLayerOpacityPercent(m_selectedLayerKey, value); + m_sceneModel->setLayerOpacityPercent(currentLayerKey(), value); } void DeviceControlWidget::onPreviewLayerGammaChanged(double value) { - m_scopeonecore->imageSceneModel()->setLayerGamma(m_selectedLayerKey, value); + m_sceneModel->setLayerGamma(currentLayerKey(), value); } void DeviceControlWidget::onPreviewLayerColormapChanged(int) { - m_scopeonecore->imageSceneModel()->setLayerColormap( - m_selectedLayerKey, m_layerColormapComboBox->currentText()); + m_sceneModel->setLayerColormap( + currentLayerKey(), m_layerColormapComboBox->currentText()); } void DeviceControlWidget::onPreviewLayerBlendingChanged(int) { - m_scopeonecore->imageSceneModel()->setLayerBlending( - m_selectedLayerKey, m_layerBlendingComboBox->currentText()); + m_sceneModel->setLayerBlending( + currentLayerKey(), m_layerBlendingComboBox->currentText()); m_layerOpacitySpinBox->setEnabled(m_layerBlendingComboBox->currentText() != QStringLiteral("Opaque")); } - void DeviceControlWidget::onPreviewLayerFrameSliderChanged(int value) + void DeviceControlWidget::onPreviewLayerAutoClicked() { - const int frameCount = m_layerFrameCounts.value(m_selectedLayerKey, 1); - if (m_selectedLayerKey.isEmpty() || frameCount <= 1) - { - return; - } + m_workspace->autoLayerLevels(currentLayerKey()); + } - const int frameIndex = qBound(0, value, frameCount - 1); - if (m_layerFrameIndices.value(m_selectedLayerKey, 0) == frameIndex) - { - return; - } + void DeviceControlWidget::onPreviewLayerFullRangeClicked() + { + m_workspace->fullLayerLevels(currentLayerKey()); + } - m_layerFrameIndices.insert(m_selectedLayerKey, frameIndex); - m_layerFrameValueLabel->setText(QStringLiteral("%1 / %2").arg(frameIndex + 1).arg(frameCount)); - emit previewLayerFrameRequested(m_selectedLayerKey, frameIndex); + void DeviceControlWidget::onPreviewLayerAutoStretchToggled(bool enabled) + { + m_workspace->setLayerAutoStretchEnabled(currentLayerKey(), enabled); } void DeviceControlWidget::onPreviewLayerSelectionChanged(int currentRow, int, int, int) { QTableWidgetItem* item = m_layerTable->item(currentRow, 1); - const QString previousLayerKey = m_selectedLayerKey; - m_selectedLayerKey = item ? item->data(Qt::UserRole).toString() : QString(); - refreshPreviewLayerSettings(); - updateControlsState(); - if (m_selectedLayerKey != previousLayerKey) + const QString layerKey = item ? item->data(Qt::UserRole).toString() : QString(); + if (m_workspace) { - emit currentLayerChanged(m_selectedLayerKey); + m_workspace->setActiveLayerKey(layerKey); } + refreshPreviewLayerSettings(); + updateControlsState(); } void DeviceControlWidget::onPreviewLayerMoveUpClicked() { - m_scopeonecore->imageSceneModel()->moveLayer(m_selectedLayerKey, -1); + m_sceneModel->moveLayer(currentLayerKey(), -1); } void DeviceControlWidget::onPreviewLayerMoveDownClicked() { - m_scopeonecore->imageSceneModel()->moveLayer(m_selectedLayerKey, 1); + m_sceneModel->moveLayer(currentLayerKey(), 1); } void DeviceControlWidget::onPreviewLayerRemoveClicked() { m_scopeonecore->removeStaticFrame( - scopeone::core::ScopeOneCore::sourceIdFromLayerKey(m_selectedLayerKey)); + scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey())); } // Use the selected raw camera layer as the hardware control target void DeviceControlWidget::syncControlTargetToSelectedRawLayer() { - if (!scopeone::core::ScopeOneCore::isRawLayerKey(m_selectedLayerKey)) + if (!scopeone::core::ScopeOneCore::isRawLayerKey(currentLayerKey())) { return; } @@ -829,13 +828,14 @@ namespace scopeone::ui const QString sourceId = selectedLayerSourceId(); if (!sourceId.isEmpty()) { - m_scopeonecore->imageSceneModel()->resetSourceDisplayTransform(sourceId); + m_sceneModel->resetSourceDisplayTransform(sourceId); } } QWidget* DeviceControlWidget::createControlGroup() { QGroupBox* group = new QGroupBox("Camera Controls"); + m_cameraControlsGroup = group; QGridLayout* layout = new QGridLayout(group); int row = 0; @@ -848,7 +848,8 @@ namespace scopeone::ui layout->addWidget(m_cameraSelectCombo, row, 1); row++; - layout->addWidget(new QLabel("Exposure (ms):"), row, 0); + m_exposureLabel = new QLabel("Exposure (ms):", group); + layout->addWidget(m_exposureLabel, row, 0); m_exposureLineEdit = new QLineEdit(); auto* exposureValidator = new QDoubleValidator(0.1, 10000.0, 16, m_exposureLineEdit); exposureValidator->setLocale(QLocale::c()); @@ -885,6 +886,7 @@ namespace scopeone::ui QWidget* DeviceControlWidget::createStageGroup() { QGroupBox* group = new QGroupBox("Stage Controls"); + m_stageControlsGroup = group; QHBoxLayout* mainLayout = new QHBoxLayout(group); QVBoxLayout* xyColumn = new QVBoxLayout(); QVBoxLayout* zColumn = new QVBoxLayout(); @@ -1162,11 +1164,11 @@ namespace scopeone::ui // Updates enabled state for stage controls void DeviceControlWidget::updateStageControlsEnabled() { - const bool hasXY = !selectedXYStageLabel().isEmpty(); - const bool hasZ = !selectedZStageLabel().isEmpty(); + const bool hasXY = m_liveViewerContext && !selectedXYStageLabel().isEmpty(); + const bool hasZ = m_liveViewerContext && !selectedZStageLabel().isEmpty(); - m_xyStageCombo->setEnabled(m_xyStageCombo->count() > 0); - m_zStageCombo->setEnabled(m_zStageCombo->count() > 0); + m_xyStageCombo->setEnabled(m_liveViewerContext && m_xyStageCombo->count() > 0); + m_zStageCombo->setEnabled(m_liveViewerContext && m_zStageCombo->count() > 0); m_xyStepLineEdit->setEnabled(hasXY); m_xyBigStepLineEdit->setEnabled(hasXY); m_zStepLineEdit->setEnabled(hasZ); @@ -1312,15 +1314,22 @@ namespace scopeone::ui // Keeps device control buttons in sync void DeviceControlWidget::updateControlsState() { - m_exposureLineEdit->setEnabled(m_cameraInitialized); - m_previewToggleButton->setEnabled(m_cameraInitialized); + const bool canControlHardware = m_cameraInitialized && m_liveViewerContext; + m_hardwareContextLabel->setVisible(!m_liveViewerContext); + m_cameraControlsGroup->setVisible(true); + m_stageControlsGroup->setVisible(true); + m_cameraControlsGroup->setEnabled(canControlHardware); + m_stageControlsGroup->setEnabled(m_liveViewerContext); + m_cameraSelectCombo->setEnabled(canControlHardware && m_controlTargetEnabled); + m_exposureLineEdit->setEnabled(canControlHardware); + m_previewToggleButton->setEnabled(canControlHardware); m_previewToggleButton->setText(m_previewRunning ? QStringLiteral("Stop Preview") : QStringLiteral("Start Preview")); const bool hasRoiTarget = !roiCameraTarget().isEmpty(); - m_drawROIButton->setEnabled(m_cameraInitialized && hasRoiTarget); - m_halfROIButton->setEnabled(m_cameraInitialized && hasRoiTarget); - m_clearROIButton->setEnabled(m_cameraInitialized); + m_drawROIButton->setEnabled(canControlHardware && hasRoiTarget); + m_halfROIButton->setEnabled(canControlHardware && hasRoiTarget); + m_clearROIButton->setEnabled(canControlHardware && hasRoiTarget); updateStageControlsEnabled(); } @@ -1358,6 +1367,11 @@ namespace scopeone::ui m_minExposureMs = lower; m_maxExposureMs = upper; } + m_exposureLabel->setText( + tr("Exposure (ms)\n%1: %2 to %3") + .arg(m_currentTarget, + formatExposureMs(m_minExposureMs), + formatExposureMs(m_maxExposureMs))); return; } @@ -1394,6 +1408,10 @@ namespace scopeone::ui m_minExposureMs = commonLower; m_maxExposureMs = commonUpper; } + m_exposureLabel->setText( + tr("Exposure (ms)\nAll cameras: %1 to %2") + .arg(formatExposureMs(m_minExposureMs), + formatExposureMs(m_maxExposureMs))); } // Updates preview running state for the control button @@ -1403,43 +1421,7 @@ namespace scopeone::ui updateControlsState(); } - // Returns the preview layer currently selected in the layer table - QString DeviceControlWidget::currentLayerKey() const - { - return m_selectedLayerKey; - } - - // Sets stack frame metadata for one preview layer - void DeviceControlWidget::setLayerFrameControl(const QString& layerKey, int frameCount, int frameIndex) - { - const QString trimmedLayerKey = layerKey.trimmed(); - if (trimmedLayerKey.isEmpty() || frameCount <= 1) - { - removeLayerFrameControl(trimmedLayerKey); - return; - } - - const int clampedCount = qMax(1, frameCount); - m_layerFrameCounts.insert(trimmedLayerKey, clampedCount); - m_layerFrameIndices.insert(trimmedLayerKey, qBound(0, frameIndex, clampedCount - 1)); - if (trimmedLayerKey == m_selectedLayerKey) - { - refreshLayerFrameControl(); - } - } - - // Removes stack frame metadata for one preview layer - void DeviceControlWidget::removeLayerFrameControl(const QString& layerKey) - { - const QString trimmedLayerKey = layerKey.trimmed(); - m_layerFrameCounts.remove(trimmedLayerKey); - m_layerFrameIndices.remove(trimmedLayerKey); - if (trimmedLayerKey.isEmpty() || trimmedLayerKey == m_selectedLayerKey) - { - refreshLayerFrameControl(); - } - } - + // Checks whether a control operation targets every camera bool DeviceControlWidget::isAllTarget(const QString& target) const { return target.compare("All", Qt::CaseInsensitive) == 0; @@ -1516,7 +1498,14 @@ namespace scopeone::ui // Enables or disables the camera target selector void DeviceControlWidget::setControlTargetEnabled(bool enabled) { - m_cameraSelectCombo->setEnabled(enabled); + m_controlTargetEnabled = enabled; + updateControlsState(); + } + + void DeviceControlWidget::setViewerContext(bool liveViewer) + { + m_liveViewerContext = liveViewer; + updateControlsState(); } // Applies a new camera control target @@ -1536,12 +1525,6 @@ namespace scopeone::ui // Starts ROI drawing for the selected camera void DeviceControlWidget::onDrawROIClicked() { - if (isAllTarget(m_currentTarget)) - { - emit requestDrawROI(QString()); - return; - } - const QString cameraId = roiCameraTarget(); if (cameraId.isEmpty()) { @@ -1566,6 +1549,10 @@ namespace scopeone::ui // Requests ROI clearing for the selected target void DeviceControlWidget::onClearROIClicked() { - emit requestClearROI(m_currentTarget); + const QString cameraId = roiCameraTarget(); + if (!cameraId.isEmpty()) + { + emit requestClearROI(cameraId); + } } } // namespace scopeone::ui diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index dff84b7..5fdfda9 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -8,6 +8,7 @@ namespace scopeone::core { + class ImageSceneModel; class ScopeOneCore; } @@ -18,12 +19,13 @@ class QGroupBox; class QLabel; class QLineEdit; class QPushButton; -class QSlider; +class QScrollArea; class QSpinBox; class QTableWidget; namespace scopeone::ui { + class ImageWorkspace; class PreviewWidget; class DeviceControlWidget : public QWidget @@ -36,9 +38,14 @@ namespace scopeone::ui void setControlTargets(const QStringList& cameraIds); + void setImageWorkspace(ImageWorkspace* workspace); void setPreviewWidget(PreviewWidget* previewWidget); + void setViewerContext(bool liveViewer); + QWidget* imageControlsWidget() const; + QWidget* hardwareControlsWidget() const; void setControlTargetEnabled(bool enabled); + void setControlsEnabled(bool enabled); void refreshStageDevices(); void refreshCameraParameters(); @@ -46,9 +53,6 @@ namespace scopeone::ui void onCameraInitialized(bool initialized); void setPreviewRunning(bool running); - QString currentLayerKey() const; - void setLayerFrameControl(const QString& layerKey, int frameCount, int frameIndex); - void removeLayerFrameControl(const QString& layerKey); signals : void startPreviewRequested(); @@ -57,8 +61,6 @@ namespace scopeone::ui void exposureValueChanged(double exposureMs); void controlTargetChanged(const QString& target); - void currentLayerChanged(const QString& layerKey); - void previewLayerFrameRequested(const QString& layerKey, int frameIndex); void stageMoveFailed(const QString& message); void requestDrawROI(const QString& cameraId); @@ -78,33 +80,33 @@ namespace scopeone::ui void onClearROIClicked(); QWidget* createPreviewControlsGroup(); - void updatePreviewZoomControls(); void rebuildPreviewLayerTable(const QStringList& layerKeys); void applyPreviewVisibility(const QStringList& layerKeys, bool notifyPreview); void refreshPreviewLayerSettings(); - void refreshLayerFrameControl(); QString selectedLayerSourceId() const; void onPreviewAvailableCameraIdsChanged(const QStringList& cameraIds); void onPreviewAvailableLayerKeysChanged(const QStringList& layerKeys); - void syncPreviewLayerLayoutCombo(int index); void onPreviewLayerInfoTextChanged(const QString& text); void refreshPreviewLayerInfoText(); - - void onPreviewZoomSpinBoxChanged(int value); - void onPreviewFitToWindowToggled(bool enabled); - void onPreviewLayerLayoutComboChanged(int index); void onPreviewLayerVisibleToggled(bool checked); void onPreviewLayerOpacityChanged(int value); void onPreviewLayerGammaChanged(double value); void onPreviewLayerColormapChanged(int index); void onPreviewLayerBlendingChanged(int index); - void onPreviewLayerFrameSliderChanged(int value); - void onPreviewLayerSelectionChanged(int currentRow, int currentColumn, int previousRow, int previousColumn); + void onPreviewLayerAutoClicked(); + void onPreviewLayerFullRangeClicked(); + void onPreviewLayerAutoStretchToggled(bool enabled); + void onPreviewLayerSelectionChanged(int currentRow, + int currentColumn, + int previousRow, + int previousColumn); void onPreviewLayerMoveUpClicked(); void onPreviewLayerMoveDownClicked(); void onPreviewLayerRemoveClicked(); void syncControlTargetToSelectedRawLayer(); void resetSelectedLayerTransform(); + void syncLayerSelection(); + QString currentLayerKey() const; void setupUI(); @@ -116,11 +118,14 @@ namespace scopeone::ui bool isAllTarget(const QString& target) const; QString roiCameraTarget() const; scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; + ImageWorkspace* m_workspace{nullptr}; + scopeone::core::ImageSceneModel* m_sceneModel{nullptr}; + QScrollArea* m_imageControlsWidget{nullptr}; + QScrollArea* m_hardwareControlsWidget{nullptr}; + QLabel* m_hardwareContextLabel{nullptr}; QGroupBox* m_previewControlsGroup{nullptr}; - QLabel* m_zoomLabel{nullptr}; - QSpinBox* m_zoomSpinBox{nullptr}; - QCheckBox* m_fitToWindowCheckBox{nullptr}; - QComboBox* m_layerLayoutCombo{nullptr}; + QGroupBox* m_cameraControlsGroup{nullptr}; + QGroupBox* m_stageControlsGroup{nullptr}; QTableWidget* m_layerTable{nullptr}; QMap m_layerRows; QGroupBox* m_layerSettingsGroup{nullptr}; @@ -132,12 +137,9 @@ namespace scopeone::ui QDoubleSpinBox* m_layerGammaSpinBox{nullptr}; QComboBox* m_layerColormapComboBox{nullptr}; QComboBox* m_layerBlendingComboBox{nullptr}; - QLabel* m_layerFrameLabel{nullptr}; - QSlider* m_layerFrameSlider{nullptr}; - QLabel* m_layerFrameValueLabel{nullptr}; - QMap m_layerFrameCounts; - QMap m_layerFrameIndices; - QString m_selectedLayerKey; + QPushButton* m_layerAutoButton{nullptr}; + QPushButton* m_layerFullRangeButton{nullptr}; + QCheckBox* m_layerAutoStretchCheckBox{nullptr}; QLabel* m_alignXLabel{nullptr}; QSpinBox* m_alignXSpinBox{nullptr}; QLabel* m_alignYLabel{nullptr}; @@ -150,6 +152,7 @@ namespace scopeone::ui PreviewWidget* m_previewWidget{nullptr}; QLineEdit* m_exposureLineEdit{nullptr}; + QLabel* m_exposureLabel{nullptr}; QPushButton* m_previewToggleButton{nullptr}; QComboBox* m_cameraSelectCombo{nullptr}; @@ -191,6 +194,8 @@ namespace scopeone::ui bool m_cameraInitialized; bool m_previewRunning; + bool m_liveViewerContext{true}; + bool m_controlTargetEnabled{true}; QString m_currentTarget; double m_minExposureMs{0.1}; double m_maxExposureMs{10000.0}; diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index 5623388..49cb90e 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -1,5 +1,7 @@ #include "ImageProcessingWidget.h" +#include "ImageWorkspace.h" + #include "scopeone/ScopeOneCore.h" #include "scopeone/ImageSceneModel.h" @@ -8,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -188,12 +191,14 @@ namespace scopeone::ui }; } - ImageProcessingWidget::ImageProcessingWidget(scopeone::core::ScopeOneCore* core, QWidget* parent) - : QWidget(parent), m_scopeonecore(core) + ImageProcessingWidget::ImageProcessingWidget(scopeone::core::ScopeOneCore* core, + ImageWorkspace* workspace, + QWidget* parent) + : QWidget(parent), m_scopeonecore(core), m_workspace(workspace) { - if (!core) + if (!core || !workspace) { - qFatal("ImageProcessingWidget requires ScopeOneCore"); + qFatal("ImageProcessingWidget requires ScopeOneCore and ImageWorkspace"); } m_processingRunning = core->isRealTimeProcessingEnabled(); setupUI(); @@ -236,13 +241,14 @@ namespace scopeone::ui this, &ImageProcessingWidget::refreshSources); connect(core->imageSceneModel(), &scopeone::core::ImageSceneModel::layersChanged, this, &ImageProcessingWidget::refreshSources); - connect(core, &scopeone::core::ScopeOneCore::layerProcessingFinished, + connect(core, &scopeone::core::ScopeOneCore::imageProcessingFinished, this, [this](quint64 requestId, - const QString& sourceLayerKey, + const QString& inputSourceId, const scopeone::core::ImageFrame& frame, const QString& error) { - if (requestId != m_offlineProcessingRequestId) + if (requestId != m_offlineProcessingRequestId + || !m_directProcessingRequest) { return; } @@ -252,14 +258,15 @@ namespace scopeone::ui QMessageBox::warning(this, tr("Processing Failed"), error); return; } - const QString sourceId = QStringLiteral("processed:%1") - .arg(scopeone::core::ScopeOneCore::sourceIdFromLayerKey( - sourceLayerKey)); + const QString outputSourceId = QStringLiteral("processed:%1") + .arg(scopeone::core::ScopeOneCore::sourceIdFromLayerKey( + inputSourceId)); const auto output = m_scopeonecore->publishStaticFrame( - sourceId, frame, tr("Processed Image")); + outputSourceId, frame, tr("Processed Image")); if (output.isValid()) { - emit processedLayerReady(scopeone::core::ScopeOneCore::staticLayerKey(sourceId)); + emit processedLayerReady( + scopeone::core::ScopeOneCore::staticLayerKey(outputSourceId)); } }); connect(core, &scopeone::core::ScopeOneCore::stackProcessingProgress, @@ -276,7 +283,8 @@ namespace scopeone::ui const std::shared_ptr& session, const QString& error) { - if (requestId != m_offlineProcessingRequestId) + if (requestId != m_offlineProcessingRequestId + || !m_directProcessingRequest) { return; } @@ -289,6 +297,43 @@ namespace scopeone::ui emit processedStackReady(session); refreshSources(); }); + connect(workspace, &ImageWorkspace::activeDocumentChanged, + this, [this]() + { + refreshSources(); + const int index = m_sourceCombo->findData( + m_workspace->activeDocumentId(), Qt::UserRole + 1); + if (index >= 0) + { + m_sourceCombo->setCurrentIndex(index); + } + }); + connect(workspace, &ImageWorkspace::documentsChanged, + this, &ImageProcessingWidget::refreshSources); + connect(workspace, &ImageWorkspace::documentProcessingProgress, + this, [this](quint64 requestId, qint64 completed, qint64 total) + { + if (!m_directProcessingRequest + && requestId == m_offlineProcessingRequestId) + { + m_processingProgress->setMaximum(static_cast(total)); + m_processingProgress->setValue(static_cast(completed)); + } + }); + connect(workspace, &ImageWorkspace::documentProcessingFinished, + this, [this](quint64 requestId, const QString&, const QString& error) + { + if (m_directProcessingRequest + || requestId != m_offlineProcessingRequestId) + { + return; + } + finishOfflineProcessing(); + if (!error.isEmpty()) + { + QMessageBox::warning(this, tr("Processing Failed"), error); + } + }); updateProcessingSettings(); refreshSources(); @@ -301,66 +346,57 @@ namespace scopeone::ui { auto* mainLayout = new QVBoxLayout(this); auto* splitter = new QSplitter(Qt::Vertical, this); + QWidget* pipelineGroup = setupModuleList(); + QWidget* parametersGroup = setupModuleConfig(); setupRunControls(); setupSourceControls(); - setupModuleList(); - setupModuleConfig(); - auto* topWidget = new QWidget(this); - auto* topLayout = new QVBoxLayout(topWidget); - topLayout->addWidget(m_runControlsWidget); - topLayout->addWidget(m_sourceControlsWidget); - topLayout->addWidget(m_moduleList->parentWidget()); - splitter->addWidget(topWidget); - splitter->addWidget(m_configStack->parentWidget()); + splitter->addWidget(pipelineGroup); + splitter->addWidget(parametersGroup); splitter->setStretchFactor(0, 2); splitter->setStretchFactor(1, 3); - mainLayout->addWidget(splitter); + mainLayout->addWidget(splitter, 1); + mainLayout->addWidget(m_runControlsWidget); + mainLayout->addWidget(m_sourceControlsWidget); } void ImageProcessingWidget::setupSourceControls() { - m_sourceControlsWidget = new QGroupBox(tr("Processing Source"), this); - auto* layout = new QVBoxLayout(m_sourceControlsWidget); + m_sourceControlsWidget = new QGroupBox(tr("Single Run"), this); + auto* layout = new QGridLayout(m_sourceControlsWidget); + layout->addWidget(new QLabel(tr("Source"), m_sourceControlsWidget), 0, 0); m_sourceCombo = new QComboBox(m_sourceControlsWidget); - layout->addWidget(m_sourceCombo); - auto* buttons = new QHBoxLayout; - m_processImageButton = new QPushButton(tr("Process Image"), m_sourceControlsWidget); - m_processStackButton = new QPushButton(tr("Process Stack"), m_sourceControlsWidget); + layout->addWidget(m_sourceCombo, 0, 1, 1, 2); + layout->addWidget(new QLabel(tr("Range"), m_sourceControlsWidget), 1, 0); + m_offlineScopeCombo = new QComboBox(m_sourceControlsWidget); + m_offlineScopeCombo->addItem(tr("Current frame"), false); + m_offlineScopeCombo->addItem(tr("Entire stack"), true); + layout->addWidget(m_offlineScopeCombo, 1, 1, 1, 2); + m_runOfflineButton = new QPushButton(tr("Run"), m_sourceControlsWidget); m_cancelProcessingButton = new QPushButton(tr("Cancel"), m_sourceControlsWidget); m_cancelProcessingButton->setEnabled(false); - connect(m_processImageButton, &QPushButton::clicked, - this, &ImageProcessingWidget::onProcessImage); - connect(m_processStackButton, &QPushButton::clicked, - this, &ImageProcessingWidget::onProcessStack); + connect(m_runOfflineButton, &QPushButton::clicked, + this, &ImageProcessingWidget::onRunOfflineProcessing); connect(m_cancelProcessingButton, &QPushButton::clicked, this, &ImageProcessingWidget::onCancelProcessing); connect(m_sourceCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { updateRunButtons(); }); - buttons->addWidget(m_processImageButton); - buttons->addWidget(m_processStackButton); - buttons->addWidget(m_cancelProcessingButton); - layout->addLayout(buttons); + connect(m_offlineScopeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, [this]() { updateRunButtons(); }); + layout->addWidget(m_runOfflineButton, 2, 1); + layout->addWidget(m_cancelProcessingButton, 2, 2); m_processingProgress = new QProgressBar(m_sourceControlsWidget); m_processingProgress->setVisible(false); - layout->addWidget(m_processingProgress); + layout->addWidget(m_processingProgress, 3, 0, 1, 3); } void ImageProcessingWidget::setupRunControls() { - m_runControlsWidget = new QWidget(this); - auto* layout = new QHBoxLayout(m_runControlsWidget); - m_startButton = new QPushButton(tr("Start Processing"), m_runControlsWidget); - m_stopButton = new QPushButton(tr("Stop Processing"), m_runControlsWidget); - m_processingBitDepthCombo = new QComboBox(m_runControlsWidget); + m_runControlsWidget = new QGroupBox(tr("Real-time Processing"), this); + auto* layout = new QGridLayout(m_runControlsWidget); + m_liveProcessingCheckBox = new QCheckBox(tr("Enabled"), m_runControlsWidget); m_liveSourceCombo = new QComboBox(m_runControlsWidget); - m_processingBitDepthCombo->addItem( - tr("8-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit8)); - m_processingBitDepthCombo->addItem( - tr("16-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit16)); - connect(m_startButton, &QPushButton::clicked, this, &ImageProcessingWidget::onStartProcessing); - connect(m_stopButton, &QPushButton::clicked, this, &ImageProcessingWidget::onStopProcessing); - connect(m_processingBitDepthCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, &ImageProcessingWidget::onProcessingBitDepthChanged); + connect(m_liveProcessingCheckBox, &QCheckBox::toggled, + this, &ImageProcessingWidget::onLiveProcessingToggled); connect(m_liveSourceCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { @@ -370,16 +406,15 @@ namespace scopeone::ui updateProcessingSettings(); } }); - layout->addWidget(m_startButton); - layout->addWidget(m_stopButton); - layout->addWidget(m_processingBitDepthCombo); - layout->addWidget(m_liveSourceCombo); - layout->addStretch(); + layout->addWidget(m_liveProcessingCheckBox, 0, 0); + layout->addWidget(new QLabel(tr("Source"), m_runControlsWidget), 0, 1); + layout->addWidget(m_liveSourceCombo, 0, 2); + layout->setColumnStretch(2, 1); } - void ImageProcessingWidget::setupModuleList() + QWidget* ImageProcessingWidget::setupModuleList() { - auto* group = new QGroupBox(tr("Processing Modules"), this); + auto* group = new QGroupBox(tr("Pipeline"), this); auto* layout = new QVBoxLayout(group); m_moduleList = new QListWidget(group); connect(m_moduleList, &QListWidget::currentRowChanged, @@ -399,11 +434,23 @@ namespace scopeone::ui controls->addWidget(m_addModuleButton); controls->addWidget(m_removeModuleButton); layout->addLayout(controls); + auto* outputLayout = new QHBoxLayout; + outputLayout->addWidget(new QLabel(tr("Bit depth"), group)); + m_processingBitDepthCombo = new QComboBox(group); + m_processingBitDepthCombo->addItem( + tr("8-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit8)); + m_processingBitDepthCombo->addItem( + tr("16-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit16)); + connect(m_processingBitDepthCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &ImageProcessingWidget::onProcessingBitDepthChanged); + outputLayout->addWidget(m_processingBitDepthCombo, 1); + layout->addLayout(outputLayout); + return group; } - void ImageProcessingWidget::setupModuleConfig() + QWidget* ImageProcessingWidget::setupModuleConfig() { - auto* group = new QGroupBox(tr("Module Configuration"), this); + auto* group = new QGroupBox(tr("Step Parameters"), this); auto* layout = new QVBoxLayout(group); m_configStack = new QStackedWidget(group); m_emptyConfigWidget = new QWidget(m_configStack); @@ -412,6 +459,7 @@ namespace scopeone::ui emptyLayout->addStretch(); m_configStack->addWidget(m_emptyConfigWidget); layout->addWidget(m_configStack); + return group; } void ImageProcessingWidget::updateModuleList() @@ -455,19 +503,69 @@ namespace scopeone::ui const bool running = m_scopeonecore->isRealTimeProcessingEnabled(); const bool hasModules = !m_scopeonecore->processingModules().isEmpty(); const bool idle = m_offlineProcessingRequestId == 0; - m_startButton->setEnabled(!running && idle && hasModules); - m_stopButton->setEnabled(running); + { + const QSignalBlocker blocker(m_liveProcessingCheckBox); + m_liveProcessingCheckBox->setChecked(running); + } + m_liveProcessingCheckBox->setEnabled(idle && (running || hasModules)); + m_liveProcessingCheckBox->setToolTip( + !idle ? tr("A single run is in progress") + : !running && !hasModules ? tr("Add at least one pipeline step first") + : QString{}); m_processingBitDepthCombo->setEnabled(!running && idle); m_liveSourceCombo->setEnabled(!running && idle); m_moduleList->parentWidget()->setEnabled(!running && idle); m_configStack->parentWidget()->setEnabled(!running && idle); const QString sourceType = m_sourceCombo->currentData(Qt::UserRole).toString(); const bool hasPipeline = !m_scopeonecore->processingModules().isEmpty(); + const QString sourceId = m_sourceCombo->currentData(Qt::UserRole + 1).toString(); + const bool hasSource = !sourceType.isEmpty(); + const bool sourceAvailable = hasSource + && (sourceType == QStringLiteral("layer") + || sourceType == QStringLiteral("stack") + || m_workspace->document(sourceId).ready); m_sourceCombo->setEnabled(!running && idle); - m_processImageButton->setEnabled(!running && idle && hasPipeline - && sourceType == QStringLiteral("layer")); - m_processStackButton->setEnabled(!running && idle && hasPipeline - && sourceType == QStringLiteral("stack")); + const bool canProcessImage = sourceType == QStringLiteral("layer") + || sourceType == QStringLiteral("document") + || sourceType == QStringLiteral("document_stack"); + const bool canProcessStack = sourceType == QStringLiteral("stack") + || sourceType == QStringLiteral("document_stack"); + if (!canProcessStack && m_offlineScopeCombo->currentData().toBool()) + { + const QSignalBlocker blocker(m_offlineScopeCombo); + m_offlineScopeCombo->setCurrentIndex(0); + } + const bool entireStack = m_offlineScopeCombo->currentData().toBool(); + m_offlineScopeCombo->setEnabled(!running && idle && canProcessStack); + const bool validScope = entireStack ? canProcessStack : canProcessImage; + const bool canRun = !running && idle && hasPipeline && sourceAvailable && validScope; + m_runOfflineButton->setEnabled(canRun); + QString runDisabledReason; + if (running) + { + runDisabledReason = tr("Disable real-time processing before starting a single run"); + } + else if (!idle) + { + runDisabledReason = tr("A single run is already in progress"); + } + else if (!hasPipeline) + { + runDisabledReason = tr("Add at least one pipeline step first"); + } + else if (!hasSource) + { + runDisabledReason = tr("Select a source to process"); + } + else if (!sourceAvailable) + { + runDisabledReason = tr("The selected source is not available"); + } + else if (!validScope) + { + runDisabledReason = tr("The selected source does not support this range"); + } + m_runOfflineButton->setToolTip(runDisabledReason); m_cancelProcessingButton->setEnabled(!idle); } @@ -510,16 +608,34 @@ namespace scopeone::ui { const QSignalBlocker liveBlocker(m_liveSourceCombo); m_liveSourceCombo->clear(); - m_liveSourceCombo->addItem(tr("Live: All Cameras"), QString{}); + m_liveSourceCombo->addItem(tr("All preview cameras"), QString{}); for (const QString& cameraId : m_scopeonecore->cameraIds()) { - m_liveSourceCombo->addItem(tr("Live: %1").arg(cameraId), cameraId); + m_liveSourceCombo->addItem(tr("Preview camera: %1").arg(cameraId), cameraId); } const int liveIndex = m_liveSourceCombo->findData(liveSource); m_liveSourceCombo->setCurrentIndex(qMax(0, liveIndex)); } m_sourceCombo->clear(); + const QString activeDocumentId = m_workspace->activeDocumentId(); + for (const ImageDocumentInfo& document : m_workspace->documents()) + { + const QString sourceType = document.frameCount > 1 + ? QStringLiteral("document_stack") + : QStringLiteral("document"); + m_sourceCombo->addItem( + document.active ? tr("Active document: %1").arg(document.title) + : tr("Document: %1").arg(document.title), + sourceType); + const int index = m_sourceCombo->count() - 1; + m_sourceCombo->setItemData(index, document.id, Qt::UserRole + 1); + if (document.id == activeDocumentId) + { + m_sourceCombo->setCurrentIndex(index); + } + } + for (const QString& layerKey : m_scopeonecore->imageSceneModel()->layerIds()) { scopeone::core::DocumentLayer layer; @@ -567,28 +683,30 @@ namespace scopeone::ui updateRunButtons(); } - void ImageProcessingWidget::onProcessImage() + void ImageProcessingWidget::onRunOfflineProcessing() { - m_offlineProcessingRequestId = m_scopeonecore->requestLayerProcessing( - m_sourceCombo->currentData(Qt::UserRole + 1).toString()); - if (m_offlineProcessingRequestId == 0) - { - QMessageBox::warning(this, tr("Processing Failed"), tr("No image is available")); - return; - } - m_processingProgress->setRange(0, 0); - m_processingProgress->setVisible(true); - updateRunButtons(); - } - - void ImageProcessingWidget::onProcessStack() - { - m_offlineProcessingRequestId = m_scopeonecore->requestRecordingSessionStackProcessing( - m_sourceCombo->currentData(Qt::UserRole + 1).toString(), - m_sourceCombo->currentData(Qt::UserRole + 2).toString()); + const QString sourceType = m_sourceCombo->currentData(Qt::UserRole).toString(); + const QString sourceId = m_sourceCombo->currentData(Qt::UserRole + 1).toString(); + const bool entireStack = m_offlineScopeCombo->currentData().toBool(); + m_directProcessingRequest = sourceType == QStringLiteral("layer") + || sourceType == QStringLiteral("stack"); + m_offlineProcessingRequestId = entireStack + ? (m_directProcessingRequest + ? m_scopeonecore->requestRecordingSessionStackProcessing( + m_sourceCombo->currentData(Qt::UserRole + 1).toString(), + m_sourceCombo->currentData(Qt::UserRole + 2).toString()) + : m_workspace->processDocument(sourceId, true)) + : (m_directProcessingRequest + ? m_scopeonecore->requestImageProcessing( + m_scopeonecore->graphFrame(sourceId), sourceId) + : m_workspace->processDocument(sourceId, false)); if (m_offlineProcessingRequestId == 0) { - QMessageBox::warning(this, tr("Processing Failed"), tr("No stack is available")); + finishOfflineProcessing(); + QMessageBox::warning(this, + tr("Processing Failed"), + entireStack ? tr("No stack is available") + : tr("No image is available")); return; } m_processingProgress->setRange(0, 0); @@ -604,6 +722,7 @@ namespace scopeone::ui void ImageProcessingWidget::finishOfflineProcessing() { m_offlineProcessingRequestId = 0; + m_directProcessingRequest = false; m_processingProgress->setVisible(false); updateRunButtons(); } @@ -646,23 +765,16 @@ namespace scopeone::ui } } - void ImageProcessingWidget::onStartProcessing() + void ImageProcessingWidget::onLiveProcessingToggled(bool enabled) { - if (!m_scopeonecore->setRealTimeProcessingEnabled(true)) + if (!m_scopeonecore->setRealTimeProcessingEnabled(enabled)) { - QMessageBox::information(this, tr("Information"), tr("Please add a processing module first")); - return; + QMessageBox::information( + this, tr("Information"), tr("Please add a processing module first")); } updateRunButtons(); - qInfo().noquote() << "Processing started"; - } - - void ImageProcessingWidget::onStopProcessing() - { - if (m_scopeonecore->setRealTimeProcessingEnabled(false)) - { - updateRunButtons(); - qInfo().noquote() << "Processing stopped"; - } + qInfo().noquote() << (m_scopeonecore->isRealTimeProcessingEnabled() + ? "Processing started" + : "Processing stopped"); } } diff --git a/src/ImageProcessingWidget.h b/src/ImageProcessingWidget.h index 4a47117..87841f3 100644 --- a/src/ImageProcessingWidget.h +++ b/src/ImageProcessingWidget.h @@ -6,6 +6,7 @@ #include class QListWidget; +class QCheckBox; class QComboBox; class QPushButton; class QProgressBar; @@ -13,12 +14,16 @@ class QStackedWidget; namespace scopeone::ui { + class ImageWorkspace; + class ImageProcessingWidget : public QWidget { Q_OBJECT public: - explicit ImageProcessingWidget(scopeone::core::ScopeOneCore* core, QWidget* parent = nullptr); + explicit ImageProcessingWidget(scopeone::core::ScopeOneCore* core, + ImageWorkspace* workspace, + QWidget* parent = nullptr); ~ImageProcessingWidget() override = default; signals: @@ -33,17 +38,15 @@ namespace scopeone::ui void onRemoveModuleClicked(); void onModuleSelectionChanged(); void onProcessingBitDepthChanged(); - void onStartProcessing(); - void onStopProcessing(); - void onProcessImage(); - void onProcessStack(); + void onLiveProcessingToggled(bool enabled); + void onRunOfflineProcessing(); void onCancelProcessing(); void setupUI(); void setupRunControls(); void setupSourceControls(); - void setupModuleList(); - void setupModuleConfig(); + QWidget* setupModuleList(); + QWidget* setupModuleConfig(); void updateProcessingSettings(); void updateModuleList(); void updateConfigWidget(); @@ -53,18 +56,18 @@ namespace scopeone::ui void finishOfflineProcessing(); scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; + ImageWorkspace* m_workspace{nullptr}; bool m_processingRunning{false}; QWidget* m_runControlsWidget{nullptr}; QWidget* m_sourceControlsWidget{nullptr}; QComboBox* m_sourceCombo{nullptr}; - QPushButton* m_startButton{nullptr}; - QPushButton* m_stopButton{nullptr}; - QPushButton* m_processImageButton{nullptr}; - QPushButton* m_processStackButton{nullptr}; + QCheckBox* m_liveProcessingCheckBox{nullptr}; + QPushButton* m_runOfflineButton{nullptr}; QPushButton* m_cancelProcessingButton{nullptr}; QProgressBar* m_processingProgress{nullptr}; QComboBox* m_processingBitDepthCombo{nullptr}; QComboBox* m_liveSourceCombo{nullptr}; + QComboBox* m_offlineScopeCombo{nullptr}; QListWidget* m_moduleList{nullptr}; QPushButton* m_addModuleButton{nullptr}; QPushButton* m_removeModuleButton{nullptr}; @@ -72,5 +75,6 @@ namespace scopeone::ui QStackedWidget* m_configStack{nullptr}; QWidget* m_emptyConfigWidget{nullptr}; quint64 m_offlineProcessingRequestId{0}; + bool m_directProcessingRequest{false}; }; } diff --git a/src/ImageWorkspace.cpp b/src/ImageWorkspace.cpp new file mode 100644 index 0000000..364b187 --- /dev/null +++ b/src/ImageWorkspace.cpp @@ -0,0 +1,1588 @@ +#include "ImageWorkspace.h" + +#include "PreviewWidget.h" +#include "scopeone/ImageSceneModel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scopeone::ui +{ + namespace + { + using RecordingSessionData = scopeone::core::ScopeOneCore::RecordingSessionData; + + class ImageDocumentPage final : public QWidget + { + Q_OBJECT + + public: + ImageDocumentPage(const QString& documentId, + const QString& title, + const QString& cameraId, + const scopeone::core::ExperimentDocument& presentation, + int frameCount, + QWidget* parent) + : QWidget(parent), m_documentId(documentId), m_sourceId(cameraId) + { + auto* scene = new scopeone::core::ImageSceneModel(this); + scopeone::core::DocumentLayer layer; + auto sourceLayer = std::find_if( + presentation.layers.cbegin(), presentation.layers.cend(), + [&cameraId](const scopeone::core::DocumentLayer& candidate) + { + return candidate.sourceId == cameraId + && candidate.kind == scopeone::core::DocumentLayerKind::Raw; + }); + if (sourceLayer == presentation.layers.cend()) + { + sourceLayer = std::find_if( + presentation.layers.cbegin(), presentation.layers.cend(), + [&cameraId](const scopeone::core::DocumentLayer& candidate) + { + return candidate.sourceId == cameraId; + }); + } + if (sourceLayer != presentation.layers.cend()) + { + layer = *sourceLayer; + } + layer.id = scopeone::core::ScopeOneCore::staticLayerKey(cameraId); + layer.sourceId = cameraId; + layer.name = title; + layer.kind = scopeone::core::DocumentLayerKind::Gallery; + layer.display.visible = true; + scene->ensureLayer(layer); + scene->setVisibleLayers({layer.id}); + if (sourceLayer != presentation.layers.cend()) + { + for (const auto& savedMarkup : presentation.markups) + { + if (savedMarkup.layerId != sourceLayer->id) + { + continue; + } + const QString markupId = + savedMarkup.type == scopeone::core::DocumentMarkupType::Line + ? scene->createLine(layer.id, + savedMarkup.start.toPoint(), + savedMarkup.end.toPoint(), + savedMarkup.label, + savedMarkup.role) + : scene->createRect(layer.id, + savedMarkup.rect.toRect(), + savedMarkup.label, + savedMarkup.role); + scene->setVisible(markupId, savedMarkup.visible); + scene->setSelected(markupId, savedMarkup.selected); + } + } + + auto* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + m_preview = new PreviewWidget(scene, this); + layout->addWidget(m_preview, 1); + + auto* navigation = new QWidget(this); + auto* navigationLayout = new QHBoxLayout(navigation); + navigationLayout->setContentsMargins(8, 4, 8, 4); + m_slider = new QSlider(Qt::Horizontal, navigation); + m_slider->setRange(0, qMax(0, frameCount - 1)); + m_frameLabel = new QLabel(navigation); + m_frameLabel->setMinimumWidth(90); + m_frameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + navigationLayout->addWidget(m_slider, 1); + navigationLayout->addWidget(m_frameLabel); + navigation->setVisible(frameCount > 1); + layout->addWidget(navigation); + updateFrameLabel(0, frameCount); + + connect(m_slider, &QSlider::valueChanged, this, + [this, frameCount](int index) + { + updateFrameLabel(index, frameCount); + emit frameIndexRequested(m_documentId, index); + }); + } + + void showFrame(const scopeone::core::ImageFrame& frame) + { + scopeone::core::ImageFrame displayFrame(frame); + displayFrame.cameraId = m_sourceId; + sceneModel()->updateLayerFrame( + scopeone::core::ScopeOneCore::staticLayerKey(m_sourceId), displayFrame); + m_preview->setGraphStaticLayerFrame(m_sourceId, displayFrame); + } + + scopeone::core::ImageSceneModel* sceneModel() const + { + return m_preview->sceneModel(); + } + + PreviewWidget* previewWidget() const + { + return m_preview; + } + + void setFrameIndex(int index) + { + const QSignalBlocker blocker(m_slider); + m_slider->setValue(index); + updateFrameLabel(index, m_slider->maximum() + 1); + } + + signals: + void frameIndexRequested(const QString& documentId, int frameIndex); + + private: + void updateFrameLabel(int index, int count) + { + m_frameLabel->setText(QStringLiteral("%1 / %2").arg(index + 1).arg(count)); + } + + QString m_documentId; + QString m_sourceId; + PreviewWidget* m_preview{nullptr}; + QSlider* m_slider{nullptr}; + QLabel* m_frameLabel{nullptr}; + }; + + QString defaultTitle(const RecordingSessionData& session, const QString& cameraId) + { + const QString baseName = session.capturePlan().baseName.trimmed(); + return baseName.isEmpty() ? cameraId : baseName + QStringLiteral(" - ") + cameraId; + } + } + + struct ImageWorkspace::Document + { + QString id; + QString title; + std::shared_ptr session; + QString cameraId; + int frameIndex{0}; + int frameCount{0}; + int requestedFrameIndex{0}; + quint64 frameRequestId{0}; + scopeone::core::ImageFrame currentFrame; + QString activeLayerKey; + QPointer page; + }; + + ImageWorkspace::ImageWorkspace(scopeone::core::ScopeOneCore* core, + QWidget* windowParent, + QObject* parent) + : QObject(parent), m_core(core) + { + if (!core) + { + qFatal("ImageWorkspace requires ScopeOneCore"); + } + + m_viewerHost = new QWidget(windowParent); + auto* hostLayout = new QVBoxLayout(m_viewerHost); + hostLayout->setContentsMargins(0, 0, 0, 0); + hostLayout->setSpacing(0); + + m_viewerToolbar = new QToolBar(tr("Viewer"), m_viewerHost); + m_viewerToolbar->setMovable(false); + m_viewerToolbar->setFloatable(false); + m_viewerToolbar->setToolButtonStyle(Qt::ToolButtonTextOnly); + hostLayout->addWidget(m_viewerToolbar); + + m_viewerStack = new QStackedWidget(m_viewerHost); + m_viewerTabs = new QTabWidget(m_viewerStack); + m_viewerTabs->setTabsClosable(true); + m_viewerTabs->setDocumentMode(true); + m_viewerStack->addWidget(m_viewerTabs); + + m_compareWidget = new QWidget(m_viewerStack); + auto* compareLayout = new QHBoxLayout(m_compareWidget); + compareLayout->setContentsMargins(0, 0, 0, 0); + auto* compareSplitter = new QSplitter(Qt::Horizontal, m_compareWidget); + m_compareLeftHost = new QGroupBox(m_compareWidget); + m_compareLeftHost->setLayout(new QVBoxLayout); + m_compareLeftHost->layout()->setContentsMargins(4, 4, 4, 4); + m_compareRightHost = new QGroupBox(m_compareWidget); + m_compareRightHost->setLayout(new QVBoxLayout); + m_compareRightHost->layout()->setContentsMargins(4, 4, 4, 4); + compareSplitter->addWidget(m_compareLeftHost); + compareSplitter->addWidget(m_compareRightHost); + compareSplitter->setSizes({1, 1}); + compareLayout->addWidget(compareSplitter); + m_viewerStack->addWidget(m_compareWidget); + hostLayout->addWidget(m_viewerStack, 1); + + setupViewerToolbar(); + connect(m_viewerTabs, &QTabWidget::currentChanged, + this, [this](int index) + { + if (index == m_liveTabIndex) + { + activateLiveViewer(); + return; + } + if (Document* document = findDocumentByPage(m_viewerTabs->widget(index))) + { + if (m_activeDocumentId != document->id) + { + setActiveDocument(document->id); + } + updateViewerToolbar(); + } + }); + connect(m_viewerTabs, &QTabWidget::tabCloseRequested, + this, [this](int index) + { + if (index == m_liveTabIndex) + { + return; + } + if (Document* document = findDocumentByPage(m_viewerTabs->widget(index))) + { + closeDocument(document->id); + } + }); + + connect(core, &scopeone::core::ScopeOneCore::recordingSessionFrameReady, + this, [this](quint64 requestId, + const std::shared_ptr&, + const QString&, + int frameIndex, + const scopeone::core::ImageFrame& frame) + { + const QString documentId = m_frameRequests.take(requestId); + Document* document = findDocument(documentId); + if (!document || document->frameRequestId != requestId) + { + return; + } + document->frameRequestId = 0; + if (!frame.isValid() && !document->currentFrame.isValid()) + { + closeDocument(documentId); + return; + } + if (frame.isValid() && frameIndex == document->requestedFrameIndex) + { + document->frameIndex = frameIndex; + document->currentFrame = frame; + document->page->setFrameIndex(frameIndex); + document->page->showFrame(frame); + if (document->id == m_activeDocumentId) + { + emit activeFrameChanged(); + updateLineProfile(*document); + } + emit documentsChanged(); + } + if (document->frameIndex != document->requestedFrameIndex) + { + if (!requestFrame(*document, document->requestedFrameIndex)) + { + document->page->setFrameIndex(document->frameIndex); + } + } + }); + connect(core, &scopeone::core::ScopeOneCore::recordingSessionClosed, + this, [this](const QString& sessionId) + { + QStringList documentIds; + for (const auto& document : m_documents) + { + if (document->session->capturePlan().experimentId == sessionId) + { + documentIds.append(document->id); + } + } + for (const QString& documentId : documentIds) + { + closeDocument(documentId); + } + }); + connect(core, &scopeone::core::ScopeOneCore::imageProcessingFinished, + this, [this](quint64 requestId, + const QString&, + const scopeone::core::ImageFrame& frame, + const QString& errorMessage) + { + const QString sourceId = m_processingRequests.take(requestId); + if (sourceId.isEmpty()) + { + return; + } + const Document* source = findDocument(sourceId); + const QString resultError = !source && errorMessage.isEmpty() + ? tr("Source image viewer was closed") + : errorMessage; + QString outputId; + if (resultError.isEmpty() && frame.isValid()) + { + outputId = openFrame(frame, + tr("Processed %1").arg(source->title)); + } + emit documentProcessingFinished(requestId, outputId, resultError); + }); + connect(core, &scopeone::core::ScopeOneCore::stackProcessingProgress, + this, [this](quint64 requestId, qint64 completed, qint64 total) + { + if (m_processingRequests.contains(requestId)) + { + emit documentProcessingProgress(requestId, completed, total); + } + }); + connect(core, &scopeone::core::ScopeOneCore::stackProcessingFinished, + this, [this](quint64 requestId, + const std::shared_ptr& session, + const QString& errorMessage) + { + const QString sourceId = m_processingRequests.take(requestId); + if (sourceId.isEmpty()) + { + return; + } + const QString resultError = !findDocument(sourceId) && errorMessage.isEmpty() + ? tr("Source image viewer was closed") + : errorMessage; + QString outputId; + if (resultError.isEmpty() && session) + { + const QStringList ids = openSession(session, tr("Processed Stack")); + outputId = ids.value(0); + emit sessionAvailable(session, tr("Processed Stack")); + } + emit documentProcessingFinished(requestId, outputId, resultError); + }); + connect(core, &scopeone::core::ScopeOneCore::recordingSessionCameraSaveFinished, + this, [this](const std::shared_ptr& session, + const QString& cameraId, + bool success, + const QString& message) + { + const QString key = session + ? session->capturePlan().experimentId + + QLatin1Char('\n') + cameraId + : QString{}; + const QString documentId = m_saveRequests.take(key); + if (!documentId.isEmpty()) + { + emit documentSaveFinished(documentId, success, message); + } + }); + } + + ImageWorkspace::~ImageWorkspace() = default; + + QWidget* ImageWorkspace::viewerHost() const + { + return m_viewerHost; + } + + // Builds the compact controls used by the active image viewer + void ImageWorkspace::setupViewerToolbar() + { + m_viewerContextLabel = new QLabel(tr("Live Preview"), m_viewerToolbar); + QFont contextFont = m_viewerContextLabel->font(); + contextFont.setBold(true); + m_viewerContextLabel->setFont(contextFont); + m_viewerContextLabel->setFixedWidth(180); + m_viewerContextLabel->setToolTip(tr("Current image viewer")); + m_viewerToolbar->addWidget(m_viewerContextLabel); + m_viewerToolbar->addSeparator(); + + m_fitToWindowAction = m_viewerToolbar->addAction(tr("Fit")); + m_fitToWindowAction->setCheckable(true); + m_fitToWindowAction->setToolTip(tr("Fit the image to the viewer")); + connect(m_fitToWindowAction, &QAction::toggled, this, + [this](bool enabled) + { + if (PreviewWidget* preview = activePreviewWidget()) + { + preview->setFitToWindow(enabled); + } + updateViewerToolbar(); + }); + + m_oneToOneAction = m_viewerToolbar->addAction(tr("1:1")); + m_oneToOneAction->setToolTip(tr("Show the image at native pixel size")); + connect(m_oneToOneAction, &QAction::triggered, this, + [this]() + { + if (PreviewWidget* preview = activePreviewWidget()) + { + preview->setFitToWindow(false); + preview->setZoomPercent(100); + } + }); + + m_viewerToolbar->addSeparator(); + m_viewerToolbar->addWidget(new QLabel(tr("Layer Layout"), m_viewerToolbar)); + m_layoutCombo = new QComboBox(m_viewerToolbar); + m_layoutCombo->addItem(tr("Side by side")); + m_layoutCombo->addItem(tr("Overlay")); + m_layoutCombo->setToolTip(tr("Choose how multiple image layers are arranged")); + m_viewerToolbar->addWidget(m_layoutCombo); + connect(m_layoutCombo, qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) + { + if (PreviewWidget* preview = activePreviewWidget()) + { + preview->setLayerLayoutMode( + index == 1 ? PreviewWidget::LayerLayoutMode::Overlay + : PreviewWidget::LayerLayoutMode::SideBySide); + } + }); + + m_viewerToolbar->addSeparator(); + m_compareAction = m_viewerToolbar->addAction(tr("Compare")); + m_compareAction->setCheckable(true); + m_compareAction->setToolTip(tr("Show two image documents side by side")); + connect(m_compareAction, &QAction::toggled, this, + [this](bool enabled) + { + if (enabled) + { + if (!beginComparison()) + { + const QSignalBlocker blocker(m_compareAction); + m_compareAction->setChecked(false); + } + } + else + { + endComparison(); + } + }); + + m_compareDocumentCombo = new QComboBox(m_viewerToolbar); + m_compareDocumentCombo->setMinimumWidth(180); + m_compareDocumentCombo->setToolTip(tr("Choose the document shown on the right")); + m_compareDocumentCombo->setVisible(false); + m_viewerToolbar->addWidget(m_compareDocumentCombo); + connect(m_compareDocumentCombo, qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) + { + if (!comparisonActive() || index < 0) + { + return; + } + QString rightId = m_compareDocumentCombo->itemData(index).toString(); + const QString leftId = m_compareLeftDocumentId; + if (rightId == QStringLiteral("duplicate")) + { + rightId = duplicateDocument(leftId); + } + if (rightId.isEmpty() || rightId == m_compareRightDocumentId) + { + return; + } + endComparison(); + setActiveDocument(leftId); + { + const QSignalBlocker blocker(m_compareAction); + m_compareAction->setChecked(true); + } + if (!beginComparison(rightId)) + { + const QSignalBlocker blocker(m_compareAction); + m_compareAction->setChecked(false); + } + }); + + m_linkFramesAction = m_viewerToolbar->addAction(tr("Link Frames")); + m_linkFramesAction->setCheckable(true); + m_linkFramesAction->setToolTip(tr("Keep both compared sequences on the same frame number")); + m_linkFramesAction->setVisible(false); + } + + // Refreshes viewer controls when the active document or display state changes + void ImageWorkspace::updateViewerToolbar() + { + PreviewWidget* preview = activePreviewWidget(); + const ImageDocumentInfo activeDocument = document(); + const QString contextTitle = activeDocument.isValid() + ? activeDocument.title + : tr("Live Preview"); + m_viewerContextLabel->setText( + QFontMetrics(m_viewerContextLabel->font()).elidedText( + contextTitle, Qt::ElideRight, m_viewerContextLabel->width())); + m_viewerContextLabel->setToolTip(activeDocument.isValid() + ? tr("Static image: %1").arg(contextTitle) + : tr("Live camera viewer")); + m_fitToWindowAction->setEnabled(preview != nullptr); + m_oneToOneAction->setEnabled(preview != nullptr); + m_compareAction->setEnabled(activeDocument.isValid()); + m_compareDocumentCombo->setVisible(comparisonActive()); + m_linkFramesAction->setVisible(comparisonActive()); + if (preview) + { + { + const QSignalBlocker blocker(m_fitToWindowAction); + m_fitToWindowAction->setChecked(preview->isFitToWindow()); + } + { + const QSignalBlocker blocker(m_layoutCombo); + m_layoutCombo->setCurrentIndex( + preview->layerLayoutMode() == PreviewWidget::LayerLayoutMode::Overlay ? 1 : 0); + } + } + + { + const QSignalBlocker blocker(m_compareDocumentCombo); + m_compareDocumentCombo->clear(); + if (!m_compareLeftDocumentId.isEmpty()) + { + const Document* left = findDocument(m_compareLeftDocumentId); + m_compareDocumentCombo->addItem( + left ? tr("Same sequence, independent frame") : tr("Duplicate current document"), + QStringLiteral("duplicate")); + for (const auto& candidate : m_documents) + { + if (candidate->id != m_compareLeftDocumentId) + { + m_compareDocumentCombo->addItem(candidate->title, candidate->id); + } + } + const int rightIndex = m_compareDocumentCombo->findData(m_compareRightDocumentId); + m_compareDocumentCombo->setCurrentIndex(rightIndex >= 0 ? rightIndex : 0); + } + } + } + + void ImageWorkspace::setLiveViewer(PreviewWidget* previewWidget) + { + m_livePreviewWidget = previewWidget; + connectViewer(previewWidget, {}); + m_liveLayerKey = previewWidget->visibleLayerKeys().value(0); + if (m_liveTabIndex < 0) + { + m_liveTabIndex = m_viewerTabs->addTab(previewWidget, tr("Live Preview")); + m_viewerTabs->tabBar()->setTabButton( + m_liveTabIndex, QTabBar::RightSide, nullptr); + m_viewerTabs->setCurrentIndex(m_liveTabIndex); + } + updateViewerToolbar(); + } + + void ImageWorkspace::activateLiveViewer() + { + if (!m_livePreviewWidget) + { + return; + } + if (comparisonActive()) + { + endComparison(); + } + m_viewerTabs->setCurrentIndex(m_liveTabIndex); + if (m_activeDocumentId.isEmpty()) + { + return; + } + m_activeDocumentId.clear(); + emit activeDocumentChanged({}); + emit activeViewerChanged(); + emit activeLayerChanged(activeLayerKey()); + emit documentsChanged(); + updateViewerToolbar(); + } + + void ImageWorkspace::setVisibleLayers(const QStringList& layerKeys, bool sideBySide) + { + scopeone::core::ImageSceneModel* scene = activeSceneModel(); + PreviewWidget* preview = activePreviewWidget(); + if (!scene || !preview) + { + return; + } + scene->setVisibleLayers(layerKeys); + preview->setLayerLayoutMode(sideBySide + ? PreviewWidget::LayerLayoutMode::SideBySide + : PreviewWidget::LayerLayoutMode::Overlay); + } + + QStringList ImageWorkspace::openSession(const std::shared_ptr& session, + const QString& title, + const QString& cameraId) + { + if (!session) + { + return {}; + } + const QStringList cameras = cameraId.trimmed().isEmpty() + ? session->recordedCameraIds() + : QStringList{cameraId.trimmed()}; + QStringList openedIds; + for (const QString& camera : cameras) + { + const qint64 count = session->recordedFrameCount(camera); + if (count <= 0 || count > (std::numeric_limits::max)()) + { + continue; + } + const QString sessionId = session->capturePlan().experimentId; + auto existing = std::find_if(m_documents.begin(), m_documents.end(), + [&sessionId, &camera](const auto& document) + { + return document->session->capturePlan().experimentId == sessionId + && document->cameraId == camera; + }); + if (existing != m_documents.end()) + { + activateDocument((*existing)->id); + openedIds.append((*existing)->id); + continue; + } + + auto document = std::make_unique(); + document->id = QUuid::createUuid().toString(QUuid::WithoutBraces); + document->title = title.trimmed().isEmpty() ? defaultTitle(*session, camera) + : title.trimmed(); + if (cameras.size() > 1 && !title.trimmed().isEmpty()) + { + document->title += QStringLiteral(" - ") + camera; + } + document->session = session; + document->cameraId = camera; + document->frameCount = static_cast(count); + document->activeLayerKey = scopeone::core::ScopeOneCore::staticLayerKey(camera); + document->page = new ImageDocumentPage(document->id, + document->title, + camera, + session->experimentDocument(), + document->frameCount, + m_viewerTabs); + connectViewer(document->page->previewWidget(), document->id); + const QString id = document->id; + connect(document->page, &ImageDocumentPage::frameIndexRequested, + this, &ImageWorkspace::requestDocumentFrame); + m_documents.push_back(std::move(document)); + m_viewerTabs->addTab(m_documents.back()->page, m_documents.back()->title); + if (!requestFrame(*m_documents.back(), 0)) + { + closeDocument(id); + continue; + } + openedIds.append(id); + } + if (!openedIds.isEmpty()) + { + activateDocument(openedIds.constLast()); + } + return openedIds; + } + + QString ImageWorkspace::openFrame(const scopeone::core::ImageFrame& frame, + const QString& title) + { + if (!frame.isValid()) + { + return {}; + } + scopeone::core::ExperimentPlan plan; + plan.experimentId = QUuid::createUuid().toString(QUuid::WithoutBraces); + plan.cameraIds = {frame.cameraId}; + plan.streamToDisk = false; + plan.baseName = title.trimmed().isEmpty() + ? QStringLiteral("image_%1").arg( + QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_hhmmss_zzz"))) + : title.trimmed(); + const auto session = m_core->createFrameSession({frame}, plan); + if (!session) + { + return {}; + } + emit sessionAvailable(session, plan.baseName); + return openSession(session, plan.baseName, frame.cameraId).value(0); + } + + QList ImageWorkspace::documents() const + { + QList result; + result.reserve(static_cast(m_documents.size())); + for (const auto& document : m_documents) + { + result.append({document->id, + document->title, + document->session->capturePlan().experimentId, + document->cameraId, + document->frameIndex, + document->frameCount, + document->currentFrame.isValid(), + document->id == m_activeDocumentId}); + } + return result; + } + + ImageDocumentInfo ImageWorkspace::document(const QString& documentId) const + { + const Document* document = findDocument(documentId); + return document + ? ImageDocumentInfo{document->id, + document->title, + document->session->capturePlan().experimentId, + document->cameraId, + document->frameIndex, + document->frameCount, + document->currentFrame.isValid(), + document->id == m_activeDocumentId} + : ImageDocumentInfo{}; + } + + QString ImageWorkspace::activeDocumentId() const + { + return m_activeDocumentId; + } + + bool ImageWorkspace::isLiveViewerActive() const + { + return m_activeDocumentId.isEmpty(); + } + + scopeone::core::ImageSceneModel* ImageWorkspace::activeSceneModel() const + { + return sceneModel(m_activeDocumentId); + } + + PreviewWidget* ImageWorkspace::activePreviewWidget() const + { + return previewWidget(m_activeDocumentId); + } + + scopeone::core::ImageSceneModel* ImageWorkspace::sceneModel(const QString& documentId) const + { + if (documentId.trimmed().isEmpty()) + { + return m_core->imageSceneModel(); + } + const Document* document = findDocument(documentId); + return document && document->page ? document->page->sceneModel() : nullptr; + } + + PreviewWidget* ImageWorkspace::previewWidget(const QString& documentId) const + { + if (documentId.trimmed().isEmpty()) + { + return m_livePreviewWidget; + } + const Document* document = findDocument(documentId); + return document && document->page ? document->page->previewWidget() : nullptr; + } + + scopeone::core::ImageFrame ImageWorkspace::frameForLayer(const QString& layerKey) const + { + if (isLiveViewerActive()) + { + return m_core->graphFrame(layerKey); + } + const Document* document = findDocument(m_activeDocumentId); + if (!document || !document->page + || !document->page->sceneModel()->layerIds().contains(layerKey)) + { + return {}; + } + return document->currentFrame; + } + + bool ImageWorkspace::histogram( + const QString& layerKey, + scopeone::core::ScopeOneCore::HistogramStats& stats) const + { + return isLiveViewerActive() + ? m_core->getLayerHistogram(layerKey, stats) + : scopeone::core::ScopeOneCore::computeHistogramStats(frameForLayer(layerKey), stats); + } + + void ImageWorkspace::requestHistogram(const QString& layerKey) + { + queueHistogramRequest(layerKey, false); + } + + // Queues a static frame histogram without blocking the viewer + void ImageWorkspace::queueHistogramRequest(const QString& layerKey, bool applyAutoLevels) + { + if (isLiveViewerActive()) + { + return; + } + const Document* document = findDocument(m_activeDocumentId); + if (!document || !document->currentFrame.isValid()) + { + return; + } + m_histogramDocumentId = document->id; + m_histogramLayerKey = layerKey; + m_histogramFrameIndex = document->frameIndex; + m_histogramFrame = document->currentFrame; + m_histogramApplyAutoLevels = applyAutoLevels; + ++m_histogramGeneration; + startHistogramRequest(); + } + + void ImageWorkspace::startHistogramRequest() + { + if (m_histogramRunning || !m_histogramFrame.isValid()) + { + return; + } + m_histogramRunning = true; + const QString documentId = m_histogramDocumentId; + const QString layerKey = m_histogramLayerKey; + const int frameIndex = m_histogramFrameIndex; + const bool applyAutoLevels = m_histogramApplyAutoLevels; + const quint64 generation = m_histogramGeneration; + const scopeone::core::ImageFrame frame = std::exchange( + m_histogramFrame, scopeone::core::ImageFrame{}); + m_histogramApplyAutoLevels = false; + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcherBase::finished, this, + [this, watcher, documentId, layerKey, frameIndex, applyAutoLevels, generation]() + { + m_histogramRunning = false; + const Document* document = findDocument(documentId); + if (generation == m_histogramGeneration + && document && documentId == m_activeDocumentId + && document->frameIndex == frameIndex) + { + const auto stats = watcher->result(); + if (applyAutoLevels) + { + activeSceneModel()->setLayerAutoStretchEnabled(layerKey, false); + activeSceneModel()->setLayerDisplayLevels( + layerKey, + stats.autoMinLevel, + stats.autoMaxLevel, + stats.maxValue); + } + emit histogramReady(layerKey, stats); + } + watcher->deleteLater(); + startHistogramRequest(); + }); + watcher->setFuture(QtConcurrent::run([frame]() + { + scopeone::core::ScopeOneCore::HistogramStats stats; + scopeone::core::ScopeOneCore::computeHistogramStats(frame, stats); + return stats; + })); + } + + bool ImageWorkspace::autoLayerLevels(const QString& layerKey) + { + if (isLiveViewerActive()) + { + return m_core->autoLayerLevels(layerKey); + } + if (!frameForLayer(layerKey).isValid()) + { + return false; + } + queueHistogramRequest(layerKey, true); + return true; + } + + bool ImageWorkspace::fullLayerLevels(const QString& layerKey) + { + if (isLiveViewerActive()) + { + return m_core->fullLayerLevels(layerKey); + } + const scopeone::core::ImageFrame frame = frameForLayer(layerKey); + if (!frame.isValid()) + { + return false; + } + activeSceneModel()->setLayerAutoStretchEnabled(layerKey, false); + return activeSceneModel()->setLayerDisplayLevels(layerKey, 0, frame.maxValue(), frame.maxValue()); + } + + bool ImageWorkspace::setLayerAutoStretchEnabled(const QString& layerKey, bool enabled) + { + if (isLiveViewerActive()) + { + return m_core->setLayerAutoStretchEnabled(layerKey, enabled); + } + if (!activeSceneModel()->setLayerAutoStretchEnabled(layerKey, enabled)) + { + return false; + } + return !enabled || autoLayerLevels(layerKey); + } + + bool ImageWorkspace::layerAutoStretchEnabled(const QString& layerKey) const + { + scopeone::core::ImageSceneModel* scene = activeSceneModel(); + return scene && scene->layerAutoStretchEnabled(layerKey); + } + + bool ImageWorkspace::lineProfile(const QString& layerKey, + const QPoint& start, + const QPoint& end, + QVector& values) const + { + if (isLiveViewerActive()) + { + return m_core->getLineProfile(layerKey, start, end, values); + } + const scopeone::core::ImageFrame frame = frameForLayer(layerKey); + if (!frame.isValid()) + { + return false; + } + const int count = qMax(qAbs(end.x() - start.x()), qAbs(end.y() - start.y())) + 1; + values.clear(); + values.reserve(count); + for (int i = 0; i < count; ++i) + { + const double t = count == 1 ? 0.0 : static_cast(i) / (count - 1); + const int x = qRound(start.x() + t * (end.x() - start.x())); + const int y = qRound(start.y() + t * (end.y() - start.y())); + if (x < 0 || y < 0 || x >= frame.width || y >= frame.height) + { + return false; + } + const uchar* row = reinterpret_cast(frame.bytes.constData()) + + y * frame.stride; + values.append(frame.bytesPerPixel() == 1 + ? row[x] + : reinterpret_cast(row)[x]); + } + return !values.isEmpty(); + } + + bool ImageWorkspace::pixelValue(const QString& layerKey, const QPoint& point, int& value) const + { + if (isLiveViewerActive()) + { + return m_core->graphPixelValue(layerKey, point, value); + } + const scopeone::core::ImageFrame frame = frameForLayer(layerKey); + if (!frame.isValid() || point.x() < 0 || point.y() < 0 + || point.x() >= frame.width || point.y() >= frame.height) + { + return false; + } + const uchar* row = reinterpret_cast(frame.bytes.constData()) + + point.y() * frame.stride; + value = frame.bytesPerPixel() == 1 + ? row[point.x()] + : reinterpret_cast(row)[point.x()]; + return true; + } + + double ImageWorkspace::pixelSizeUm(const QString& layerKey) const + { + if (isLiveViewerActive()) + { + return m_core->cameraPixelSizeUm( + scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey)); + } + const Document* document = findDocument(m_activeDocumentId); + return document ? document->session->cameraPixelSizeUm(document->cameraId) : 0.0; + } + + QString ImageWorkspace::activeLayerKey() const + { + if (isLiveViewerActive()) + { + const scopeone::core::ImageSceneModel* scene = activeSceneModel(); + return scene && scene->visibleLayerIds().contains(m_liveLayerKey) + ? m_liveLayerKey + : scene ? scene->visibleLayerIds().value(0) : QString{}; + } + const Document* document = findDocument(m_activeDocumentId); + scopeone::core::ImageSceneModel* scene = activeSceneModel(); + if (document && scene && scene->layerIds().contains(document->activeLayerKey)) + { + return document->activeLayerKey; + } + return scene ? scene->visibleLayerIds().value(0) : QString{}; + } + + void ImageWorkspace::setActiveLayerKey(const QString& layerKey) + { + const QString normalizedLayerKey = layerKey.trimmed(); + const QString previousLayerKey = activeLayerKey(); + if (isLiveViewerActive()) + { + if (!activeSceneModel()->layerIds().contains(normalizedLayerKey)) + { + return; + } + m_liveLayerKey = normalizedLayerKey; + } + else if (Document* document = findDocument(m_activeDocumentId)) + { + if (!activeSceneModel()->layerIds().contains(normalizedLayerKey)) + { + return; + } + document->activeLayerKey = normalizedLayerKey; + } + if (previousLayerKey != normalizedLayerKey) + { + emit activeLayerChanged(normalizedLayerKey); + } + updateViewerToolbar(); + } + + // Keeps the active layer aligned with the visible layers in one viewer + void ImageWorkspace::syncActiveLayer(const QString& documentId) + { + scopeone::core::ImageSceneModel* scene = sceneModel(documentId); + if (!scene) + { + return; + } + + const QString previousLayerKey = documentId == m_activeDocumentId + ? activeLayerKey() + : QString{}; + const QStringList visibleLayerKeys = scene->visibleLayerIds(); + if (documentId.isEmpty()) + { + if (!visibleLayerKeys.contains(m_liveLayerKey)) + { + m_liveLayerKey = visibleLayerKeys.value(0); + } + } + else if (Document* document = findDocument(documentId)) + { + if (!visibleLayerKeys.contains(document->activeLayerKey)) + { + document->activeLayerKey = visibleLayerKeys.value(0); + } + } + + updateViewerToolbar(); + if (documentId == m_activeDocumentId && previousLayerKey != activeLayerKey()) + { + emit activeLayerChanged(activeLayerKey()); + } + } + + scopeone::core::ImageFrame ImageWorkspace::currentFrame(const QString& documentId) const + { + const Document* document = findDocument(documentId); + return document ? document->currentFrame : scopeone::core::ImageFrame{}; + } + + bool ImageWorkspace::activateDocument(const QString& documentId) + { + Document* document = findDocument(documentId); + if (!document || !document->page) + { + return false; + } + if (comparisonActive()) + { + if (documentId == m_compareLeftDocumentId + || documentId == m_compareRightDocumentId) + { + setActiveDocument(documentId); + return true; + } + endComparison(); + } + m_viewerTabs->setCurrentWidget(document->page); + return true; + } + + void ImageWorkspace::setActiveDocument(const QString& documentId) + { + if (m_activeDocumentId == documentId || !findDocument(documentId)) + { + return; + } + m_activeDocumentId = documentId; + if (comparisonActive()) + { + const Document* left = findDocument(m_compareLeftDocumentId); + const Document* right = findDocument(m_compareRightDocumentId); + const QString leftTitle = left ? left->title : QString{}; + const QString rightTitle = right ? right->title : QString{}; + m_compareLeftHost->setTitle(documentId == m_compareLeftDocumentId + ? tr("Active: %1").arg(leftTitle) + : leftTitle); + m_compareRightHost->setTitle(documentId == m_compareRightDocumentId + ? tr("Active: %1").arg(rightTitle) + : rightTitle); + } + emit activeDocumentChanged(documentId); + emit activeViewerChanged(); + emit activeLayerChanged(activeLayerKey()); + emit documentsChanged(); + updateViewerToolbar(); + } + + bool ImageWorkspace::comparisonActive() const + { + return !m_compareLeftDocumentId.isEmpty() + && !m_compareRightDocumentId.isEmpty(); + } + + bool ImageWorkspace::beginComparison(const QString& rightDocumentId) + { + Document* left = findDocument(m_activeDocumentId); + if (!left || comparisonActive()) + { + return false; + } + + QString rightId = rightDocumentId; + if (rightId.isEmpty()) + { + for (const auto& candidate : m_documents) + { + if (candidate->id != left->id) + { + rightId = candidate->id; + break; + } + } + } + if (rightId.isEmpty()) + { + rightId = duplicateDocument(left->id); + } + Document* right = findDocument(rightId); + if (!right || right == left) + { + return false; + } + + m_compareLeftDocumentId = left->id; + m_compareRightDocumentId = right->id; + { + const QSignalBlocker blocker(m_viewerTabs); + const int leftIndex = m_viewerTabs->indexOf(left->page); + if (leftIndex >= 0) + { + m_viewerTabs->removeTab(leftIndex); + } + const int rightIndex = m_viewerTabs->indexOf(right->page); + if (rightIndex >= 0) + { + m_viewerTabs->removeTab(rightIndex); + } + } + m_compareLeftHost->layout()->addWidget(left->page); + m_compareRightHost->layout()->addWidget(right->page); + m_viewerStack->setCurrentWidget(m_compareWidget); + m_compareLeftHost->setTitle(tr("Active: %1").arg(left->title)); + m_compareRightHost->setTitle(right->title); + updateViewerToolbar(); + return true; + } + + void ImageWorkspace::endComparison() + { + if (!comparisonActive()) + { + return; + } + Document* left = findDocument(m_compareLeftDocumentId); + Document* right = findDocument(m_compareRightDocumentId); + if (left && left->page) + { + m_compareLeftHost->layout()->removeWidget(left->page); + } + if (right && right->page) + { + m_compareRightHost->layout()->removeWidget(right->page); + } + m_compareLeftDocumentId.clear(); + m_compareRightDocumentId.clear(); + rebuildViewerTabs(); + m_viewerStack->setCurrentWidget(m_viewerTabs); + { + const QSignalBlocker blocker(m_compareAction); + m_compareAction->setChecked(false); + } + updateViewerToolbar(); + } + + void ImageWorkspace::rebuildViewerTabs() + { + const QSignalBlocker blocker(m_viewerTabs); + while (m_viewerTabs->count() > 0) + { + m_viewerTabs->removeTab(0); + } + m_liveTabIndex = m_livePreviewWidget + ? m_viewerTabs->addTab(m_livePreviewWidget, tr("Live Preview")) + : -1; + if (m_liveTabIndex >= 0) + { + m_viewerTabs->tabBar()->setTabButton(m_liveTabIndex, QTabBar::RightSide, nullptr); + } + for (const auto& document : m_documents) + { + m_viewerTabs->addTab(document->page, document->title); + } + if (Document* active = findDocument(m_activeDocumentId)) + { + m_viewerTabs->setCurrentWidget(active->page); + } + else if (m_liveTabIndex >= 0) + { + m_viewerTabs->setCurrentIndex(m_liveTabIndex); + } + } + + bool ImageWorkspace::closeDocument(const QString& documentId) + { + Document* document = findDocument(documentId); + if (!document || !document->page) + { + return false; + } + removeDocument(documentId); + return true; + } + + quint64 ImageWorkspace::processDocument(const QString& documentId, bool completeStack) + { + Document* document = findDocument(documentId); + if (!document) + { + return 0; + } + const quint64 requestId = completeStack + ? m_core->requestRecordingSessionStackProcessing( + document->session->capturePlan().experimentId, + document->cameraId) + : m_core->requestImageProcessing(document->currentFrame, + document->id); + if (requestId != 0) + { + m_processingRequests.insert(requestId, document->id); + } + return requestId; + } + + bool ImageWorkspace::saveDocument( + const QString& documentId, + const scopeone::core::ScopeOneCore::RecordingSaveOptions& options) + { + Document* document = findDocument(documentId); + if (!document + || !m_core->saveRecordingSessionCamera( + document->session, + document->cameraId, + options, + &document->page->sceneModel()->document())) + { + return false; + } + m_saveRequests.insert(document->session->capturePlan().experimentId + + QLatin1Char('\n') + document->cameraId, + document->id); + return true; + } + + void ImageWorkspace::saveDocumentAs(const QString& documentId) + { + Document* document = findDocument(documentId); + if (!document) + { + return; + } + const QString saveDir = QFileDialog::getExistingDirectory( + document->page, + tr("Select Dataset Folder"), + QDir::homePath()); + if (saveDir.isEmpty()) + { + return; + } + bool accepted = false; + QString baseName = QInputDialog::getText( + document->page, + tr("Save Image Dataset As"), + tr("Dataset name and optional format suffix"), + QLineEdit::Normal, + document->title + QStringLiteral(".ome.tiff"), + &accepted) + .trimmed(); + if (!accepted || baseName.isEmpty()) + { + return; + } + scopeone::core::ScopeOneCore::RecordingSaveOptions options; + if (baseName.endsWith(QStringLiteral(".ome.tiff"), Qt::CaseInsensitive)) + { + baseName.chop(9); + options.format = scopeone::core::RecordingFormat::OmeTiff; + } + else if (baseName.endsWith(QStringLiteral(".ome.zarr"), Qt::CaseInsensitive)) + { + baseName.chop(9); + options.format = scopeone::core::RecordingFormat::OmeZarr; + } + else if (baseName.endsWith(QStringLiteral(".tiff"), Qt::CaseInsensitive)) + { + baseName.chop(5); + options.format = scopeone::core::RecordingFormat::Tiff; + } + else if (baseName.endsWith(QStringLiteral(".bin"), Qt::CaseInsensitive)) + { + baseName.chop(4); + options.format = scopeone::core::RecordingFormat::Binary; + } + options.saveDir = saveDir; + options.baseName = baseName; + options.enableCompression = options.format != scopeone::core::RecordingFormat::Binary; + saveDocument(document->id, options); + } + + ImageWorkspace::Document* ImageWorkspace::findDocument(const QString& documentId) const + { + const QString id = documentId.trimmed().isEmpty() ? m_activeDocumentId + : documentId.trimmed(); + const auto it = std::find_if(m_documents.begin(), m_documents.end(), + [&id](const auto& document) + { + return document->id == id; + }); + return it == m_documents.end() ? nullptr : it->get(); + } + + ImageWorkspace::Document* ImageWorkspace::findDocumentByPage(QWidget* page) const + { + const auto it = std::find_if(m_documents.cbegin(), m_documents.cend(), + [page](const auto& document) + { + return document->page == page; + }); + return it == m_documents.cend() ? nullptr : it->get(); + } + + QString ImageWorkspace::duplicateDocument(const QString& documentId) + { + const Document* source = findDocument(documentId); + if (!source || !source->session) + { + return {}; + } + auto document = std::make_unique(); + document->id = QUuid::createUuid().toString(QUuid::WithoutBraces); + document->title = tr("%1 Copy").arg(source->title); + document->session = source->session; + document->cameraId = source->cameraId; + document->frameCount = source->frameCount; + document->frameIndex = source->frameIndex; + document->requestedFrameIndex = source->frameIndex; + document->activeLayerKey = scopeone::core::ScopeOneCore::staticLayerKey( + document->cameraId); + document->page = new ImageDocumentPage(document->id, + document->title, + document->cameraId, + document->session->experimentDocument(), + document->frameCount, + m_viewerTabs); + connectViewer(document->page->previewWidget(), document->id); + connect(document->page, &ImageDocumentPage::frameIndexRequested, + this, &ImageWorkspace::requestDocumentFrame); + const QString id = document->id; + m_documents.push_back(std::move(document)); + m_viewerTabs->addTab(m_documents.back()->page, m_documents.back()->title); + if (!requestFrame(*m_documents.back(), source->frameIndex)) + { + removeDocument(id); + return {}; + } + emit documentsChanged(); + return id; + } + + void ImageWorkspace::requestDocumentFrame(const QString& documentId, int frameIndex) + { + Document* document = findDocument(documentId); + if (!document) + { + return; + } + requestFrame(*document, frameIndex); + if (!comparisonActive() || !m_linkFramesAction->isChecked()) + { + return; + } + const QString peerId = documentId == m_compareLeftDocumentId + ? m_compareRightDocumentId + : documentId == m_compareRightDocumentId + ? m_compareLeftDocumentId + : QString{}; + if (Document* peer = findDocument(peerId)) + { + requestFrame(*peer, frameIndex); + } + } + + bool ImageWorkspace::requestFrame(Document& document, int frameIndex) + { + document.requestedFrameIndex = qBound(0, frameIndex, document.frameCount - 1); + if (document.frameRequestId != 0) + { + return true; + } + document.frameRequestId = m_core->requestRecordingSessionFrame( + document.session, document.cameraId, document.requestedFrameIndex); + if (document.frameRequestId != 0) + { + m_frameRequests.insert(document.frameRequestId, document.id); + return true; + } + return false; + } + + void ImageWorkspace::removeDocument(const QString& documentId) + { + if (comparisonActive() + && (documentId == m_compareLeftDocumentId + || documentId == m_compareRightDocumentId)) + { + endComparison(); + } + const auto it = std::find_if(m_documents.begin(), m_documents.end(), + [&documentId](const auto& document) + { + return document->id == documentId; + }); + if (it == m_documents.end()) + { + return; + } + if ((*it)->frameRequestId != 0) + { + m_frameRequests.remove((*it)->frameRequestId); + } + for (auto request = m_processingRequests.constBegin(); + request != m_processingRequests.constEnd(); ++request) + { + if (request.value() == documentId) + { + m_core->cancelProcessingRequest(request.key()); + } + } + const int tabIndex = m_viewerTabs->indexOf((*it)->page); + if (tabIndex >= 0) + { + m_viewerTabs->removeTab(tabIndex); + } + delete (*it)->page; + m_documents.erase(it); + if (m_activeDocumentId == documentId) + { + if (Document* active = findDocumentByPage(m_viewerTabs->currentWidget())) + { + m_activeDocumentId = active->id; + } + else + { + m_activeDocumentId.clear(); + } + emit activeDocumentChanged(m_activeDocumentId); + emit activeViewerChanged(); + emit activeLayerChanged(activeLayerKey()); + updateViewerToolbar(); + } + emit documentsChanged(); + } + + void ImageWorkspace::connectViewer(PreviewWidget* preview, const QString& documentId) + { + connect(preview, &PreviewWidget::activated, this, [this, documentId]() + { + if (documentId.isEmpty()) + { + activateLiveViewer(); + } + else + { + setActiveDocument(documentId); + } + }); + connect(preview, &PreviewWidget::measurementLineDrawn, + this, &ImageWorkspace::measurementLineDrawn); + connect(preview, &PreviewWidget::measurementLineInspected, + this, &ImageWorkspace::measurementLineInspected); + connect(preview, &PreviewWidget::measurementLineCleared, + this, &ImageWorkspace::measurementLineCleared); + connect(preview, &PreviewWidget::mousePositionChanged, + this, [this, documentId](const QPoint& position) + { + if (documentId == m_activeDocumentId) + { + emit mousePositionChanged(position); + } + }); + auto* scene = preview->sceneModel(); + connect(preview, &PreviewWidget::availableLayerKeysChanged, + this, [this](const QStringList&) { updateViewerToolbar(); }); + connect(preview, &PreviewWidget::zoomLevelChanged, + this, [this](int) { updateViewerToolbar(); }); + connect(preview, &PreviewWidget::fitToWindowChanged, + this, [this](bool) { updateViewerToolbar(); }); + connect(preview, &PreviewWidget::layerLayoutModeChanged, + this, [this](PreviewWidget::LayerLayoutMode) { updateViewerToolbar(); }); + connect(scene, &scopeone::core::ImageSceneModel::layersChanged, + this, [this, documentId]() { syncActiveLayer(documentId); }); + if (documentId.isEmpty()) + { + return; + } + connect(scene, &scopeone::core::ImageSceneModel::markupsChanged, + this, [this, documentId]() + { + Document* document = findDocument(documentId); + if (document && documentId == m_activeDocumentId) + { + updateLineProfile(*document); + } + }); + } + + void ImageWorkspace::updateLineProfile(Document& document) + { + for (const auto& markup : document.page->sceneModel()->markups()) + { + if (markup.role == scopeone::core::ImageSceneModel::MarkupRole::CrossSection) + { + QVector values; + if (lineProfile(markup.layerKey, markup.start, markup.end, values)) + { + emit lineProfileUpdated(markup.layerKey, values); + } + return; + } + } + } +} + +#include "ImageWorkspace.moc" diff --git a/src/ImageWorkspace.h b/src/ImageWorkspace.h new file mode 100644 index 0000000..b7d9a7c --- /dev/null +++ b/src/ImageWorkspace.h @@ -0,0 +1,179 @@ +#pragma once + +#include "scopeone/ScopeOneCore.h" + +#include +#include +#include +#include +#include +#include + +class QAction; +class QComboBox; +class QGroupBox; +class QLabel; +class QStackedWidget; +class QWidget; +class QTabWidget; +class QToolBar; + +namespace scopeone::core +{ + class ImageSceneModel; +} + +namespace scopeone::ui +{ + class PreviewWidget; + + struct ImageDocumentInfo + { + QString id; + QString title; + QString sessionId; + QString cameraId; + int frameIndex{0}; + int frameCount{0}; + bool ready{false}; + bool active{false}; + + bool isValid() const { return !id.isEmpty(); } + }; + + class ImageWorkspace : public QObject + { + Q_OBJECT + + public: + explicit ImageWorkspace(scopeone::core::ScopeOneCore* core, + QWidget* windowParent, + QObject* parent = nullptr); + ~ImageWorkspace() override; + + QWidget* viewerHost() const; + void setLiveViewer(PreviewWidget* previewWidget); + void activateLiveViewer(); + void setVisibleLayers(const QStringList& layerKeys, bool sideBySide = false); + + QStringList openSession( + const std::shared_ptr& session, + const QString& title = QString(), + const QString& cameraId = QString()); + QString openFrame(const scopeone::core::ImageFrame& frame, + const QString& title); + QList documents() const; + ImageDocumentInfo document(const QString& documentId = QString()) const; + QString activeDocumentId() const; + bool isLiveViewerActive() const; + scopeone::core::ImageSceneModel* activeSceneModel() const; + PreviewWidget* activePreviewWidget() const; + scopeone::core::ImageFrame frameForLayer(const QString& layerKey) const; + bool histogram(const QString& layerKey, + scopeone::core::ScopeOneCore::HistogramStats& stats) const; + void requestHistogram(const QString& layerKey); + bool autoLayerLevels(const QString& layerKey); + bool fullLayerLevels(const QString& layerKey); + bool setLayerAutoStretchEnabled(const QString& layerKey, bool enabled); + bool layerAutoStretchEnabled(const QString& layerKey) const; + bool lineProfile(const QString& layerKey, + const QPoint& start, + const QPoint& end, + QVector& values) const; + bool pixelValue(const QString& layerKey, const QPoint& point, int& value) const; + double pixelSizeUm(const QString& layerKey) const; + QString activeLayerKey() const; + void setActiveLayerKey(const QString& layerKey); + scopeone::core::ImageSceneModel* sceneModel(const QString& documentId) const; + PreviewWidget* previewWidget(const QString& documentId) const; + scopeone::core::ImageFrame currentFrame(const QString& documentId = QString()) const; + bool activateDocument(const QString& documentId); + bool closeDocument(const QString& documentId); + quint64 processDocument(const QString& documentId, bool completeStack); + bool saveDocument(const QString& documentId, + const scopeone::core::ScopeOneCore::RecordingSaveOptions& options); + void saveDocumentAs(const QString& documentId = QString()); + + signals: + void documentsChanged(); + void activeDocumentChanged(const QString& documentId); + void activeViewerChanged(); + void activeLayerChanged(const QString& layerKey); + void activeFrameChanged(); + void histogramReady(const QString& layerKey, + const scopeone::core::ScopeOneCore::HistogramStats& stats); + void mousePositionChanged(const QPoint& widgetPos); + void documentProcessingProgress(quint64 requestId, qint64 completed, qint64 total); + void documentProcessingFinished(quint64 requestId, + const QString& outputDocumentId, + const QString& errorMessage); + void documentSaveFinished(const QString& documentId, + bool success, + const QString& message); + void sessionAvailable( + const std::shared_ptr& session, + const QString& title); + void measurementLineDrawn(const QString& layerKey, + const QPoint& start, + const QPoint& end); + void measurementLineInspected(const QString& layerKey, + const QPoint& start, + const QPoint& end); + void measurementLineCleared(); + void lineProfileUpdated(const QString& layerKey, const QVector& values); + + private: + struct Document; + Document* findDocument(const QString& documentId) const; + Document* findDocumentByPage(QWidget* page) const; + QString duplicateDocument(const QString& documentId); + bool requestFrame(Document& document, int frameIndex); + void requestDocumentFrame(const QString& documentId, int frameIndex); + void removeDocument(const QString& documentId); + void connectViewer(PreviewWidget* previewWidget, const QString& documentId); + void setupViewerToolbar(); + void updateViewerToolbar(); + void setActiveDocument(const QString& documentId); + bool beginComparison(const QString& rightDocumentId = QString()); + void endComparison(); + void rebuildViewerTabs(); + bool comparisonActive() const; + void syncActiveLayer(const QString& documentId); + void updateLineProfile(Document& document); + void queueHistogramRequest(const QString& layerKey, bool applyAutoLevels); + void startHistogramRequest(); + + scopeone::core::ScopeOneCore* m_core{nullptr}; + QWidget* m_viewerHost{nullptr}; + QToolBar* m_viewerToolbar{nullptr}; + QStackedWidget* m_viewerStack{nullptr}; + QTabWidget* m_viewerTabs{nullptr}; + QWidget* m_compareWidget{nullptr}; + QGroupBox* m_compareLeftHost{nullptr}; + QGroupBox* m_compareRightHost{nullptr}; + QAction* m_fitToWindowAction{nullptr}; + QAction* m_oneToOneAction{nullptr}; + QAction* m_compareAction{nullptr}; + QAction* m_linkFramesAction{nullptr}; + QLabel* m_viewerContextLabel{nullptr}; + QComboBox* m_layoutCombo{nullptr}; + QComboBox* m_compareDocumentCombo{nullptr}; + int m_liveTabIndex{-1}; + PreviewWidget* m_livePreviewWidget{nullptr}; + std::vector> m_documents; + QString m_activeDocumentId; + QString m_compareLeftDocumentId; + QString m_compareRightDocumentId; + QString m_liveLayerKey; + QHash m_frameRequests; + QHash m_processingRequests; + QHash m_saveRequests; + QString m_histogramDocumentId; + QString m_histogramLayerKey; + int m_histogramFrameIndex{-1}; + scopeone::core::ImageFrame m_histogramFrame; + bool m_histogramApplyAutoLevels{false}; + bool m_histogramRunning{false}; + quint64 m_histogramGeneration{0}; + }; +} diff --git a/src/InspectWidget.cpp b/src/InspectWidget.cpp index e70cfd0..cbbe43e 100644 --- a/src/InspectWidget.cpp +++ b/src/InspectWidget.cpp @@ -1,9 +1,11 @@ #include "InspectWidget.h" +#include "ImageWorkspace.h" #include "scopeone/ImageSceneModel.h" #include #include #include +#include #include #include #include @@ -18,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +41,15 @@ namespace scopeone::ui : QStringLiteral("raw"); } + QString inspectLayerTitle(const QString& layerKey, bool active) + { + const QString cameraId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey); + return QStringLiteral("%1 - %2 [%3]") + .arg(active ? QStringLiteral("Active Layer") : QStringLiteral("Layer"), + cameraId, + inspectLayerSourceLabel(layerKey)); + } + // Check whether a preview layer can use live core inspection bool isLiveLayerKey(const QString& layerKey) { @@ -71,6 +83,11 @@ namespace scopeone::ui update(); } + QVector values() const + { + return m_values; + } + protected: // Paint the cross section curve and its summary labels void paintEvent(QPaintEvent*) override @@ -214,9 +231,9 @@ namespace scopeone::ui const QRect rect = this->rect().adjusted( yLabelWidth + 6, - labelHeight / 2 + 2, + labelHeight + 2, -(xLabelWidth / 2 + 4), - -(labelHeight + 8)); + -(2 * labelHeight + 8)); painter.fillRect(rect, colors.brush(QPalette::Base)); painter.setPen(QPen(colors.color(QPalette::Mid), 1)); @@ -416,6 +433,20 @@ namespace scopeone::ui painter.drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, label); painter.setPen(QPen(axisColor, 1)); } + + painter.setPen(textColor); + painter.drawText(QRect(rect.left(), + rect.bottom() + labelHeight + 5, + rect.width(), + labelHeight), + Qt::AlignCenter, + QStringLiteral("Intensity")); + painter.drawText(QRect(0, + rect.top() - labelHeight, + rect.left() - 8, + labelHeight), + Qt::AlignRight | Qt::AlignVCenter, + QStringLiteral("Count")); } QHash m_layerData; @@ -423,13 +454,16 @@ namespace scopeone::ui }; // Create the inspection panel and subscribe to core analysis signals - InspectWidget::InspectWidget(scopeone::core::ScopeOneCore* core, QWidget* parent) + InspectWidget::InspectWidget(scopeone::core::ScopeOneCore* core, + ImageWorkspace* workspace, + QWidget* parent) : QWidget(parent) , m_scopeonecore(core) + , m_workspace(workspace) { - if (!core) + if (!core || !workspace) { - qFatal("InspectWidget requires ScopeOneCore"); + qFatal("InspectWidget requires ScopeOneCore and ImageWorkspace"); } setWindowTitle(QStringLiteral("Inspect")); @@ -444,6 +478,78 @@ namespace scopeone::ui this, &InspectWidget::setLayerCrossSectionProfile); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::lineProfileCleared, this, &InspectWidget::clearCrossSectionProfile); + connect(m_workspace, &ImageWorkspace::activeViewerChanged, + this, &InspectWidget::refreshActiveViewer); + connect(m_workspace, &ImageWorkspace::activeFrameChanged, + this, [this]() + { + if (m_workspace->isLiveViewerActive() || currentLayerKey().isEmpty()) + { + return; + } + m_workspace->requestHistogram(currentLayerKey()); + requestVisibleHistograms(); + }); + connect(m_workspace, &ImageWorkspace::activeLayerChanged, + this, [this](const QString&) + { + const QString layerKey = currentLayerKey(); + if (m_workspace->isLiveViewerActive()) + { + m_scopeonecore->setActiveHistogramLayer(layerKey); + } + else if (!layerKey.isEmpty()) + { + m_workspace->requestHistogram(layerKey); + } + requestVisibleHistograms(); + updateLayerVisibility(); + updateControlsState(); + }); + connect(m_workspace, &ImageWorkspace::histogramReady, + this, [this](const QString& layerKey, + const scopeone::core::ScopeOneCore::HistogramStats& stats) + { + if (layerKey == currentLayerKey() && !m_workspace->isLiveViewerActive()) + { + setLayerInspect(layerKey, stats); + if (m_workspace->layerAutoStretchEnabled(layerKey)) + { + m_sceneModel->setLayerDisplayLevels( + layerKey, + stats.autoMinLevel, + stats.autoMaxLevel, + stats.maxValue); + } + } + }); + connect(m_workspace, &ImageWorkspace::lineProfileUpdated, + this, &InspectWidget::setLayerCrossSectionProfile); + refreshActiveViewer(); + } + + void InspectWidget::refreshActiveViewer() + { + saveViewerState(); + const bool inspectLive = m_workspace->isLiveViewerActive(); + const QString viewerStateId = m_workspace->activeDocumentId(); + if (m_inspectingLive && !inspectLive) + { + m_scopeonecore->setActiveHistogramLayer({}); + } + m_inspectingLive = inspectLive; + m_activeViewerStateId = viewerStateId; + restoreViewerState(); + if (m_sceneModel) + { + disconnect(m_sceneModel, nullptr, this, nullptr); + } + m_sceneModel = m_workspace->activeSceneModel(); + setAvailableLayers(m_sceneModel ? m_sceneModel->layerIds() : QStringList{}); + if (!m_sceneModel) + { + return; + } const auto refreshLayerDisplay = [this](const QString& layerKey) { const auto state = m_layerStates.constFind(layerKey); @@ -452,50 +558,103 @@ namespace scopeone::ui updateLayerInspect(layerKey, state->stats); } }; - connect(m_scopeonecore->imageSceneModel(), - &scopeone::core::ImageSceneModel::layerDisplayChanged, - this, refreshLayerDisplay); - connect(m_scopeonecore->imageSceneModel(), - &scopeone::core::ImageSceneModel::layerAutoStretchChanged, + connect(m_sceneModel, &scopeone::core::ImageSceneModel::layerDisplayChanged, + this, [this, refreshLayerDisplay](const QString& layerKey) + { + refreshLayerDisplay(layerKey); + updateLayerVisibility(); + updateControlsState(); + }); + connect(m_sceneModel, &scopeone::core::ImageSceneModel::layerAutoStretchChanged, this, [refreshLayerDisplay](const QString& layerKey, bool) { refreshLayerDisplay(layerKey); }); + connect(m_sceneModel, &scopeone::core::ImageSceneModel::layersChanged, + this, [this]() + { + setAvailableLayers(m_sceneModel->layerIds()); + }); + const QString layerKey = currentLayerKey(); + if (m_workspace->isLiveViewerActive()) + { + m_scopeonecore->setActiveHistogramLayer(layerKey); + } + else if (!layerKey.isEmpty()) + { + m_workspace->requestHistogram(layerKey); + } + updateLayerVisibility(); + for (auto it = m_layerStates.cbegin(); it != m_layerStates.cend(); ++it) + { + if (it->hasStats) + { + updateLayerInspect(it.key(), it->stats); + } + } + requestVisibleHistograms(); + updateControlsState(); } - InspectWidget::~InspectWidget() + void InspectWidget::saveViewerState() { - m_scopeonecore->setActiveHistogramLayer({}); + ViewerInspectState& state = m_viewerStates[m_activeViewerStateId]; + state.layerStates = m_layerStates; + state.crossSectionLayerKey = m_crossSectionLayerKey; + state.crossSectionValues = m_crossSectionWidget->values(); + state.measurementLayerKey = m_measurementLayerKey; + state.measurementInfo = m_measurementInfoLabel->text(); } - // Enable inspect controls when camera state changes - void InspectWidget::onCameraInitialized(bool initialized) + void InspectWidget::restoreViewerState() { - m_cameraInitialized = initialized; - updateControlsState(); + const auto it = m_viewerStates.constFind(m_activeViewerStateId); + if (it == m_viewerStates.constEnd()) + { + m_layerStates.clear(); + m_crossSectionLayerKey.clear(); + m_crossSectionWidget->clear(); + m_measurementLayerKey.clear(); + m_measurementInfoLabel->clear(); + m_measurementInfoLabel->hide(); + return; + } - if (!initialized) + const ViewerInspectState& state = it.value(); + m_layerStates = state.layerStates; + m_crossSectionLayerKey = state.crossSectionLayerKey; + if (state.crossSectionLayerKey.isEmpty() || state.crossSectionValues.isEmpty()) { - clearCrossSectionProfile(); + m_crossSectionWidget->clear(); + } + else + { + m_crossSectionWidget->setProfile( + state.crossSectionLayerKey, state.crossSectionValues); } + m_measurementLayerKey = state.measurementLayerKey; + m_measurementInfoLabel->setText(state.measurementInfo); + m_measurementInfoLabel->setVisible(!state.measurementInfo.isEmpty()); } - // Track the currently selected preview layer - void InspectWidget::setCurrentLayer(const QString& layerKey) + InspectWidget::~InspectWidget() { - m_currentLayerKey = layerKey.trimmed(); - m_scopeonecore->setActiveHistogramLayer(m_currentLayerKey); - if (!m_crossSectionLayerKey.isEmpty() && m_crossSectionLayerKey != m_currentLayerKey) + if (m_workspace->isLiveViewerActive()) { - clearCrossSectionProfile(); - emit requestClearCrossSection(); + m_scopeonecore->setActiveHistogramLayer({}); } - if (!m_measurementLayerKey.isEmpty() && m_measurementLayerKey != m_currentLayerKey) + } + + // Enable inspect controls when camera state changes + void InspectWidget::onCameraInitialized(bool initialized) + { + m_cameraInitialized = initialized; + updateControlsState(); + + if (!initialized) { - clearMeasurementLine(); + clearCrossSectionProfile(); } - updateLayerVisibility(); - updateControlsState(); } // Remove inspect state for layers that are no longer available @@ -528,6 +687,11 @@ namespace scopeone::ui removeLayerInfo(key); } + for (const QString& key : m_availableLayerKeys) + { + addLayerInfo(key); + } + if (!m_crossSectionLayerKey.isEmpty() && !m_availableLayerKeys.contains(m_crossSectionLayerKey)) { clearCrossSectionProfile(); @@ -538,14 +702,21 @@ namespace scopeone::ui clearMeasurementLine(); } - if (!m_currentLayerKey.isEmpty() && !m_availableLayerKeys.contains(m_currentLayerKey)) + if (!currentLayerKey().isEmpty() && !m_availableLayerKeys.contains(currentLayerKey())) { - m_currentLayerKey.clear(); - m_scopeonecore->setActiveHistogramLayer({}); + if (m_workspace->isLiveViewerActive()) + { + m_scopeonecore->setActiveHistogramLayer({}); + } clearCrossSectionProfile(); } updateLayerVisibility(); updateControlsState(); + + if (m_workspace->isLiveViewerActive()) + { + m_scopeonecore->setActiveHistogramLayer(currentLayerKey()); + } } // Store live camera availability for core backed tools @@ -562,23 +733,20 @@ namespace scopeone::ui emit requestClearCrossSection(); } - if (!m_currentLayerKey.isEmpty() - && isLiveLayerKey(m_currentLayerKey) + if (!currentLayerKey().isEmpty() + && isLiveLayerKey(currentLayerKey()) && !m_availableCameraIds.contains(currentLayerCameraId())) { - m_currentLayerKey.clear(); - m_scopeonecore->setActiveHistogramLayer({}); + if (m_workspace->isLiveViewerActive()) + { + m_scopeonecore->setActiveHistogramLayer({}); + } clearCrossSectionProfile(); } updateLayerVisibility(); updateControlsState(); } - void InspectWidget::setCrossSectionVisible(bool visible) - { - m_crossSectionGroup->setVisible(visible); - } - // Show inspect data for an explicit preview layer void InspectWidget::setLayerInspect( const QString& layerKey, @@ -597,14 +765,6 @@ namespace scopeone::ui updateLayerInspect(trimmedLayerKey, stats); } - // Clear all layer inspect groups - void InspectWidget::clearInspect() - { - clearMeasurementLine(); - setAvailableLayers({}); - setAvailableCameras({}); - } - // Remove cached inspect data for one graph layer void InspectWidget::clearLayerInspect(const QString& layerKey) { @@ -616,7 +776,7 @@ namespace scopeone::ui m_layerStates.remove(trimmedLayerKey); removeLayerInfo(trimmedLayerKey); - if (m_currentLayerKey == trimmedLayerKey) + if (currentLayerKey() == trimmedLayerKey) { clearCrossSectionProfile(); } @@ -652,6 +812,7 @@ namespace scopeone::ui } QStringList lines{ + QStringLiteral("Layer: %1").arg(m_measurementLayerKey), QStringLiteral("Start: (%1, %2)").arg(start.x()).arg(start.y()), QStringLiteral("Angle: %1°").arg(angleDegrees, 0, 'f', 1), QStringLiteral("Length: %1 px").arg(lengthPixels, 0, 'f', 2) @@ -661,6 +822,10 @@ namespace scopeone::ui lines.append(QStringLiteral("Actual: %1 µm") .arg(actualLengthUm, 0, 'f', 3)); } + else + { + lines.append(QStringLiteral("Scale: not calibrated")); + } m_measurementInfoLabel->setText(lines.join('\n')); m_measurementInfoLabel->show(); } @@ -677,7 +842,7 @@ namespace scopeone::ui void InspectWidget::setLayerCrossSectionProfile(const QString& layerKey, const QVector& values) { const QString trimmedLayerKey = layerKey.trimmed(); - if (trimmedLayerKey.isEmpty() || trimmedLayerKey != m_currentLayerKey) + if (trimmedLayerKey.isEmpty() || trimmedLayerKey != currentLayerKey()) { return; } @@ -707,7 +872,7 @@ namespace scopeone::ui auto* annotationGroup = new QGroupBox(QStringLiteral("Annotation"), contentContainer); auto* annotationLayout = new QVBoxLayout(annotationGroup); auto* annotationButtons = new QHBoxLayout(); - m_drawMeasurementLineButton = new QPushButton(QStringLiteral("Line"), annotationGroup); + m_drawMeasurementLineButton = new QPushButton(QStringLiteral("Measure Line"), annotationGroup); m_clearMeasurementLinesButton = new QPushButton(QStringLiteral("Clear"), annotationGroup); annotationButtons->addWidget(m_drawMeasurementLineButton); annotationButtons->addWidget(m_clearMeasurementLinesButton); @@ -720,8 +885,8 @@ namespace scopeone::ui m_crossSectionGroup = new QGroupBox(QStringLiteral("Cross Section"), contentContainer); auto* crossSectionLayout = new QVBoxLayout(m_crossSectionGroup); auto* crossSectionButtons = new QHBoxLayout(); - m_drawCrossSectionButton = new QPushButton(QStringLiteral("Draw Cross Section"), m_crossSectionGroup); - m_clearCrossSectionButton = new QPushButton(QStringLiteral("Clear Cross Section"), m_crossSectionGroup); + m_drawCrossSectionButton = new QPushButton(QStringLiteral("Intensity Profile"), m_crossSectionGroup); + m_clearCrossSectionButton = new QPushButton(QStringLiteral("Clear Profile"), m_crossSectionGroup); crossSectionButtons->addWidget(m_drawCrossSectionButton); crossSectionButtons->addWidget(m_clearCrossSectionButton); crossSectionButtons->addStretch(); @@ -732,17 +897,28 @@ namespace scopeone::ui m_histogramContainerLayout = new QVBoxLayout(); m_histogramContainerLayout->setSpacing(10); + m_compareLayersCheckBox = new QCheckBox(QStringLiteral("Show all visible layers"), contentContainer); + connect(m_compareLayersCheckBox, &QCheckBox::toggled, + this, [this](bool enabled) + { + updateLayerVisibility(); + if (enabled) + { + requestVisibleHistograms(); + } + }); + contentLayout->addWidget(m_compareLayersCheckBox); contentLayout->addLayout(m_histogramContainerLayout); contentLayout->addStretch(); connect(m_drawCrossSectionButton, &QPushButton::clicked, this, [this]() { - if (m_currentLayerKey.isEmpty()) + if (currentLayerKey().isEmpty()) { return; } - m_crossSectionLayerKey = m_currentLayerKey; - emit requestDrawCrossSectionLayer(m_currentLayerKey); + m_crossSectionLayerKey = currentLayerKey(); + emit requestDrawCrossSectionLayer(currentLayerKey()); }); connect(m_clearCrossSectionButton, &QPushButton::clicked, this, [this]() { @@ -751,11 +927,12 @@ namespace scopeone::ui }); connect(m_drawMeasurementLineButton, &QPushButton::clicked, this, [this]() { - emit requestDrawMeasurementLine(m_currentLayerKey); + emit requestDrawMeasurementLine(currentLayerKey()); }); connect(m_clearMeasurementLinesButton, &QPushButton::clicked, this, [this]() { - emit requestClearMeasurementLines(m_currentLayerKey); + emit requestClearMeasurementLines( + m_measurementLayerKey.isEmpty() ? currentLayerKey() : m_measurementLayerKey); }); scrollArea->setWidget(contentContainer); @@ -766,10 +943,7 @@ namespace scopeone::ui QWidget* InspectWidget::createLayerInfoGroup(const QString& layerKey) { const QString normalizedLayerKey = layerKey.trimmed(); - const QString cameraId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(normalizedLayerKey); - auto* group = new QGroupBox( - QStringLiteral("Layer - %1 [%2]").arg(cameraId, inspectLayerSourceLabel(normalizedLayerKey)), - this); + auto* group = new QGroupBox(inspectLayerTitle(normalizedLayerKey, false), this); auto* layout = new QVBoxLayout(group); LayerInfoGroup infoGroup; infoGroup.layerKey = normalizedLayerKey; @@ -814,20 +988,11 @@ namespace scopeone::ui layout->addLayout(slidersLayout); auto* histControlLayout = new QHBoxLayout(); - auto* autoButton = new QPushButton(QStringLiteral("Auto"), group); - auto* fullButton = new QPushButton(QStringLiteral("Full"), group); - auto* autoStretchCheckBox = new QCheckBox(QStringLiteral("Auto-stretch"), group); auto* logScaleCheckBox = new QCheckBox(QStringLiteral("Log hist"), group); - histControlLayout->addWidget(autoButton); - histControlLayout->addWidget(fullButton); - histControlLayout->addWidget(autoStretchCheckBox); histControlLayout->addWidget(logScaleCheckBox); histControlLayout->addStretch(); layout->addLayout(histControlLayout); - infoGroup.autoButton = autoButton; - infoGroup.fullButton = fullButton; - infoGroup.autoStretchCheckBox = autoStretchCheckBox; infoGroup.logScaleCheckBox = logScaleCheckBox; infoGroup.minSlider = minSlider; infoGroup.maxSlider = maxSlider; @@ -836,18 +1001,6 @@ namespace scopeone::ui layout->addWidget(createStatisticsGroup(infoGroup)); m_layerInfoGroups.insert(normalizedLayerKey, infoGroup); - connect(autoButton, &QPushButton::clicked, this, [this, normalizedLayerKey]() - { - onAutoButtonClicked(normalizedLayerKey); - }); - connect(fullButton, &QPushButton::clicked, this, [this, normalizedLayerKey]() - { - onFullButtonClicked(normalizedLayerKey); - }); - connect(autoStretchCheckBox, &QCheckBox::toggled, this, [this, normalizedLayerKey](bool checked) - { - onAutoStretchChanged(normalizedLayerKey, checked); - }); connect(logScaleCheckBox, &QCheckBox::toggled, this, [this, normalizedLayerKey](bool checked) { onLogScaleChanged(normalizedLayerKey, checked); @@ -967,7 +1120,7 @@ namespace scopeone::ui } scopeone::core::DocumentLayer layer; - if (!m_scopeonecore->imageSceneModel()->findLayer(normalizedLayerKey, layer)) + if (!m_sceneModel || !m_sceneModel->findLayer(normalizedLayerKey, layer)) { return; } @@ -988,34 +1141,10 @@ namespace scopeone::ui } infoGroup.minSliderValueLabel->setText(QString::number(displayMin)); infoGroup.maxSliderValueLabel->setText(QString::number(displayMax)); - { - QSignalBlocker blocker(infoGroup.autoStretchCheckBox); - infoGroup.autoStretchCheckBox->setChecked( - m_scopeonecore->layerAutoStretchEnabled(normalizedLayerKey)); - } - updateStatisticsDisplay(normalizedLayerKey, stats); updateControlsState(); } - // Apply the computed auto display range once - void InspectWidget::onAutoButtonClicked(const QString& layerKey) - { - m_scopeonecore->autoLayerLevels(layerKey); - } - - // Expand the display range to the full pixel range - void InspectWidget::onFullButtonClicked(const QString& layerKey) - { - m_scopeonecore->fullLayerLevels(layerKey); - } - - // Toggle continuous auto stretch for one layer - void InspectWidget::onAutoStretchChanged(const QString& layerKey, bool checked) - { - m_scopeonecore->setLayerAutoStretchEnabled(layerKey, checked); - } - // Toggle logarithmic histogram scaling for one layer void InspectWidget::onLogScaleChanged(const QString& layerKey, bool checked) { @@ -1060,19 +1189,20 @@ namespace scopeone::ui // Enable controls according to live camera and selected layer state void InspectWidget::updateControlsState() { - const auto currentState = m_layerStates.constFind(m_currentLayerKey); + const QString layerKey = currentLayerKey(); + const auto currentState = m_layerStates.constFind(layerKey); const bool currentLayerHasStats = currentState != m_layerStates.constEnd() && currentState.value().hasStats; const bool liveCrossSectionEnabled = m_cameraInitialized - && isLiveLayerKey(m_currentLayerKey) + && isLiveLayerKey(layerKey) && m_availableCameraIds.contains(currentLayerCameraId()); - const bool staticCrossSectionEnabled = scopeone::core::ScopeOneCore::isStaticLayerKey(m_currentLayerKey) + const bool staticCrossSectionEnabled = scopeone::core::ScopeOneCore::isStaticLayerKey(layerKey) && currentLayerHasStats; - const bool crossSectionEnabled = !m_currentLayerKey.isEmpty() + const bool crossSectionEnabled = !layerKey.isEmpty() && (liveCrossSectionEnabled || staticCrossSectionEnabled); m_drawCrossSectionButton->setEnabled(crossSectionEnabled); - m_clearCrossSectionButton->setEnabled(m_cameraInitialized || !m_currentLayerKey.isEmpty()); - const bool annotationEnabled = !m_currentLayerKey.isEmpty() - && m_availableLayerKeys.contains(m_currentLayerKey); + m_clearCrossSectionButton->setEnabled(m_cameraInitialized || !layerKey.isEmpty()); + const bool annotationEnabled = !layerKey.isEmpty() + && m_availableLayerKeys.contains(layerKey); m_drawMeasurementLineButton->setEnabled(annotationEnabled); m_clearMeasurementLinesButton->setEnabled(annotationEnabled); @@ -1081,9 +1211,9 @@ namespace scopeone::ui LayerInfoGroup& infoGroup = it.value(); const auto stateIt = m_layerStates.constFind(infoGroup.layerKey); const bool hasStats = stateIt != m_layerStates.constEnd() && stateIt.value().hasStats; - infoGroup.autoButton->setEnabled(hasStats); - infoGroup.fullButton->setEnabled(hasStats); - infoGroup.autoStretchCheckBox->setEnabled(hasStats); + const bool isActiveLayer = infoGroup.layerKey == layerKey; + infoGroup.minSlider->setEnabled(hasStats && isActiveLayer); + infoGroup.maxSlider->setEnabled(hasStats && isActiveLayer); infoGroup.logScaleCheckBox->setEnabled(hasStats); } } @@ -1091,12 +1221,93 @@ namespace scopeone::ui // Shows inspect controls for the selected preview layer void InspectWidget::updateLayerVisibility() { + const QString layerKey = currentLayerKey(); + const QStringList visibleLayerKeys = m_sceneModel + ? m_sceneModel->visibleLayerIds() + : QStringList{}; + const bool visibleLayersChanged = visibleLayerKeys != m_visibleLayerKeys; + m_visibleLayerKeys = visibleLayerKeys; for (auto it = m_layerInfoGroups.begin(); it != m_layerInfoGroups.end(); ++it) { LayerInfoGroup& infoGroup = it.value(); - infoGroup.groupBox->setVisible(!m_currentLayerKey.isEmpty() - && infoGroup.layerKey == m_currentLayerKey); + const bool showLayer = visibleLayerKeys.contains(infoGroup.layerKey) + && (m_compareLayersCheckBox->isChecked() + || infoGroup.layerKey == layerKey); + infoGroup.groupBox->setVisible(showLayer); + infoGroup.groupBox->setTitle( + inspectLayerTitle(infoGroup.layerKey, infoGroup.layerKey == layerKey)); + } + if (visibleLayersChanged) + { + requestVisibleHistograms(); + } + } + + void InspectWidget::requestVisibleHistograms() + { + const quint64 generation = ++m_histogramRequestGeneration; + if (!m_compareLayersCheckBox->isChecked() + || !m_sceneModel + || m_visibleLayerKeys.isEmpty()) + { + return; + } + + const QString viewerId = m_activeViewerStateId; + const QString activeLayerKey = currentLayerKey(); + QHash frames; + for (const QString& layerKey : m_visibleLayerKeys) + { + if (layerKey == activeLayerKey) + { + continue; + } + const scopeone::core::ImageFrame frame = m_workspace->isLiveViewerActive() + ? m_scopeonecore->graphFrame(layerKey) + : m_workspace->frameForLayer(layerKey); + if (!frame.isValid()) + { + continue; + } + frames.insert(layerKey, frame); + } + if (frames.isEmpty()) + { + return; } + + auto* watcher = new QFutureWatcher>(this); + connect(watcher, &QFutureWatcherBase::finished, this, + [this, watcher, generation, viewerId]() + { + if (generation == m_histogramRequestGeneration + && viewerId == m_activeViewerStateId) + { + const auto results = watcher->result(); + for (auto it = results.cbegin(); it != results.cend(); ++it) + { + if (m_visibleLayerKeys.contains(it.key())) + { + setLayerInspect(it.key(), it.value()); + } + } + } + watcher->deleteLater(); + }); + watcher->setFuture(QtConcurrent::run([frames]() + { + QHash results; + for (auto it = frames.cbegin(); it != frames.cend(); ++it) + { + scopeone::core::ScopeOneCore::HistogramStats stats; + if (scopeone::core::ScopeOneCore::computeHistogramStats(it.value(), stats)) + { + results.insert(it.key(), stats); + } + } + return results; + })); } // Return persistent inspect state for one preview layer @@ -1139,13 +1350,18 @@ namespace scopeone::ui { return; } - m_scopeonecore->setLayerAutoStretchEnabled(layerKey, false); - m_scopeonecore->imageSceneModel()->setLayerDisplayLevels( + m_workspace->setLayerAutoStretchEnabled(layerKey, false); + m_sceneModel->setLayerDisplayLevels( layerKey, minValue, maxValue, qMax(1, state.stats.maxValue)); } QString InspectWidget::currentLayerCameraId() const { - return scopeone::core::ScopeOneCore::sourceIdFromLayerKey(m_currentLayerKey); + return scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey()); + } + + QString InspectWidget::currentLayerKey() const + { + return m_workspace ? m_workspace->activeLayerKey() : QString{}; } } // namespace scopeone::ui diff --git a/src/InspectWidget.h b/src/InspectWidget.h index 38ca46a..05877bb 100644 --- a/src/InspectWidget.h +++ b/src/InspectWidget.h @@ -19,6 +19,7 @@ class QVBoxLayout; namespace scopeone::ui { + class ImageWorkspace; class InspectCrossSectionWidget; class InspectHistogramWidget; @@ -34,17 +35,16 @@ namespace scopeone::ui bool hasStats{false}; }; - explicit InspectWidget(scopeone::core::ScopeOneCore* core, QWidget* parent = nullptr); + explicit InspectWidget(scopeone::core::ScopeOneCore* core, + ImageWorkspace* workspace, + QWidget* parent = nullptr); ~InspectWidget() override; void onCameraInitialized(bool initialized); - void setCurrentLayer(const QString& layerKey); void setAvailableLayers(const QStringList& layerKeys); void setAvailableCameras(const QStringList& cameraIds); - void setCrossSectionVisible(bool visible); void setLayerInspect(const QString& layerKey, const scopeone::core::ScopeOneCore::HistogramStats& stats); - void clearInspect(); void clearLayerInspect(const QString& layerKey); void clearCrossSectionProfile(); void setLayerCrossSectionProfile(const QString& layerKey, const QVector& values); @@ -53,6 +53,7 @@ namespace scopeone::ui const QPoint& end, double actualLengthUm); void clearMeasurementLine(); + void refreshActiveViewer(); signals: void requestDrawCrossSectionLayer(const QString& layerKey); @@ -66,9 +67,6 @@ namespace scopeone::ui QString layerKey; QGroupBox* groupBox{nullptr}; InspectHistogramWidget* histogramWidget{nullptr}; - QPushButton* autoButton{nullptr}; - QPushButton* fullButton{nullptr}; - QCheckBox* autoStretchCheckBox{nullptr}; QCheckBox* logScaleCheckBox{nullptr}; QSlider* minSlider{nullptr}; QSlider* maxSlider{nullptr}; @@ -81,6 +79,15 @@ namespace scopeone::ui QLabel* pixelCountLabel{nullptr}; }; + struct ViewerInspectState + { + QHash layerStates; + QString crossSectionLayerKey; + QVector crossSectionValues; + QString measurementLayerKey; + QString measurementInfo; + }; + void setupUI(); QWidget* createLayerInfoGroup(const QString& layerKey); QWidget* createStatisticsGroup(LayerInfoGroup& infoGroup); @@ -88,25 +95,29 @@ namespace scopeone::ui void removeLayerInfo(const QString& layerKey); void updateLayerInspect(const QString& layerKey, const scopeone::core::ScopeOneCore::HistogramStats& stats); - void onAutoButtonClicked(const QString& layerKey); - void onFullButtonClicked(const QString& layerKey); - void onAutoStretchChanged(const QString& layerKey, bool checked); void onLogScaleChanged(const QString& layerKey, bool checked); void updateStatisticsDisplay(const QString& layerKey, const scopeone::core::ScopeOneCore::HistogramStats& stats); void updateControlsState(); void updateLayerVisibility(); + void requestVisibleHistograms(); + void saveViewerState(); + void restoreViewerState(); + QString currentLayerKey() const; LayerInspectState& getOrCreateLayerState(const QString& layerKey); QColor getLayerColor(const QString& layerKey) const; void onLayerSliderChanged(const QString& layerKey, int minValue, int maxValue); QString currentLayerCameraId() const; scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; + ImageWorkspace* m_workspace{nullptr}; + scopeone::core::ImageSceneModel* m_sceneModel{nullptr}; QHash m_layerInfoGroups; QHash m_layerStates; QStringList m_availableLayerKeys; QStringList m_availableCameraIds; QVBoxLayout* m_histogramContainerLayout{nullptr}; + QCheckBox* m_compareLayersCheckBox{nullptr}; QPushButton* m_drawMeasurementLineButton{nullptr}; QPushButton* m_clearMeasurementLinesButton{nullptr}; QLabel* m_measurementInfoLabel{nullptr}; @@ -115,8 +126,12 @@ namespace scopeone::ui QPushButton* m_drawCrossSectionButton{nullptr}; QPushButton* m_clearCrossSectionButton{nullptr}; bool m_cameraInitialized{false}; - QString m_currentLayerKey; QString m_measurementLayerKey; QString m_crossSectionLayerKey; + QHash m_viewerStates; + QString m_activeViewerStateId; + QStringList m_visibleLayerKeys; + quint64 m_histogramRequestGeneration{0}; + bool m_inspectingLive{true}; }; } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 13b4b47..1ac3895 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -10,6 +10,7 @@ #include "PreviewWidget.h" #include "scopeone/ImageSceneModel.h" #include "ImageGalleryWidget.h" +#include "ImageWorkspace.h" #include "ImageToolsDialog.h" #include "ImageProcessingWidget.h" #include "RecordingWidget.h" @@ -40,7 +41,6 @@ #include #include #include -#include #include #include @@ -105,27 +105,6 @@ namespace scopeone::ui } } - // Build a stable key for one gallery session - QString gallerySessionKey( - const std::shared_ptr& session) - { - return session ? session->capturePlan().experimentId : QString(); - } - - // Build the static layer id for one recorded camera in a gallery session - QString gallerySessionLayerId( - const std::shared_ptr& session, - const QString& cameraId) - { - return QStringLiteral("gallery:%1:%2").arg(gallerySessionKey(session), cameraId); - } - - int uiFrameCount(qint64 frameCount) - { - return static_cast( - qBound(1, frameCount, static_cast((std::numeric_limits::max)()))); - } - // Detect the standard 64 bit Micro-Manager installation QString detectedMicroManagerDirectory() { @@ -164,17 +143,6 @@ namespace scopeone::ui } } - // Remove graph layers that belong to one gallery session - void removeGallerySessionPreview( - scopeone::core::ScopeOneCore& core, - const std::shared_ptr& session) - { - for (const QString& cameraId : session->recordedCameraIds()) - { - core.removeStaticFrame(gallerySessionLayerId(session, cameraId)); - } - } - void updateSessionPresentation( scopeone::core::ScopeOneCore& core, const scopeone::core::ImageSceneModel& sceneModel, @@ -206,9 +174,11 @@ namespace scopeone::ui } applyStoredApplicationSettings(); + m_imageWorkspace = new ImageWorkspace(core, this, this); setupUI(); + m_imageWorkspace->setLiveViewer(m_previewWidget); setupSignalWiring(); - new ScopeOneLocalApiServer(m_scopeonecore, m_previewWidget, this); + new ScopeOneLocalApiServer(m_scopeonecore, m_previewWidget, m_imageWorkspace, this); logStartupSummary(); setWindowTitle("ScopeOne"); @@ -252,14 +222,20 @@ namespace scopeone::ui m_closePendingSessions = unsavedSessions; m_closeSaveTotal = unsavedSessions.size(); m_closeSaveInProgress = true; + m_closeAfterSave = true; m_closeSaveProgress = new QProgressDialog( - tr("Saving gallery images..."), QString(), 0, m_closeSaveTotal, this); + tr("Saving gallery images..."), tr("Keep Open"), 0, m_closeSaveTotal, this); m_closeSaveProgress->setWindowTitle(tr("Saving")); - m_closeSaveProgress->setWindowModality(Qt::ApplicationModal); - m_closeSaveProgress->setCancelButton(nullptr); + m_closeSaveProgress->setWindowModality(Qt::WindowModal); m_closeSaveProgress->setMinimumDuration(0); m_closeSaveProgress->setAutoClose(false); m_closeSaveProgress->setValue(0); + connect(m_closeSaveProgress, &QProgressDialog::canceled, this, [this]() + { + m_closeAfterSave = false; + m_closeSaveProgress->deleteLater(); + m_closeSaveProgress = nullptr; + }); m_closeSaveProgress->show(); for (const auto& session : unsavedSessions) { @@ -286,7 +262,7 @@ namespace scopeone::ui !configurationRunning && !m_recentConfigurationsMenu->isEmpty()); m_propertyBrowser->setEnabled(!configurationRunning); m_configPresetWidget->setEnabled(!configurationRunning); - m_deviceControlWidget->setEnabled(!configurationRunning); + m_deviceControlWidget->setControlsEnabled(!configurationRunning); m_toolRegistry->setEnabled(!configurationRunning); const QStringList cameraIds = m_scopeonecore->cameraIds(); @@ -341,8 +317,6 @@ namespace scopeone::ui }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::configurationUnloadFinished, this, &MainWindow::handleConfigurationUnloadFinished); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::recordingSessionFrameReady, - this, &MainWindow::handleGalleryFrameReady); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::deviceStateChanged, this, [this]() { @@ -365,7 +339,7 @@ namespace scopeone::ui showStatusMessage(running ? tr("Live preview started") : tr("Live preview stopped"), 3000); }); - connect(m_previewWidget, &PreviewWidget::mousePositionChanged, + connect(m_imageWorkspace, &ImageWorkspace::mousePositionChanged, this, &MainWindow::handlePreviewMousePosition); connect(m_previewWidget, &PreviewWidget::roiDrawn, this, &MainWindow::handleRoiDrawn); @@ -402,20 +376,11 @@ namespace scopeone::ui this, [this](const QString& sourceId) { const QString layerKey = scopeone::core::ScopeOneCore::staticLayerKey(sourceId); - m_galleryLayerFrameControls.remove(layerKey); - m_galleryFrameRequests.remove(layerKey); - m_deviceControlWidget->removeLayerFrameControl(layerKey); m_previewWidget->removeStaticLayer(layerKey); }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::staticFramesCleared, this, [this]() { - m_galleryFrameRequests.clear(); - for (const QString& layerKey : m_galleryLayerFrameControls.keys()) - { - m_deviceControlWidget->removeLayerFrameControl(layerKey); - } - m_galleryLayerFrameControls.clear(); m_previewWidget->clearStaticLayers(); }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::liveFramesCleared, @@ -498,12 +463,6 @@ namespace scopeone::ui connect(m_deviceControlWidget, &DeviceControlWidget::controlTargetChanged, this, &MainWindow::updateControlTarget); - connect(m_deviceControlWidget, &DeviceControlWidget::currentLayerChanged, - m_inspectWidget, &InspectWidget::setCurrentLayer); - connect(m_deviceControlWidget, &DeviceControlWidget::previewLayerFrameRequested, - this, &MainWindow::updateGalleryLayerFrame); - connect(m_previewWidget, &PreviewWidget::availableLayerKeysChanged, - m_inspectWidget, &InspectWidget::setAvailableLayers); connect(m_deviceControlWidget, &DeviceControlWidget::exposureValueChanged, this, [this](double ms) { @@ -518,71 +477,56 @@ namespace scopeone::ui connect(m_inspectWidget, &InspectWidget::requestDrawCrossSectionLayer, this, [this](const QString& layerKey) { - m_previewWidget->startCrossSectionDrawingForLayer(layerKey); + m_imageWorkspace->activePreviewWidget()->startCrossSectionDrawingForLayer(layerKey); showStatusMessage(tr("Drag a line on the preview"), 5000); }); connect(m_inspectWidget, &InspectWidget::requestClearCrossSection, this, [this]() { - m_previewWidget->clearCrossSection(); + m_imageWorkspace->activePreviewWidget()->clearCrossSection(); }); connect(m_inspectWidget, &InspectWidget::requestDrawMeasurementLine, this, [this](const QString& layerKey) { - m_previewWidget->startMeasurementLineDrawingForLayer(layerKey); + m_imageWorkspace->activePreviewWidget()->startMeasurementLineDrawingForLayer(layerKey); showStatusMessage(tr("Drag a line on the preview"), 5000); }); connect(m_inspectWidget, &InspectWidget::requestClearMeasurementLines, this, [this](const QString& layerKey) { - m_imageSceneModel->clearRole(ImageSceneModel::MarkupRole::Measurement, layerKey); + m_imageWorkspace->activeSceneModel()->clearRole( + ImageSceneModel::MarkupRole::Measurement, layerKey); m_inspectWidget->clearMeasurementLine(); }); - connect(m_previewWidget, &PreviewWidget::measurementLineDrawn, + connect(m_imageWorkspace, &ImageWorkspace::measurementLineDrawn, this, [this](const QString& layerKey, const QPoint& start, const QPoint& end) { - const QString markupId = m_imageSceneModel->createLine( + ImageSceneModel* sceneModel = m_imageWorkspace->activeSceneModel(); + const QString markupId = sceneModel->createLine( layerKey, start, end, QString(), ImageSceneModel::MarkupRole::Measurement); - m_imageSceneModel->selectOnly(markupId); + sceneModel->selectOnly(markupId); showMeasurementLine(layerKey, start, end); }); - connect(m_previewWidget, &PreviewWidget::measurementLineInspected, + connect(m_imageWorkspace, &ImageWorkspace::measurementLineInspected, this, [this](const QString& layerKey, const QPoint& start, const QPoint& end) { showMeasurementLine(layerKey, start, end); }); - connect(m_previewWidget, &PreviewWidget::measurementLineCleared, + connect(m_imageWorkspace, &ImageWorkspace::measurementLineCleared, m_inspectWidget, &InspectWidget::clearMeasurementLine); - connect(m_imageSceneModel, &ImageSceneModel::markupsChanged, - this, [this]() - { - for (const ImageSceneModel::Markup& markup : m_imageSceneModel->markups()) - { - if (markup.selected - && markup.type == ImageSceneModel::MarkupType::Line - && markup.role == ImageSceneModel::MarkupRole::Measurement) - { - showMeasurementLine(markup.layerKey, markup.start, markup.end); - return; - } - } - m_inspectWidget->clearMeasurementLine(); - }); - - m_inspectWidget->setAvailableLayers(m_previewWidget->availableLayerKeys()); - m_inspectWidget->setCurrentLayer(m_deviceControlWidget->currentLayerKey()); connect(m_imageProcessingWidget, &ImageProcessingWidget::processingStarted, this, [this]() { + m_imageWorkspace->activateLiveViewer(); setStatusLabelText(m_statusProcessingLabel, tr("Processing: Live"), tr("Processing is running")); @@ -616,8 +560,7 @@ namespace scopeone::ui } } - m_imageSceneModel->setVisibleLayers(visibleLayerKeys); - m_previewWidget->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::SideBySide); + m_imageWorkspace->setVisibleLayers(visibleLayerKeys, true); }); connect(m_imageProcessingWidget, &ImageProcessingWidget::processingStopped, this, [this]() @@ -631,7 +574,7 @@ namespace scopeone::ui { visibleLayerKeys = rawLayerKeys(m_previewWidget->availableCameraIds()); } - m_imageSceneModel->setVisibleLayers(visibleLayerKeys); + m_imageWorkspace->setVisibleLayers(visibleLayerKeys); }); connect(m_imageProcessingWidget, &ImageProcessingWidget::processedLayerReady, this, [this](const QString& layerKey) @@ -642,7 +585,30 @@ namespace scopeone::ui connect(m_imageProcessingWidget, &ImageProcessingWidget::processedStackReady, this, [this](const std::shared_ptr& session) { - presentSession(session, tr("Processed Stack")); + m_imageGalleryWidget->addSession(session, tr("Processed Stack")); + m_imageWorkspace->openSession(session, tr("Processed Stack")); + }); + + connect(m_imageWorkspace, &ImageWorkspace::sessionAvailable, + m_imageGalleryWidget, &ImageGalleryWidget::addSession); + connect(m_imageWorkspace, &ImageWorkspace::activeViewerChanged, + this, [this]() + { + const bool liveViewer = m_imageWorkspace->isLiveViewerActive(); + m_deviceControlWidget->setPreviewWidget( + m_imageWorkspace->activePreviewWidget()); + m_deviceControlWidget->setViewerContext(liveViewer); + m_inspectorDockWidget->setWindowTitle(tr("Inspector")); + }); + connect(m_imageWorkspace, &ImageWorkspace::activeDocumentChanged, + this, [this](const QString& documentId) + { + m_saveImageAsAction->setEnabled(!documentId.isEmpty()); + }); + connect(m_imageWorkspace, &ImageWorkspace::documentSaveFinished, + this, [this](const QString&, bool success, const QString& message) + { + showStatusMessage(message, success ? 5000 : 8000); }); connect(m_exitAction, &QAction::triggered, this, &QWidget::close); @@ -714,18 +680,7 @@ namespace scopeone::ui showStatusMessage(tr("No gallery image available for preview"), 5000); return; } - registerGallerySessionFrameControls(session, 0); - for (const QString& cameraId : session->recordedCameraIds()) - { - if (session->recordedFrameCount(cameraId) > 0) - { - updateGalleryLayerFrame( - scopeone::core::ScopeOneCore::staticLayerKey( - gallerySessionLayerId(session, cameraId)), - 0); - } - } - showStatusMessage(tr("Loading gallery preview...")); + m_imageWorkspace->openSession(session); }); connect(m_imageGalleryWidget, &ImageGalleryWidget::livePreviewRequested, this, &MainWindow::showLivePreview); @@ -733,8 +688,6 @@ namespace scopeone::ui this, [this](const std::shared_ptr& session) { - removeGallerySessionFrameControls(session); - removeGallerySessionPreview(*m_scopeonecore, session); m_scopeonecore->closeRecordingSession( session->capturePlan().experimentId); }); @@ -789,7 +742,7 @@ namespace scopeone::ui void MainWindow::setupUI() { m_previewWidget = new PreviewWidget(m_imageSceneModel, this); - setCentralWidget(m_previewWidget); + setCentralWidget(m_imageWorkspace->viewerHost()); setupTools(); setupStatusBar(); @@ -844,15 +797,12 @@ namespace scopeone::ui QString MainWindow::currentLayerKey() const { - return m_deviceControlWidget->currentLayerKey(); + return m_imageWorkspace->activeLayerKey(); } void MainWindow::showLayers(const QStringList& layerKeys, bool sideBySide) { - m_imageSceneModel->setVisibleLayers(layerKeys); - m_previewWidget->setLayerLayoutMode( - sideBySide ? PreviewWidget::LayerLayoutMode::SideBySide - : PreviewWidget::LayerLayoutMode::Overlay); + m_imageWorkspace->setVisibleLayers(layerKeys, sideBySide); } void MainWindow::showToolStatus(const QString& message, int timeoutMs) @@ -869,17 +819,7 @@ namespace scopeone::ui return; } m_imageGalleryWidget->addSession(session, title); - registerGallerySessionFrameControls(session, 0); - for (const QString& cameraId : session->recordedCameraIds()) - { - if (session->recordedFrameCount(cameraId) > 0) - { - updateGalleryLayerFrame( - scopeone::core::ScopeOneCore::staticLayerKey( - gallerySessionLayerId(session, cameraId)), - 0); - } - } + m_imageWorkspace->openSession(session, title); showStatusMessage(tr("Images added to Gallery"), 5000); } @@ -935,6 +875,11 @@ namespace scopeone::ui refreshRecentConfigurationsMenu(); m_unloadConfigurationAction = m_fileMenu->addAction(tr("&Unload Configuration")); m_fileMenu->addSeparator(); + m_saveImageAsAction = m_fileMenu->addAction(tr("Save Image &As...")); + m_saveImageAsAction->setEnabled(false); + connect(m_saveImageAsAction, &QAction::triggered, + m_imageWorkspace, [this]() { m_imageWorkspace->saveDocumentAs(); }); + m_fileMenu->addSeparator(); m_exitAction = m_fileMenu->addAction(tr("E&xit")); m_viewMenu = menuBar()->addMenu(tr("&View")); @@ -962,8 +907,8 @@ namespace scopeone::ui // Create the camera control dock void MainWindow::setupDeviceControl() { - m_deviceControlDockWidget = new QDockWidget(tr("Control"), this); m_deviceControlWidget = new DeviceControlWidget(m_scopeonecore, this); + m_deviceControlWidget->setImageWorkspace(m_imageWorkspace); m_deviceControlWidget->setPreviewWidget(m_previewWidget); connect(m_deviceControlWidget, &DeviceControlWidget::stageMoveFailed, this, [this](const QString& message) @@ -971,40 +916,29 @@ namespace scopeone::ui showStatusMessage(message, 5000); qWarning().noquote() << message; }); - m_deviceControlDockWidget->setWidget(m_deviceControlWidget); - - addDockWidget(Qt::RightDockWidgetArea, m_deviceControlDockWidget); } // Create the image inspection dock void MainWindow::setupInspect() { - m_inspectDockWidget = new QDockWidget(tr("Inspect"), this); - m_inspectDockWidget->setFeatures(QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetClosable); - - m_inspectWidget = new InspectWidget(m_scopeonecore, this); - m_inspectDockWidget->setWidget(m_inspectWidget); - - addDockWidget(Qt::RightDockWidgetArea, m_inspectDockWidget); - tabifyDockWidget(m_deviceControlDockWidget, m_inspectDockWidget); + m_inspectWidget = new InspectWidget(m_scopeonecore, m_imageWorkspace, this); } // Create the processing module dock void MainWindow::setupImageProcessing() { - m_imageProcessingDockWidget = new QDockWidget(tr("Image Processing"), this); - m_imageProcessingDockWidget->setFeatures(QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetClosable); - - m_imageProcessingWidget = new ImageProcessingWidget(m_scopeonecore, this); - m_imageProcessingDockWidget->setWidget(m_imageProcessingWidget); - - addDockWidget(Qt::RightDockWidgetArea, m_imageProcessingDockWidget); - tabifyDockWidget(m_inspectDockWidget, m_imageProcessingDockWidget); - m_deviceControlDockWidget->raise(); + m_imageProcessingWidget = new ImageProcessingWidget(m_scopeonecore, m_imageWorkspace, this); + m_inspectorDockWidget = new QDockWidget(tr("Inspector"), this); + m_inspectorDockWidget->setFeatures(QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable | + QDockWidget::DockWidgetClosable); + m_inspectorTabs = new QTabWidget(m_inspectorDockWidget); + m_inspectorTabs->addTab(m_deviceControlWidget->hardwareControlsWidget(), tr("Acquire")); + m_inspectorTabs->addTab(m_deviceControlWidget->imageControlsWidget(), tr("View")); + m_inspectorTabs->addTab(m_inspectWidget, tr("Analyze")); + m_inspectorTabs->addTab(m_imageProcessingWidget, tr("Process")); + m_inspectorDockWidget->setWidget(m_inspectorTabs); + addDockWidget(Qt::RightDockWidgetArea, m_inspectorDockWidget); } // Create the log console dock and install the Qt message sink @@ -1016,8 +950,7 @@ namespace scopeone::ui ConsoleWidget::installAsQtMessageSink(m_consoleWidget); - addDockWidget(Qt::RightDockWidgetArea, m_consoleDockWidget); - splitDockWidget(m_deviceControlDockWidget, m_consoleDockWidget, Qt::Vertical); + addDockWidget(Qt::BottomDockWidgetArea, m_consoleDockWidget); } // Create the device property and config preset dock @@ -1064,7 +997,7 @@ namespace scopeone::ui addDockWidget(Qt::LeftDockWidgetArea, m_imageGalleryDockWidget); tabifyDockWidget(m_recordingDockWidget, m_imageGalleryDockWidget); - m_recordingDockWidget->raise(); + m_imageGalleryDockWidget->raise(); } // Close the modal configuration progress dialog if present @@ -1090,29 +1023,24 @@ namespace scopeone::ui return; } + m_imageWorkspace->activateLiveViewer(); + m_scopeonecore->clearStaticFrames(); if (m_currentControlTarget.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) { - m_imageSceneModel->setVisibleLayers(rawLayerKeys(cameraIds)); - m_previewWidget->setLayerLayoutMode(cameraIds.size() > 1 - ? PreviewWidget::LayerLayoutMode::SideBySide - : PreviewWidget::LayerLayoutMode::Overlay); + m_imageWorkspace->setVisibleLayers(rawLayerKeys(cameraIds), cameraIds.size() > 1); return; } if (cameraIds.contains(m_currentControlTarget)) { - m_imageSceneModel->setVisibleLayers( + m_imageWorkspace->setVisibleLayers( {scopeone::core::ScopeOneCore::rawLayerKey(m_currentControlTarget)}); - m_previewWidget->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::Overlay); return; } - m_imageSceneModel->setVisibleLayers(rawLayerKeys(cameraIds)); - m_previewWidget->setLayerLayoutMode(cameraIds.size() > 1 - ? PreviewWidget::LayerLayoutMode::SideBySide - : PreviewWidget::LayerLayoutMode::Overlay); + m_imageWorkspace->setVisibleLayers(rawLayerKeys(cameraIds), cameraIds.size() > 1); } // Switch the active camera target and preview selection @@ -1162,9 +1090,7 @@ namespace scopeone::ui addDock(m_recordingDockWidget, QStringLiteral("Recording")); addDock(m_imageGalleryDockWidget, QStringLiteral("Image Gallery")); addDock(m_consoleDockWidget, QStringLiteral("Console")); - addDock(m_deviceControlDockWidget, QStringLiteral("Control")); - addDock(m_inspectDockWidget, QStringLiteral("Inspect")); - addDock(m_imageProcessingDockWidget, QStringLiteral("Image Processing")); + addDock(m_inspectorDockWidget, QStringLiteral("Inspector")); } // Push loaded camera ids into every dependent panel @@ -1178,11 +1104,11 @@ namespace scopeone::ui if (cameraIds.size() > 1) { - m_imageSceneModel->setVisibleLayers(rawLayerKeys(cameraIds)); + m_imageWorkspace->setVisibleLayers(rawLayerKeys(cameraIds), true); } else if (!cameraIds.isEmpty()) { - m_imageSceneModel->setVisibleLayers( + m_imageWorkspace->setVisibleLayers( {scopeone::core::ScopeOneCore::rawLayerKey(cameraIds.first())}); } @@ -1299,14 +1225,15 @@ namespace scopeone::ui } PreviewWidget::PreviewInteractionTarget target; - if (!m_previewWidget->interactionTargetAt(m_lastPreviewMousePos, target)) + PreviewWidget* preview = m_imageWorkspace->activePreviewWidget(); + if (!preview || !preview->interactionTargetAt(m_lastPreviewMousePos, target)) { clearCursorStatus(); return; } int value = 0; - const bool valueOk = m_scopeonecore->graphPixelValue(target.layerKey, target.imagePos, value); + const bool valueOk = m_imageWorkspace->pixelValue(target.layerKey, target.imagePos, value); const QString msg = QStringLiteral("x=%1 y=%2 value=%3") .arg(target.imagePos.x(), 5, 10, QLatin1Char(' ')) .arg(target.imagePos.y(), 5, 10, QLatin1Char(' ')) @@ -1333,20 +1260,10 @@ namespace scopeone::ui const QPoint& end) { double actualLengthUm = 0.0; - double pixelSizeUm = 0.0; - const auto galleryControl = m_galleryLayerFrameControls.constFind(layerKey); - if (galleryControl != m_galleryLayerFrameControls.constEnd() - && galleryControl->session) - { - pixelSizeUm = galleryControl->session->cameraPixelSizeUm(galleryControl->cameraId); - } - else - { - const QString cameraId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey); - pixelSizeUm = m_scopeonecore->cameraPixelSizeUm(cameraId); - } + const double pixelSizeUm = m_imageWorkspace->pixelSizeUm(layerKey); scopeone::core::DocumentLayer layer; - if (pixelSizeUm > 0.0 && m_imageSceneModel->findLayer(layerKey, layer)) + if (pixelSizeUm > 0.0 + && m_imageWorkspace->activeSceneModel()->findLayer(layerKey, layer)) { const QPointF sensorStart = layer.pixelToSensor.map(QPointF(start)); const QPointF sensorEnd = layer.pixelToSensor.map(QPointF(end)); @@ -1357,164 +1274,6 @@ namespace scopeone::ui m_inspectWidget->setMeasurementLine(layerKey, start, end, actualLengthUm); } - // Registers right panel frame sliders for stack backed gallery layers - void MainWindow::registerGallerySessionFrameControls( - const std::shared_ptr& session, - int frameIndex) - { - if (!session) - { - return; - } - - for (const QString& cameraId : session->recordedCameraIds()) - { - const QString layerKey = scopeone::core::ScopeOneCore::staticLayerKey( - gallerySessionLayerId(session, cameraId)); - const int frameCount = uiFrameCount(session->recordedFrameCount(cameraId)); - m_galleryLayerFrameControls.insert(layerKey, {session, cameraId}); - if (frameCount <= 1) - { - m_deviceControlWidget->removeLayerFrameControl(layerKey); - continue; - } - - m_deviceControlWidget->setLayerFrameControl( - layerKey, - frameCount, - qBound(0, frameIndex, frameCount - 1)); - } - } - - // Removes right panel frame sliders for one gallery session - void MainWindow::removeGallerySessionFrameControls( - const std::shared_ptr& session) - { - if (!session) - { - return; - } - - for (const QString& cameraId : session->recordedCameraIds()) - { - const QString layerKey = scopeone::core::ScopeOneCore::staticLayerKey( - gallerySessionLayerId(session, cameraId)); - m_galleryLayerFrameControls.remove(layerKey); - m_galleryFrameRequests.remove(layerKey); - m_deviceControlWidget->removeLayerFrameControl(layerKey); - } - } - - // Updates gallery static layers when a layer frame slider moves - void MainWindow::updateGalleryLayerFrame(const QString& layerKey, int frameIndex) - { - const auto it = m_galleryLayerFrameControls.constFind(layerKey); - if (it == m_galleryLayerFrameControls.constEnd()) - { - return; - } - - const auto session = it.value().session; - if (!session) - { - return; - } - - const QString cameraId = it.value().cameraId; - const qint64 cameraFrameCount = session->recordedFrameCount(cameraId); - if (cameraFrameCount <= 0) - { - return; - } - - const int cameraFrameIndex = static_cast( - qBound(0, static_cast(frameIndex), cameraFrameCount - 1)); - GalleryFrameRequestState& state = m_galleryFrameRequests[layerKey]; - state.latestFrameIndex = cameraFrameIndex; - if (state.requestId == 0) - { - requestLatestGalleryFrame(layerKey); - } - } - - // Starts the newest pending frame read for one gallery layer - void MainWindow::requestLatestGalleryFrame(const QString& layerKey) - { - const auto controlIt = m_galleryLayerFrameControls.constFind(layerKey); - auto stateIt = m_galleryFrameRequests.find(layerKey); - if (controlIt == m_galleryLayerFrameControls.constEnd() - || stateIt == m_galleryFrameRequests.end() - || stateIt->requestId != 0) - { - return; - } - - stateIt->requestId = m_scopeonecore->requestRecordingSessionFrame( - controlIt->session, - controlIt->cameraId, - stateIt->latestFrameIndex); - } - - // Displays a decoded frame only if it is still the latest slider request - void MainWindow::handleGalleryFrameReady( - quint64 requestId, - const std::shared_ptr& session, - const QString& cameraId, - int frameIndex, - const scopeone::core::ImageFrame& frame) - { - const QString layerKey = scopeone::core::ScopeOneCore::staticLayerKey( - gallerySessionLayerId(session, cameraId)); - auto stateIt = m_galleryFrameRequests.find(layerKey); - if (stateIt == m_galleryFrameRequests.end() || stateIt->requestId != requestId) - { - return; - } - - stateIt->requestId = 0; - if (stateIt->latestFrameIndex != frameIndex) - { - requestLatestGalleryFrame(layerKey); - return; - } - if (!frame.isValid()) - { - showStatusMessage(tr("Failed to load gallery frame"), 5000); - return; - } - - const QString layerId = gallerySessionLayerId(session, cameraId); - const qint64 cameraFrameCount = session->recordedFrameCount(cameraId); - const QString displayName = cameraFrameCount > 1 - ? tr("Gallery %1 Frame %2").arg(cameraId).arg(frameIndex + 1) - : tr("Gallery %1").arg(cameraId); - const scopeone::core::ImageFrame graphFrame = m_scopeonecore->publishStaticFrame( - layerId, - frame, - displayName); - if (!graphFrame.isValid()) - { - return; - } - - QStringList visibleLayers = m_imageSceneModel->visibleLayerIds(); - if (!visibleLayers.contains(layerKey)) - { - visibleLayers.append(layerKey); - m_imageSceneModel->setVisibleLayers(visibleLayers); - } - m_previewWidget->setLayerLayoutMode( - visibleLayers.size() > 1 - ? PreviewWidget::LayerLayoutMode::SideBySide - : PreviewWidget::LayerLayoutMode::Overlay); - if (cameraFrameCount > 1) - { - m_deviceControlWidget->setLayerFrameControl( - layerKey, uiFrameCount(cameraFrameCount), frameIndex); - } - showStatusMessage(tr("Gallery %1 frame %2").arg(cameraId).arg(frameIndex + 1), 1500); - } - // Edit persistent application settings void MainWindow::openSettingsDialog() { @@ -1721,6 +1480,7 @@ namespace scopeone::ui if (!session || !session->isSaved()) { m_closeSaveInProgress = false; + m_closeAfterSave = false; m_closePendingSessions.clear(); if (m_closeSaveProgress) { @@ -1742,6 +1502,8 @@ namespace scopeone::ui } m_closeSaveInProgress = false; + const bool closeAfterSave = m_closeAfterSave; + m_closeAfterSave = false; if (m_closeSaveProgress) { m_closeSaveProgress->setValue(m_closeSaveTotal); @@ -1749,7 +1511,10 @@ namespace scopeone::ui m_closeSaveProgress->deleteLater(); m_closeSaveProgress = nullptr; } - QTimer::singleShot(0, this, &QWidget::close); + if (closeAfterSave) + { + QTimer::singleShot(0, this, &QWidget::close); + } } // Load a Micro Manager config selected by the user diff --git a/src/MainWindow.h b/src/MainWindow.h index 0830d4b..11ee68c 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -3,7 +3,6 @@ #include "scopeone/ScopeOneCore.h" #include "ScopeOneToolPlugin.h" -#include #include #include #include @@ -17,6 +16,7 @@ class QDockWidget; class QLabel; class QMenu; class QProgressDialog; +class QTabWidget; class QTimer; namespace scopeone::core @@ -30,6 +30,7 @@ namespace scopeone::ui class DevicePropertyWidget; class ConfigPresetWidget; class ImageGalleryWidget; + class ImageWorkspace; class ImageProcessingWidget; class InspectWidget; class PreviewWidget; @@ -89,20 +90,6 @@ namespace scopeone::ui void showMeasurementLine(const QString& layerKey, const QPoint& start, const QPoint& end); - void registerGallerySessionFrameControls( - const std::shared_ptr& session, - int frameIndex); - void removeGallerySessionFrameControls( - const std::shared_ptr& session); - void updateGalleryLayerFrame(const QString& layerKey, int frameIndex); - void requestLatestGalleryFrame(const QString& layerKey); - void handleGalleryFrameReady( - quint64 requestId, - const std::shared_ptr& session, - const QString& cameraId, - int frameIndex, - const scopeone::core::ImageFrame& frame); - void handlePreviewMousePosition(const QPoint& pos); void handleRoiDrawn(const QString& cameraId, int x, @@ -142,27 +129,14 @@ namespace scopeone::ui RecordingWidget* m_recordingWidget{nullptr}; QDockWidget* m_imageGalleryDockWidget{nullptr}; ImageGalleryWidget* m_imageGalleryWidget{nullptr}; + ImageWorkspace* m_imageWorkspace{nullptr}; - struct GalleryLayerFrameControl - { - std::shared_ptr session; - QString cameraId; - }; - QHash m_galleryLayerFrameControls; - struct GalleryFrameRequestState - { - quint64 requestId{0}; - int latestFrameIndex{0}; - }; - QHash m_galleryFrameRequests; - - QDockWidget* m_deviceControlDockWidget{nullptr}; DeviceControlWidget* m_deviceControlWidget{nullptr}; - QDockWidget* m_imageProcessingDockWidget{nullptr}; ImageProcessingWidget* m_imageProcessingWidget{nullptr}; - QDockWidget* m_inspectDockWidget{nullptr}; + QDockWidget* m_inspectorDockWidget{nullptr}; + QTabWidget* m_inspectorTabs{nullptr}; InspectWidget* m_inspectWidget{nullptr}; QMenu* m_fileMenu{nullptr}; @@ -176,6 +150,7 @@ namespace scopeone::ui QAction* m_fullScreenAction{nullptr}; QAction* m_loadConfigurationAction{nullptr}; QAction* m_unloadConfigurationAction{nullptr}; + QAction* m_saveImageAsAction{nullptr}; QAction* m_settingsAction{nullptr}; QAction* m_aboutAction{nullptr}; QAction* m_aboutQtAction{nullptr}; @@ -196,5 +171,6 @@ namespace scopeone::ui QList> m_closePendingSessions; int m_closeSaveTotal{0}; bool m_closeSaveInProgress{false}; + bool m_closeAfterSave{false}; }; } diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index bc0d228..a7334a9 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -787,24 +787,6 @@ namespace scopeone::ui return Blending::Translucent; } - QString PreviewWidget::blendingName(Blending blending) const - { - switch (blending) - { - case Blending::Additive: - return QStringLiteral("Additive"); - case Blending::Minimum: - return QStringLiteral("Minimum"); - case Blending::Opaque: - return QStringLiteral("Opaque"); - case Blending::Multiplicative: - return QStringLiteral("Multiplicative"); - case Blending::Translucent: - return QStringLiteral("Translucent"); - } - return QStringLiteral("Translucent"); - } - // Removes stored state for one static image source void PreviewWidget::removeStaticLayerData(const QString& sourceId) { @@ -2502,6 +2484,7 @@ namespace scopeone::ui // Starts active drawing interactions from a mouse press void PreviewWidget::mousePressEvent(QMouseEvent* event) { + emit activated(); emit mousePositionChanged(event->pos()); if (m_measurementLineDrawingMode && event->button() == Qt::LeftButton) { diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index 060d1e8..3038841 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -50,6 +50,7 @@ namespace scopeone::ui PreviewWidget(ImageSceneModel* sceneModel, QWidget* parent); ~PreviewWidget() override; + ImageSceneModel* sceneModel() const { return m_sceneModel; } void setGraphProcessedFrame(const scopeone::core::ImageFrame& frame); void setGraphRawFrame(const scopeone::core::ImageFrame& frame); @@ -94,6 +95,7 @@ namespace scopeone::ui void layerInfoTextChanged(const QString& text); void zoomLevelChanged(int zoomPercent); void fitToWindowChanged(bool enabled); + void activated(); void mousePositionChanged(const QPoint& widgetPos); void roiDrawn(const QString& cameraId, int x, @@ -265,7 +267,6 @@ namespace scopeone::ui LayerDisplaySettings defaultLayerDisplaySettings(bool processed) const; LayerDisplaySettings layerDisplaySettings(const QString& layerKey) const; Blending blendingFromName(const QString& name) const; - QString blendingName(Blending blending) const; void removeStaticLayerData(const QString& sourceId); QSet validLayerKeys() const; bool hasRawFrame(const FrameSourceState& frameState) const; diff --git a/src/ScopeOneLocalApiServer.cpp b/src/ScopeOneLocalApiServer.cpp index d74ffe3..75df87a 100644 --- a/src/ScopeOneLocalApiServer.cpp +++ b/src/ScopeOneLocalApiServer.cpp @@ -2,6 +2,7 @@ #include "scopeone/ImageSceneModel.h" #include "PreviewWidget.h" +#include "ImageWorkspace.h" #include "scopeone/ScopeOneCore.h" #include @@ -118,6 +119,30 @@ namespace scopeone::ui return response; } + QJsonObject imageDocumentToJson(const ImageDocumentInfo& document) + { + QJsonObject object; + object.insert(QStringLiteral("documentId"), document.id); + object.insert(QStringLiteral("title"), document.title); + object.insert(QStringLiteral("sessionId"), document.sessionId); + object.insert(QStringLiteral("cameraId"), document.cameraId); + object.insert(QStringLiteral("frameIndex"), document.frameIndex); + object.insert(QStringLiteral("frameCount"), document.frameCount); + object.insert(QStringLiteral("ready"), document.ready); + object.insert(QStringLiteral("active"), document.active); + return object; + } + + QJsonArray imageDocumentsToJson(const QList& documents) + { + QJsonArray array; + for (const ImageDocumentInfo& document : documents) + { + array.append(imageDocumentToJson(document)); + } + return array; + } + // Converts one markup to local API JSON QJsonObject markupToJson(const ImageSceneModel::Markup& markup) { @@ -710,6 +735,11 @@ namespace scopeone::ui QStringLiteral("show_frame_mapping_as_layer"), QStringLiteral("save_frame_mapping") })); + groups.insert(QStringLiteral("imageWorkspace"), QJsonArray::fromStringList(QStringList{ + QStringLiteral("image_windows"), QStringLiteral("open_image_window"), + QStringLiteral("activate_image_window"), QStringLiteral("close_image_window"), + QStringLiteral("process_image_window"), QStringLiteral("save_image_window") + })); QJsonObject object; object.insert(QStringLiteral("localOnly"), true); @@ -722,7 +752,8 @@ namespace scopeone::ui object.insert(QStringLiteral("operationGroups"), groups); object.insert(QStringLiteral("longRunningOperations"), QJsonArray::fromStringList(QStringList{ QStringLiteral("start_experiment"), QStringLiteral("record"), - QStringLiteral("start_stage_mosaic") + QStringLiteral("start_stage_mosaic"), QStringLiteral("process_image_window"), + QStringLiteral("save_image_window") })); object.insert(QStringLiteral("hardwareMutationOperations"), QJsonArray::fromStringList(QStringList{ QStringLiteral("load_config"), QStringLiteral("unload_config"), @@ -737,14 +768,16 @@ namespace scopeone::ui })); object.insert(QStringLiteral("filesystemMutationOperations"), QJsonArray::fromStringList(QStringList{ QStringLiteral("save_experiment"), QStringLiteral("start_experiment"), - QStringLiteral("save_frame_mapping"), QStringLiteral("session_save") + QStringLiteral("save_frame_mapping"), QStringLiteral("session_save"), + QStringLiteral("save_image_window") })); object.insert(QStringLiteral("destructiveOperations"), QJsonArray::fromStringList(QStringList{ QStringLiteral("unload_config"), QStringLiteral("remove_static_layer"), QStringLiteral("clear_static_layers"), QStringLiteral("remove_markup"), QStringLiteral("clear_markups"), QStringLiteral("remove_processing_module"), QStringLiteral("load_experiment"), QStringLiteral("cancel_experiment"), - QStringLiteral("session_close"), QStringLiteral("cancel_stage_mosaic") + QStringLiteral("session_close"), QStringLiteral("cancel_stage_mosaic"), + QStringLiteral("close_image_window") })); object.insert(QStringLiteral("frameTransport"), QStringLiteral("shared_memory")); return object; @@ -1085,18 +1118,26 @@ namespace scopeone::ui // Starts the local API pipe server and frame mapping ScopeOneLocalApiServer::ScopeOneLocalApiServer(scopeone::core::ScopeOneCore* core, PreviewWidget* previewWidget, + ImageWorkspace* imageWorkspace, QObject* parent) : QObject(parent) , m_scopeonecore(core) , m_previewWidget(previewWidget) + , m_imageWorkspace(imageWorkspace) , m_sceneModel(core ? core->imageSceneModel() : nullptr) , m_server(new QLocalServer(this)) { - if (!core) + if (!core || !previewWidget || !imageWorkspace) { - qFatal("ScopeOneLocalApiServer requires ScopeOneCore"); + qFatal("ScopeOneLocalApiServer requires Core, PreviewWidget and ImageWorkspace"); } m_taskPool.setMaxThreadCount(1); + connect(m_imageWorkspace, &ImageWorkspace::activeViewerChanged, + this, [this]() + { + m_sceneModel = m_imageWorkspace->activeSceneModel(); + m_previewWidget = m_imageWorkspace->activePreviewWidget(); + }); QLocalServer::removeServer(kServerName); connect(m_server, &QLocalServer::newConnection, @@ -1323,6 +1364,189 @@ namespace scopeone::ui } }; + if (type == QStringLiteral("open_image_window")) + { + const QString sessionId = request.value(QStringLiteral("sessionId")).toString().trimmed(); + const auto session = m_scopeonecore->recordingSession(sessionId); + if (!session) + { + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), QStringLiteral("Unknown session")); + finish(std::move(response)); + return true; + } + + auto* context = new QObject(this); + auto documentIds = std::make_shared(); + auto completed = std::make_shared(false); + const auto respondWhenReady = [this, context, finish, type, documentIds, completed]() + { + if (*completed || documentIds->isEmpty()) + { + return; + } + for (const QString& documentId : *documentIds) + { + const ImageDocumentInfo document = m_imageWorkspace->document(documentId); + if (!document.isValid()) + { + *completed = true; + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + QStringLiteral("Image window closed before its first frame loaded")); + finish(std::move(response)); + context->deleteLater(); + return; + } + if (!document.ready) + { + return; + } + } + *completed = true; + QJsonObject response = makeResponse(type, true); + response.insert(QStringLiteral("documentIds"), + QJsonArray::fromStringList(*documentIds)); + response.insert(QStringLiteral("activeDocumentId"), + m_imageWorkspace->activeDocumentId()); + finish(std::move(response)); + context->deleteLater(); + }; + connect(m_imageWorkspace, &ImageWorkspace::documentsChanged, + context, respondWhenReady); + *documentIds = m_imageWorkspace->openSession( + session, + request.value(QStringLiteral("title")).toString(), + request.value(QStringLiteral("cameraId")).toString()); + if (documentIds->isEmpty()) + { + *completed = true; + delete context; + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + QStringLiteral("Session has no matching image data")); + finish(std::move(response)); + } + else + { + respondWhenReady(); + } + return true; + } + + if (type == QStringLiteral("process_image_window")) + { + const QJsonValue completeStackValue = request.value(QStringLiteral("completeStack")); + const ImageDocumentInfo source = m_imageWorkspace->document( + request.value(QStringLiteral("documentId")).toString()); + if (!source.isValid() + || (!completeStackValue.isUndefined() && !completeStackValue.isBool())) + { + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + source.isValid() + ? QStringLiteral("completeStack must be a boolean") + : QStringLiteral("Unknown image window")); + finish(std::move(response)); + return true; + } + + auto* context = new QObject(this); + auto operationId = std::make_shared(0); + connect(m_imageWorkspace, &ImageWorkspace::documentProcessingFinished, + context, + [this, context, finish, type, operationId]( + quint64 completedId, + const QString& outputDocumentId, + const QString& errorMessage) + { + if (completedId != *operationId) + { + return; + } + const bool success = errorMessage.isEmpty() && !outputDocumentId.isEmpty(); + QJsonObject response = makeResponse(type, success); + if (success) + { + response.insert(QStringLiteral("document"), + imageDocumentToJson( + m_imageWorkspace->document(outputDocumentId))); + } + else + { + response.insert(QStringLiteral("error"), + errorMessage.isEmpty() + ? QStringLiteral("Image processing failed") + : errorMessage); + } + finish(std::move(response)); + context->deleteLater(); + }); + *operationId = m_imageWorkspace->processDocument( + source.id, completeStackValue.toBool(false)); + if (*operationId == 0) + { + delete context; + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + QStringLiteral("No image data or processing modules are available")); + finish(std::move(response)); + } + return true; + } + + if (type == QStringLiteral("save_image_window")) + { + const ImageDocumentInfo document = m_imageWorkspace->document( + request.value(QStringLiteral("documentId")).toString()); + const QString saveError = saveRequestError(request); + if (!document.isValid() || !saveError.isEmpty()) + { + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + document.isValid() ? saveError + : QStringLiteral("Unknown image window")); + finish(std::move(response)); + return true; + } + + scopeone::core::ScopeOneCore::RecordingSaveOptions options; + applySaveRequest(request, options); + auto* context = new QObject(this); + connect(m_imageWorkspace, &ImageWorkspace::documentSaveFinished, + context, + [context, finish, type, document](const QString& completedDocumentId, + bool success, + const QString& message) + { + if (completedDocumentId != document.id) + { + return; + } + QJsonObject response = makeResponse(type, success); + response.insert(QStringLiteral("documentId"), document.id); + if (success) + { + response.insert(QStringLiteral("message"), message); + } + else + { + response.insert(QStringLiteral("error"), message); + } + finish(std::move(response)); + context->deleteLater(); + }); + if (!m_imageWorkspace->saveDocument(document.id, options)) + { + delete context; + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + QStringLiteral("Failed to start image window save")); + finish(std::move(response)); + } + return true; + } + if (type == QStringLiteral("load_config")) { const QString configPath = request.value(QStringLiteral("configPath")).toString().trimmed(); @@ -1485,7 +1709,7 @@ namespace scopeone::ui int maxParticles = 1000; const QJsonValue exportMaskValue = request.value(QStringLiteral("exportMask")); const QJsonValue publishMaskValue = request.value(QStringLiteral("publishMask")); - const scopeone::core::ImageFrame frame = m_scopeonecore->graphFrame(layerKey); + const scopeone::core::ImageFrame frame = m_imageWorkspace->frameForLayer(layerKey); if (layerKey.isEmpty() || !intField(request, QStringLiteral("threshold"), threshold) || !intField(request, QStringLiteral("minArea"), minArea) @@ -1509,8 +1733,11 @@ namespace scopeone::ui return true; } threshold = qMin(threshold, frame.maxValue()); + const bool sourceWasLive = m_imageWorkspace->isLiveViewerActive(); + const QPointer sourceScene = m_sceneModel; + const QPointer sourcePreview = m_previewWidget; const quint64 analysisId = m_scopeonecore->detectParticles( - layerKey, threshold, minArea, maxArea, maxParticles); + frame, layerKey, threshold, minArea, maxArea, maxParticles); if (analysisId == 0) { QJsonObject response = makeResponse(type, false); @@ -1523,6 +1750,7 @@ namespace scopeone::ui connect(m_scopeonecore, &scopeone::core::ScopeOneCore::particleDetectionFinished, context, [this, context, finish, type, analysisId, threshold, minArea, maxArea, + sourceWasLive, sourceScene, sourcePreview, publishMask = publishMaskValue.toBool(false), exportMask = exportMaskValue.toBool(false)]( quint64 completedId, @@ -1559,28 +1787,52 @@ namespace scopeone::ui if (publishMask) { - const QString maskLayerId = QStringLiteral("particle_mask"); - const auto publishedMask = m_scopeonecore->publishStaticFrame( - maskLayerId, result.mask, QStringLiteral("Particle Mask")); - if (!publishedMask.isValid()) + if (!sourceWasLive) + { + const QString documentId = m_imageWorkspace->openFrame( + result.mask, QStringLiteral("Particle Mask")); + if (documentId.isEmpty()) + { + response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + QStringLiteral("Failed to open particle mask")); + } + else + { + response.insert(QStringLiteral("maskDocumentId"), documentId); + } + } + else if (!sourceScene || !sourcePreview) { response = makeResponse(type, false); response.insert(QStringLiteral("error"), - QStringLiteral("Failed to publish particle mask")); + QStringLiteral("Source viewer was closed")); } else { - const QString maskLayerKey = - scopeone::core::ScopeOneCore::staticLayerKey(maskLayerId); - m_sceneModel->setLayerColormap(maskLayerKey, QStringLiteral("Magenta")); - m_sceneModel->setLayerOpacityPercent(maskLayerKey, 70); - m_sceneModel->setLayerBlending(maskLayerKey, QStringLiteral("Additive")); - QStringList visibleLayers = m_sceneModel->visibleLayerIds(); - if (!visibleLayers.contains(resultLayerKey)) visibleLayers.append(resultLayerKey); - if (!visibleLayers.contains(maskLayerKey)) visibleLayers.append(maskLayerKey); - m_sceneModel->setVisibleLayers(visibleLayers); - m_previewWidget->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::Overlay); - response.insert(QStringLiteral("maskLayerKey"), maskLayerKey); + const QString maskLayerId = QStringLiteral("particle_mask"); + const auto publishedMask = m_scopeonecore->publishStaticFrame( + maskLayerId, result.mask, QStringLiteral("Particle Mask")); + if (!publishedMask.isValid()) + { + response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + QStringLiteral("Failed to publish particle mask")); + } + else + { + const QString maskLayerKey = + scopeone::core::ScopeOneCore::staticLayerKey(maskLayerId); + sourceScene->setLayerColormap(maskLayerKey, QStringLiteral("Magenta")); + sourceScene->setLayerOpacityPercent(maskLayerKey, 70); + sourceScene->setLayerBlending(maskLayerKey, QStringLiteral("Additive")); + QStringList visibleLayers = sourceScene->visibleLayerIds(); + if (!visibleLayers.contains(resultLayerKey)) visibleLayers.append(resultLayerKey); + if (!visibleLayers.contains(maskLayerKey)) visibleLayers.append(maskLayerKey); + sourceScene->setVisibleLayers(visibleLayers); + sourcePreview->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::Overlay); + response.insert(QStringLiteral("maskLayerKey"), maskLayerKey); + } } } if (response.value(QStringLiteral("ok")).toBool() && exportMask) @@ -2050,6 +2302,45 @@ namespace scopeone::ui return response; } + if (type == QStringLiteral("image_windows")) + { + QJsonObject response = makeResponse(type, true); + response.insert(QStringLiteral("activeDocumentId"), + m_imageWorkspace->activeDocumentId()); + response.insert(QStringLiteral("documents"), + imageDocumentsToJson(m_imageWorkspace->documents())); + return response; + } + + if (type == QStringLiteral("activate_image_window")) + { + const QString documentId = request.value(QStringLiteral("documentId")).toString(); + const bool ok = m_imageWorkspace->activateDocument(documentId); + QJsonObject response = makeResponse(type, ok); + if (ok) + { + response.insert(QStringLiteral("document"), + imageDocumentToJson(m_imageWorkspace->document(documentId))); + } + else + { + response.insert(QStringLiteral("error"), QStringLiteral("Unknown image window")); + } + return response; + } + + if (type == QStringLiteral("close_image_window")) + { + const bool ok = m_imageWorkspace->closeDocument( + request.value(QStringLiteral("documentId")).toString()); + QJsonObject response = makeResponse(type, ok); + if (!ok) + { + response.insert(QStringLiteral("error"), QStringLiteral("Unknown image window")); + } + return response; + } + if (type == QStringLiteral("version")) { QJsonObject response = makeResponse(type, true); @@ -2289,6 +2580,8 @@ namespace scopeone::ui if (type == QStringLiteral("list_layers")) { QJsonObject response = makeResponse(type, true); + response.insert(QStringLiteral("activeDocumentId"), + m_imageWorkspace->activeDocumentId()); response.insert(QStringLiteral("layers"), layersToJson(m_sceneModel, m_previewWidget)); return response; } @@ -2299,7 +2592,7 @@ namespace scopeone::ui scopeone::core::ScopeOneCore::HistogramStats stats; QJsonObject response = makeResponse( type, - !layerKey.isEmpty() && m_scopeonecore->getLayerHistogram(layerKey, stats)); + !layerKey.isEmpty() && m_imageWorkspace->histogram(layerKey, stats)); if (!response.value(QStringLiteral("ok")).toBool()) { response.insert(QStringLiteral("error"), QStringLiteral("Layer has no current frame")); @@ -2320,7 +2613,7 @@ namespace scopeone::ui if (layerKey.isEmpty() || !intField(request, QStringLiteral("x"), x) || !intField(request, QStringLiteral("y"), y) - || !m_scopeonecore->graphPixelValue(layerKey, QPoint(x, y), value)) + || !m_imageWorkspace->pixelValue(layerKey, QPoint(x, y), value)) { response.insert(QStringLiteral("error"), QStringLiteral("Layer has no current frame or position is outside the image")); @@ -2346,8 +2639,8 @@ namespace scopeone::ui return response; } const bool ok = type == QStringLiteral("auto_layer_levels") - ? m_scopeonecore->autoLayerLevels(layerKey) - : m_scopeonecore->fullLayerLevels(layerKey); + ? m_imageWorkspace->autoLayerLevels(layerKey) + : m_imageWorkspace->fullLayerLevels(layerKey); if (!ok) { response.insert(QStringLiteral("error"), QStringLiteral("Layer has no current frame")); @@ -2357,7 +2650,7 @@ namespace scopeone::ui response = makeResponse(type, true); response.insert(QStringLiteral("layerKey"), layerKey); insertLayerDisplayFields( - response, layer, m_scopeonecore->layerAutoStretchEnabled(layerKey)); + response, layer, m_imageWorkspace->layerAutoStretchEnabled(layerKey)); return response; } @@ -2377,12 +2670,12 @@ namespace scopeone::ui response.insert(QStringLiteral("error"), QStringLiteral("Missing enabled value")); return response; } - m_scopeonecore->setLayerAutoStretchEnabled(layerKey, enabledValue.toBool()); + m_imageWorkspace->setLayerAutoStretchEnabled(layerKey, enabledValue.toBool()); m_sceneModel->findLayer(layerKey, layer); response = makeResponse(type, true); response.insert(QStringLiteral("layerKey"), layerKey); insertLayerDisplayFields( - response, layer, m_scopeonecore->layerAutoStretchEnabled(layerKey)); + response, layer, m_imageWorkspace->layerAutoStretchEnabled(layerKey)); return response; } @@ -2406,10 +2699,10 @@ namespace scopeone::ui } QVector values; - if (!m_scopeonecore->getLineProfile(layerKey, - QPoint(x1, y1), - QPoint(x2, y2), - values)) + if (!m_imageWorkspace->lineProfile(layerKey, + QPoint(x1, y1), + QPoint(x2, y2), + values)) { response.insert(QStringLiteral("error"), QStringLiteral("Layer has no current frame or line is outside the image")); @@ -2656,7 +2949,7 @@ namespace scopeone::ui } if (hasLevels) { - m_scopeonecore->setLayerAutoStretchEnabled(layerKey, false); + m_imageWorkspace->setLayerAutoStretchEnabled(layerKey, false); m_sceneModel->setLayerDisplayLevels(layerKey, minLevel, maxLevel, maxPossible); } @@ -2664,7 +2957,7 @@ namespace scopeone::ui response = makeResponse(type, true); response.insert(QStringLiteral("layerKey"), layerKey); insertLayerDisplayFields( - response, layer, m_scopeonecore->layerAutoStretchEnabled(layerKey)); + response, layer, m_imageWorkspace->layerAutoStretchEnabled(layerKey)); return response; } @@ -2749,7 +3042,8 @@ namespace scopeone::ui const QString layerKey = request.value(QStringLiteral("layerKey")).toString().trimmed(); const QString sourceId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey).trimmed(); QJsonObject response = makeResponse(type, false); - if (!scopeone::core::ScopeOneCore::isStaticLayerKey(layerKey) + if (!m_imageWorkspace->isLiveViewerActive() + || !scopeone::core::ScopeOneCore::isStaticLayerKey(layerKey) || sourceId.isEmpty() || !m_previewWidget->availableLayerKeys().contains(layerKey)) { @@ -2763,6 +3057,13 @@ namespace scopeone::ui if (type == QStringLiteral("clear_static_layers")) { + if (!m_imageWorkspace->isLiveViewerActive()) + { + QJsonObject response = makeResponse(type, false); + response.insert(QStringLiteral("error"), + QStringLiteral("Static preview layers belong to the live viewer")); + return response; + } m_scopeonecore->clearStaticFrames(); return makeResponse(type, true); } @@ -3756,7 +4057,9 @@ namespace scopeone::ui return response; } - const scopeone::core::ImageFrame frame = m_scopeonecore->graphFrame(layerKey); + const scopeone::core::ImageFrame frame = rawFrameRequest + ? m_scopeonecore->graphFrame(layerKey) + : m_imageWorkspace->frameForLayer(layerKey); if (!frame.isValid()) { response.insert(QStringLiteral("error"), diff --git a/src/ScopeOneLocalApiServer.h b/src/ScopeOneLocalApiServer.h index 3b20642..2b84c4b 100644 --- a/src/ScopeOneLocalApiServer.h +++ b/src/ScopeOneLocalApiServer.h @@ -17,6 +17,7 @@ class QLocalSocket; namespace scopeone::ui { class PreviewWidget; + class ImageWorkspace; class ScopeOneLocalApiServer : public QObject { @@ -25,6 +26,7 @@ namespace scopeone::ui public: explicit ScopeOneLocalApiServer(scopeone::core::ScopeOneCore* core, PreviewWidget* previewWidget, + ImageWorkspace* imageWorkspace, QObject* parent = nullptr); ~ScopeOneLocalApiServer() override; @@ -52,6 +54,7 @@ namespace scopeone::ui scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; PreviewWidget* m_previewWidget{nullptr}; + ImageWorkspace* m_imageWorkspace{nullptr}; scopeone::core::ImageSceneModel* m_sceneModel{nullptr}; QLocalServer* m_server{nullptr}; QHash m_readBuffers; diff --git a/src/ScopeOneMcpServer.cpp b/src/ScopeOneMcpServer.cpp index fb153e5..7aef6e7 100644 --- a/src/ScopeOneMcpServer.cpp +++ b/src/ScopeOneMcpServer.cpp @@ -227,6 +227,77 @@ namespace }), {}, ToolAccess::Confirmed), + makeTool( + QStringLiteral("image_windows"), + QStringLiteral("List independent image windows and the active window")), + makeTool( + QStringLiteral("open_image_window"), + QStringLiteral("Open retained session images in independent image windows"), + inputProperties({ + {QStringLiteral("sessionId"), + inputProperty(QStringLiteral("string"), QStringLiteral("Recording session ID"))}, + {QStringLiteral("title"), + inputProperty(QStringLiteral("string"), QStringLiteral("Optional window title"))}, + {QStringLiteral("cameraId"), + inputProperty(QStringLiteral("string"), QStringLiteral("Optional camera ID filter"))} + }), + {QStringLiteral("sessionId")}, + ToolAccess::StateChanging), + makeTool( + QStringLiteral("activate_image_window"), + QStringLiteral("Activate an independent image window"), + inputProperties({ + {QStringLiteral("documentId"), + inputProperty(QStringLiteral("string"), QStringLiteral("Image window document ID"))} + }), + {QStringLiteral("documentId")}, + ToolAccess::StateChanging), + makeTool( + QStringLiteral("close_image_window"), + QStringLiteral("Close an image window, or the active window when omitted"), + inputProperties({ + {QStringLiteral("documentId"), + inputProperty(QStringLiteral("string"), QStringLiteral("Image window document ID"))} + }), + {}, + ToolAccess::Destructive), + makeTool( + QStringLiteral("process_image_window"), + QStringLiteral("Process one frame or stack from an image window, using the active window when omitted"), + inputProperties({ + {QStringLiteral("documentId"), + inputProperty(QStringLiteral("string"), QStringLiteral("Image window document ID"))}, + {QStringLiteral("completeStack"), + inputProperty(QStringLiteral("boolean"), QStringLiteral("Process the complete stack"), false)} + }), + {}, + ToolAccess::StateChanging), + makeTool( + QStringLiteral("save_image_window"), + QStringLiteral("Save an image window to disk, using the active window when omitted"), + inputProperties({ + {QStringLiteral("documentId"), + inputProperty(QStringLiteral("string"), QStringLiteral("Image window document ID"))}, + {QStringLiteral("saveDir"), + inputProperty(QStringLiteral("string"), QStringLiteral("Destination directory"))}, + {QStringLiteral("baseName"), + inputProperty(QStringLiteral("string"), QStringLiteral("Output base name"))}, + {QStringLiteral("format"), + withEnum( + inputProperty(QStringLiteral("string"), QStringLiteral("Output format"), + QStringLiteral("ome-tiff")), + {QStringLiteral("ome-tiff"), QStringLiteral("ome-zarr"), QStringLiteral("tiff"), QStringLiteral("binary")})}, + {QStringLiteral("compression"), + inputProperty(QStringLiteral("boolean"), QStringLiteral("Enable output compression"), false)}, + {QStringLiteral("compressionLevel"), + withMaximum( + withMinimum( + inputProperty(QStringLiteral("integer"), QStringLiteral("Compression level"), 6), + 0.0), + 9.0)} + }), + {QStringLiteral("saveDir"), QStringLiteral("baseName")}, + ToolAccess::Confirmed), makeTool( QStringLiteral("list_layers"), QStringLiteral("List image layers and their current display state")), @@ -452,7 +523,7 @@ namespace QStringLiteral("Export the particle mask to shared memory"), false)}, {QStringLiteral("publishMask"), inputProperty(QStringLiteral("boolean"), - QStringLiteral("Publish the particle mask as a preview layer"), false)} + QStringLiteral("Display the particle mask in ScopeOne"), false)} }), {QStringLiteral("layerKey"), QStringLiteral("threshold"), QStringLiteral("minArea"), QStringLiteral("maxArea")}, From 0f344244c46bb72fab5a6203f2e7f63bff64d6b4 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:05:23 +0200 Subject: [PATCH 05/37] Add external plugin SDK and examples --- .gitignore | 2 + CMakeLists.txt | 22 +- README.md | 15 +- ScopeOneCore/CMakeLists.txt | 20 +- ScopeOneCore/README.md | 8 +- .../include/scopeone/PluginManifest.h | 34 ++ ScopeOneCore/include/scopeone/ToolPlugin.h | 57 +++ ScopeOneCore/src/DriverHostMain.cpp | 17 + ScopeOneCore/src/PluginManifest.cpp | 74 ++++ ScopeOneCore/src/ProcessingModuleRegistry.cpp | 12 + ScopeOneCore/src/ScopeOneCore.cpp | 28 +- ScopeOneCore/src/SimulatorProvider.json | 4 - ScopeOneCore/src/SimulatorProviderPlugin.cpp | 49 --- VERSION | 2 +- plugins/examples/CMakeLists.txt | 24 ++ plugins/examples/README.md | 19 + plugins/examples/hardware/CMakeLists.txt | 6 + .../hardware/ExampleHardwarePlugin.cpp | 44 ++ plugins/examples/hardware/plugin.json | 9 + plugins/examples/processing/CMakeLists.txt | 6 + .../processing/ExampleProcessingPlugin.cpp | 49 +++ plugins/examples/processing/plugin.json | 7 + plugins/examples/tool/CMakeLists.txt | 6 + plugins/examples/tool/ExampleToolPlugin.cpp | 43 ++ plugins/examples/tool/plugin.json | 7 + scripts/build.ps1 | 73 +++- src/DeviceControlWidget.cpp | 13 +- src/DeviceControlWidget.h | 2 + src/MainWindow.cpp | 27 ++ src/PluginManagerDialog.cpp | 392 ++++++++++++++++++ src/PluginManagerDialog.h | 34 ++ src/RecordingWidget.cpp | 25 +- src/RecordingWidget.h | 5 +- src/ScopeOneToolPlugin.cpp | 12 + src/ScopeOneToolPlugin.h | 48 +-- src/main.cpp | 5 + 36 files changed, 1023 insertions(+), 177 deletions(-) create mode 100644 ScopeOneCore/include/scopeone/PluginManifest.h create mode 100644 ScopeOneCore/include/scopeone/ToolPlugin.h create mode 100644 ScopeOneCore/src/PluginManifest.cpp delete mode 100644 ScopeOneCore/src/SimulatorProvider.json delete mode 100644 ScopeOneCore/src/SimulatorProviderPlugin.cpp create mode 100644 plugins/examples/CMakeLists.txt create mode 100644 plugins/examples/README.md create mode 100644 plugins/examples/hardware/CMakeLists.txt create mode 100644 plugins/examples/hardware/ExampleHardwarePlugin.cpp create mode 100644 plugins/examples/hardware/plugin.json create mode 100644 plugins/examples/processing/CMakeLists.txt create mode 100644 plugins/examples/processing/ExampleProcessingPlugin.cpp create mode 100644 plugins/examples/processing/plugin.json create mode 100644 plugins/examples/tool/CMakeLists.txt create mode 100644 plugins/examples/tool/ExampleToolPlugin.cpp create mode 100644 plugins/examples/tool/plugin.json create mode 100644 src/PluginManagerDialog.cpp create mode 100644 src/PluginManagerDialog.h diff --git a/.gitignore b/.gitignore index 6ebdb3c..ef39800 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ !/src/** !/ScopeOneCore/ !/ScopeOneCore/** +!/plugins/ +!/plugins/** /ScopeOneCore/install/ /ScopeOneCore/build/ /ScopeOneCore/external/* diff --git a/CMakeLists.txt b/CMakeLists.txt index b362896..7712e03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ set(SCOPEONE_UI_SOURCES src/ConsoleWidget.cpp src/AboutDialog.cpp src/SettingsDialog.cpp + src/PluginManagerDialog.cpp src/DeviceControlWidget.cpp src/RecordingWidget.cpp src/DevicePropertyWidget.cpp @@ -97,6 +98,7 @@ set(SCOPEONE_UI_HEADERS src/ConsoleWidget.h src/AboutDialog.h src/SettingsDialog.h + src/PluginManagerDialog.h src/DeviceControlWidget.h src/RecordingWidget.h src/DevicePropertyWidget.h @@ -204,15 +206,17 @@ if (SCOPEONE_CORE_RUNTIME_FILES) ) endif () -set(SCOPEONE_PROVIDER_DIR "${ScopeOneCore_ROOT}/${CMAKE_INSTALL_BINDIR}/plugins/hardware") -if (EXISTS "${SCOPEONE_PROVIDER_DIR}") - add_custom_command(TARGET ScopeOne POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${SCOPEONE_PROVIDER_DIR}" - "$/plugins/hardware" - ) - install(DIRECTORY "${SCOPEONE_PROVIDER_DIR}/" DESTINATION plugins/hardware) -endif () +foreach (_plugin_kind IN ITEMS processing tools hardware) + 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) diff --git a/README.md b/README.md index 899aefa..a6d75ce 100644 --- a/README.md +++ b/README.md @@ -73,16 +73,15 @@ ScopeWriter contains its filesystem Zarr V3 writer and carries libtiff, zlib, zs ### Plugin layout -ScopeOne discovers optional extensions beside the application executable: +ScopeOne loads external plugins from three directories beside the application: -```text -plugins/ - hardware/ Native hardware providers loaded in isolated DriverHost processes - processing/ Image-processing modules loaded by ScopeOneCore - tools/ Optional desktop tools loaded by the Qt 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` + +All plugins use the installed `scopeone::PluginSDK` CMake target and a common manifest with `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. Minimal standalone projects are in `plugins/examples`. -Micro-Manager remains the built-in hardware provider and continues to load its Device Adapters from `.cfg` files. Native devices that do not belong in Micro-Manager use the `HardwareProvider` plugin contract. Built-in and external processing modules share stable string IDs and parameter descriptors, so the Image Processing panel creates the same controls for both. Built-in desktop tools and external tool plugins likewise share the tool registry. +Micro-Manager remains the built-in hardware provider and continues to load its Device Adapters from `.cfg` files. Native devices that do not belong in Micro-Manager use the `HardwareProvider` plugin contract. 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. diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 098b2e3..3cd1b8a 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -81,6 +81,7 @@ set(CORE_SOURCES src/DriverHostProviderProxy.cpp src/FrameRouter.cpp src/HardwareProvider.cpp + src/PluginManifest.cpp src/HardwareRuntime.cpp src/MicroManagerProvider.cpp src/SimulatorProvider.cpp @@ -114,6 +115,7 @@ set(CORE_HEADERS include/scopeone/HardwareCapabilities.h include/scopeone/HardwareTypes.h include/scopeone/HardwareProvider.h + include/scopeone/PluginManifest.h include/scopeone/SimulatorProvider.h include/scopeone/ImageSceneModel.h internal/HardwareRuntime.h @@ -127,6 +129,7 @@ set(CORE_HEADERS internal/ProcessingModule.h internal/ProcessingModuleRegistry.h include/scopeone/ProcessingPlugin.h + include/scopeone/ToolPlugin.h internal/DriverHostProtocol.h internal/SharedFrameRing.h internal/DriverHostProviderProxy.h @@ -147,6 +150,10 @@ set(CORE_HEADERS 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_link_libraries(ScopeOneCore PUBLIC Qt::Core @@ -230,13 +237,6 @@ target_link_directories(ScopeOne_DriverHost PRIVATE ${MMCORE_BIN_DIR}) scopeone_add_runtime_copy_commands(ScopeOne_DriverHost $) -add_library(ScopeOne_SimulatorProvider MODULE - src/SimulatorProviderPlugin.cpp - src/SimulatorProvider.json -) -target_link_libraries(ScopeOne_SimulatorProvider PRIVATE ScopeOneCore Qt::Core) -set_target_properties(ScopeOne_SimulatorProvider PROPERTIES PREFIX "") - # Fast Release build # if (MSVC) # target_compile_options(ScopeOneCore PRIVATE @@ -273,7 +273,7 @@ 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}" @@ -282,10 +282,6 @@ install(TARGETS ScopeOneCore install(TARGETS ScopeOne_DriverHost RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) -install(TARGETS ScopeOne_SimulatorProvider - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}/plugins/hardware" - LIBRARY DESTINATION "${CMAKE_INSTALL_BINDIR}/plugins/hardware" -) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" FILES_MATCHING diff --git a/ScopeOneCore/README.md b/ScopeOneCore/README.md index 36fe1d6..36e1748 100644 --- a/ScopeOneCore/README.md +++ b/ScopeOneCore/README.md @@ -88,11 +88,11 @@ Outputs: - `build/Release/ScopeOneCore.dll` - `build/Release/ScopeOneCore.lib` - `build/Release/ScopeOne_DriverHost.exe` -- `build/Release/ScopeOne_SimulatorProvider.dll` +- The external hardware example is built under `plugins/examples/hardware`. - `build/ScopeOneCoreConfig.cmake` - `install/bin/ScopeOneCore.dll` - `install/bin/ScopeOne_DriverHost.exe` -- `install/bin/plugins/hardware/ScopeOne_SimulatorProvider.dll` +- External hardware plugins are installed under `plugins/hardware`. - `install/lib/cmake/ScopeOneCore/ScopeOneCoreConfig.cmake` @@ -104,6 +104,8 @@ The installed headers are the source of truth for the public API: - `HardwareProvider.h`, `HardwareCapabilities.h` and `CameraProvider.h` define provider discovery, device control and frame delivery. - `DriverHostProviderPlugin.h` defines the module factory used to load external providers in isolated DriverHost processes. - `ProcessingPlugin.h` defines processing module descriptors, runtime modules and the external processing plugin contract. +- `ToolPlugin.h` defines the restricted desktop tool context and external tool contract. +- `PluginManifest.h` validates the common plugin identity and API version metadata. - `HardwareTypes.h` defines provider-independent device identity, state and endpoint metadata. - `SimulatorProvider.h` provides an in-process reference provider. - `ImageFrame.h` defines the image payload and metadata exchanged across Core features. @@ -122,6 +124,8 @@ Providers use ScopeOne logical device IDs and publish `ImageFrame` objects throu - `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 and processing contracts are installed public Core APIs. Desktop tool plugins target the ScopeOne application UI contract. ## Processing Data Flow diff --git a/ScopeOneCore/include/scopeone/PluginManifest.h b/ScopeOneCore/include/scopeone/PluginManifest.h new file mode 100644 index 0000000..6868462 --- /dev/null +++ b/ScopeOneCore/include/scopeone/PluginManifest.h @@ -0,0 +1,34 @@ +#pragma once + +#include "scopeone/scopeone_core_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_CORE_EXPORT QString pluginKindName(PluginKind kind); + SCOPEONE_CORE_EXPORT bool parsePluginManifest(const QJsonObject& metadata, + PluginKind expectedKind, + PluginManifest& manifest, + QString* errorMessage = nullptr); +} diff --git a/ScopeOneCore/include/scopeone/ToolPlugin.h b/ScopeOneCore/include/scopeone/ToolPlugin.h new file mode 100644 index 0000000..27cc9ae --- /dev/null +++ b/ScopeOneCore/include/scopeone/ToolPlugin.h @@ -0,0 +1,57 @@ +#pragma once + +#include "scopeone/ScopeOneCore.h" + +#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 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/src/DriverHostMain.cpp b/ScopeOneCore/src/DriverHostMain.cpp index 8150ccd..740eff0 100644 --- a/ScopeOneCore/src/DriverHostMain.cpp +++ b/ScopeOneCore/src/DriverHostMain.cpp @@ -32,6 +32,7 @@ #include "internal/DriverHostProtocol.h" #include "scopeone/CameraProvider.h" #include "scopeone/DriverHostProviderPlugin.h" +#include "scopeone/PluginManifest.h" #include "scopeone/HardwareCapabilities.h" #include "scopeone/SharedFrame.h" @@ -1706,6 +1707,22 @@ namespace scopeone::core::internal { m_lastError.clear(); m_pluginLoader = std::make_unique(m_pluginPath); + PluginManifest manifest; + if (!parsePluginManifest( + m_pluginLoader->metaData().value(QStringLiteral("MetaData")).toObject(), + PluginKind::Hardware, + manifest, + &m_lastError)) + { + return false; + } + if (manifest.id != m_providerId + || manifest.metadata.value(QStringLiteral("providerId")).toString().trimmed() + != m_providerId) + { + m_lastError = QStringLiteral("Provider manifest identity mismatch"); + return false; + } QObject* const instance = m_pluginLoader->instance(); if (!instance) { diff --git a/ScopeOneCore/src/PluginManifest.cpp b/ScopeOneCore/src/PluginManifest.cpp new file mode 100644 index 0000000..6005aa5 --- /dev/null +++ b/ScopeOneCore/src/PluginManifest.cpp @@ -0,0 +1,74 @@ +#include "scopeone/PluginManifest.h" + +#include + +namespace scopeone::core +{ + QString pluginKindName(PluginKind kind) + { + switch (kind) + { + case PluginKind::Processing: + return QStringLiteral("processing"); + case PluginKind::Tool: + return QStringLiteral("tool"); + case PluginKind::Hardware: + return QStringLiteral("hardware"); + } + return {}; + } + + bool parsePluginManifest(const QJsonObject& metadata, + PluginKind expectedKind, + PluginManifest& manifest, + QString* errorMessage) + { + const QString id = metadata.value(QStringLiteral("id")).toString().trimmed(); + const QString name = metadata.value(QStringLiteral("name")).toString().trimmed(); + const QString version = metadata.value(QStringLiteral("version")).toString().trimmed(); + const QString kind = metadata.value(QStringLiteral("kind")).toString().trimmed(); + const int apiVersion = metadata.value(QStringLiteral("scopeOneApi")).toInt(); + const QString expectedKindName = pluginKindName(expectedKind); + + QString error; + static const QRegularExpression idPattern( + QStringLiteral("^[A-Za-z0-9][A-Za-z0-9._-]*$")); + if (id.isEmpty() || name.isEmpty() || version.isEmpty()) + { + error = QStringLiteral("plugin manifest requires id, name and version"); + } + else if (!idPattern.match(id).hasMatch()) + { + error = QStringLiteral("plugin id contains unsupported characters"); + } + else if (kind != expectedKindName) + { + error = QStringLiteral("plugin kind must be '%1'").arg(expectedKindName); + } + else if (apiVersion != ScopeOnePluginApiVersion) + { + error = QStringLiteral("unsupported ScopeOne plugin API %1").arg(apiVersion); + } + + if (!error.isEmpty()) + { + if (errorMessage) + { + *errorMessage = error; + } + return false; + } + + manifest.id = id; + manifest.name = name; + manifest.version = version; + manifest.kind = expectedKind; + manifest.autoLoad = metadata.value(QStringLiteral("autoLoad")).toBool(); + manifest.metadata = metadata; + if (errorMessage) + { + errorMessage->clear(); + } + return true; + } +} diff --git a/ScopeOneCore/src/ProcessingModuleRegistry.cpp b/ScopeOneCore/src/ProcessingModuleRegistry.cpp index d0018e4..20f0cc6 100644 --- a/ScopeOneCore/src/ProcessingModuleRegistry.cpp +++ b/ScopeOneCore/src/ProcessingModuleRegistry.cpp @@ -5,6 +5,7 @@ #include "internal/FFTModule.h" #include "internal/GaussianBlurModule.h" #include "internal/SpatiotemporalBinningModule.h" +#include "scopeone/PluginManifest.h" #include #include @@ -230,6 +231,17 @@ namespace scopeone::core::internal } auto loader = std::make_unique(file.absoluteFilePath()); + PluginManifest manifest; + QString manifestError; + if (!parsePluginManifest( + loader->metaData().value(QStringLiteral("MetaData")).toObject(), + PluginKind::Processing, + manifest, + &manifestError)) + { + errors.append(QStringLiteral("%1: %2").arg(file.fileName(), manifestError)); + continue; + } QObject* instance = loader->instance(); auto* plugin = qobject_cast(instance); if (!plugin) diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index f25da4d..5a2ea07 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -25,9 +25,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -1132,7 +1132,11 @@ namespace scopeone::core std::make_unique(); const QStringList processingPluginErrors = m_managers->processingModuleRegistry->loadPlugins( QDir(QCoreApplication::applicationDirPath()).filePath(QStringLiteral("plugins/processing"))); - for (const QString& error : processingPluginErrors) + QStringList allProcessingPluginErrors = processingPluginErrors; + allProcessingPluginErrors.append(m_managers->processingModuleRegistry->loadPlugins( + QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)) + .filePath(QStringLiteral("plugins/processing")))); + for (const QString& error : allProcessingPluginErrors) { qWarning().noquote() << QStringLiteral("Failed to load processing plugin %1").arg(error); } @@ -1370,26 +1374,6 @@ namespace scopeone::core connect(m_managers->imageProcessingManager, &ImageProcessingManager::processingError, this, &ScopeOneCore::processingError); - const QDir hardwarePlugins( - QDir(QCoreApplication::applicationDirPath()).filePath(QStringLiteral("plugins/hardware"))); - for (const QFileInfo& file : hardwarePlugins.entryInfoList(QDir::Files, QDir::Name)) - { - QPluginLoader loader(file.absoluteFilePath()); - const QJsonObject metadata = loader.metaData().value(QStringLiteral("MetaData")).toObject(); - if (!metadata.value(QStringLiteral("autoLoad")).toBool()) - { - continue; - } - const QString providerId = metadata.value(QStringLiteral("providerId")).toString().trimmed(); - QString errorMessage; - if (providerId.isEmpty() - || !registerDriverHostProvider(providerId, file.absoluteFilePath(), {}, &errorMessage)) - { - qWarning().noquote() - << QStringLiteral("Failed to discover hardware plugin %1: %2") - .arg(file.fileName(), errorMessage); - } - } } // Release loaded devices before the facade is destroyed diff --git a/ScopeOneCore/src/SimulatorProvider.json b/ScopeOneCore/src/SimulatorProvider.json deleted file mode 100644 index a82e981..0000000 --- a/ScopeOneCore/src/SimulatorProvider.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "providerId": "scopeone.simulator", - "autoLoad": false -} diff --git a/ScopeOneCore/src/SimulatorProviderPlugin.cpp b/ScopeOneCore/src/SimulatorProviderPlugin.cpp deleted file mode 100644 index c670ffd..0000000 --- a/ScopeOneCore/src/SimulatorProviderPlugin.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include - -#include "scopeone/DriverHostProviderPlugin.h" -#include "scopeone/SimulatorProvider.h" - -namespace scopeone::core -{ - class SimulatorProviderPlugin final : public QObject, - public DriverHostProviderPlugin - { - Q_OBJECT - Q_PLUGIN_METADATA(IID ScopeOneDriverHostProviderPlugin_iid FILE "SimulatorProvider.json") - Q_INTERFACES(scopeone::core::DriverHostProviderPlugin) - - public: - QString providerId() const override - { - return QStringLiteral("simulator"); - } - - HardwareProviderPtr createProvider(const QJsonObject& options, - QString* errorMessage) override - { - if (errorMessage) - { - errorMessage->clear(); - } - const QString deviceId = options.value(QStringLiteral("cameraId")) - .toString(QStringLiteral("camera.simulator")) - .trimmed(); - const int width = options.value(QStringLiteral("width")).toInt(512); - const int height = options.value(QStringLiteral("height")).toInt(512); - if (deviceId.isEmpty() || width <= 0 || height <= 0) - { - if (errorMessage) - { - *errorMessage = QStringLiteral("Invalid simulator options"); - } - return {}; - } - return std::make_shared(deviceId, - width, - height, - providerId()); - } - }; -} - -#include "SimulatorProviderPlugin.moc" diff --git a/VERSION b/VERSION index 8d6ccce..9a0d26a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.023 +1.5.024 diff --git a/plugins/examples/CMakeLists.txt b/plugins/examples/CMakeLists.txt new file mode 100644 index 0000000..6584669 --- /dev/null +++ b/plugins/examples/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.23) +project(ScopeOnePluginExamples LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +find_package(ScopeOneCore CONFIG REQUIRED) + +function(scopeone_example_plugin target kind) + set_target_properties(${target} PROPERTIES + PREFIX "" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/${kind}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/${kind}" + ) + install(TARGETS ${target} + RUNTIME DESTINATION "plugins/${kind}" + LIBRARY DESTINATION "plugins/${kind}" + ) +endfunction() + +add_subdirectory(processing) +add_subdirectory(tool) +add_subdirectory(hardware) diff --git a/plugins/examples/README.md b/plugins/examples/README.md new file mode 100644 index 0000000..d540e15 --- /dev/null +++ b/plugins/examples/README.md @@ -0,0 +1,19 @@ +# ScopeOne plugin examples + +These projects use the installed `scopeone::PluginSDK` target and do not depend on ScopeOne source files. + +The normal ScopeOne build script builds and installs these examples automatically during a full build: + +```powershell +.\scripts\build.ps1 +``` + +To build only the examples: + +```powershell +.\scripts\build.ps1 --target plugins +``` + +Each example plugin declares `id`, `name`, `version`, `scopeOneApi`, and `kind` in `plugin.json`. Hardware providers use the public `DriverHostProviderPlugin` contract and run in `ScopeOne_DriverHost`. `ExampleHardwarePlugin` wraps the public `SimulatorProvider` to demonstrate a complete external hardware plugin. + +Install processing plugins under `plugins/processing`, tool plugins under `plugins/tools`, and hardware plugins under `plugins/hardware`. diff --git a/plugins/examples/hardware/CMakeLists.txt b/plugins/examples/hardware/CMakeLists.txt new file mode 100644 index 0000000..409aaa5 --- /dev/null +++ b/plugins/examples/hardware/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(ScopeOneExampleHardware MODULE + ExampleHardwarePlugin.cpp + plugin.json +) +target_link_libraries(ScopeOneExampleHardware PRIVATE scopeone::PluginSDK Qt6::Core) +scopeone_example_plugin(ScopeOneExampleHardware hardware) diff --git a/plugins/examples/hardware/ExampleHardwarePlugin.cpp b/plugins/examples/hardware/ExampleHardwarePlugin.cpp new file mode 100644 index 0000000..a18ce79 --- /dev/null +++ b/plugins/examples/hardware/ExampleHardwarePlugin.cpp @@ -0,0 +1,44 @@ +#include "scopeone/DriverHostProviderPlugin.h" +#include "scopeone/SimulatorProvider.h" + +#include + +namespace +{ + class ExampleHardwarePlugin final : public QObject, + public scopeone::core::DriverHostProviderPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneDriverHostProviderPlugin_iid FILE "plugin.json") + Q_INTERFACES(scopeone::core::DriverHostProviderPlugin) + + public: + QString providerId() const override + { + return QStringLiteral("example.hardware"); + } + + scopeone::core::HardwareProviderPtr createProvider(const QJsonObject& options, + QString* errorMessage) override + { + if (errorMessage) + { + errorMessage->clear(); + } + const int width = options.value(QStringLiteral("width")).toInt(512); + const int height = options.value(QStringLiteral("height")).toInt(512); + if (width <= 0 || height <= 0) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("width and height must be positive"); + } + return {}; + } + return std::make_shared( + QStringLiteral("camera.example"), width, height, providerId()); + } + }; +} + +#include "ExampleHardwarePlugin.moc" diff --git a/plugins/examples/hardware/plugin.json b/plugins/examples/hardware/plugin.json new file mode 100644 index 0000000..b4b0867 --- /dev/null +++ b/plugins/examples/hardware/plugin.json @@ -0,0 +1,9 @@ +{ + "id": "example.hardware", + "name": "Example Hardware", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "hardware", + "providerId": "example.hardware", + "autoLoad": false +} diff --git a/plugins/examples/processing/CMakeLists.txt b/plugins/examples/processing/CMakeLists.txt new file mode 100644 index 0000000..50fef99 --- /dev/null +++ b/plugins/examples/processing/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(ScopeOneExampleProcessing MODULE + ExampleProcessingPlugin.cpp + plugin.json +) +target_link_libraries(ScopeOneExampleProcessing PRIVATE scopeone::PluginSDK Qt6::Core) +scopeone_example_plugin(ScopeOneExampleProcessing processing) diff --git a/plugins/examples/processing/ExampleProcessingPlugin.cpp b/plugins/examples/processing/ExampleProcessingPlugin.cpp new file mode 100644 index 0000000..4d031b8 --- /dev/null +++ b/plugins/examples/processing/ExampleProcessingPlugin.cpp @@ -0,0 +1,49 @@ +#include "scopeone/ProcessingPlugin.h" + +#include + +namespace +{ + class PassthroughModule final : public scopeone::core::ProcessingModule + { + public: + QString id() const override { return QStringLiteral("example.passthrough"); } + QString name() const override { return QStringLiteral("Example Passthrough"); } + QVariantMap parameters() const override { return {}; } + void setParameters(const QVariantMap&) override {} + std::unique_ptr createRuntime() const override + { + return std::make_unique(); + } + scopeone::core::ProcessingResult process(const scopeone::core::ImageFrame& frame, + int) override + { + return {frame, {}}; + } + }; + + class ExampleProcessingPlugin final : public QObject, + public scopeone::core::ProcessingPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneProcessingPlugin_iid FILE "plugin.json") + Q_INTERFACES(scopeone::core::ProcessingPlugin) + + public: + QList processingModules() const override + { + return {{QStringLiteral("example.passthrough"), + QStringLiteral("Example Passthrough")}}; + } + + std::unique_ptr createProcessingModule( + const QString& moduleId) override + { + return moduleId == QStringLiteral("example.passthrough") + ? std::make_unique() + : nullptr; + } + }; +} + +#include "ExampleProcessingPlugin.moc" diff --git a/plugins/examples/processing/plugin.json b/plugins/examples/processing/plugin.json new file mode 100644 index 0000000..5be3b4f --- /dev/null +++ b/plugins/examples/processing/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "example.processing", + "name": "Example Processing", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "processing" +} diff --git a/plugins/examples/tool/CMakeLists.txt b/plugins/examples/tool/CMakeLists.txt new file mode 100644 index 0000000..5a8bd7b --- /dev/null +++ b/plugins/examples/tool/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(ScopeOneExampleTool MODULE + ExampleToolPlugin.cpp + plugin.json +) +target_link_libraries(ScopeOneExampleTool PRIVATE scopeone::PluginSDK Qt6::Widgets) +scopeone_example_plugin(ScopeOneExampleTool tools) diff --git a/plugins/examples/tool/ExampleToolPlugin.cpp b/plugins/examples/tool/ExampleToolPlugin.cpp new file mode 100644 index 0000000..571bd86 --- /dev/null +++ b/plugins/examples/tool/ExampleToolPlugin.cpp @@ -0,0 +1,43 @@ +#include "scopeone/ToolPlugin.h" + +#include +#include +#include +#include + +namespace +{ + class ExampleToolPlugin final : public QObject, + public scopeone::ui::ScopeOneToolPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneToolPlugin_iid FILE "plugin.json") + Q_INTERFACES(scopeone::ui::ScopeOneToolPlugin) + + public: + QList tools() const override + { + return {{QStringLiteral("example.tool.window"), + QStringLiteral("Example Tool"), + QStringLiteral("Examples")}}; + } + + QWidget* createTool(const QString& toolId, + scopeone::ui::ScopeOneToolContext& context, + QWidget* parent) override + { + if (toolId != QStringLiteral("example.tool.window")) + { + return nullptr; + } + auto* window = new QWidget(parent, Qt::Window); + window->setWindowTitle(QStringLiteral("Example Tool")); + auto* layout = new QVBoxLayout(window); + layout->addWidget(new QLabel( + QStringLiteral("Active layer: %1").arg(context.currentLayerKey()), window)); + return window; + } + }; +} + +#include "ExampleToolPlugin.moc" diff --git a/plugins/examples/tool/plugin.json b/plugins/examples/tool/plugin.json new file mode 100644 index 0000000..b9e207b --- /dev/null +++ b/plugins/examples/tool/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "example.tool", + "name": "Example Tool", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "tool" +} diff --git a/scripts/build.ps1 b/scripts/build.ps1 index f2ed4cd..2a36d7b 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -76,10 +76,10 @@ for ($i = 0; $i -lt $args.Count; $i++) { } } -if ($target -notin @("all", "core", "gui", "scopewriter")) { - throw "Invalid target '$target'. Expected one of: all, core, gui, scopewriter." +if ($target -notin @("all", "core", "gui", "plugins", "scopewriter")) { + throw "Invalid target '$target'. Expected one of: all, core, gui, plugins, scopewriter." } -if ($package -and $target -in @("core", "scopewriter")) { +if ($package -and $target -in @("core", "plugins", "scopewriter")) { throw "--package requires --target gui or --target all." } if ($run -and $target -eq "scopewriter") { @@ -236,9 +236,14 @@ $writerBuildDir = Join-Path $writerBuildRoot "standalone" $writerInstallDir = Join-Path $writerSourceDir "install" $writerConsumerSourceDir = Join-Path $writerSourceDir "tests\consumer" $writerConsumerBuildDir = Join-Path $writerBuildRoot "consumer" +$pluginSourceDir = Join-Path $repoRoot "plugins\examples" +$pluginBuildDir = Join-Path $repoRoot "build\plugins" +$pluginInstallDir = Join-Path $coreInstallDir "bin" $config = "Release" $coreCachePath = Join-Path $coreBuildDir "CMakeCache.txt" $guiCachePath = Join-Path $guiBuildDir "CMakeCache.txt" +$pluginCachePath = Join-Path $pluginBuildDir "CMakeCache.txt" +$qtPrefixPath = "C:/Qt/6.11.0/msvc2022_64" if ($clean) { if ($target -eq "scopewriter") { @@ -255,6 +260,10 @@ if ($clean) { Write-Step "Removing GUI build directory" Remove-Item -LiteralPath $guiBuildDir -Recurse -Force } + if ($target -in @("all", "plugins") -and (Test-Path $pluginBuildDir)) { + Write-Step "Removing plugin build directory" + Remove-Item -LiteralPath $pluginBuildDir -Recurse -Force + } if ($target -in @("all", "core") -and (Test-Path $coreBuildDir)) { Write-Step "Removing ScopeOneCore build directory" Remove-Item -LiteralPath $coreBuildDir -Recurse -Force @@ -268,6 +277,17 @@ $guiConfigureOptionOverride = $guiConfigureOption.Count -gt 0 $needCoreConfigure = $configure -or $coreConfigureOptionOverride -or -not (Test-Path $coreCachePath) $needGuiConfigure = $configure -or $guiConfigureOptionOverride -or -not (Test-Path $guiCachePath) +$pluginBuildFilesExist = (Test-Path (Join-Path $pluginBuildDir "ALL_BUILD.vcxproj")) -or + (Test-Path (Join-Path $pluginBuildDir "build.ninja")) -or + (Test-Path (Join-Path $pluginBuildDir "Makefile")) +$needPluginConfigure = $configure -or -not (Test-Path $pluginCachePath) -or + -not $pluginBuildFilesExist +$pluginCachedInstallPrefix = Normalize-CMakePath ( + Get-CMakeCacheValue -CachePath $pluginCachePath -Key "CMAKE_INSTALL_PREFIX") +if ($pluginCachedInstallPrefix -and + $pluginCachedInstallPrefix -ne (Normalize-CMakePath $pluginInstallDir)) { + $needPluginConfigure = $true +} $installPrefixOverride = Find-ConfigureOverride -Options $coreConfigureOption -Prefix "-DCMAKE_INSTALL_PREFIX=" @@ -401,6 +421,50 @@ if ($target -in @("all", "core")) { } } +if ($target -in @("all", "plugins")) { + $coreConfigFile = Join-Path $coreInstallDir "lib\cmake\ScopeOneCore\ScopeOneCoreConfig.cmake" + if (-not (Test-Path $coreConfigFile)) { + throw "ScopeOneCore is not installed at $coreInstallDir. Build the core first." + } + + if ($needPluginConfigure) { + Invoke-Step ` + -Label "Configuring ScopeOne plugin examples" ` + -FilePath $cmake ` + -Arguments @( + "-S", $pluginSourceDir, + "-B", $pluginBuildDir, + "-DScopeOneCore_ROOT=$coreInstallDir", + "-DCMAKE_PREFIX_PATH=$qtPrefixPath;$coreInstallDir", + "-DCMAKE_INSTALL_PREFIX=$pluginInstallDir" + ) ` + -WorkingDirectory $repoRoot + } + + Invoke-Step ` + -Label "Building ScopeOne plugin examples ($config)" ` + -FilePath $cmake ` + -Arguments @( + "--build", $pluginBuildDir, + "--config", $config, + "--parallel" + ) ` + -WorkingDirectory $repoRoot + + Invoke-Step ` + -Label "Installing ScopeOne plugin examples" ` + -FilePath $cmake ` + -Arguments @( + "--install", $pluginBuildDir, + "--config", $config + ) ` + -WorkingDirectory $repoRoot + + if ($target -eq "all") { + $needGuiConfigure = $true + } +} + if ($target -in @("all", "gui")) { if ($env:OS -eq "Windows_NT") { $guiBuildPrefix = [System.IO.Path]::GetFullPath($guiBuildDir).TrimEnd( @@ -472,6 +536,9 @@ if ($target -eq "scopewriter") { } else { Write-Host "ScopeOneCore install: $coreInstallDir" + if ($target -in @("all", "plugins")) { + Write-Host "Plugin examples: $pluginBuildDir" + } } if ($target -ne "scopewriter") { if (Test-Path $guiExe) { diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index ca7ac34..9963030 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -862,11 +862,17 @@ namespace scopeone::ui row++; m_previewToggleButton = new QPushButton("Start Preview"); - m_previewToggleButton->setMinimumWidth(140); m_previewToggleButton->setMinimumHeight(30); connect(m_previewToggleButton, &QPushButton::clicked, this, &DeviceControlWidget::onPreviewToggleClicked); - - layout->addWidget(m_previewToggleButton, row, 0, 1, 2); + m_snapButton = new QPushButton("Snap", group); + m_snapButton->setMinimumHeight(30); + m_snapButton->setToolTip(tr("Capture the latest frame from the selected target")); + connect(m_snapButton, &QPushButton::clicked, this, [this]() + { + emit snapRequested(m_currentTarget); + }); + layout->addWidget(m_previewToggleButton, row, 0); + layout->addWidget(m_snapButton, row, 1); QHBoxLayout* roiLayout = new QHBoxLayout(); m_drawROIButton = new QPushButton("Draw ROI", group); @@ -1323,6 +1329,7 @@ namespace scopeone::ui m_cameraSelectCombo->setEnabled(canControlHardware && m_controlTargetEnabled); m_exposureLineEdit->setEnabled(canControlHardware); m_previewToggleButton->setEnabled(canControlHardware); + m_snapButton->setEnabled(canControlHardware); m_previewToggleButton->setText(m_previewRunning ? QStringLiteral("Stop Preview") : QStringLiteral("Start Preview")); diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index 5fdfda9..5e74643 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -61,6 +61,7 @@ namespace scopeone::ui void exposureValueChanged(double exposureMs); void controlTargetChanged(const QString& target); + void snapRequested(const QString& target); void stageMoveFailed(const QString& message); void requestDrawROI(const QString& cameraId); @@ -155,6 +156,7 @@ namespace scopeone::ui QLabel* m_exposureLabel{nullptr}; QPushButton* m_previewToggleButton{nullptr}; + QPushButton* m_snapButton{nullptr}; QComboBox* m_cameraSelectCombo{nullptr}; QPushButton* m_drawROIButton{nullptr}; QPushButton* m_halfROIButton{nullptr}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 1ac3895..a221d04 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -15,6 +15,7 @@ #include "ImageProcessingWidget.h" #include "RecordingWidget.h" #include "SettingsDialog.h" +#include "PluginManagerDialog.h" #include "ScopeOneLocalApiServer.h" #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -630,6 +632,19 @@ namespace scopeone::ui { updateSessionPresentation(*m_scopeonecore, *m_imageSceneModel, session); m_imageGalleryWidget->addSession(session); + m_imageWorkspace->openSession(session); + }); + connect(m_deviceControlWidget, &DeviceControlWidget::snapRequested, + this, [this](const QString& target) + { + if (!m_recordingWidget->snapToGallery(target)) + { + showStatusMessage(tr("No current frame is available to capture"), 5000); + } + else + { + showStatusMessage(tr("Snapshot captured"), 3000); + } }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::recordingStopped, this, @@ -788,6 +803,13 @@ namespace scopeone::ui { qWarning().noquote() << QStringLiteral("Failed to load tool plugin %1").arg(error); } + const QString userPluginDirectory = + QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)) + .filePath(QStringLiteral("plugins/tools")); + for (const QString& error : m_toolRegistry->loadPlugins(userPluginDirectory)) + { + qWarning().noquote() << QStringLiteral("Failed to load tool plugin %1").arg(error); + } } scopeone::core::ScopeOneCore& MainWindow::core() const @@ -891,6 +913,11 @@ namespace scopeone::ui m_toolsMenu = menuBar()->addMenu(tr("&Tools")); m_toolRegistry->populateMenu(m_toolsMenu, this); m_toolsMenu->addSeparator(); + auto* pluginManagerAction = m_toolsMenu->addAction(tr("Plugin &Manager...")); + connect(pluginManagerAction, &QAction::triggered, this, [this]() + { + PluginManagerDialog(this).exec(); + }); m_settingsAction = m_toolsMenu->addAction(tr("&Settings...")); m_helpMenu = menuBar()->addMenu(tr("&Help")); diff --git a/src/PluginManagerDialog.cpp b/src/PluginManagerDialog.cpp new file mode 100644 index 0000000..bdce7c6 --- /dev/null +++ b/src/PluginManagerDialog.cpp @@ -0,0 +1,392 @@ +#include "PluginManagerDialog.h" + +#include "scopeone/PluginManifest.h" +#include "scopeone/DriverHostProviderPlugin.h" +#include "scopeone/ProcessingPlugin.h" +#include "scopeone/ScopeOneCore.h" +#include "scopeone/ToolPlugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scopeone::ui +{ + namespace + { + constexpr int kIdRole = Qt::UserRole; + constexpr int kKindRole = Qt::UserRole + 1; + + struct DiscoveredPlugin + { + scopeone::core::PluginManifest manifest; + scopeone::core::PluginKind expectedKind{scopeone::core::PluginKind::Processing}; + QString path; + QString error; + }; + + QString pluginInterfaceId(scopeone::core::PluginKind kind); + + QList discoverPlugins() + { + using scopeone::core::PluginKind; + const QList> directories{ + {QStringLiteral("processing"), PluginKind::Processing}, + {QStringLiteral("tools"), PluginKind::Tool}, + {QStringLiteral("hardware"), PluginKind::Hardware} + }; + QList plugins; + const QStringList roots{ + QDir(QCoreApplication::applicationDirPath()).filePath(QStringLiteral("plugins")), + QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)) + .filePath(QStringLiteral("plugins")) + }; + for (const QString& rootPath : roots) + { + const QDir root(rootPath); + for (const auto& [directoryName, kind] : directories) + { + const QDir directory(root.filePath(directoryName)); + for (const QFileInfo& file : directory.entryInfoList(QDir::Files, QDir::Name)) + { + if (!QLibrary::isLibrary(file.absoluteFilePath())) + { + continue; + } + QPluginLoader loader(file.absoluteFilePath()); + DiscoveredPlugin plugin; + plugin.expectedKind = kind; + plugin.path = file.absoluteFilePath(); + const QJsonObject loaderMetadata = loader.metaData(); + scopeone::core::parsePluginManifest( + loaderMetadata.value(QStringLiteral("MetaData")).toObject(), + kind, + plugin.manifest, + &plugin.error); + if (plugin.error.isEmpty() + && loaderMetadata.value(QStringLiteral("IID")).toString() + != pluginInterfaceId(kind)) + { + plugin.error = QStringLiteral("plugin interface does not match its kind"); + } + plugins.append(std::move(plugin)); + } + } + } + return plugins; + } + + QString settingsKey(const QString& pluginId, const QString& name) + { + return QStringLiteral("Plugins/%1/%2").arg(pluginId, name); + } + + QString pluginDirectoryName(scopeone::core::PluginKind kind) + { + return kind == scopeone::core::PluginKind::Tool + ? QStringLiteral("tools") + : scopeone::core::pluginKindName(kind); + } + + QString pluginInterfaceId(scopeone::core::PluginKind kind) + { + switch (kind) + { + case scopeone::core::PluginKind::Processing: + return QStringLiteral(ScopeOneProcessingPlugin_iid); + case scopeone::core::PluginKind::Tool: + return QStringLiteral(ScopeOneToolPlugin_iid); + case scopeone::core::PluginKind::Hardware: + return QStringLiteral(ScopeOneDriverHostProviderPlugin_iid); + } + return {}; + } + } + + QStringList loadConfiguredHardwarePlugins(scopeone::core::ScopeOneCore& core) + { + QStringList errors; + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + for (const DiscoveredPlugin& plugin : discoverPlugins()) + { + if (plugin.expectedKind != scopeone::core::PluginKind::Hardware) + { + continue; + } + if (!plugin.error.isEmpty()) + { + errors.append(QStringLiteral("%1: %2") + .arg(QFileInfo(plugin.path).fileName(), plugin.error)); + continue; + } + + const QString id = plugin.manifest.id; + const QString enabledKey = settingsKey(id, QStringLiteral("enabled")); + const bool enabled = settings.contains(enabledKey) + ? settings.value(enabledKey).toBool() + : plugin.manifest.autoLoad; + if (!enabled) + { + continue; + } + + const QString providerId = plugin.manifest.metadata + .value(QStringLiteral("providerId")) + .toString().trimmed(); + if (providerId != id) + { + errors.append(QStringLiteral("%1: providerId must match plugin id") + .arg(QFileInfo(plugin.path).fileName())); + continue; + } + const QVariantMap options = settings + .value(settingsKey(id, QStringLiteral("options"))) + .toMap(); + QString error; + if (!core.registerDriverHostProvider(providerId, plugin.path, options, &error)) + { + errors.append(QStringLiteral("%1: %2") + .arg(QFileInfo(plugin.path).fileName(), error)); + } + } + return errors; + } + + PluginManagerDialog::PluginManagerDialog(QWidget* parent) + : QDialog(parent) + { + setWindowTitle(tr("Plugin Manager")); + resize(820, 460); + + auto* layout = new QVBoxLayout(this); + m_table = new QTableWidget(this); + m_table->setColumnCount(6); + m_table->setHorizontalHeaderLabels( + {tr("Enabled"), tr("Name"), tr("Type"), tr("Version"), tr("Status"), tr("Location")}); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_table->verticalHeader()->setVisible(false); + m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + m_table->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch); + layout->addWidget(m_table, 1); + + layout->addWidget(new QLabel(tr("Hardware options (JSON)"), this)); + m_optionsEdit = new QPlainTextEdit(this); + m_optionsEdit->setMaximumHeight(100); + m_optionsEdit->setPlaceholderText(QStringLiteral("{}")); + layout->addWidget(m_optionsEdit); + + auto* buttons = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Close, + this); + auto* installButton = buttons->addButton(tr("Install..."), QDialogButtonBox::ActionRole); + layout->addWidget(buttons); + connect(buttons->button(QDialogButtonBox::Save), &QPushButton::clicked, + this, &PluginManagerDialog::saveHardwareSettings); + connect(installButton, &QPushButton::clicked, + this, &PluginManagerDialog::installPlugin); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(m_table, &QTableWidget::itemSelectionChanged, + this, &PluginManagerDialog::showSelectedOptions); + refreshPlugins(); + } + + void PluginManagerDialog::refreshPlugins() + { + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + const QList plugins = discoverPlugins(); + m_table->setRowCount(plugins.size()); + for (int row = 0; row < plugins.size(); ++row) + { + const DiscoveredPlugin& plugin = plugins.at(row); + const bool hardware = plugin.expectedKind == scopeone::core::PluginKind::Hardware; + auto* enabled = new QTableWidgetItem(); + enabled->setData(kIdRole, plugin.manifest.id); + enabled->setData(kKindRole, static_cast(plugin.expectedKind)); + if (hardware && plugin.error.isEmpty()) + { + enabled->setFlags(enabled->flags() | Qt::ItemIsUserCheckable); + const QString key = settingsKey(plugin.manifest.id, QStringLiteral("enabled")); + const bool checked = settings.contains(key) + ? settings.value(key).toBool() + : plugin.manifest.autoLoad; + enabled->setCheckState(checked ? Qt::Checked : Qt::Unchecked); + } + m_table->setItem(row, 0, enabled); + m_table->setItem(row, 1, new QTableWidgetItem( + plugin.manifest.name.isEmpty() + ? QFileInfo(plugin.path).completeBaseName() + : plugin.manifest.name)); + m_table->setItem(row, 2, new QTableWidgetItem( + scopeone::core::pluginKindName(plugin.expectedKind))); + m_table->setItem(row, 3, new QTableWidgetItem(plugin.manifest.version)); + m_table->setItem(row, 4, new QTableWidgetItem( + plugin.error.isEmpty() ? tr("Compatible") : plugin.error)); + m_table->setItem(row, 5, new QTableWidgetItem(plugin.path)); + } + if (m_table->rowCount() > 0) + { + m_table->selectRow(0); + } + } + + void PluginManagerDialog::showSelectedOptions() + { + const int row = m_table->currentRow(); + if (row < 0) + { + m_optionsEdit->clear(); + m_optionsEdit->setEnabled(false); + return; + } + const QTableWidgetItem* item = m_table->item(row, 0); + const auto kind = static_cast(item->data(kKindRole).toInt()); + const QString id = item->data(kIdRole).toString(); + const bool hardware = kind == scopeone::core::PluginKind::Hardware && !id.isEmpty(); + m_optionsEdit->setEnabled(hardware); + if (!hardware) + { + m_optionsEdit->clear(); + return; + } + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + const QJsonObject options = QJsonObject::fromVariantMap( + settings.value(settingsKey(id, QStringLiteral("options"))).toMap()); + m_optionsEdit->setPlainText(QString::fromUtf8( + QJsonDocument(options).toJson(QJsonDocument::Indented))); + } + + void PluginManagerDialog::installPlugin() + { + const QString sourcePath = QFileDialog::getOpenFileName( + this, tr("Install Plugin"), {}, tr("Plugin libraries (*.dll *.so *.dylib)")); + if (sourcePath.isEmpty()) + { + return; + } + + QPluginLoader loader(sourcePath); + const QJsonObject loaderMetadata = loader.metaData(); + const QJsonObject metadata = loaderMetadata.value(QStringLiteral("MetaData")).toObject(); + scopeone::core::PluginManifest manifest; + QString error; + bool valid = false; + scopeone::core::PluginKind kind = scopeone::core::PluginKind::Processing; + for (const auto candidate : {scopeone::core::PluginKind::Processing, + scopeone::core::PluginKind::Tool, + scopeone::core::PluginKind::Hardware}) + { + if (scopeone::core::parsePluginManifest(metadata, candidate, manifest, &error)) + { + kind = candidate; + valid = true; + break; + } + } + if (!valid) + { + QMessageBox::warning(this, tr("Plugin Manager"), error); + return; + } + if (loaderMetadata.value(QStringLiteral("IID")).toString() != pluginInterfaceId(kind)) + { + QMessageBox::warning(this, tr("Plugin Manager"), + tr("The plugin interface does not match its declared type.")); + return; + } + + QDir userRoot(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); + const QString pluginDirectory = userRoot.filePath( + QStringLiteral("plugins/%1").arg(pluginDirectoryName(kind))); + if (!QDir().mkpath(pluginDirectory)) + { + QMessageBox::warning(this, tr("Plugin Manager"), tr("The plugin directory could not be created.")); + return; + } + const QString destination = QDir(pluginDirectory).filePath(QFileInfo(sourcePath).fileName()); + if (QFileInfo(sourcePath).canonicalFilePath() == QFileInfo(destination).canonicalFilePath()) + { + QMessageBox::information(this, tr("Plugin Manager"), tr("This plugin is already installed.")); + return; + } + if (QFileInfo::exists(destination) && !QFile::remove(destination)) + { + QMessageBox::warning(this, tr("Plugin Manager"), tr("The existing plugin could not be replaced.")); + return; + } + if (!QFile::copy(sourcePath, destination)) + { + QMessageBox::warning(this, tr("Plugin Manager"), tr("The plugin could not be installed.")); + return; + } + refreshPlugins(); + QMessageBox::information(this, tr("Plugin Manager"), + tr("The plugin will be available after ScopeOne restarts.")); + } + + void PluginManagerDialog::saveHardwareSettings() + { + const int row = m_table->currentRow(); + if (row < 0) + { + return; + } + QTableWidgetItem* item = m_table->item(row, 0); + const auto kind = static_cast(item->data(kKindRole).toInt()); + const QString id = item->data(kIdRole).toString(); + QVariantMap selectedOptions; + if (kind == scopeone::core::PluginKind::Hardware && !id.isEmpty()) + { + QJsonParseError parseError; + QByteArray optionJson = m_optionsEdit->toPlainText().trimmed().toUtf8(); + if (optionJson.isEmpty()) + { + optionJson = QByteArrayLiteral("{}"); + } + const QJsonDocument options = QJsonDocument::fromJson(optionJson, &parseError); + if (parseError.error != QJsonParseError::NoError || !options.isObject()) + { + QMessageBox::warning(this, tr("Plugin Manager"), tr("Hardware options must be a JSON object.")); + return; + } + selectedOptions = options.object().toVariantMap(); + } + + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + for (int pluginRow = 0; pluginRow < m_table->rowCount(); ++pluginRow) + { + QTableWidgetItem* pluginItem = m_table->item(pluginRow, 0); + const auto pluginKind = static_cast( + pluginItem->data(kKindRole).toInt()); + const QString pluginId = pluginItem->data(kIdRole).toString(); + if (pluginKind == scopeone::core::PluginKind::Hardware && !pluginId.isEmpty()) + { + settings.setValue(settingsKey(pluginId, QStringLiteral("enabled")), + pluginItem->checkState() == Qt::Checked); + } + } + if (kind == scopeone::core::PluginKind::Hardware && !id.isEmpty()) + { + settings.setValue(settingsKey(id, QStringLiteral("options")), selectedOptions); + } + QMessageBox::information(this, tr("Plugin Manager"), + tr("Plugin settings will take effect after ScopeOne restarts.")); + } +} diff --git a/src/PluginManagerDialog.h b/src/PluginManagerDialog.h new file mode 100644 index 0000000..ffcb54c --- /dev/null +++ b/src/PluginManagerDialog.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +class QPlainTextEdit; +class QTableWidget; + +namespace scopeone::core +{ + class ScopeOneCore; +} + +namespace scopeone::ui +{ + QStringList loadConfiguredHardwarePlugins(scopeone::core::ScopeOneCore& core); + + class PluginManagerDialog final : public QDialog + { + Q_OBJECT + + public: + explicit PluginManagerDialog(QWidget* parent = nullptr); + + private: + void refreshPlugins(); + void installPlugin(); + void showSelectedOptions(); + void saveHardwareSettings(); + + QTableWidget* m_table{nullptr}; + QPlainTextEdit* m_optionsEdit{nullptr}; + }; +} diff --git a/src/RecordingWidget.cpp b/src/RecordingWidget.cpp index ce0d3da..092542f 100644 --- a/src/RecordingWidget.cpp +++ b/src/RecordingWidget.cpp @@ -225,8 +225,6 @@ namespace scopeone::ui connect(m_startStopButton, &QPushButton::clicked, this, &RecordingWidget::onStartStopClicked); connect(m_burstModeCheck, &QCheckBox::toggled, this, [this]() { updateUiState(); }); connect(m_detectorCombo, &QComboBox::currentTextChanged, this, [this]() { updateUiState(); }); - connect(m_snapToGalleryButton, &QPushButton::clicked, this, - [this]() { appendSelectedFramesToGallery(); }); connect(m_saveDirLineEdit, &QLineEdit::textChanged, this, [this]() { updateUiState(); }); connect(m_fileNameLineEdit, &QLineEdit::textChanged, this, [this]() { updateUiState(); }); connect(m_formatCombo, QOverload::of(&QComboBox::currentIndexChanged), this, @@ -438,13 +436,6 @@ namespace scopeone::ui captureLayout->addWidget(m_fileNameLineEdit, 2, 1); captureLayout->addWidget(m_autoNameButton, 2, 2); - auto* galleryRow = new QHBoxLayout(); - galleryRow->setSpacing(6); - m_snapToGalleryButton = new QPushButton("Snap to Gallery", this); - galleryRow->addWidget(m_snapToGalleryButton); - captureLayout->addWidget(new QLabel("Gallery:", this), 3, 0); - captureLayout->addLayout(galleryRow, 3, 1, 1, 2); - contentLayout->addWidget(captureGroup); auto* formatGroup = new QGroupBox("Format", this); @@ -720,7 +711,6 @@ namespace scopeone::ui m_browseButton->setEnabled(editingEnabled); m_fileNameLineEdit->setEnabled(editingEnabled); m_autoNameButton->setEnabled(editingEnabled); - m_snapToGalleryButton->setEnabled(hasSelectedCameras); m_formatCombo->setEnabled(editingEnabled); const bool binaryFormat = m_formatCombo->currentData().toInt() == static_cast(scopeone::core::RecordingFormat::Binary); @@ -976,10 +966,19 @@ namespace scopeone::ui return true; } - // Captures latest selected frames into the gallery - bool RecordingWidget::appendSelectedFramesToGallery() + // Captures the Acquire target into the gallery + bool RecordingWidget::snapToGallery(const QString& target) { - const QStringList cameraIds = selectedCameraIds(); + const QString normalizedTarget = target.trimmed(); + QStringList cameraIds; + if (normalizedTarget.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) + { + cameraIds = m_availableCameraIds; + } + else if (m_availableCameraIds.contains(normalizedTarget)) + { + cameraIds = {normalizedTarget}; + } if (cameraIds.isEmpty()) { qWarning().noquote() << "No camera available for gallery capture"; diff --git a/src/RecordingWidget.h b/src/RecordingWidget.h index 2f0923a..f8d4597 100644 --- a/src/RecordingWidget.h +++ b/src/RecordingWidget.h @@ -28,6 +28,7 @@ namespace scopeone::ui ~RecordingWidget() override; void setAvailableCameras(const QStringList& cameraIds); + bool snapToGallery(const QString& target); signals: void gallerySessionCaptured( @@ -44,8 +45,6 @@ namespace scopeone::ui void updateStorageStatusText(qint64 availableBytes); void moveOrderItem(int delta); void syncOrderList(); - bool appendSelectedFramesToGallery(); - QString getLastSaveDirectory() const; void setLastSaveDirectory(const QString& path); QString buildTimestampBaseName() const; @@ -60,8 +59,6 @@ namespace scopeone::ui QPushButton* m_browseButton{nullptr}; QLineEdit* m_fileNameLineEdit{nullptr}; QPushButton* m_autoNameButton{nullptr}; - QPushButton* m_snapToGalleryButton{nullptr}; - QCheckBox* m_compressionCheck{nullptr}; QSpinBox* m_compressionLevelSpin{nullptr}; QComboBox* m_formatCombo{nullptr}; diff --git a/src/ScopeOneToolPlugin.cpp b/src/ScopeOneToolPlugin.cpp index 2bf64f8..41660e5 100644 --- a/src/ScopeOneToolPlugin.cpp +++ b/src/ScopeOneToolPlugin.cpp @@ -1,4 +1,5 @@ #include "ScopeOneToolPlugin.h" +#include "scopeone/PluginManifest.h" #include #include @@ -67,6 +68,17 @@ namespace scopeone::ui continue; } auto loader = std::make_unique(file.absoluteFilePath()); + scopeone::core::PluginManifest manifest; + QString manifestError; + if (!scopeone::core::parsePluginManifest( + loader->metaData().value(QStringLiteral("MetaData")).toObject(), + scopeone::core::PluginKind::Tool, + manifest, + &manifestError)) + { + errors.append(QStringLiteral("%1: %2").arg(file.fileName(), manifestError)); + continue; + } auto* plugin = qobject_cast(loader->instance()); if (!plugin) { diff --git a/src/ScopeOneToolPlugin.h b/src/ScopeOneToolPlugin.h index e0eb938..3cc019a 100644 --- a/src/ScopeOneToolPlugin.h +++ b/src/ScopeOneToolPlugin.h @@ -1,11 +1,8 @@ #pragma once -#include "scopeone/ScopeOneCore.h" +#include "scopeone/ToolPlugin.h" -#include -#include #include -#include #include #include #include @@ -17,46 +14,6 @@ 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 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; - }; - class ToolRegistry { public: @@ -81,6 +38,3 @@ namespace scopeone::ui bool m_enabled{true}; }; } - -#define ScopeOneToolPlugin_iid "org.scopeone.ToolPlugin/1.0" -Q_DECLARE_INTERFACE(scopeone::ui::ScopeOneToolPlugin, ScopeOneToolPlugin_iid) diff --git a/src/main.cpp b/src/main.cpp index cbeb0ab..1437ae1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include "scopeone/ScopeOneCore.h" #include "ConsoleWidget.h" #include "MainWindow.h" +#include "PluginManagerDialog.h" int main(int argc, char *argv[]) { @@ -25,6 +26,10 @@ int main(int argc, char *argv[]) scopeone::ui::ConsoleWidget::installQtMessageHandler(); auto scopeOneCore = std::make_unique(); + for (const QString& error : scopeone::ui::loadConfiguredHardwarePlugins(*scopeOneCore)) + { + qWarning().noquote() << QStringLiteral("Failed to load hardware plugin %1").arg(error); + } scopeone::ui::MainWindow window(scopeOneCore.get()); window.show(); return app.exec(); From aeaee700a0c3db8bda6719f3fbeb5ffb74bea183 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:37:32 +0200 Subject: [PATCH 06/37] Unify dock panels and streamline preview controls --- VERSION | 2 +- src/DeviceControlWidget.cpp | 45 -------------------- src/DeviceControlWidget.h | 1 - src/ImageGalleryWidget.cpp | 8 ++-- src/ImageGalleryWidget.h | 2 +- src/ImageProcessingWidget.cpp | 29 +++++++++++-- src/ImageWorkspace.cpp | 51 ++++++++++++++++++++-- src/MainWindow.cpp | 80 ++++++++++++++++++++--------------- src/MainWindow.h | 10 +++-- 9 files changed, 132 insertions(+), 96 deletions(-) diff --git a/VERSION b/VERSION index 9a0d26a..03a70cf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.024 +1.5.025 diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 9963030..2ab2b63 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -129,26 +129,8 @@ namespace scopeone::ui auto* hardwareLayout = new QVBoxLayout(hardwareContainer); hardwareLayout->setSpacing(5); hardwareLayout->setContentsMargins(5, 5, 5, 5); - m_hardwareContextLabel = new QLabel( - QStringLiteral("Hardware controls are available in Live"), hardwareContainer); - m_hardwareContextLabel->setWordWrap(true); - hardwareLayout->addWidget(m_hardwareContextLabel); hardwareLayout->addWidget(createControlGroup()); - auto* stageToggle = new QToolButton(hardwareContainer); - stageToggle->setText(QStringLiteral("Stage")); - stageToggle->setCheckable(true); - stageToggle->setChecked(false); - stageToggle->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - stageToggle->setArrowType(Qt::RightArrow); auto* stageGroup = createStageGroup(); - stageGroup->setVisible(false); - connect(stageToggle, &QToolButton::toggled, - hardwareContainer, [stageToggle, stageGroup](bool expanded) - { - stageToggle->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow); - stageGroup->setVisible(expanded); - }); - hardwareLayout->addWidget(stageToggle); hardwareLayout->addWidget(stageGroup); hardwareLayout->addStretch(); m_hardwareControlsWidget->setWidget(hardwareContainer); @@ -347,8 +329,6 @@ namespace scopeone::ui auto* transformGroup = new QGroupBox(QStringLiteral("Transform"), this); auto* transformLayout = new QGridLayout(transformGroup); transformLayout->setContentsMargins(6, 6, 6, 6); - transformGroup->setCheckable(true); - transformGroup->setChecked(false); m_alignXLabel = new QLabel("X offset:", transformGroup); m_alignXSpinBox = new QSpinBox(transformGroup); m_alignXSpinBox->setRange(-1000, 1000); @@ -377,30 +357,6 @@ namespace scopeone::ui m_alignResetButton->setMaximumWidth(50); m_alignResetButton->setToolTip("Reset offset and flip"); - const QList transformWidgets{ - m_alignXLabel, - m_alignXSpinBox, - m_alignYLabel, - m_alignYSpinBox, - m_alignZoomLabel, - m_alignZoomSpinBox, - m_alignFlipXCheckBox, - m_alignFlipYCheckBox, - m_alignResetButton, - }; - connect(transformGroup, &QGroupBox::toggled, - transformGroup, [transformWidgets](bool expanded) - { - for (QWidget* widget : transformWidgets) - { - widget->setVisible(expanded); - } - }); - for (QWidget* widget : transformWidgets) - { - widget->setVisible(false); - } - transformLayout->addWidget(m_alignXLabel, 0, 0); transformLayout->addWidget(m_alignXSpinBox, 0, 1, Qt::AlignLeft); transformLayout->addWidget(m_alignYLabel, 0, 2); @@ -1321,7 +1277,6 @@ namespace scopeone::ui void DeviceControlWidget::updateControlsState() { const bool canControlHardware = m_cameraInitialized && m_liveViewerContext; - m_hardwareContextLabel->setVisible(!m_liveViewerContext); m_cameraControlsGroup->setVisible(true); m_stageControlsGroup->setVisible(true); m_cameraControlsGroup->setEnabled(canControlHardware); diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index 5e74643..a994ffb 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -123,7 +123,6 @@ namespace scopeone::ui scopeone::core::ImageSceneModel* m_sceneModel{nullptr}; QScrollArea* m_imageControlsWidget{nullptr}; QScrollArea* m_hardwareControlsWidget{nullptr}; - QLabel* m_hardwareContextLabel{nullptr}; QGroupBox* m_previewControlsGroup{nullptr}; QGroupBox* m_cameraControlsGroup{nullptr}; QGroupBox* m_stageControlsGroup{nullptr}; diff --git a/src/ImageGalleryWidget.cpp b/src/ImageGalleryWidget.cpp index e52af37..e770b3a 100644 --- a/src/ImageGalleryWidget.cpp +++ b/src/ImageGalleryWidget.cpp @@ -212,11 +212,11 @@ namespace scopeone::ui m_liveButton = new QPushButton(QStringLiteral("Live"), this); m_openButton = new QPushButton(QStringLiteral("Preview"), this); m_saveCheckedButton = new QPushButton(QStringLiteral("Save Checked"), this); - m_removeButton = new QPushButton(QStringLiteral("Remove"), this); + m_deleteButton = new QPushButton(QStringLiteral("Delete"), this); buttonLayout->addWidget(m_liveButton); buttonLayout->addWidget(m_openButton); buttonLayout->addWidget(m_saveCheckedButton); - buttonLayout->addWidget(m_removeButton); + buttonLayout->addWidget(m_deleteButton); layout->addLayout(buttonLayout); connect(m_sessionList, &QListWidget::currentItemChanged, this, @@ -253,7 +253,7 @@ namespace scopeone::ui emit saveSessionsRequested(sessions); } }); - connect(m_removeButton, &QPushButton::clicked, this, + connect(m_deleteButton, &QPushButton::clicked, this, [this]() { QListWidgetItem* item = m_sessionList->currentItem(); @@ -278,7 +278,7 @@ namespace scopeone::ui const auto session = currentSession(); const bool hasCurrent = session != nullptr; m_openButton->setEnabled(canPreviewSession(session)); - m_removeButton->setEnabled(hasCurrent); + m_deleteButton->setEnabled(hasCurrent); m_saveCheckedButton->setEnabled(!checkedSessions().isEmpty()); } diff --git a/src/ImageGalleryWidget.h b/src/ImageGalleryWidget.h index 82c1c4e..5b7aeda 100644 --- a/src/ImageGalleryWidget.h +++ b/src/ImageGalleryWidget.h @@ -47,7 +47,7 @@ namespace scopeone::ui QPushButton* m_liveButton{nullptr}; QPushButton* m_openButton{nullptr}; QPushButton* m_saveCheckedButton{nullptr}; - QPushButton* m_removeButton{nullptr}; + QPushButton* m_deleteButton{nullptr}; int m_nextSnapshotTitleIndex{1}; int m_nextRecordingTitleIndex{1}; }; diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index 49cb90e..f6bc43a 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -10,14 +10,17 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -200,6 +203,8 @@ namespace scopeone::ui { qFatal("ImageProcessingWidget requires ScopeOneCore and ImageWorkspace"); } + setAutoFillBackground(true); + setBackgroundRole(QPalette::Window); m_processingRunning = core->isRealTimeProcessingEnabled(); setupUI(); @@ -345,7 +350,21 @@ namespace scopeone::ui void ImageProcessingWidget::setupUI() { auto* mainLayout = new QVBoxLayout(this); - auto* splitter = new QSplitter(Qt::Vertical, this); + mainLayout->setContentsMargins(0, 0, 0, 0); + auto* scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setFrameShape(QFrame::NoFrame); + + auto* content = new QWidget(scrollArea); + content->setAutoFillBackground(true); + content->setBackgroundRole(QPalette::Window); + auto* contentLayout = new QVBoxLayout(content); + contentLayout->setContentsMargins(5, 5, 5, 5); + auto* splitter = new QSplitter(Qt::Vertical, content); + splitter->setAutoFillBackground(true); + splitter->setBackgroundRole(QPalette::Window); QWidget* pipelineGroup = setupModuleList(); QWidget* parametersGroup = setupModuleConfig(); setupRunControls(); @@ -354,9 +373,11 @@ namespace scopeone::ui splitter->addWidget(parametersGroup); splitter->setStretchFactor(0, 2); splitter->setStretchFactor(1, 3); - mainLayout->addWidget(splitter, 1); - mainLayout->addWidget(m_runControlsWidget); - mainLayout->addWidget(m_sourceControlsWidget); + contentLayout->addWidget(splitter, 1); + contentLayout->addWidget(m_runControlsWidget); + contentLayout->addWidget(m_sourceControlsWidget); + scrollArea->setWidget(content); + mainLayout->addWidget(scrollArea); } void ImageProcessingWidget::setupSourceControls() diff --git a/src/ImageWorkspace.cpp b/src/ImageWorkspace.cpp index 364b187..d5b7c6e 100644 --- a/src/ImageWorkspace.cpp +++ b/src/ImageWorkspace.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -177,6 +178,41 @@ namespace scopeone::ui const QString baseName = session.capturePlan().baseName.trimmed(); return baseName.isEmpty() ? cameraId : baseName + QStringLiteral(" - ") + cameraId; } + + QString compactViewerTitle(const QString& title) + { + static const QRegularExpression timestampPattern( + QStringLiteral("(\\d{8})_(\\d{6})")); + const QRegularExpressionMatch firstMatch = timestampPattern.match(title); + if (!firstMatch.hasMatch()) + { + return title; + } + const QDateTime timestamp = QDateTime::fromString( + firstMatch.captured(1) + QStringLiteral("_") + firstMatch.captured(2), + QStringLiteral("yyyyMMdd_HHmmss")); + if (!timestamp.isValid()) + { + return title; + } + QString compact = title.left(firstMatch.capturedStart()) + + timestamp.toString(QStringLiteral("MM-dd HH:mm:ss")) + + title.mid(firstMatch.capturedEnd()); + + QRegularExpressionMatchIterator iterator = timestampPattern.globalMatch(compact); + QList> duplicateRanges; + while (iterator.hasNext()) + { + const QRegularExpressionMatch duplicate = iterator.next(); + duplicateRanges.append({duplicate.capturedStart(), duplicate.capturedLength()}); + } + for (auto it = duplicateRanges.crbegin(); it != duplicateRanges.crend(); ++it) + { + compact.remove(it->first, it->second); + } + compact.remove(QRegularExpression(QStringLiteral("\\s*[-|_]\\s*$"))); + return compact.trimmed(); + } } struct ImageWorkspace::Document @@ -239,6 +275,9 @@ namespace scopeone::ui hostLayout->addWidget(m_viewerStack, 1); setupViewerToolbar(); + const int viewerToolbarHeight = m_viewerToolbar->sizeHint().height(); + m_viewerToolbar->setMinimumHeight(viewerToolbarHeight); + m_viewerToolbar->setMaximumHeight(viewerToolbarHeight); connect(m_viewerTabs, &QTabWidget::currentChanged, this, [this](int index) { @@ -682,7 +721,9 @@ namespace scopeone::ui connect(document->page, &ImageDocumentPage::frameIndexRequested, this, &ImageWorkspace::requestDocumentFrame); m_documents.push_back(std::move(document)); - m_viewerTabs->addTab(m_documents.back()->page, m_documents.back()->title); + const int tabIndex = m_viewerTabs->addTab( + m_documents.back()->page, compactViewerTitle(m_documents.back()->title)); + m_viewerTabs->setTabToolTip(tabIndex, m_documents.back()->title); if (!requestFrame(*m_documents.back(), 0)) { closeDocument(id); @@ -1243,7 +1284,9 @@ namespace scopeone::ui } for (const auto& document : m_documents) { - m_viewerTabs->addTab(document->page, document->title); + const int tabIndex = m_viewerTabs->addTab( + document->page, compactViewerTitle(document->title)); + m_viewerTabs->setTabToolTip(tabIndex, document->title); } if (Document* active = findDocument(m_activeDocumentId)) { @@ -1411,7 +1454,9 @@ namespace scopeone::ui this, &ImageWorkspace::requestDocumentFrame); const QString id = document->id; m_documents.push_back(std::move(document)); - m_viewerTabs->addTab(m_documents.back()->page, m_documents.back()->title); + const int tabIndex = m_viewerTabs->addTab( + m_documents.back()->page, compactViewerTitle(m_documents.back()->title)); + m_viewerTabs->setTabToolTip(tabIndex, m_documents.back()->title); if (!requestFrame(*m_documents.back(), source->frameIndex)) { removeDocument(id); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index a221d04..5c03f4c 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -38,7 +38,6 @@ #include #include #include -#include #include #include #include @@ -600,7 +599,6 @@ namespace scopeone::ui m_deviceControlWidget->setPreviewWidget( m_imageWorkspace->activePreviewWidget()); m_deviceControlWidget->setViewerContext(liveViewer); - m_inspectorDockWidget->setWindowTitle(tr("Inspector")); }); connect(m_imageWorkspace, &ImageWorkspace::activeDocumentChanged, this, [this](const QString& documentId) @@ -632,7 +630,6 @@ namespace scopeone::ui { updateSessionPresentation(*m_scopeonecore, *m_imageSceneModel, session); m_imageGalleryWidget->addSession(session); - m_imageWorkspace->openSession(session); }); connect(m_deviceControlWidget, &DeviceControlWidget::snapRequested, this, [this](const QString& target) @@ -955,59 +952,72 @@ namespace scopeone::ui void MainWindow::setupImageProcessing() { m_imageProcessingWidget = new ImageProcessingWidget(m_scopeonecore, m_imageWorkspace, this); - m_inspectorDockWidget = new QDockWidget(tr("Inspector"), this); - m_inspectorDockWidget->setFeatures(QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetClosable); - m_inspectorTabs = new QTabWidget(m_inspectorDockWidget); - m_inspectorTabs->addTab(m_deviceControlWidget->hardwareControlsWidget(), tr("Acquire")); - m_inspectorTabs->addTab(m_deviceControlWidget->imageControlsWidget(), tr("View")); - m_inspectorTabs->addTab(m_inspectWidget, tr("Analyze")); - m_inspectorTabs->addTab(m_imageProcessingWidget, tr("Process")); - m_inspectorDockWidget->setWidget(m_inspectorTabs); - addDockWidget(Qt::RightDockWidgetArea, m_inspectorDockWidget); - } - // Create the log console dock and install the Qt message sink - void MainWindow::setupConsole() - { + m_controlDockWidget = new QDockWidget(tr("Control"), this); + m_controlDockWidget->setWidget(m_deviceControlWidget->hardwareControlsWidget()); + m_controlDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); + addDockWidget(Qt::RightDockWidgetArea, m_controlDockWidget); + + m_viewDockWidget = new QDockWidget(tr("View"), this); + m_viewDockWidget->setWidget(m_deviceControlWidget->imageControlsWidget()); + m_viewDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); + tabifyDockWidget(m_controlDockWidget, m_viewDockWidget); + + m_analyzeDockWidget = new QDockWidget(tr("Analyze"), this); + m_analyzeDockWidget->setWidget(m_inspectWidget); + m_analyzeDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); + tabifyDockWidget(m_controlDockWidget, m_analyzeDockWidget); + + m_processDockWidget = new QDockWidget(tr("Process"), this); + m_processDockWidget->setWidget(m_imageProcessingWidget); + m_processDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); + tabifyDockWidget(m_controlDockWidget, m_processDockWidget); + m_consoleDockWidget = new QDockWidget(tr("Console"), this); - m_consoleWidget = new ConsoleWidget(this); + m_consoleWidget = new ConsoleWidget(m_consoleDockWidget); m_consoleDockWidget->setWidget(m_consoleWidget); + m_consoleDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); + tabifyDockWidget(m_controlDockWidget, m_consoleDockWidget); + m_controlDockWidget->raise(); + } + // Install the Qt message sink for the embedded console + void MainWindow::setupConsole() + { ConsoleWidget::installAsQtMessageSink(m_consoleWidget); - - addDockWidget(Qt::BottomDockWidgetArea, m_consoleDockWidget); } // Create the device property and config preset dock void MainWindow::setupPropertyBrowser() { - m_propertyDockWidget = new QDockWidget(tr("Device Properties"), this); + m_propertyDockWidget = new QDockWidget(tr("Properties"), this); m_propertyDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); m_propertyBrowser = new DevicePropertyWidget(m_scopeonecore, this); m_configPresetWidget = new ConfigPresetWidget(m_scopeonecore, this); - auto* tabWidget = new QTabWidget(m_propertyDockWidget); - tabWidget->addTab(m_propertyBrowser, tr("Properties")); - tabWidget->addTab(m_configPresetWidget, tr("Configs")); - m_propertyDockWidget->setWidget(tabWidget); - + m_propertyDockWidget->setWidget(m_propertyBrowser); + m_propertyDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::LeftDockWidgetArea, m_propertyDockWidget); + + m_configPresetDockWidget = new QDockWidget(tr("Configs"), this); + m_configPresetDockWidget->setWidget(m_configPresetWidget); + m_configPresetDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + tabifyDockWidget(m_propertyDockWidget, m_configPresetDockWidget); } // Create the recording control dock void MainWindow::setupRecording() { m_recordingDockWidget = new QDockWidget(tr("Recording"), this); - m_recordingDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + m_recordingDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); m_recordingWidget = new RecordingWidget(m_scopeonecore, this); m_recordingDockWidget->setWidget(m_recordingWidget); - addDockWidget(Qt::LeftDockWidgetArea, m_recordingDockWidget); - splitDockWidget(m_propertyDockWidget, m_recordingDockWidget, Qt::Vertical); + addDockWidget(Qt::RightDockWidgetArea, m_recordingDockWidget); + tabifyDockWidget(m_controlDockWidget, m_recordingDockWidget); + m_controlDockWidget->raise(); } // Create the image gallery dock @@ -1023,7 +1033,7 @@ namespace scopeone::ui m_imageGalleryDockWidget->setWidget(m_imageGalleryWidget); addDockWidget(Qt::LeftDockWidgetArea, m_imageGalleryDockWidget); - tabifyDockWidget(m_recordingDockWidget, m_imageGalleryDockWidget); + tabifyDockWidget(m_propertyDockWidget, m_imageGalleryDockWidget); m_imageGalleryDockWidget->raise(); } @@ -1113,11 +1123,15 @@ namespace scopeone::ui m_dockWidgetsMenu->addAction(action); }; - addDock(m_propertyDockWidget, QStringLiteral("Device Properties")); + addDock(m_propertyDockWidget, QStringLiteral("Properties")); + addDock(m_configPresetDockWidget, QStringLiteral("Configs")); addDock(m_recordingDockWidget, QStringLiteral("Recording")); addDock(m_imageGalleryDockWidget, QStringLiteral("Image Gallery")); + addDock(m_controlDockWidget, QStringLiteral("Control")); + addDock(m_viewDockWidget, QStringLiteral("View")); + addDock(m_analyzeDockWidget, QStringLiteral("Analyze")); + addDock(m_processDockWidget, QStringLiteral("Process")); addDock(m_consoleDockWidget, QStringLiteral("Console")); - addDock(m_inspectorDockWidget, QStringLiteral("Inspector")); } // Push loaded camera ids into every dependent panel diff --git a/src/MainWindow.h b/src/MainWindow.h index 11ee68c..98711de 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -16,7 +16,6 @@ class QDockWidget; class QLabel; class QMenu; class QProgressDialog; -class QTabWidget; class QTimer; namespace scopeone::core @@ -118,10 +117,10 @@ namespace scopeone::ui scopeone::core::ImageSceneModel* m_imageSceneModel{nullptr}; PreviewWidget* m_previewWidget{nullptr}; - QDockWidget* m_consoleDockWidget{nullptr}; ConsoleWidget* m_consoleWidget{nullptr}; QDockWidget* m_propertyDockWidget{nullptr}; + QDockWidget* m_configPresetDockWidget{nullptr}; DevicePropertyWidget* m_propertyBrowser{nullptr}; ConfigPresetWidget* m_configPresetWidget{nullptr}; @@ -135,8 +134,11 @@ namespace scopeone::ui ImageProcessingWidget* m_imageProcessingWidget{nullptr}; - QDockWidget* m_inspectorDockWidget{nullptr}; - QTabWidget* m_inspectorTabs{nullptr}; + QDockWidget* m_controlDockWidget{nullptr}; + QDockWidget* m_viewDockWidget{nullptr}; + QDockWidget* m_analyzeDockWidget{nullptr}; + QDockWidget* m_processDockWidget{nullptr}; + QDockWidget* m_consoleDockWidget{nullptr}; InspectWidget* m_inspectWidget{nullptr}; QMenu* m_fileMenu{nullptr}; From b9e19fecc059b38239ebac42b562b6366c240ad9 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:17:19 +0200 Subject: [PATCH 07/37] Add composable FFT mask and frequency processing --- ScopeOneCore/CMakeLists.txt | 6 + .../include/scopeone/ProcessingPlugin.h | 70 +++- ScopeOneCore/internal/FFTModule.h | 31 +- .../internal/FrequencyDomainFilterModule.h | 55 ++++ ScopeOneCore/internal/IFFTModule.h | 19 ++ .../internal/ImageProcessingFramework.h | 2 +- ScopeOneCore/internal/MaskModule.h | 41 +++ .../src/BackgroundCalibrationModule.cpp | 10 +- .../src/DifferentialRollingModule.cpp | 6 +- ScopeOneCore/src/ExperimentDocument.cpp | 2 +- ScopeOneCore/src/FFTModule.cpp | 305 +++++------------- .../src/FrequencyDomainFilterModule.cpp | 253 +++++++++++++++ ScopeOneCore/src/GaussianBlurModule.cpp | 8 +- ScopeOneCore/src/IFFTModule.cpp | 81 +++++ ScopeOneCore/src/ImageProcessingFramework.cpp | 50 ++- ScopeOneCore/src/MaskModule.cpp | 288 +++++++++++++++++ ScopeOneCore/src/ProcessingModuleRegistry.cpp | 41 ++- ScopeOneCore/src/ScopeOneCore.cpp | 2 +- .../src/SpatiotemporalBinningModule.cpp | 6 +- src/ImageProcessingWidget.cpp | 190 +++++++++++ 20 files changed, 1173 insertions(+), 293 deletions(-) create mode 100644 ScopeOneCore/internal/FrequencyDomainFilterModule.h create mode 100644 ScopeOneCore/internal/IFFTModule.h create mode 100644 ScopeOneCore/internal/MaskModule.h create mode 100644 ScopeOneCore/src/FrequencyDomainFilterModule.cpp create mode 100644 ScopeOneCore/src/IFFTModule.cpp create mode 100644 ScopeOneCore/src/MaskModule.cpp diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 3cd1b8a..91cbd52 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -95,6 +95,9 @@ set(CORE_SOURCES 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 @@ -136,6 +139,9 @@ set(CORE_HEADERS 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 diff --git a/ScopeOneCore/include/scopeone/ProcessingPlugin.h b/ScopeOneCore/include/scopeone/ProcessingPlugin.h index 14aa0bc..9516c86 100644 --- a/ScopeOneCore/include/scopeone/ProcessingPlugin.h +++ b/ScopeOneCore/include/scopeone/ProcessingPlugin.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include namespace scopeone::core { @@ -48,12 +50,68 @@ namespace scopeone::core 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; - bool succeeded() const { return error.isEmpty() && frame.isValid(); } + 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_CORE_EXPORT ProcessingModule @@ -68,6 +126,16 @@ namespace scopeone::core 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); + } }; class ProcessingPlugin diff --git a/ScopeOneCore/internal/FFTModule.h b/ScopeOneCore/internal/FFTModule.h index a5b4075..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 - }; - 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/IFFTModule.h b/ScopeOneCore/internal/IFFTModule.h new file mode 100644 index 0000000..85469a0 --- /dev/null +++ b/ScopeOneCore/internal/IFFTModule.h @@ -0,0 +1,19 @@ +#pragma once + +#include "internal/ProcessingModule.h" + +namespace scopeone::core::internal +{ + class IFFTModule final : public ProcessingModule + { + public: + QString id() const override { return QStringLiteral("ifft"); } + QString name() const override { return QStringLiteral("IFFT"); } + QVariantMap parameters() const override { return {}; } + void setParameters(const QVariantMap&) override {} + std::unique_ptr createRuntime() const override; + ProcessingResult process(const ImageFrame& frame, int processingBitDepth) override; + ProcessingResult processValue(const ProcessingValue& input, + int processingBitDepth) override; + }; +} diff --git a/ScopeOneCore/internal/ImageProcessingFramework.h b/ScopeOneCore/internal/ImageProcessingFramework.h index 0957d0c..1304b65 100644 --- a/ScopeOneCore/internal/ImageProcessingFramework.h +++ b/ScopeOneCore/internal/ImageProcessingFramework.h @@ -22,7 +22,7 @@ namespace scopeone::core::internal ProcessingResult processThrough(int endModuleIndex, const ImageFrame& input, int processingBitDepth); private: - ProcessingResult processRange(const ImageFrame& input, + ProcessingResult processRange(const ProcessingValue& input, int processingBitDepth, int startModuleIndex, int endModuleIndexExclusive); diff --git a/ScopeOneCore/internal/MaskModule.h b/ScopeOneCore/internal/MaskModule.h new file mode 100644 index 0000000..8cdbe35 --- /dev/null +++ b/ScopeOneCore/internal/MaskModule.h @@ -0,0 +1,41 @@ +#pragma once + +#include "internal/ProcessingModule.h" + +#include + +namespace scopeone::core::internal +{ + class MaskModule final : public ProcessingModule + { + public: + QString id() const override { return QStringLiteral("mask"); } + QString name() const override { return QStringLiteral("Mask"); } + QVariantMap parameters() const override; + void setParameters(const QVariantMap& parameters) 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); + const cv::Mat& frequencyMaskForSize(const cv::Size& size); + void invalidateMask(); + + int m_shape{0}; + double m_centerX{0.0}; + double m_centerY{0.0}; + double m_sizeX{0.1}; + double m_sizeY{0.1}; + double m_innerSize{0.0}; + double m_rotation{0.0}; + double m_edgeWidth{0.0}; + bool m_invert{false}; + + cv::Mat m_mask; + cv::Mat m_frequencyMask; + cv::Size m_maskSize; + QVariantMap m_maskParameters; + }; +} diff --git a/ScopeOneCore/src/BackgroundCalibrationModule.cpp b/ScopeOneCore/src/BackgroundCalibrationModule.cpp index c88a54d..6b82098 100644 --- a/ScopeOneCore/src/BackgroundCalibrationModule.cpp +++ b/ScopeOneCore/src/BackgroundCalibrationModule.cpp @@ -213,7 +213,7 @@ namespace scopeone::core::internal const qint64 pixelCount = static_cast(workingFrame.width) * workingFrame.height; if (pixelCount <= 0) { - return {{}, QStringLiteral("Invalid running background dimensions")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid running background dimensions")); } if (m_runningSum.size() != static_cast(pixelCount)) { @@ -276,7 +276,7 @@ namespace scopeone::core::internal if (outputBytes.isEmpty()) { - return {{}, QStringLiteral("Failed to allocate running background output")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Failed to allocate running background output")); } ImageFrame output = makeFrameLike(workingFrame, @@ -293,7 +293,7 @@ namespace scopeone::core::internal { if (!frame.isValid()) { - return {{}, QStringLiteral("Invalid input")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid input")); } try @@ -301,7 +301,7 @@ namespace scopeone::core::internal ImageFrame workingFrame; if (!convertFrameForProcessing(frame, workingFrame, processingBitDepth)) { - return {{}, QStringLiteral("Unsupported input frame")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Unsupported input frame")); } if ((!m_buffer.empty() && !m_buffer.front().isCompatibleWith(workingFrame)) @@ -396,7 +396,7 @@ namespace scopeone::core::internal } catch (const std::exception& e) { - return {{}, QString("Background calibration failed: %1").arg(e.what())}; + return ProcessingResult(ImageFrame{}, QString("Background calibration failed: %1").arg(e.what())); } } diff --git a/ScopeOneCore/src/DifferentialRollingModule.cpp b/ScopeOneCore/src/DifferentialRollingModule.cpp index 4170c4c..f31f992 100644 --- a/ScopeOneCore/src/DifferentialRollingModule.cpp +++ b/ScopeOneCore/src/DifferentialRollingModule.cpp @@ -212,7 +212,7 @@ namespace scopeone::core::internal { if (!frame.isValid()) { - return {{}, QStringLiteral("Invalid input")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid input")); } try @@ -220,7 +220,7 @@ namespace scopeone::core::internal ImageFrame workingFrame; if (!convertFrameForProcessing(frame, workingFrame, processingBitDepth)) { - return {{}, QStringLiteral("Unsupported input frame")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Unsupported input frame")); } const bool incompatibleBuffers = (!m_state.batchA.empty() && !m_state.batchA.front().isCompatibleWith( @@ -279,7 +279,7 @@ namespace scopeone::core::internal } catch (const std::exception& e) { - return {{}, QString("Differential rolling failed: %1").arg(e.what())}; + return ProcessingResult(ImageFrame{}, QString("Differential rolling failed: %1").arg(e.what())); } } diff --git a/ScopeOneCore/src/ExperimentDocument.cpp b/ScopeOneCore/src/ExperimentDocument.cpp index a9b018b..534feca 100644 --- a/ScopeOneCore/src/ExperimentDocument.cpp +++ b/ScopeOneCore/src/ExperimentDocument.cpp @@ -589,7 +589,7 @@ namespace scopeone::core } }; - if (moduleId == QStringLiteral("fft")) + if (moduleId == QStringLiteral("frequency_domain_filter")) { normalizeDouble(QStringLiteral("min_feature_size")); normalizeDouble(QStringLiteral("max_feature_size")); diff --git a/ScopeOneCore/src/FFTModule.cpp b/ScopeOneCore/src/FFTModule.cpp index 2b44438..9fe5653 100644 --- a/ScopeOneCore/src/FFTModule.cpp +++ b/ScopeOneCore/src/FFTModule.cpp @@ -1,10 +1,7 @@ #include "internal/FFTModule.h" + #include "internal/FrameBufferUtils.h" -#include -#include -#include -#include #include #include @@ -12,286 +9,142 @@ namespace scopeone::core::internal { namespace { - // Converts a mono frame into a float matrix for OpenCV DFT - bool frameToGrayFloat(const ImageFrame& frame, cv::Mat& output) + bool frameToFloat(const ImageFrame& frame, cv::Mat& output) { if (!frame.isValid() || (!frame.isMono8() && !frame.isMono16())) { return false; } - - const int cvType = frame.isMono16() ? CV_16UC1 : CV_8UC1; - cv::Mat input(frame.height, - frame.width, - cvType, - const_cast(frame.bytes.constData()), - frame.stride); + const int type = frame.isMono16() ? CV_16UC1 : CV_8UC1; + const cv::Mat input(frame.height, + frame.width, + type, + const_cast(frame.bytes.constData()), + frame.stride); input.convertTo(output, CV_32F); return true; } - // Normalizes an OpenCV matrix into an output image frame - ImageFrame matToOutputFrame(const cv::Mat& input, - const ImageFrame& reference) + void fftShift(const cv::Mat& input, cv::Mat& output) { - const int targetType = reference.isMono16() ? CV_16U : CV_8U; - const double targetMax = static_cast(reference.maxValue()); - QByteArray bytes = reference.isMono16() - ? allocatePixelBytes(input.cols, input.rows) - : allocatePixelBytes(input.cols, input.rows); - if (bytes.isEmpty()) - { - return {}; - } - cv::Mat normalized(input.rows, - input.cols, - targetType, - bytes.data(), - input.cols * reference.bytesPerPixel()); - cv::normalize(input, normalized, 0.0, targetMax, cv::NORM_MINMAX, targetType); - return makeFrameLike(reference, normalized.cols, normalized.rows, std::move(bytes)); - } - - // Moves the zero frequency component to the image center - void fftShift(const cv::Mat& image, cv::Mat& shifted) - { - shifted.create(image.size(), image.type()); - const int xOffset = image.cols / 2; - const int yOffset = image.rows / 2; - const size_t tailBytes = static_cast(image.cols - xOffset) - * sizeof(float); - const size_t headBytes = static_cast(xOffset) * sizeof(float); - parallelForImageRows(image.cols, image.rows, [&](int firstRow, int lastRow) + output.create(input.size(), input.type()); + const int xOffset = (input.cols + 1) / 2; + const int yOffset = (input.rows + 1) / 2; + parallelForImageRows(input.cols, input.rows, [&](int firstRow, int lastRow) { for (int y = firstRow; y < lastRow; ++y) { - const float* srcRow = image.ptr((y + yOffset) % image.rows); - float* dstRow = shifted.ptr(y); - std::memcpy(dstRow, srcRow + xOffset, tailBytes); - std::memcpy(dstRow + image.cols - xOffset, srcRow, headBytes); + const float* source = input.ptr((y + yOffset) % input.rows); + float* destination = output.ptr(y); + for (int x = 0; x < input.cols; ++x) + { + destination[x] = source[(x + xOffset) % input.cols]; + } } }); } - // Computes a log magnitude spectrum from complex DFT planes - void magnitudeSpectrum(const cv::Mat* planes, cv::Mat& magnitude, cv::Mat& shifted) + ImageFrame spectrumFrame(const cv::Mat* planes, + cv::Mat& magnitude, + cv::Mat& shifted, + const ImageFrame& reference) { cv::magnitude(planes[0], planes[1], magnitude); magnitude += cv::Scalar::all(1.0); cv::log(magnitude, magnitude); fftShift(magnitude, shifted); - } - // Crops the center region of a matrix to a target size - cv::Mat cropCenter(const cv::Mat& image, const cv::Size& size) - { - const int x = (image.cols - size.width) / 2; - const int y = (image.rows - size.height) / 2; - return image(cv::Rect(x, y, size.width, size.height)); - } - - // Builds the frequency domain bandpass mask - cv::Mat buildMask(const cv::Size& size, - double minFeatureSize, - double maxFeatureSize, - FFTModule::FilterKind filterKind) - { - constexpr double kTwoPi = 2.0 * std::numbers::pi_v; - cv::Mat centered(size, CV_32F); - for (int y = 0; y < size.height; ++y) + cv::Mat visible = shifted; + if (visible.cols != reference.width || visible.rows != reference.height) { - const double fy = (static_cast(y) - size.height / 2.0) / static_cast(size.height); - float* row = centered.ptr(y); - for (int x = 0; x < size.width; ++x) - { - const double fx = (static_cast(x) - size.width / 2.0) / static_cast(size.width); - const double rsq = (kTwoPi * fx) * (kTwoPi * fx) + (kTwoPi * fy) * (kTwoPi * fy); - if (filterKind == FFTModule::FilterKind::Hard) - { - row[x] = (rsq * maxFeatureSize * maxFeatureSize > 1.0 - && rsq * minFeatureSize * minFeatureSize < 1.0) - ? 1.0f - : 0.0f; - } - else - { - row[x] = static_cast( - std::exp(-rsq * minFeatureSize * minFeatureSize / 2.0) - - std::exp(-rsq * maxFeatureSize * maxFeatureSize / 2.0)); - } - } + const int x = (visible.cols - reference.width) / 2; + const int y = (visible.rows - reference.height) / 2; + visible = visible(cv::Rect(x, y, reference.width, reference.height)); } - - cv::Mat mask; - fftShift(centered, mask); - return mask; + cv::Mat preview; + cv::normalize(visible, preview, 0.0, 255.0, cv::NORM_MINMAX, CV_8U); + ImageFrame frame = makeMono8Frame(reference.cameraId, + preview.cols, + preview.rows, + copyMatBytes(preview)); + copyFrameMetadata(reference, frame); + return frame; } } - // Creates an independent FFT runtime std::unique_ptr FFTModule::createRuntime() const { - auto module = std::make_unique(); - module->setParameters(parameters()); - return module; + return std::make_unique(); } - // Returns a cached mask for the current FFT parameters - const cv::Mat& FFTModule::maskForSize(const cv::Size& size) + ProcessingResult FFTModule::process(const ImageFrame& frame, int processingBitDepth) { - if (m_mask.empty() - || m_maskSize != size - || m_maskMinFeatureSize != m_minFeatureSize - || m_maskMaxFeatureSize != m_maxFeatureSize - || m_maskFilterKind != m_filterKind) - { - m_mask = buildMask(size, m_minFeatureSize, m_maxFeatureSize, m_filterKind); - m_maskSize = size; - m_maskMinFeatureSize = m_minFeatureSize; - m_maskMaxFeatureSize = m_maxFeatureSize; - m_maskFilterKind = m_filterKind; - } - return m_mask; + return processValue(ProcessingValue{frame}, processingBitDepth); } - // Clears the cached FFT mask - void FFTModule::invalidateMask() + ProcessingResult FFTModule::processValue(const ProcessingValue& input, + int processingBitDepth) { - m_mask.release(); - m_maskSize = {}; - } + if (!std::holds_alternative(input)) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("FFT requires an image")); + } - // Runs FFT spectrum or bandpass processing on one frame - ProcessingResult FFTModule::process(const ImageFrame& frame, int processingBitDepth) - { - if (!frame.isValid()) + const ImageFrame& frame = std::get(input); + ImageFrame workingFrame; + if (!convertFrameForProcessing(frame, workingFrame, processingBitDepth) + || !frameToFloat(workingFrame, m_grayFloat)) { - return {{}, QStringLiteral("Invalid input")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Unsupported FFT input")); } try { - ImageFrame workingFrame; - if (!convertFrameForProcessing(frame, workingFrame, processingBitDepth)) - { - return {{}, QStringLiteral("Unsupported input frame")}; - } - - if (!frameToGrayFloat(workingFrame, m_grayFloat)) - { - return {{}, QStringLiteral("Failed to convert frame to grayscale")}; - } - - int optRows = cv::getOptimalDFTSize(m_grayFloat.rows); - int optCols = cv::getOptimalDFTSize(m_grayFloat.cols); + const int rows = cv::getOptimalDFTSize(m_grayFloat.rows); + const int columns = cv::getOptimalDFTSize(m_grayFloat.cols); cv::copyMakeBorder(m_grayFloat, m_padded, 0, - optRows - m_grayFloat.rows, + rows - m_grayFloat.rows, 0, - optCols - m_grayFloat.cols, + columns - m_grayFloat.cols, cv::BORDER_CONSTANT, 0); - cv::dft(m_padded, m_complex, cv::DFT_COMPLEX_OUTPUT); cv::split(m_complex, m_planes); - if (m_outputMode == OutputMode::Spectrum || m_outputMode == OutputMode::BandpassSpectrum) - { - if (m_outputMode == OutputMode::BandpassSpectrum) - { - const cv::Mat& mask = maskForSize(m_padded.size()); - cv::multiply(m_planes[0], mask, m_planes[0]); - cv::multiply(m_planes[1], mask, m_planes[1]); - } - - magnitudeSpectrum(m_planes, m_spectrumMagnitude, m_shiftedSpectrum); - cv::Mat spectrum = m_shiftedSpectrum; - if (spectrum.size() != m_grayFloat.size()) - { - spectrum = cropCenter(spectrum, m_grayFloat.size()); - } - return {matToOutputFrame(spectrum, workingFrame), {}}; - } - - const cv::Mat& mask = maskForSize(m_padded.size()); - cv::multiply(m_planes[0], mask, m_planes[0]); - cv::multiply(m_planes[1], mask, m_planes[1]); - - cv::merge(m_planes, 2, m_filteredComplex); - cv::dft(m_filteredComplex, m_filtered, cv::DFT_INVERSE | cv::DFT_REAL_OUTPUT | cv::DFT_SCALE); - - const cv::Mat cropped = m_filtered(cv::Rect(0, 0, m_grayFloat.cols, m_grayFloat.rows)); - return {matToOutputFrame(cropped, workingFrame), {}}; - } - catch (const std::exception& e) - { - return {{}, QString("FFT processing failed: %1").arg(e.what())}; + ComplexFrame output; + output.sourceId = frame.cameraId; + output.width = columns; + output.height = rows; + output.stride = columns; + output.sourceWidth = frame.width; + output.sourceHeight = frame.height; + output.frameIndex = frame.frameIndex; + output.timestampNs = frame.timestampNs; + output.real = copyMatBytes(m_planes[0]); + output.imaginary = copyMatBytes(m_planes[1]); + + ProcessingResult result(ProcessingValue{std::move(output)}); + result.frame = spectrumFrame(m_planes, + m_spectrumMagnitude, + m_shiftedSpectrum, + workingFrame); + return result; + } + catch (const std::exception& error) + { + return ProcessingResult(ImageFrame{}, + QString("FFT failed: %1").arg(error.what())); } } - // Returns the current FFT module parameters QVariantMap FFTModule::parameters() const { - QVariantMap params; - params["min_feature_size"] = m_minFeatureSize; - params["max_feature_size"] = m_maxFeatureSize; - params["filter_kind"] = static_cast(m_filterKind); - params["output_mode"] = static_cast(m_outputMode); - return params; + return {}; } - // Updates FFT module parameters and invalidates cached masks - void FFTModule::setParameters(const QVariantMap& params) + void FFTModule::setParameters(const QVariantMap&) { - bool maskChanged = false; - if (params.contains("min_feature_size")) - { - const double oldValue = m_minFeatureSize; - m_minFeatureSize = qMax(0.0, params.value("min_feature_size").toDouble()); - if (m_minFeatureSize != oldValue) - { - maskChanged = true; - } - } - if (params.contains("max_feature_size")) - { - const double oldValue = m_maxFeatureSize; - m_maxFeatureSize = qMax(0.0, params.value("max_feature_size").toDouble()); - if (m_maxFeatureSize != oldValue) - { - maskChanged = true; - } - } - if (m_minFeatureSize > m_maxFeatureSize) - { - std::swap(m_minFeatureSize, m_maxFeatureSize); - maskChanged = true; - } - if (params.contains("filter_kind")) - { - const int filterKind = params.value("filter_kind").toInt(); - if (filterKind == 0 || filterKind == 1) - { - const auto newFilterKind = static_cast(filterKind); - if (m_filterKind != newFilterKind) - { - maskChanged = true; - } - m_filterKind = newFilterKind; - } - } - if (params.contains("output_mode")) - { - const int outputMode = params.value("output_mode").toInt(); - if (outputMode >= 0 && outputMode <= 2) - { - m_outputMode = static_cast(outputMode); - } - } - if (maskChanged) - { - invalidateMask(); - } } } // namespace scopeone::core::internal diff --git a/ScopeOneCore/src/FrequencyDomainFilterModule.cpp b/ScopeOneCore/src/FrequencyDomainFilterModule.cpp new file mode 100644 index 0000000..3e749a5 --- /dev/null +++ b/ScopeOneCore/src/FrequencyDomainFilterModule.cpp @@ -0,0 +1,253 @@ +#include "internal/FrequencyDomainFilterModule.h" + +#include "internal/FrameBufferUtils.h" + +#include +#include +#include +#include +#include +#include + +namespace scopeone::core::internal +{ + namespace + { + bool frameToFloat(const ImageFrame& frame, cv::Mat& output) + { + if (!frame.isValid() || (!frame.isMono8() && !frame.isMono16())) + { + return false; + } + const int type = frame.isMono16() ? CV_16UC1 : CV_8UC1; + const cv::Mat input(frame.height, + frame.width, + type, + const_cast(frame.bytes.constData()), + frame.stride); + input.convertTo(output, CV_32F); + return true; + } + + ImageFrame outputFrame(const cv::Mat& input, const ImageFrame& reference) + { + const int type = reference.isMono16() ? CV_16U : CV_8U; + QByteArray bytes = reference.isMono16() + ? allocatePixelBytes(input.cols, input.rows) + : allocatePixelBytes(input.cols, input.rows); + if (bytes.isEmpty()) + { + return {}; + } + cv::Mat output(input.rows, + input.cols, + type, + bytes.data(), + input.cols * reference.bytesPerPixel()); + cv::normalize(input, + output, + 0.0, + static_cast(reference.maxValue()), + cv::NORM_MINMAX, + type); + return makeFrameLike(reference, output.cols, output.rows, std::move(bytes)); + } + + void fftShift(const cv::Mat& input, cv::Mat& output) + { + output.create(input.size(), input.type()); + const int xOffset = input.cols / 2; + const int yOffset = input.rows / 2; + const size_t tailBytes = static_cast(input.cols - xOffset) * sizeof(float); + const size_t headBytes = static_cast(xOffset) * sizeof(float); + parallelForImageRows(input.cols, input.rows, [&](int firstRow, int lastRow) + { + for (int y = firstRow; y < lastRow; ++y) + { + const float* source = input.ptr((y + yOffset) % input.rows); + float* destination = output.ptr(y); + std::memcpy(destination, source + xOffset, tailBytes); + std::memcpy(destination + input.cols - xOffset, source, headBytes); + } + }); + } + + cv::Mat buildMask(const cv::Size& size, + double minFeatureSize, + double maxFeatureSize, + FrequencyDomainFilterModule::FilterKind filterKind) + { + constexpr double kTwoPi = 2.0 * std::numbers::pi_v; + cv::Mat centered(size, CV_32F); + for (int y = 0; y < size.height; ++y) + { + const double fy = (static_cast(y) - size.height / 2.0) / size.height; + float* row = centered.ptr(y); + for (int x = 0; x < size.width; ++x) + { + const double fx = (static_cast(x) - size.width / 2.0) / size.width; + const double radiusSquared = (kTwoPi * fx) * (kTwoPi * fx) + + (kTwoPi * fy) * (kTwoPi * fy); + if (filterKind == FrequencyDomainFilterModule::FilterKind::Hard) + { + row[x] = radiusSquared * maxFeatureSize * maxFeatureSize > 1.0 + && radiusSquared * minFeatureSize * minFeatureSize < 1.0 + ? 1.0f + : 0.0f; + } + else + { + row[x] = static_cast( + std::exp(-radiusSquared * minFeatureSize * minFeatureSize / 2.0) + - std::exp(-radiusSquared * maxFeatureSize * maxFeatureSize / 2.0)); + } + } + } + cv::Mat mask; + fftShift(centered, mask); + return mask; + } + + cv::Mat spectrum(const cv::Mat* planes, cv::Mat& magnitude, cv::Mat& shifted) + { + cv::magnitude(planes[0], planes[1], magnitude); + magnitude += cv::Scalar::all(1.0); + cv::log(magnitude, magnitude); + fftShift(magnitude, shifted); + return shifted; + } + } + + std::unique_ptr FrequencyDomainFilterModule::createRuntime() const + { + auto module = std::make_unique(); + module->setParameters(parameters()); + return module; + } + + const cv::Mat& FrequencyDomainFilterModule::maskForSize(const cv::Size& size) + { + if (m_mask.empty() + || m_maskSize != size + || m_maskMinFeatureSize != m_minFeatureSize + || m_maskMaxFeatureSize != m_maxFeatureSize + || m_maskFilterKind != m_filterKind) + { + m_mask = buildMask(size, m_minFeatureSize, m_maxFeatureSize, m_filterKind); + m_maskSize = size; + m_maskMinFeatureSize = m_minFeatureSize; + m_maskMaxFeatureSize = m_maxFeatureSize; + m_maskFilterKind = m_filterKind; + } + return m_mask; + } + + void FrequencyDomainFilterModule::invalidateMask() + { + m_mask.release(); + m_maskSize = {}; + } + + ProcessingResult FrequencyDomainFilterModule::process(const ImageFrame& frame, + int processingBitDepth) + { + ImageFrame workingFrame; + if (!convertFrameForProcessing(frame, workingFrame, processingBitDepth) + || !frameToFloat(workingFrame, m_grayFloat)) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("Unsupported frequency filter input")); + } + + try + { + const int rows = cv::getOptimalDFTSize(m_grayFloat.rows); + const int columns = cv::getOptimalDFTSize(m_grayFloat.cols); + cv::copyMakeBorder(m_grayFloat, + m_padded, + 0, + rows - m_grayFloat.rows, + 0, + columns - m_grayFloat.cols, + cv::BORDER_CONSTANT, + 0); + cv::dft(m_padded, m_complex, cv::DFT_COMPLEX_OUTPUT); + cv::split(m_complex, m_planes); + + if (m_outputMode != OutputMode::Spectrum) + { + const cv::Mat& mask = maskForSize(m_padded.size()); + cv::multiply(m_planes[0], mask, m_planes[0]); + cv::multiply(m_planes[1], mask, m_planes[1]); + } + + if (m_outputMode != OutputMode::FilteredImage) + { + cv::Mat visible = spectrum(m_planes, m_spectrumMagnitude, m_shiftedSpectrum); + if (visible.size() != m_grayFloat.size()) + { + const int x = (visible.cols - m_grayFloat.cols) / 2; + const int y = (visible.rows - m_grayFloat.rows) / 2; + visible = visible(cv::Rect(x, y, m_grayFloat.cols, m_grayFloat.rows)); + } + return ProcessingResult(outputFrame(visible, workingFrame)); + } + + cv::merge(m_planes, 2, m_filteredComplex); + cv::dft(m_filteredComplex, + m_filtered, + cv::DFT_INVERSE | cv::DFT_REAL_OUTPUT | cv::DFT_SCALE); + const cv::Mat visible = m_filtered(cv::Rect(0, + 0, + m_grayFloat.cols, + m_grayFloat.rows)); + return ProcessingResult(outputFrame(visible, workingFrame)); + } + catch (const std::exception& error) + { + return ProcessingResult(ImageFrame{}, + QString("Frequency domain filtering failed: %1") + .arg(error.what())); + } + } + + QVariantMap FrequencyDomainFilterModule::parameters() const + { + return {{QStringLiteral("min_feature_size"), m_minFeatureSize}, + {QStringLiteral("max_feature_size"), m_maxFeatureSize}, + {QStringLiteral("filter_kind"), static_cast(m_filterKind)}, + {QStringLiteral("output_mode"), static_cast(m_outputMode)}}; + } + + void FrequencyDomainFilterModule::setParameters(const QVariantMap& parameters) + { + const double oldMinimum = m_minFeatureSize; + const double oldMaximum = m_maxFeatureSize; + const FilterKind oldKind = m_filterKind; + m_minFeatureSize = qMax(0.0, + parameters.value(QStringLiteral("min_feature_size"), + m_minFeatureSize).toDouble()); + m_maxFeatureSize = qMax(0.0, + parameters.value(QStringLiteral("max_feature_size"), + m_maxFeatureSize).toDouble()); + if (m_minFeatureSize > m_maxFeatureSize) + { + std::swap(m_minFeatureSize, m_maxFeatureSize); + } + m_filterKind = static_cast(qBound( + 0, + parameters.value(QStringLiteral("filter_kind"), + static_cast(m_filterKind)).toInt(), + 1)); + m_outputMode = static_cast(qBound( + 0, + parameters.value(QStringLiteral("output_mode"), + static_cast(m_outputMode)).toInt(), + 2)); + if (oldMinimum != m_minFeatureSize + || oldMaximum != m_maxFeatureSize + || oldKind != m_filterKind) + { + invalidateMask(); + } + } +} // namespace scopeone::core::internal diff --git a/ScopeOneCore/src/GaussianBlurModule.cpp b/ScopeOneCore/src/GaussianBlurModule.cpp index 123157b..f9e5781 100644 --- a/ScopeOneCore/src/GaussianBlurModule.cpp +++ b/ScopeOneCore/src/GaussianBlurModule.cpp @@ -20,7 +20,7 @@ namespace scopeone::core::internal { if (!frame.isValid()) { - return {{}, QStringLiteral("Invalid input")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid input")); } try @@ -28,7 +28,7 @@ namespace scopeone::core::internal ImageFrame workingFrame; if (!convertFrameForProcessing(frame, workingFrame, processingBitDepth)) { - return {{}, QStringLiteral("Unsupported input frame")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Unsupported input frame")); } const int cvType = workingFrame.isMono16() ? CV_16UC1 : CV_8UC1; @@ -40,7 +40,7 @@ namespace scopeone::core::internal : allocatePixelBytes(workingFrame.width, workingFrame.height); if (bytes.isEmpty()) { - return {{}, QStringLiteral("Failed to allocate Gaussian blur output")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Failed to allocate Gaussian blur output")); } cv::Mat blurred(workingFrame.height, workingFrame.width, @@ -53,7 +53,7 @@ namespace scopeone::core::internal } catch (const std::exception& e) { - return {{}, QString("Gaussian blur failed: %1").arg(e.what())}; + return ProcessingResult(ImageFrame{}, QString("Gaussian blur failed: %1").arg(e.what())); } } diff --git a/ScopeOneCore/src/IFFTModule.cpp b/ScopeOneCore/src/IFFTModule.cpp new file mode 100644 index 0000000..01fa723 --- /dev/null +++ b/ScopeOneCore/src/IFFTModule.cpp @@ -0,0 +1,81 @@ +#include "internal/IFFTModule.h" + +#include "internal/FrameBufferUtils.h" + +#include +#include +#include + +namespace scopeone::core::internal +{ + std::unique_ptr IFFTModule::createRuntime() const + { + return std::make_unique(); + } + + ProcessingResult IFFTModule::process(const ImageFrame&, int) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("IFFT requires a complex field")); + } + + ProcessingResult IFFTModule::processValue(const ProcessingValue& input, int processingBitDepth) + { + if (!std::holds_alternative(input)) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("IFFT requires a complex field")); + } + const ComplexFrame& source = std::get(input); + if (!source.isValid()) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid complex field")); + } + + try + { + cv::Mat real(source.height, source.width, CV_32F, + const_cast(source.real.constData()), + source.stride * static_cast(sizeof(float))); + cv::Mat imaginary(source.height, source.width, CV_32F, + const_cast(source.imaginary.constData()), + source.stride * static_cast(sizeof(float))); + cv::Mat planes[] = {real, imaginary}; + cv::Mat complex; + cv::merge(planes, 2, complex); + cv::Mat inverse; + cv::dft(complex, inverse, cv::DFT_INVERSE | cv::DFT_REAL_OUTPUT | cv::DFT_SCALE); + + const cv::Mat visible = source.sourceWidth > 0 + && source.sourceHeight > 0 + && source.sourceWidth <= inverse.cols + && source.sourceHeight <= inverse.rows + ? inverse(cv::Rect(0, + 0, + source.sourceWidth, + source.sourceHeight)) + : inverse; + + const int bits = processingBitDepth >= 16 ? 16 : 8; + const int type = bits == 16 ? CV_16U : CV_8U; + const int maxValue = bits == 16 ? 65535 : 255; + cv::Mat output(visible.size(), type); + cv::normalize(visible, output, 0.0, maxValue, cv::NORM_MINMAX, type); + QByteArray bytes(static_cast(output.total() * output.elemSize()), Qt::Uninitialized); + std::memcpy(bytes.data(), output.data, static_cast(bytes.size())); + ImageFrame frame; + frame.cameraId = source.sourceId; + frame.width = output.cols; + frame.height = output.rows; + frame.stride = static_cast(output.step); + frame.bitsPerSample = bits; + frame.pixelFormat = bits == 16 ? ImagePixelFormat::Mono16 : ImagePixelFormat::Mono8; + frame.frameIndex = source.frameIndex; + frame.timestampNs = source.timestampNs; + frame.bytes = std::move(bytes); + return ProcessingResult(std::move(frame)); + } + catch (const std::exception& e) + { + return ProcessingResult(ImageFrame{}, QString("IFFT failed: %1").arg(e.what())); + } + } +} diff --git a/ScopeOneCore/src/ImageProcessingFramework.cpp b/ScopeOneCore/src/ImageProcessingFramework.cpp index 858e9c7..62176c7 100644 --- a/ScopeOneCore/src/ImageProcessingFramework.cpp +++ b/ScopeOneCore/src/ImageProcessingFramework.cpp @@ -30,7 +30,7 @@ namespace scopeone::core::internal // Runs all runtime modules in order for one frame ProcessingResult ProcessingPipelineRuntime::process(const ImageFrame& input, int processingBitDepth) { - return processRange(input, + return processRange(ProcessingValue{input}, processingBitDepth, 0, (std::numeric_limits::max)()); @@ -41,7 +41,7 @@ namespace scopeone::core::internal const ImageFrame& input, int processingBitDepth) { - return processRange(input, + return processRange(ProcessingValue{input}, processingBitDepth, startModuleIndex, (std::numeric_limits::max)()); @@ -55,30 +55,32 @@ namespace scopeone::core::internal const int endModuleIndexExclusive = endModuleIndex >= (std::numeric_limits::max)() - 1 ? (std::numeric_limits::max)() : qMax(0, endModuleIndex + 1); - return processRange(input, + return processRange(ProcessingValue{input}, processingBitDepth, 0, endModuleIndexExclusive); } // Runs a runtime pipeline segment and returns its frame or error - ProcessingResult ProcessingPipelineRuntime::processRange(const ImageFrame& input, + ProcessingResult ProcessingPipelineRuntime::processRange(const ProcessingValue& input, int processingBitDepth, int startModuleIndex, int endModuleIndexExclusive) { - if (!input.isValid()) + if (!std::holds_alternative(input) + || !std::get(input).isValid()) { - return {{}, QStringLiteral("Invalid processing input")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid processing input")); } - ImageFrame currentFrame(input); + ProcessingValue currentValue(input); + ImageFrame displayFrame; const int startIndex = std::clamp(startModuleIndex, 0, static_cast(m_modules.size())); const int endIndex = std::clamp(endModuleIndexExclusive, startIndex, static_cast(m_modules.size())); for (int moduleIndex = startIndex; moduleIndex < endIndex; ++moduleIndex) { ProcessingModule* module = m_modules[static_cast(moduleIndex)].get(); - ProcessingResult result = module->process(currentFrame, processingBitDepth); + ProcessingResult result = module->processValue(currentValue, processingBitDepth); if (!result.succeeded()) { if (result.error.isEmpty()) @@ -88,10 +90,30 @@ namespace scopeone::core::internal return result; } - copyFrameMetadata(currentFrame, result.frame); - currentFrame = std::move(result.frame); + if (result.hasImage()) + { + ImageFrame image = std::holds_alternative(result.value) + ? std::get(result.value) + : result.frame; + if (std::holds_alternative(currentValue)) + { + copyFrameMetadata(std::get(currentValue), image); + } + result.value = image; + result.frame = image; + } + else if (result.frame.isValid()) + { + displayFrame = result.frame; + } + currentValue = std::move(result.value); + } + ProcessingResult result(std::move(currentValue)); + if (!result.hasImage()) + { + result.frame = std::move(displayFrame); } - return {std::move(currentFrame), {}}; + return result; } // Adds one module to the editable pipeline definition @@ -327,11 +349,13 @@ namespace scopeone::core::internal if (!result.succeeded()) { emit processingError(result.error.isEmpty() - ? QStringLiteral("Processing returned an invalid frame") + ? QStringLiteral("Processing pipeline returned no output") : result.error); return {}; } - return std::move(result.frame); + return result.hasImage() + ? std::get(std::move(result.value)) + : std::move(result.frame); } // Builds a stable key for camera specific processing state diff --git a/ScopeOneCore/src/MaskModule.cpp b/ScopeOneCore/src/MaskModule.cpp new file mode 100644 index 0000000..92da108 --- /dev/null +++ b/ScopeOneCore/src/MaskModule.cpp @@ -0,0 +1,288 @@ +#include "internal/MaskModule.h" + +#include "internal/FrameBufferUtils.h" + +#include +#include +#include + +namespace scopeone::core::internal +{ + namespace + { + float smoothBoundary(float distance, double edgeWidth) + { + if (edgeWidth <= 0.0) + { + return distance <= 0.0f ? 1.0f : 0.0f; + } + const double value = qBound(0.0, 0.5 - static_cast(distance) / edgeWidth, 1.0); + return static_cast(value * value * (3.0 - 2.0 * value)); + } + + cv::Mat buildMask(const cv::Size& size, + int shape, + double centerX, + double centerY, + double sizeX, + double sizeY, + double innerSize, + double rotation, + double edgeWidth, + bool invert) + { + cv::Mat mask(size, CV_32F); + const double angle = rotation * std::numbers::pi_v / 180.0; + const double cosine = std::cos(angle); + const double sine = std::sin(angle); + const double halfWidth = qMax(sizeX, 1.0e-9) / 2.0; + const double halfHeight = qMax(sizeY, 1.0e-9) / 2.0; + const double innerRadius = qBound(0.0, innerSize, 1.0); + const double outerRadius = qMax(halfWidth, 1.0e-9); + + for (int y = 0; y < size.height; ++y) + { + float* row = mask.ptr(y); + const double frequencyY = (static_cast(y) - size.height / 2.0) + / static_cast(size.height); + for (int x = 0; x < size.width; ++x) + { + const double frequencyX = (static_cast(x) - size.width / 2.0) + / static_cast(size.width); + const double dx = frequencyX - centerX; + const double dy = frequencyY - centerY; + const double rotatedX = cosine * dx + sine * dy; + const double rotatedY = -sine * dx + cosine * dy; + float value = 0.0f; + + if (shape == 1) + { + const double distance = qMax(std::abs(rotatedX) - halfWidth, + std::abs(rotatedY) - halfHeight); + value = smoothBoundary(static_cast(distance), edgeWidth); + } + else if (shape == 2) + { + const double radius = std::sqrt(rotatedX * rotatedX + rotatedY * rotatedY); + const double outerDistance = radius - outerRadius; + const double innerDistance = innerRadius - radius; + value = qMin(smoothBoundary(static_cast(outerDistance), edgeWidth), + smoothBoundary(static_cast(innerDistance), edgeWidth)); + } + else + { + const double distance = std::sqrt( + (rotatedX / halfWidth) * (rotatedX / halfWidth) + + (rotatedY / halfHeight) * (rotatedY / halfHeight)) - 1.0; + value = smoothBoundary(static_cast(distance), edgeWidth); + } + row[x] = invert ? 1.0f - value : value; + } + } + return mask; + } + + ImageFrame maskPreview(const ComplexFrame& source, const cv::Mat& mask) + { + cv::Mat preview; + mask.convertTo(preview, CV_8U, 255.0); + return makeMono8Frame(source.sourceId, preview.cols, preview.rows, copyMatBytes(preview)); + } + } + + std::unique_ptr MaskModule::createRuntime() const + { + auto module = std::make_unique(); + module->setParameters(parameters()); + return module; + } + + const cv::Mat& MaskModule::maskForSize(const cv::Size& size) + { + const QVariantMap currentParameters = parameters(); + if (m_mask.empty() || m_maskSize != size || m_maskParameters != currentParameters) + { + m_mask = buildMask(size, + m_shape, + m_centerX, + m_centerY, + m_sizeX, + m_sizeY, + m_innerSize, + m_rotation, + m_edgeWidth, + m_invert); + m_maskSize = size; + m_maskParameters = currentParameters; + } + return m_mask; + } + + const cv::Mat& MaskModule::frequencyMaskForSize(const cv::Size& size) + { + const cv::Mat& centeredMask = maskForSize(size); + if (m_frequencyMask.empty() || m_frequencyMask.size() != size) + { + m_frequencyMask.create(size, CV_32F); + const int xOffset = size.width / 2; + const int yOffset = size.height / 2; + for (int y = 0; y < size.height; ++y) + { + const float* source = centeredMask.ptr((y + yOffset) % size.height); + float* output = m_frequencyMask.ptr(y); + for (int x = 0; x < size.width; ++x) + { + output[x] = source[(x + xOffset) % size.width]; + } + } + } + return m_frequencyMask; + } + + void MaskModule::invalidateMask() + { + m_mask.release(); + m_frequencyMask.release(); + m_maskSize = {}; + m_maskParameters.clear(); + } + + ProcessingResult MaskModule::process(const ImageFrame& frame, int processingBitDepth) + { + if (!frame.isValid()) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid mask input")); + } + + ImageFrame workingFrame; + if (!convertFrameForProcessing(frame, workingFrame, processingBitDepth)) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("Unsupported mask input")); + } + + const cv::Mat& mask = maskForSize({workingFrame.width, workingFrame.height}); + QByteArray bytes = workingFrame.isMono16() + ? allocatePixelBytes(workingFrame.width, workingFrame.height) + : allocatePixelBytes(workingFrame.width, workingFrame.height); + if (bytes.isEmpty()) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("Failed to allocate mask output")); + } + + if (workingFrame.isMono16()) + { + parallelForImageRows(workingFrame.width, workingFrame.height, [&](int firstRow, int lastRow) + { + for (int y = firstRow; y < lastRow; ++y) + { + const quint16* source = frameRowData(workingFrame, y); + const float* weights = mask.ptr(y); + quint16* output = reinterpret_cast( + bytes.data() + static_cast(y) * workingFrame.width * sizeof(quint16)); + for (int x = 0; x < workingFrame.width; ++x) + { + output[x] = static_cast(source[x] * weights[x]); + } + } + }); + } + else + { + parallelForImageRows(workingFrame.width, workingFrame.height, [&](int firstRow, int lastRow) + { + for (int y = firstRow; y < lastRow; ++y) + { + const uchar* source = frameRowData(workingFrame, y); + const float* weights = mask.ptr(y); + uchar* output = reinterpret_cast( + bytes.data() + static_cast(y) * workingFrame.width); + for (int x = 0; x < workingFrame.width; ++x) + { + output[x] = static_cast(source[x] * weights[x]); + } + } + }); + } + return ProcessingResult(makeFrameLike(workingFrame, + workingFrame.width, + workingFrame.height, + std::move(bytes))); + } + + ProcessingResult MaskModule::processValue(const ProcessingValue& input, int processingBitDepth) + { + if (std::holds_alternative(input)) + { + return process(std::get(input), processingBitDepth); + } + if (!std::holds_alternative(input)) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("Unsupported mask input")); + } + const ComplexFrame& source = std::get(input); + if (!source.isValid()) + { + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid complex field")); + } + + try + { + const cv::Size size(source.width, source.height); + const cv::Mat& previewMask = maskForSize(size); + const cv::Mat& mask = frequencyMaskForSize(size); + const cv::Mat real(source.height, source.width, CV_32F, + const_cast(source.real.constData()), + source.stride * static_cast(sizeof(float))); + const cv::Mat imaginary(source.height, source.width, CV_32F, + const_cast(source.imaginary.constData()), + source.stride * static_cast(sizeof(float))); + cv::Mat maskedReal; + cv::Mat maskedImaginary; + cv::multiply(real, mask, maskedReal); + cv::multiply(imaginary, mask, maskedImaginary); + + ComplexFrame output = source; + output.real = copyMatBytes(maskedReal); + output.imaginary = copyMatBytes(maskedImaginary); + output.stride = source.width; + ProcessingResult result(ProcessingValue{std::move(output)}); + result.frame = maskPreview(source, previewMask); + return result; + } + catch (const std::exception& e) + { + return ProcessingResult(ImageFrame{}, QString("Mask processing failed: %1").arg(e.what())); + } + } + + QVariantMap MaskModule::parameters() const + { + return {{QStringLiteral("shape"), m_shape}, + {QStringLiteral("center_x"), m_centerX}, + {QStringLiteral("center_y"), m_centerY}, + {QStringLiteral("size_x"), m_sizeX}, + {QStringLiteral("size_y"), m_sizeY}, + {QStringLiteral("inner_size"), m_innerSize}, + {QStringLiteral("rotation"), m_rotation}, + {QStringLiteral("edge_width"), m_edgeWidth}, + {QStringLiteral("invert"), m_invert}}; + } + + void MaskModule::setParameters(const QVariantMap& parameters) + { + const QVariantMap oldParameters = this->parameters(); + m_shape = qBound(0, parameters.value(QStringLiteral("shape"), m_shape).toInt(), 2); + m_centerX = qBound(-0.5, parameters.value(QStringLiteral("center_x"), m_centerX).toDouble(), 0.5); + m_centerY = qBound(-0.5, parameters.value(QStringLiteral("center_y"), m_centerY).toDouble(), 0.5); + m_sizeX = qBound(0.001, parameters.value(QStringLiteral("size_x"), m_sizeX).toDouble(), 1.0); + m_sizeY = qBound(0.001, parameters.value(QStringLiteral("size_y"), m_sizeY).toDouble(), 1.0); + m_innerSize = qBound(0.0, parameters.value(QStringLiteral("inner_size"), m_innerSize).toDouble(), 1.0); + m_rotation = qBound(-180.0, parameters.value(QStringLiteral("rotation"), m_rotation).toDouble(), 180.0); + m_edgeWidth = qBound(0.0, parameters.value(QStringLiteral("edge_width"), m_edgeWidth).toDouble(), 0.5); + m_invert = parameters.value(QStringLiteral("invert"), m_invert).toBool(); + if (oldParameters != this->parameters()) + { + invalidateMask(); + } + } +} diff --git a/ScopeOneCore/src/ProcessingModuleRegistry.cpp b/ScopeOneCore/src/ProcessingModuleRegistry.cpp index 20f0cc6..801eca3 100644 --- a/ScopeOneCore/src/ProcessingModuleRegistry.cpp +++ b/ScopeOneCore/src/ProcessingModuleRegistry.cpp @@ -3,7 +3,10 @@ #include "internal/BackgroundCalibrationModule.h" #include "internal/DifferentialRollingModule.h" #include "internal/FFTModule.h" +#include "internal/FrequencyDomainFilterModule.h" +#include "internal/MaskModule.h" #include "internal/GaussianBlurModule.h" +#include "internal/IFFTModule.h" #include "internal/SpatiotemporalBinningModule.h" #include "scopeone/PluginManifest.h" @@ -136,14 +139,40 @@ namespace scopeone::core::internal registerBuiltIn( registry, - {QStringLiteral("fft"), - QStringLiteral("FFT"), + {QStringLiteral("fft"), QStringLiteral("FFT"), 1, {}}); + + registerBuiltIn( + registry, + {QStringLiteral("frequency_domain_filter"), + QStringLiteral("Frequency Domain Filter"), 1, {choiceParameter("output_mode", "Output", 2, - {"FFT Spectrum", "Bandpass FFT Spectrum", "Bandpass IFFT Image"}), - realParameter("min_feature_size", "Min feature size", 2.0, 0.0, 1000.0, 0.1, 2), - realParameter("max_feature_size", "Max feature size", 10.0, 0.0, 1000.0, 0.1, 2), - choiceParameter("filter_kind", "Filter kind", 0, {"Smooth", "Hard"})}}); + {"Spectrum", "Filtered spectrum", "Filtered image"}), + realParameter("min_feature_size", "Min feature size", 2.0, + 0.0, 1000.0, 0.1, 2), + realParameter("max_feature_size", "Max feature size", 10.0, + 0.0, 1000.0, 0.1, 2), + choiceParameter("filter_kind", "Filter kind", 0, + {"Smooth", "Hard"})}}); + + registerBuiltIn( + registry, + {QStringLiteral("mask"), + QStringLiteral("Mask"), + 1, + {choiceParameter("shape", "Shape", 0, {"Ellipse", "Rectangle", "Annulus"}), + realParameter("center_x", "Center X", 0.0, -0.5, 0.5, 0.001, 3), + realParameter("center_y", "Center Y", 0.0, -0.5, 0.5, 0.001, 3), + realParameter("size_x", "Size X", 0.1, 0.001, 1.0, 0.001, 3), + realParameter("size_y", "Size Y", 0.1, 0.001, 1.0, 0.001, 3), + realParameter("inner_size", "Inner size", 0.0, 0.0, 1.0, 0.001, 3), + realParameter("rotation", "Rotation", 0.0, -180.0, 180.0, 0.1, 1), + realParameter("edge_width", "Edge width", 0.0, 0.0, 0.5, 0.001, 3), + booleanParameter("invert", "Invert", false)}}); + + registerBuiltIn( + registry, + {QStringLiteral("ifft"), QStringLiteral("IFFT"), 1, {}}); registerBuiltIn( registry, diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 5a2ea07..3e608ee 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -3993,7 +3993,7 @@ namespace scopeone::core { if (cancelToken->load()) { - return ProcessingResult{{}, QStringLiteral("Processing canceled")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Processing canceled")); } return pipeline->process(frame, bitDepth); })); diff --git a/ScopeOneCore/src/SpatiotemporalBinningModule.cpp b/ScopeOneCore/src/SpatiotemporalBinningModule.cpp index 1cbb97a..50986f8 100644 --- a/ScopeOneCore/src/SpatiotemporalBinningModule.cpp +++ b/ScopeOneCore/src/SpatiotemporalBinningModule.cpp @@ -352,7 +352,7 @@ namespace scopeone::core::internal { if (!frame.isValid()) { - return {{}, QStringLiteral("Invalid input")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid input")); } try @@ -360,7 +360,7 @@ namespace scopeone::core::internal ImageFrame workingFrame; if (!convertFrameForProcessing(frame, workingFrame, processingBitDepth)) { - return {{}, QStringLiteral("Unsupported input frame")}; + return ProcessingResult(ImageFrame{}, QStringLiteral("Unsupported input frame")); } if (!m_frameBuffer.empty() && !m_frameBuffer.front().isCompatibleWith(workingFrame)) { @@ -418,7 +418,7 @@ namespace scopeone::core::internal } catch (const std::exception& e) { - return {{}, QString("Spatiotemporal binning failed: %1").arg(e.what())}; + return ProcessingResult(ImageFrame{}, QString("Spatiotemporal binning failed: %1").arg(e.what())); } } diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index f6bc43a..9fcb3d9 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -15,9 +15,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -26,8 +28,12 @@ #include #include #include +#include #include +#include +#include + namespace scopeone::ui { namespace @@ -37,6 +43,173 @@ namespace scopeone::ui using ProcessingParameterDescriptor = scopeone::core::ProcessingParameterDescriptor; using ProcessingParameterType = scopeone::core::ProcessingParameterType; + class MaskPreviewWidget final : public QWidget + { + public: + explicit MaskPreviewWidget(QWidget* parent) + : QWidget(parent) + { + setMinimumSize(220, 220); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + setMouseTracking(true); + } + + void setParameters(const QVariantMap& parameters) + { + m_parameters = parameters; + update(); + } + + void setParametersChanged(std::function callback) + { + m_parametersChanged = std::move(callback); + } + + protected: + void paintEvent(QPaintEvent*) override + { + QPainter painter(this); + painter.fillRect(rect(), QColor(24, 27, 31)); + painter.setRenderHint(QPainter::Antialiasing); + const QRectF area = plotArea(); + painter.setPen(QColor(82, 88, 96)); + painter.drawLine(area.center().x(), area.top(), area.center().x(), area.bottom()); + painter.drawLine(area.left(), area.center().y(), area.right(), area.center().y()); + + const QPointF center = toWidget(m_parameters.value("center_x").toDouble(), + m_parameters.value("center_y").toDouble()); + const double sizeX = m_parameters.value("size_x", 0.1).toDouble() * area.width(); + const double sizeY = m_parameters.value("size_y", 0.1).toDouble() * area.height(); + const double rotation = m_parameters.value("rotation").toDouble(); + const int shape = m_parameters.value("shape").toInt(); + painter.save(); + painter.translate(center); + painter.rotate(-rotation); + painter.setPen(QPen(QColor(90, 210, 255), 2)); + painter.setBrush(QColor(90, 210, 255, 70)); + if (shape == 1) + { + painter.drawRect(QRectF(-sizeX / 2.0, -sizeY / 2.0, sizeX, sizeY)); + } + else + { + painter.drawEllipse(QRectF(-sizeX / 2.0, -sizeY / 2.0, sizeX, sizeY)); + if (shape == 2) + { + const double inner = m_parameters.value("inner_size").toDouble() + * area.width(); + painter.setBrush(QColor(24, 27, 31)); + painter.drawEllipse(QRectF(-inner / 2.0, -inner / 2.0, inner, inner)); + } + } + painter.restore(); + + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(255, 120, 90)); + painter.drawEllipse(center, 4, 4); + painter.setBrush(QColor(90, 210, 255)); + painter.drawEllipse(toWidget(m_parameters.value("center_x").toDouble() + + m_parameters.value("size_x", 0.1).toDouble() / 2.0, + m_parameters.value("center_y").toDouble()), + 5, 5); + } + + void mousePressEvent(QMouseEvent* event) override + { + const QPointF center = toWidget(m_parameters.value("center_x").toDouble(), + m_parameters.value("center_y").toDouble()); + const QPointF handle = toWidget(m_parameters.value("center_x").toDouble() + + m_parameters.value("size_x", 0.1).toDouble() / 2.0, + m_parameters.value("center_y").toDouble()); + if (QLineF(event->position(), center).length() < 14.0) + { + m_dragMode = DragMode::Move; + } + else if (QLineF(event->position(), handle).length() < 14.0) + { + m_dragMode = DragMode::Resize; + } + else + { + m_dragMode = DragMode::None; + } + m_lastPosition = event->position(); + } + + void mouseMoveEvent(QMouseEvent* event) override + { + if (m_dragMode == DragMode::None) + { + return; + } + QVariantMap parameters = m_parameters; + if (m_dragMode == DragMode::Move) + { + const QPointF delta = event->position() - m_lastPosition; + parameters["center_x"] = qBound(-0.5, + parameters.value("center_x").toDouble() + + delta.x() / plotArea().width(), + 0.5); + parameters["center_y"] = qBound(-0.5, + parameters.value("center_y").toDouble() + + delta.y() / plotArea().height(), + 0.5); + } + else + { + const QPointF center = toWidget(parameters.value("center_x").toDouble(), + parameters.value("center_y").toDouble()); + parameters["size_x"] = qBound(0.001, + 2.0 * std::abs(event->position().x() - center.x()) + / plotArea().width(), + 1.0); + parameters["size_y"] = qBound(0.001, + 2.0 * std::abs(event->position().y() - center.y()) + / plotArea().height(), + 1.0); + } + m_lastPosition = event->position(); + if (m_parametersChanged) + { + m_parametersChanged(parameters); + } + } + + void mouseReleaseEvent(QMouseEvent*) override + { + m_dragMode = DragMode::None; + } + + private: + enum class DragMode + { + None, + Move, + Resize + }; + + QPointF toWidget(double x, double y) const + { + const QRectF area = plotArea(); + return {area.left() + (x + 0.5) * area.width(), + area.top() + (y + 0.5) * area.height()}; + } + + QRectF plotArea() const + { + const qreal side = qMin(width(), height()); + return QRectF((width() - side) / 2.0, + (height() - side) / 2.0, + side, + side); + } + + QVariantMap m_parameters; + std::function m_parametersChanged; + QPointF m_lastPosition; + DragMode m_dragMode{DragMode::None}; + }; + void configureSpinBox(QAbstractSpinBox* spinBox) { spinBox->setKeyboardTracking(false); @@ -55,6 +228,18 @@ namespace scopeone::ui auto* layout = new QVBoxLayout(this); auto* group = new QGroupBox(info.name(), this); auto* form = new QFormLayout(group); + if (info.descriptor().id == QStringLiteral("mask")) + { + auto* preview = new MaskPreviewWidget(group); + preview->setParameters(info.parameters()); + preview->setParametersChanged([this, preview](const QVariantMap& parameters) + { + preview->setParameters(parameters); + m_core->setProcessingModuleParameters(m_moduleIndex, parameters); + }); + form->addRow(preview); + m_maskPreview = preview; + } for (const ProcessingParameterDescriptor& parameter : info.descriptor().parameters) { QWidget* editor = createEditor(parameter, @@ -105,6 +290,10 @@ namespace scopeone::ui } } } + if (m_maskPreview) + { + m_maskPreview->setParameters(parameters); + } } private: @@ -191,6 +380,7 @@ namespace scopeone::ui int m_moduleIndex; QHash m_editors; QHash m_descriptors; + MaskPreviewWidget* m_maskPreview{nullptr}; }; } From d75281f6677f91215d9f297243d398c550245e93 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Sat, 29 Aug 2026 04:01:22 +0200 Subject: [PATCH 08/37] Fix raw intensity profile updates --- ScopeOneCore/CMakeLists.txt | 8 +- .../include/scopeone/ExperimentDocument.h | 1 + .../include/scopeone/ProcessingPipeline.h | 34 +++ ScopeOneCore/include/scopeone/ScopeOneCore.h | 18 +- .../include/scopeone/SimulatorProvider.h | 7 + .../include/scopeone/ToolFrameStream.h | 42 +++ ScopeOneCore/include/scopeone/ToolPlugin.h | 5 + ScopeOneCore/include/scopeone/ToolTask.h | 37 +++ ScopeOneCore/internal/AcquisitionEngine.h | 9 +- ScopeOneCore/internal/FrameRouter.h | 21 -- ScopeOneCore/internal/HardwareRuntime.h | 4 +- .../internal/ImageProcessingFramework.h | 1 + ScopeOneCore/src/AcquisitionEngine.cpp | 25 +- ScopeOneCore/src/DriverHostMain.cpp | 4 - ScopeOneCore/src/ExperimentDocument.cpp | 7 + ScopeOneCore/src/FrameRouter.cpp | 14 - ScopeOneCore/src/HardwareRuntime.cpp | 22 +- ScopeOneCore/src/ImageProcessingFramework.cpp | 16 +- ScopeOneCore/src/ProcessingPipeline.cpp | 86 ++++++ ScopeOneCore/src/ScopeOneCore.cpp | 99 ++++++- ScopeOneCore/src/SimulatorProvider.cpp | 85 +++++- ScopeOneCore/src/ToolFrameStream.cpp | 65 +++++ ScopeOneCore/src/ToolTask.cpp | 83 ++++++ plugins/examples/README.md | 12 + plugins/examples/tool/CMakeLists.txt | 13 + plugins/examples/tool/DhmReconstruction.cpp | 251 ++++++++++++++++ plugins/examples/tool/DhmReconstruction.h | 17 ++ plugins/examples/tool/DhmToolPlugin.cpp | 271 ++++++++++++++++++ plugins/examples/tool/dhm_plugin.json | 7 + scripts/build.ps1 | 20 +- src/DeviceControlWidget.cpp | 8 - src/ImageProcessingWidget.cpp | 4 - src/ImageWorkspace.cpp | 5 - src/InspectWidget.cpp | 15 +- src/MainWindow.cpp | 32 +++ src/MainWindow.h | 5 + src/PreviewWidget.cpp | 49 +++- src/PreviewWidget.h | 3 + src/ScopeOneLocalApiServer.cpp | 6 +- src/ScopeOneToolPlugin.cpp | 46 +-- 40 files changed, 1292 insertions(+), 165 deletions(-) create mode 100644 ScopeOneCore/include/scopeone/ProcessingPipeline.h create mode 100644 ScopeOneCore/include/scopeone/ToolFrameStream.h create mode 100644 ScopeOneCore/include/scopeone/ToolTask.h delete mode 100644 ScopeOneCore/internal/FrameRouter.h delete mode 100644 ScopeOneCore/src/FrameRouter.cpp create mode 100644 ScopeOneCore/src/ProcessingPipeline.cpp create mode 100644 ScopeOneCore/src/ToolFrameStream.cpp create mode 100644 ScopeOneCore/src/ToolTask.cpp create mode 100644 plugins/examples/tool/DhmReconstruction.cpp create mode 100644 plugins/examples/tool/DhmReconstruction.h create mode 100644 plugins/examples/tool/DhmToolPlugin.cpp create mode 100644 plugins/examples/tool/dhm_plugin.json diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 91cbd52..34b9dee 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -79,7 +79,6 @@ set(CORE_SOURCES src/ExperimentDocument.cpp src/AcquisitionEngine.cpp src/DriverHostProviderProxy.cpp - src/FrameRouter.cpp src/HardwareProvider.cpp src/PluginManifest.cpp src/HardwareRuntime.cpp @@ -91,6 +90,9 @@ set(CORE_SOURCES 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 @@ -122,7 +124,6 @@ set(CORE_HEADERS include/scopeone/SimulatorProvider.h include/scopeone/ImageSceneModel.h internal/HardwareRuntime.h - internal/FrameRouter.h internal/MicroManagerProvider.h internal/MMCoreManager.h internal/FrameBufferUtils.h @@ -132,6 +133,9 @@ set(CORE_HEADERS internal/ProcessingModule.h internal/ProcessingModuleRegistry.h include/scopeone/ProcessingPlugin.h + include/scopeone/ProcessingPipeline.h + include/scopeone/ToolTask.h + include/scopeone/ToolFrameStream.h include/scopeone/ToolPlugin.h internal/DriverHostProtocol.h internal/SharedFrameRing.h diff --git a/ScopeOneCore/include/scopeone/ExperimentDocument.h b/ScopeOneCore/include/scopeone/ExperimentDocument.h index 8f7e98f..cc59837 100644 --- a/ScopeOneCore/include/scopeone/ExperimentDocument.h +++ b/ScopeOneCore/include/scopeone/ExperimentDocument.h @@ -54,6 +54,7 @@ namespace scopeone::core { Raw, Processed, + Tool, Static, Gallery }; 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/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index cddffc2..7f74559 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -24,6 +24,7 @@ #include "scopeone/HardwareProvider.h" #include "scopeone/ImageFrame.h" #include "scopeone/ProcessingPlugin.h" +#include "scopeone/ProcessingPipeline.h" #include "scopeone/scopeone_core_export.h" class CMMCore; @@ -515,10 +516,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; } @@ -561,6 +564,9 @@ namespace scopeone::core ImageFrame publishStaticFrame(const QString& sourceId, const ImageFrame& frame, const QString& displayName = QString()); + 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(); @@ -654,6 +660,8 @@ namespace scopeone::core ImageFrame processFrameFrom(int startModuleIndex, const ImageFrame& frame) const; ImageFrame processFrameThrough(int endModuleIndex, const ImageFrame& frame) const; QList availableProcessingModules() const; + std::unique_ptr createProcessingModule(const QString& moduleId) const; + std::unique_ptr createProcessingPipeline() const; QList processingModules() const; bool addProcessingModule(const QString& moduleId); bool removeProcessingModule(int index); @@ -728,6 +736,9 @@ namespace scopeone::core 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); @@ -804,6 +815,7 @@ namespace scopeone::core { Raw, Processed, + Tool, Static, External }; @@ -824,6 +836,7 @@ namespace scopeone::core QHash m_rawFrames; QHash m_processedFrames; + QHash m_toolFrames; QHash m_staticFrames; QHash m_externalFrames; }; @@ -871,12 +884,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); @@ -895,6 +910,7 @@ namespace scopeone::core QPoint start; QPoint end; bool processed{false}; + bool toolSource{false}; bool staticSource{false}; bool active{false}; }; diff --git a/ScopeOneCore/include/scopeone/SimulatorProvider.h b/ScopeOneCore/include/scopeone/SimulatorProvider.h index 95f5a7a..e00f3ca 100644 --- a/ScopeOneCore/include/scopeone/SimulatorProvider.h +++ b/ScopeOneCore/include/scopeone/SimulatorProvider.h @@ -60,6 +60,12 @@ namespace scopeone::core int timeoutMs) override; private: + enum class ImageMode + { + Gradient, + Hologram + }; + bool accepts(const QString& cameraIdOrAll) const; ImageFrame makeFrame(); void updateTimerInterval(); @@ -70,6 +76,7 @@ namespace scopeone::core 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; diff --git a/ScopeOneCore/include/scopeone/ToolFrameStream.h b/ScopeOneCore/include/scopeone/ToolFrameStream.h new file mode 100644 index 0000000..f3f25d7 --- /dev/null +++ b/ScopeOneCore/include/scopeone/ToolFrameStream.h @@ -0,0 +1,42 @@ +#pragma once + +#include "scopeone/ImageFrame.h" +#include "scopeone/scopeone_core_export.h" + +#include +#include + +namespace scopeone::core +{ + class ScopeOneCore; +} + +namespace scopeone::ui +{ + class SCOPEONE_CORE_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 index 27cc9ae..1c286e9 100644 --- a/ScopeOneCore/include/scopeone/ToolPlugin.h +++ b/ScopeOneCore/include/scopeone/ToolPlugin.h @@ -34,6 +34,11 @@ namespace scopeone::ui virtual scopeone::core::ScopeOneCore& core() const = 0; virtual QString currentLayerKey() const = 0; + virtual scopeone::core::ImageFrame currentFrame() 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( diff --git a/ScopeOneCore/include/scopeone/ToolTask.h b/ScopeOneCore/include/scopeone/ToolTask.h new file mode 100644 index 0000000..078facc --- /dev/null +++ b/ScopeOneCore/include/scopeone/ToolTask.h @@ -0,0 +1,37 @@ +#pragma once + +#include "scopeone/scopeone_core_export.h" + +#include +#include +#include +#include +#include + +namespace scopeone::ui +{ + class SCOPEONE_CORE_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/internal/AcquisitionEngine.h b/ScopeOneCore/internal/AcquisitionEngine.h index 2293794..aae9b16 100644 --- a/ScopeOneCore/internal/AcquisitionEngine.h +++ b/ScopeOneCore/internal/AcquisitionEngine.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include "scopeone/CameraProvider.h" @@ -9,17 +8,15 @@ namespace scopeone::core::internal { class DeviceRegistry; - class AcquisitionEngine : public QObject + class AcquisitionEngine { - Q_OBJECT - public: - AcquisitionEngine(DeviceRegistry* deviceRegistry, QObject* parent = nullptr); + explicit AcquisitionEngine(DeviceRegistry& deviceRegistry); bool start(const QString& cameraIdOrAll); bool stop(const QString& cameraIdOrAll); private: - DeviceRegistry* m_deviceRegistry{nullptr}; + DeviceRegistry& m_deviceRegistry; }; } diff --git a/ScopeOneCore/internal/FrameRouter.h b/ScopeOneCore/internal/FrameRouter.h deleted file mode 100644 index 1e9a062..0000000 --- a/ScopeOneCore/internal/FrameRouter.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -#include "scopeone/ImageFrame.h" - -namespace scopeone::core::internal -{ - class FrameRouter : public QObject - { - Q_OBJECT - - public: - explicit FrameRouter(QObject* parent = nullptr); - void publish(const scopeone::core::ImageFrame& frame); - - signals: - void frameReady(const scopeone::core::ImageFrame& frame); - - }; -} diff --git a/ScopeOneCore/internal/HardwareRuntime.h b/ScopeOneCore/internal/HardwareRuntime.h index 952752a..1c00444 100644 --- a/ScopeOneCore/internal/HardwareRuntime.h +++ b/ScopeOneCore/internal/HardwareRuntime.h @@ -12,7 +12,6 @@ #include "scopeone/CameraProvider.h" #include "internal/AcquisitionEngine.h" #include "internal/CameraRuntimeControl.h" -#include "internal/FrameRouter.h" namespace scopeone::core::internal { @@ -149,7 +148,6 @@ namespace scopeone::core::internal DeviceRegistry* deviceRegistry() { return &m_registry; } const DeviceRegistry* deviceRegistry() const { return &m_registry; } - FrameRouter* frameRouter() { return &m_frameRouter; } void clear(); bool registerProvider(const HardwareProviderPtr& provider); void unregisterProvider(const QString& providerId); @@ -157,6 +155,7 @@ namespace scopeone::core::internal bool stopPreviewForProvider(const QString& providerId); signals: + void frameReady(const ImageFrame& frame); void devicesChanged(); void previewStateChanged(bool running); @@ -172,7 +171,6 @@ namespace scopeone::core::internal const QStringList& cameraIds) const; DeviceRegistry m_registry; - FrameRouter m_frameRouter; AcquisitionEngine m_acquisitionEngine; FrameSink m_frameSink; PreviewStateSink m_previewStateSink; diff --git a/ScopeOneCore/internal/ImageProcessingFramework.h b/ScopeOneCore/internal/ImageProcessingFramework.h index 1304b65..76f8b56 100644 --- a/ScopeOneCore/internal/ImageProcessingFramework.h +++ b/ScopeOneCore/internal/ImageProcessingFramework.h @@ -18,6 +18,7 @@ namespace scopeone::core::internal explicit ProcessingPipelineRuntime(std::vector> modules); ProcessingResult process(const ImageFrame& input, int processingBitDepth); + ProcessingResult processValue(const ProcessingValue& input, int processingBitDepth); ProcessingResult processFrom(int startModuleIndex, const ImageFrame& input, int processingBitDepth); ProcessingResult processThrough(int endModuleIndex, const ImageFrame& input, int processingBitDepth); diff --git a/ScopeOneCore/src/AcquisitionEngine.cpp b/ScopeOneCore/src/AcquisitionEngine.cpp index 792e022..9a86d93 100644 --- a/ScopeOneCore/src/AcquisitionEngine.cpp +++ b/ScopeOneCore/src/AcquisitionEngine.cpp @@ -5,18 +5,13 @@ namespace scopeone::core::internal { - AcquisitionEngine::AcquisitionEngine(DeviceRegistry* deviceRegistry, QObject* parent) - : QObject(parent) - , m_deviceRegistry(deviceRegistry) + AcquisitionEngine::AcquisitionEngine(DeviceRegistry& deviceRegistry) + : m_deviceRegistry(deviceRegistry) { } bool AcquisitionEngine::start(const QString& cameraIdOrAll) { - if (!m_deviceRegistry) - { - return false; - } const QString target = cameraIdOrAll.trimmed(); if (target.isEmpty()) { @@ -31,14 +26,14 @@ namespace scopeone::core::internal }; QList startedCameras; bool found = false; - for (const HardwareDeviceDescriptor& device : m_deviceRegistry->devices()) + for (const HardwareDeviceDescriptor& device : m_deviceRegistry.devices()) { if (device.kind != HardwareDeviceKind::Camera) { continue; } found = true; - const HardwareProviderPtr provider = m_deviceRegistry->provider(device.providerId); + const HardwareProviderPtr provider = m_deviceRegistry.provider(device.providerId); auto* cameraProvider = dynamic_cast(provider.get()); if (!cameraProvider) { @@ -64,17 +59,13 @@ namespace scopeone::core::internal } return found; } - const HardwareProviderPtr provider = m_deviceRegistry->providerForDevice(target); + const HardwareProviderPtr provider = m_deviceRegistry.providerForDevice(target); auto* cameraProvider = dynamic_cast(provider.get()); return cameraProvider && cameraProvider->startPreviewFor(target); } bool AcquisitionEngine::stop(const QString& cameraIdOrAll) { - if (!m_deviceRegistry) - { - return false; - } const QString target = cameraIdOrAll.trimmed(); if (target.isEmpty()) { @@ -84,14 +75,14 @@ namespace scopeone::core::internal { bool found = false; bool stopped = true; - for (const HardwareDeviceDescriptor& device : m_deviceRegistry->devices()) + for (const HardwareDeviceDescriptor& device : m_deviceRegistry.devices()) { if (device.kind != HardwareDeviceKind::Camera) { continue; } found = true; - const HardwareProviderPtr provider = m_deviceRegistry->provider(device.providerId); + const HardwareProviderPtr provider = m_deviceRegistry.provider(device.providerId); auto* cameraProvider = dynamic_cast(provider.get()); if (!cameraProvider) { @@ -105,7 +96,7 @@ namespace scopeone::core::internal } return found && stopped; } - const HardwareProviderPtr provider = m_deviceRegistry->providerForDevice(target); + const HardwareProviderPtr provider = m_deviceRegistry.providerForDevice(target); auto* cameraProvider = dynamic_cast(provider.get()); return cameraProvider && cameraProvider->stopPreviewFor(target); } diff --git a/ScopeOneCore/src/DriverHostMain.cpp b/ScopeOneCore/src/DriverHostMain.cpp index 740eff0..cf10a62 100644 --- a/ScopeOneCore/src/DriverHostMain.cpp +++ b/ScopeOneCore/src/DriverHostMain.cpp @@ -93,10 +93,6 @@ namespace scopeone::core::internal , m_connectionId(connectionId) , m_socket(socket) { - if (!socket) - { - qFatal("ControlConnection requires QLocalSocket"); - } m_socket->setParent(this); connect(m_socket, &QLocalSocket::readyRead, diff --git a/ScopeOneCore/src/ExperimentDocument.cpp b/ScopeOneCore/src/ExperimentDocument.cpp index 534feca..3588ac5 100644 --- a/ScopeOneCore/src/ExperimentDocument.cpp +++ b/ScopeOneCore/src/ExperimentDocument.cpp @@ -770,6 +770,11 @@ namespace scopeone::core kind = DocumentLayerKind::Static; return true; } + if (name == QStringLiteral("Tool")) + { + kind = DocumentLayerKind::Tool; + return true; + } if (name == QStringLiteral("Gallery")) { kind = DocumentLayerKind::Gallery; @@ -3181,6 +3186,8 @@ namespace scopeone::core return QStringLiteral("Processed"); case DocumentLayerKind::Static: return QStringLiteral("Static"); + case DocumentLayerKind::Tool: + return QStringLiteral("Tool"); case DocumentLayerKind::Gallery: return QStringLiteral("Gallery"); } diff --git a/ScopeOneCore/src/FrameRouter.cpp b/ScopeOneCore/src/FrameRouter.cpp deleted file mode 100644 index 04f6ae7..0000000 --- a/ScopeOneCore/src/FrameRouter.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "internal/FrameRouter.h" - -namespace scopeone::core::internal -{ - FrameRouter::FrameRouter(QObject* parent) - : QObject(parent) - { - } - - void FrameRouter::publish(const scopeone::core::ImageFrame& frame) - { - emit frameReady(frame); - } -} diff --git a/ScopeOneCore/src/HardwareRuntime.cpp b/ScopeOneCore/src/HardwareRuntime.cpp index a69702b..2df7493 100644 --- a/ScopeOneCore/src/HardwareRuntime.cpp +++ b/ScopeOneCore/src/HardwareRuntime.cpp @@ -173,19 +173,10 @@ namespace scopeone::core::internal HardwareRuntime::HardwareRuntime(QObject* parent) : QObject(parent) , m_registry(this) - , m_frameRouter(this) - , m_acquisitionEngine(&m_registry, this) + , m_acquisitionEngine(m_registry) { connect(&m_registry, &DeviceRegistry::changed, this, &HardwareRuntime::devicesChanged); - connect(&m_frameRouter, &FrameRouter::frameReady, - this, [this](const ImageFrame& frame) - { - if (m_frameSink) - { - m_frameSink(frame); - } - }); } void HardwareRuntime::setFrameSink(FrameSink sink) @@ -882,7 +873,11 @@ namespace scopeone::core::internal { cameraProvider->setFrameSink([this](const ImageFrame& frame) { - m_frameRouter.publish(frame); + if (m_frameSink) + { + m_frameSink(frame); + } + emit frameReady(frame); }); cameraProvider->setPreviewStateSink([this](bool) { @@ -898,7 +893,10 @@ namespace scopeone::core::internal break; } } - if (m_previewStateSink) m_previewStateSink(running); + if (m_previewStateSink) + { + m_previewStateSink(running); + } emit previewStateChanged(running); }; if (QThread::currentThread() == thread()) publishState(); diff --git a/ScopeOneCore/src/ImageProcessingFramework.cpp b/ScopeOneCore/src/ImageProcessingFramework.cpp index 62176c7..b51dddb 100644 --- a/ScopeOneCore/src/ImageProcessingFramework.cpp +++ b/ScopeOneCore/src/ImageProcessingFramework.cpp @@ -30,7 +30,13 @@ namespace scopeone::core::internal // Runs all runtime modules in order for one frame ProcessingResult ProcessingPipelineRuntime::process(const ImageFrame& input, int processingBitDepth) { - return processRange(ProcessingValue{input}, + return processValue(ProcessingValue{input}, processingBitDepth); + } + + ProcessingResult ProcessingPipelineRuntime::processValue(const ProcessingValue& input, + int processingBitDepth) + { + return processRange(input, processingBitDepth, 0, (std::numeric_limits::max)()); @@ -67,8 +73,8 @@ namespace scopeone::core::internal int startModuleIndex, int endModuleIndexExclusive) { - if (!std::holds_alternative(input) - || !std::get(input).isValid()) + if (std::holds_alternative(input) + && !std::get(input).isValid()) { return ProcessingResult(ImageFrame{}, QStringLiteral("Invalid processing input")); } @@ -119,10 +125,6 @@ namespace scopeone::core::internal // Adds one module to the editable pipeline definition void ProcessingPipelineDefinition::addModule(std::unique_ptr module) { - if (!module) - { - qFatal("Processing pipeline requires a valid module"); - } QMutexLocker locker(&m_modulesMutex); m_modules.push_back(std::move(module)); } diff --git a/ScopeOneCore/src/ProcessingPipeline.cpp b/ScopeOneCore/src/ProcessingPipeline.cpp new file mode 100644 index 0000000..6d3c8a0 --- /dev/null +++ b/ScopeOneCore/src/ProcessingPipeline.cpp @@ -0,0 +1,86 @@ +#include "scopeone/ProcessingPipeline.h" + +#include "internal/ImageProcessingFramework.h" + +namespace scopeone::core +{ + struct ProcessingPipeline::Impl + { + internal::ProcessingPipelineDefinition definition; + std::shared_ptr runtime; + + void rebuildRuntime() + { + runtime = definition.createRuntime(); + } + }; + + ProcessingPipeline::ProcessingPipeline() + : m_impl(std::make_unique()) + { + m_impl->rebuildRuntime(); + } + + ProcessingPipeline::~ProcessingPipeline() = default; + ProcessingPipeline::ProcessingPipeline(ProcessingPipeline&&) noexcept = default; + ProcessingPipeline& ProcessingPipeline::operator=(ProcessingPipeline&&) noexcept = default; + + bool ProcessingPipeline::addModule(std::unique_ptr module) + { + if (!module) + { + return false; + } + m_impl->definition.addModule(std::move(module)); + m_impl->rebuildRuntime(); + return true; + } + + bool ProcessingPipeline::removeModule(int index) + { + const bool removed = m_impl->definition.removeModule(index); + if (removed) + { + m_impl->rebuildRuntime(); + } + return removed; + } + + bool ProcessingPipeline::setModuleParameters(int index, const QVariantMap& parameters) + { + const bool updated = m_impl->definition.withModule(index, [¶meters](ProcessingModule* module) + { + module->setParameters(parameters); + }); + if (updated) + { + m_impl->rebuildRuntime(); + } + return updated; + } + + bool ProcessingPipeline::resetModuleState(int index) + { + bool reset = false; + m_impl->definition.withModule(index, [&reset](ProcessingModule* module) + { + reset = module->resetState(); + }); + if (reset) + { + m_impl->rebuildRuntime(); + } + return reset; + } + + int ProcessingPipeline::moduleCount() const + { + return m_impl->definition.moduleCount(); + } + + ProcessingResult ProcessingPipeline::process(const ProcessingValue& input, + int processingBitDepth) const + { + return m_impl->runtime->processValue(input, processingBitDepth); + } +} diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 3e608ee..ee06d46 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -4,7 +4,6 @@ #include "internal/AcquisitionEngine.h" #include "internal/ImageProcessingFramework.h" #include "internal/ProcessingModuleRegistry.h" -#include "internal/FrameRouter.h" #include "internal/DriverHostProviderProxy.h" #include "internal/HardwareRuntime.h" #include "internal/MMCoreManager.h" @@ -451,7 +450,6 @@ namespace scopeone::core { using scopeone::core::internal::ImageProcessingManager; using scopeone::core::internal::HardwareRuntime; - using scopeone::core::internal::FrameRouter; using scopeone::core::internal::MMCoreManager; using scopeone::core::internal::CameraManager; using scopeone::core::internal::CameraRuntimeControl; @@ -490,6 +488,7 @@ namespace scopeone::core { m_rawFrames.clear(); m_processedFrames.clear(); + m_toolFrames.clear(); m_staticFrames.clear(); m_externalFrames.clear(); } @@ -552,6 +551,10 @@ namespace scopeone::core { return m_staticFrames; } + if (stream == FrameGraphStream::Tool) + { + return m_toolFrames; + } if (stream == FrameGraphStream::External) { return m_externalFrames; @@ -569,6 +572,10 @@ namespace scopeone::core { return m_staticFrames; } + if (stream == FrameGraphStream::Tool) + { + return m_toolFrames; + } if (stream == FrameGraphStream::External) { return m_externalFrames; @@ -1066,6 +1073,12 @@ namespace scopeone::core return QStringLiteral("proc:%1").arg(cameraId.trimmed()); } + // Build the graph layer key for one tool stream + QString ScopeOneCore::toolLayerKey(const QString& sourceId) + { + return QStringLiteral("tool:%1").arg(sourceId.trimmed()); + } + // Build the graph layer key for one static source QString ScopeOneCore::staticLayerKey(const QString& sourceId) { @@ -1090,6 +1103,11 @@ namespace scopeone::core return layerKey.trimmed().startsWith(QStringLiteral("proc:")); } + bool ScopeOneCore::isToolLayerKey(const QString& layerKey) + { + return layerKey.trimmed().startsWith(QStringLiteral("tool:")); + } + bool ScopeOneCore::isStaticLayerKey(const QString& layerKey) { return layerKey.trimmed().startsWith(QStringLiteral("static:")); @@ -1242,9 +1260,9 @@ namespace scopeone::core session.setPresentationState(layers, markups); }); - connect(m_managers->hardwareRuntime->frameRouter(), &FrameRouter::frameReady, + connect(m_managers->hardwareRuntime, &HardwareRuntime::frameReady, this, &ScopeOneCore::handleIncomingRawFrame); - connect(m_managers->hardwareRuntime->frameRouter(), &FrameRouter::frameReady, + connect(m_managers->hardwareRuntime, &HardwareRuntime::frameReady, this, [this](const ImageFrame& frame) { const HardwareDeviceDescriptor device = @@ -2115,7 +2133,8 @@ namespace scopeone::core void ScopeOneCore::setLineProfile(const QString& cameraId, const QPoint& start, const QPoint& end, - bool processed) + bool processed, + bool toolSource) { const QString trimmedCameraId = cameraId.trimmed(); if (trimmedCameraId.isEmpty()) @@ -2128,16 +2147,26 @@ namespace scopeone::core m_activeLineProfile.start = start; m_activeLineProfile.end = end; m_activeLineProfile.processed = processed; + m_activeLineProfile.toolSource = toolSource; m_activeLineProfile.staticSource = false; m_activeLineProfile.active = true; m_lineProfileUpdateTimer.invalidate(); + if (toolSource) + { + const ImageFrame frame = graphFrame(toolLayerKey(trimmedCameraId)); + if (frame.isValid()) + { + updateLineProfile(trimmedCameraId, false, true, frame); + } + return; + } if (processed) { const ImageFrame frame = graphFrame(processedLayerKey(trimmedCameraId)); if (frame.isValid()) { - updateLineProfile(trimmedCameraId, true, frame); + updateLineProfile(trimmedCameraId, true, false, frame); } return; } @@ -2145,7 +2174,7 @@ namespace scopeone::core const ImageFrame frame = graphFrame(rawLayerKey(trimmedCameraId)); if (frame.isValid()) { - updateLineProfile(trimmedCameraId, false, frame); + updateLineProfile(trimmedCameraId, false, false, frame); } } @@ -2211,7 +2240,8 @@ namespace scopeone::core setLineProfile(markup.sourceId, markup.start, markup.end, - markup.layerKind == DocumentLayerKind::Processed); + markup.layerKind == DocumentLayerKind::Processed, + markup.layerKind == DocumentLayerKind::Tool); } return; } @@ -2239,7 +2269,7 @@ namespace scopeone::core emit newRawFrameReady(normalizedFrame); queuePreviewRawFrame(normalizedFrame); scheduleHistogramStats(layerKey, normalizedFrame); - updateLineProfile(cameraId, false, normalizedFrame); + updateLineProfile(cameraId, false, false, normalizedFrame); } // Submits one acquisition frame without crossing the UI event queue @@ -2333,7 +2363,7 @@ namespace scopeone::core emit processedFramesCompleted(frame.cameraId, it.value().completedCount); m_pendingPreviewProcessedFrames.insert(frame.cameraId, frame); scheduleHistogramStats(layerKey, frame); - updateLineProfile(frame.cameraId, true, frame); + updateLineProfile(frame.cameraId, true, false, frame); } } @@ -2402,6 +2432,10 @@ namespace scopeone::core { return m_frameGraph.latest(FrameGraphStream::Static, sourceId); } + if (isToolLayerKey(trimmedLayerKey)) + { + return m_frameGraph.latest(FrameGraphStream::Tool, sourceId); + } if (trimmedLayerKey.startsWith(QStringLiteral("external:"))) { return m_frameGraph.latest(FrameGraphStream::External, sourceId); @@ -2511,6 +2545,33 @@ namespace scopeone::core return storedFrame; } + // Publish the latest frame of a tool-owned realtime stream + ImageFrame ScopeOneCore::publishToolStreamFrame(const QString& sourceId, + const ImageFrame& frame, + const QString& displayName) + { + if (!m_frameGraph.publishLatest(FrameGraphStream::Tool, sourceId, frame)) + { + return {}; + } + const ImageFrame storedFrame = graphFrame(toolLayerKey(sourceId)); + const QString layerKey = toolLayerKey(storedFrame.cameraId); + ensureSceneLayer(layerKey, + storedFrame.cameraId, + displayName.trimmed().isEmpty() ? storedFrame.cameraId : displayName.trimmed(), + DocumentLayerKind::Tool); + m_imageSceneModel->setLayerName( + layerKey, + displayName.trimmed().isEmpty() ? storedFrame.cameraId : displayName.trimmed()); + m_imageSceneModel->updateLayerFrame(layerKey, storedFrame); + m_imageSceneModel->setLayerVisible(layerKey, true); + m_latestHistogramStats.remove(layerKey); + scheduleHistogramStats(layerKey, storedFrame); + emit toolStreamFramePublished(storedFrame.cameraId, displayName.trimmed(), storedFrame); + updateLineProfile(storedFrame.cameraId, false, true, storedFrame); + return storedFrame; + } + // Publish an externally supplied frame to the central graph ImageFrame ScopeOneCore::publishExternalFrame(const QString& sourceId, const ImageFrame& frame) { @@ -2628,6 +2689,7 @@ namespace scopeone::core m_frameGraph.remove(FrameGraphStream::Raw, trimmedCameraId); m_frameGraph.remove(FrameGraphStream::Processed, trimmedCameraId); + m_frameGraph.remove(FrameGraphStream::Tool, trimmedCameraId); { QMutexLocker locker(&m_managers->processedDeliveryMutex); m_managers->pendingProcessedFrames.remove(trimmedCameraId); @@ -3039,6 +3101,7 @@ namespace scopeone::core // Emit a line profile when the active request matches this frame void ScopeOneCore::updateLineProfile(const QString& cameraId, bool processed, + bool toolSource, const ImageFrame& frame) { if (!frame.isValid()) @@ -3048,6 +3111,7 @@ namespace scopeone::core if (!m_activeLineProfile.active || m_activeLineProfile.staticSource + || m_activeLineProfile.toolSource != toolSource || m_activeLineProfile.processed != processed || m_activeLineProfile.sourceId != cameraId) { @@ -3070,7 +3134,9 @@ namespace scopeone::core return; } - const QString layerKey = histogramLayerKey(cameraId, processed); + const QString layerKey = m_activeLineProfile.toolSource + ? toolLayerKey(cameraId) + : histogramLayerKey(cameraId, processed); emit lineProfileUpdated(cameraId, processed, values); emit layerLineProfileUpdated(layerKey, values); } @@ -3856,6 +3922,17 @@ namespace scopeone::core return m_managers->processingModuleRegistry->descriptors(); } + std::unique_ptr ScopeOneCore::createProcessingModule( + const QString& moduleId) const + { + return m_managers->processingModuleRegistry->create(moduleId); + } + + std::unique_ptr ScopeOneCore::createProcessingPipeline() const + { + return std::make_unique(); + } + // Export processing module descriptions for the UI QList ScopeOneCore::processingModules() const { diff --git a/ScopeOneCore/src/SimulatorProvider.cpp b/ScopeOneCore/src/SimulatorProvider.cpp index b441aad..cadf40e 100644 --- a/ScopeOneCore/src/SimulatorProvider.cpp +++ b/ScopeOneCore/src/SimulatorProvider.cpp @@ -151,6 +151,7 @@ namespace scopeone::core { return accepts(cameraId) ? QStringList{QStringLiteral("Exposure"), + QStringLiteral("ImageMode"), QStringLiteral("SensorWidth"), QStringLiteral("SensorHeight")} : QStringList{}; @@ -169,6 +170,13 @@ namespace scopeone::core QMutexLocker locker(&m_mutex); return QString::number(m_exposureMs, 'g', 12); } + if (name == QStringLiteral("ImageMode")) + { + QMutexLocker locker(&m_mutex); + return m_imageMode == ImageMode::Hologram + ? QStringLiteral("Hologram") + : QStringLiteral("Gradient"); + } if (name == QStringLiteral("SensorWidth")) { return QString::number(m_sensorWidth); @@ -189,7 +197,7 @@ namespace scopeone::core { errorMessage->clear(); } - if (!accepts(cameraId) || name != QStringLiteral("Exposure")) + if (!accepts(cameraId)) { if (errorMessage) { @@ -197,17 +205,41 @@ namespace scopeone::core } return false; } - bool ok = false; - const double exposureMs = value.toDouble(&ok); - if (!ok || !setExposure(cameraId, exposureMs)) + if (name == QStringLiteral("ImageMode")) + { + const QString mode = value.trimmed(); + if (mode != QStringLiteral("Gradient") && mode != QStringLiteral("Hologram")) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Invalid image mode"); + } + return false; + } + QMutexLocker locker(&m_mutex); + m_imageMode = mode == QStringLiteral("Hologram") + ? ImageMode::Hologram + : ImageMode::Gradient; + return true; + } + if (name == QStringLiteral("Exposure")) { + bool ok = false; + const double exposureMs = value.toDouble(&ok); + if (ok && setExposure(cameraId, exposureMs)) + { + return true; + } if (errorMessage) { *errorMessage = QStringLiteral("Invalid exposure value"); } - return false; } - return true; + else if (errorMessage) + { + *errorMessage = QStringLiteral("Property is not writable"); + } + return false; } QString SimulatorProvider::getPropertyType(const QString& cameraId, const QString& name) @@ -215,13 +247,17 @@ namespace scopeone::core return accepts(cameraId) && listProperties(cameraId).contains(name) ? (name == QStringLiteral("Exposure") ? QStringLiteral("Float") - : QStringLiteral("Integer")) + : name == QStringLiteral("ImageMode") + ? QStringLiteral("String") + : QStringLiteral("Integer")) : QStringLiteral("Unknown"); } bool SimulatorProvider::isPropertyReadOnly(const QString& cameraId, const QString& name) { - return !accepts(cameraId) || name != QStringLiteral("Exposure"); + return !accepts(cameraId) + || (name != QStringLiteral("Exposure") + && name != QStringLiteral("ImageMode")); } bool SimulatorProvider::isPropertyPreInit(const QString&, const QString&) @@ -229,8 +265,13 @@ namespace scopeone::core return false; } - QStringList SimulatorProvider::getAllowedPropertyValues(const QString&, const QString&) + QStringList SimulatorProvider::getAllowedPropertyValues(const QString& cameraId, + const QString& name) { + if (accepts(cameraId) && name == QStringLiteral("ImageMode")) + { + return {QStringLiteral("Gradient"), QStringLiteral("Hologram")}; + } return {}; } @@ -331,13 +372,37 @@ namespace scopeone::core frame.sourceRoiWidth = m_roi.width(); frame.sourceRoiHeight = m_roi.height(); frame.bytes.resize(static_cast(frame.width) * frame.height); + const bool hologram = m_imageMode == ImageMode::Hologram; + constexpr double twoPi = 6.28318530717958647692; for (int y = 0; y < frame.height; ++y) { uchar* row = reinterpret_cast(frame.bytes.data()) + static_cast(y) * frame.stride; for (int x = 0; x < frame.width; ++x) { - row[x] = static_cast((x + y + frame.frameIndex) & 0xffu); + if (!hologram) + { + row[x] = static_cast((x + y + frame.frameIndex) & 0xffu); + continue; + } + + const int sensorX = m_roi.x() + x; + const int sensorY = m_roi.y() + y; + const double nx = (sensorX - 0.5 * m_sensorWidth) / m_sensorWidth; + const double ny = (sensorY - 0.5 * m_sensorHeight) / m_sensorHeight; + const double objectAmplitude = + 0.65 * std::exp(-35.0 * (nx * nx + ny * ny)) + + 0.35 * std::exp(-90.0 * ((nx - 0.18) * (nx - 0.18) + + (ny + 0.12) * (ny + 0.12))); + const double objectPhase = 18.0 * (nx * nx + ny * ny) + + 0.015 * static_cast(frame.frameIndex); + const double carrier = twoPi * (48.0 * sensorX / m_sensorWidth + + 32.0 * sensorY / m_sensorHeight); + const double intensity = 70.0 + + 55.0 * objectAmplitude * objectAmplitude + + 120.0 * objectAmplitude + * std::cos(carrier + objectPhase); + row[x] = static_cast(std::clamp(intensity, 0.0, 255.0)); } } return frame; diff --git a/ScopeOneCore/src/ToolFrameStream.cpp b/ScopeOneCore/src/ToolFrameStream.cpp new file mode 100644 index 0000000..99c1829 --- /dev/null +++ b/ScopeOneCore/src/ToolFrameStream.cpp @@ -0,0 +1,65 @@ +#include "scopeone/ToolFrameStream.h" +#include "scopeone/ScopeOneCore.h" + +namespace scopeone::ui +{ + ScopeOneToolFrameStream::ScopeOneToolFrameStream(scopeone::core::ScopeOneCore& core, + QObject* parent) + : QObject(parent), m_core(core) + { + connect(&m_core, &scopeone::core::ScopeOneCore::previewRawFrameReady, + this, &ScopeOneToolFrameStream::acceptFrame); + } + + void ScopeOneToolFrameStream::setSourceId(const QString& cameraId) + { + m_sourceId = cameraId.trimmed(); + } + + void ScopeOneToolFrameStream::setEnabled(bool enabled) + { + m_enabled = enabled; + if (!m_enabled) + { + clearPendingFrame(); + } + } + + void ScopeOneToolFrameStream::setProcessing(bool processing) + { + if (m_processing == processing) + { + return; + } + m_processing = processing; + if (!m_processing && m_pendingFrame.isValid()) + { + const auto frame = m_pendingFrame; + m_pendingFrame = {}; + m_processing = true; + emit frameReady(frame); + } + } + + void ScopeOneToolFrameStream::clearPendingFrame() + { + m_pendingFrame = {}; + } + + void ScopeOneToolFrameStream::acceptFrame(const scopeone::core::ImageFrame& frame) + { + if (!m_enabled || !frame.isValid() + || (!m_sourceId.isEmpty() && frame.cameraId != m_sourceId)) + { + return; + } + if (m_processing) + { + m_pendingFrame = frame; + return; + } + m_processing = true; + emit frameReady(frame); + } + +} diff --git a/ScopeOneCore/src/ToolTask.cpp b/ScopeOneCore/src/ToolTask.cpp new file mode 100644 index 0000000..30085f7 --- /dev/null +++ b/ScopeOneCore/src/ToolTask.cpp @@ -0,0 +1,83 @@ +#include "scopeone/ToolTask.h" + +#include +#include +#include +#include + +namespace scopeone::ui +{ + struct ScopeOneToolTask::Impl + { + Work work; + std::atomic_bool cancelRequested{false}; + bool running{false}; + QString error; + QFutureWatcher watcher; + }; + + ScopeOneToolTask::ScopeOneToolTask(Work work, QObject* parent) + : QObject(parent), m_impl(std::make_unique()) + { + m_impl->work = std::move(work); + connect(&m_impl->watcher, &QFutureWatcher::finished, this, [this]() + { + m_impl->running = false; + if (!m_impl->error.isEmpty()) + { + emit failed(m_impl->error); + return; + } + if (m_impl->cancelRequested) + { + emit canceled(); + } + else + { + emit finished(); + } + }); + } + + ScopeOneToolTask::~ScopeOneToolTask() + { + cancel(); + m_impl->watcher.waitForFinished(); + } + + void ScopeOneToolTask::start() + { + if (!m_impl->work || m_impl->running) + { + return; + } + m_impl->running = true; + m_impl->cancelRequested = false; + m_impl->error.clear(); + const Work work = m_impl->work; + m_impl->watcher.setFuture(QtConcurrent::run([this, work]() + { + try + { + work(m_impl->cancelRequested, [this](int percent) + { + emit progressChanged(qBound(0, percent, 100)); + }); + } + catch (const std::exception& exception) + { + m_impl->error = QString::fromLocal8Bit(exception.what()); + } + catch (...) + { + m_impl->error = QStringLiteral("Tool task failed"); + } + })); + } + + void ScopeOneToolTask::cancel() + { + m_impl->cancelRequested = true; + } + +} diff --git a/plugins/examples/README.md b/plugins/examples/README.md index d540e15..ab25b3e 100644 --- a/plugins/examples/README.md +++ b/plugins/examples/README.md @@ -17,3 +17,15 @@ To build only the examples: Each example plugin declares `id`, `name`, `version`, `scopeOneApi`, and `kind` in `plugin.json`. Hardware providers use the public `DriverHostProviderPlugin` contract and run in `ScopeOne_DriverHost`. `ExampleHardwarePlugin` wraps the public `SimulatorProvider` to demonstrate a complete external hardware plugin. Install processing plugins under `plugins/processing`, tool plugins under `plugins/tools`, and hardware plugins under `plugins/hardware`. + +Tool plugins can create an independent processing pipeline without changing the main Process panel: + +```cpp +auto pipeline = context.core().createProcessingPipeline(); +pipeline->addModule(context.core().createProcessingModule("fft")); +pipeline->addModule(context.core().createProcessingModule("mask")); +pipeline->addModule(context.core().createProcessingModule("ifft")); +auto result = pipeline->process(scopeone::core::ProcessingValue{inputFrame}); +``` + +The pipeline accepts image and complex intermediate values. Publish image results with `publishExternalFrame` and show the returned layer through the tool context. diff --git a/plugins/examples/tool/CMakeLists.txt b/plugins/examples/tool/CMakeLists.txt index 5a8bd7b..320236c 100644 --- a/plugins/examples/tool/CMakeLists.txt +++ b/plugins/examples/tool/CMakeLists.txt @@ -4,3 +4,16 @@ add_library(ScopeOneExampleTool MODULE ) target_link_libraries(ScopeOneExampleTool PRIVATE scopeone::PluginSDK Qt6::Widgets) scopeone_example_plugin(ScopeOneExampleTool tools) + +add_library(ScopeOneDhmTool MODULE + DhmToolPlugin.cpp + DhmReconstruction.cpp + DhmReconstruction.h + dhm_plugin.json +) +target_compile_features(ScopeOneDhmTool PRIVATE cxx_std_20) +target_link_libraries(ScopeOneDhmTool PRIVATE + scopeone::PluginSDK + Qt6::Widgets +) +scopeone_example_plugin(ScopeOneDhmTool tools) diff --git a/plugins/examples/tool/DhmReconstruction.cpp b/plugins/examples/tool/DhmReconstruction.cpp new file mode 100644 index 0000000..20cab6e --- /dev/null +++ b/plugins/examples/tool/DhmReconstruction.cpp @@ -0,0 +1,251 @@ +#include "DhmReconstruction.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ + using Complex = std::complex; + constexpr float Pi = 3.14159265358979323846f; + + int nextPowerOfTwo(int value) + { + int size = 1; + while (size < value) + { + size <<= 1; + } + return size; + } + + int wrapIndex(int value, int size) + { + value %= size; + return value < 0 ? value + size : value; + } + + void fft1d(std::vector& values, bool inverse) + { + const int size = static_cast(values.size()); + for (int i = 1, j = 0; i < size; ++i) + { + int bit = size >> 1; + for (; j & bit; bit >>= 1) + { + j ^= bit; + } + j ^= bit; + if (i < j) + { + std::swap(values[i], values[j]); + } + } + + for (int length = 2; length <= size; length <<= 1) + { + const float angle = 2.0f * Pi / static_cast(length) + * (inverse ? 1.0f : -1.0f); + const Complex step(std::cos(angle), std::sin(angle)); + for (int start = 0; start < size; start += length) + { + Complex factor(1.0f, 0.0f); + const int half = length / 2; + for (int index = 0; index < half; ++index) + { + const Complex even = values[start + index]; + const Complex odd = factor * values[start + index + half]; + values[start + index] = even + odd; + values[start + index + half] = even - odd; + factor *= step; + } + } + } + + if (inverse) + { + const float scale = 1.0f / static_cast(size); + for (Complex& value : values) + { + value *= scale; + } + } + } + + void fft2d(std::vector& values, + int width, + int height, + bool inverse, + const std::atomic_bool& cancel, + const std::function& progress, + int progressStart, + int progressEnd) + { + std::vector line(static_cast(std::max(width, height))); + for (int y = 0; y < height; ++y) + { + if (cancel) + { + return; + } + line.assign(values.begin() + static_cast(y) * width, + values.begin() + static_cast(y + 1) * width); + fft1d(line, inverse); + std::copy(line.begin(), line.begin() + width, + values.begin() + static_cast(y) * width); + progress(progressStart + (progressEnd - progressStart) * y / (2 * height)); + } + + line.resize(height); + for (int x = 0; x < width; ++x) + { + if (cancel) + { + return; + } + for (int y = 0; y < height; ++y) + { + line[y] = values[static_cast(y) * width + x]; + } + fft1d(line, inverse); + for (int y = 0; y < height; ++y) + { + values[static_cast(y) * width + x] = line[y]; + } + progress(progressStart + (progressEnd - progressStart) + * (height + x) / (height + width)); + } + } + + std::vector toComplex(const scopeone::core::ImageFrame& frame, + int width, + int height) + { + std::vector values(static_cast(width) * height); + for (int y = 0; y < frame.height; ++y) + { + const auto* row = reinterpret_cast(frame.bytes.constData() + + y * frame.stride); + for (int x = 0; x < frame.width; ++x) + { + const float value = frame.isMono16() + ? static_cast(reinterpret_cast(row)[x]) + : static_cast(row[x]); + values[static_cast(y) * width + x] = Complex(value, 0.0f); + } + } + return values; + } + + scopeone::core::ImageFrame phaseFrame(const scopeone::core::ImageFrame& input, + const std::vector& field, + int width) + { + scopeone::core::ImageFrame output; + output.cameraId = QStringLiteral("dhm.phase"); + output.width = input.width; + output.height = input.height; + output.stride = input.width * static_cast(sizeof(unsigned short)); + output.bitsPerSample = 16; + output.pixelFormat = scopeone::core::ImagePixelFormat::Mono16; + output.frameIndex = input.frameIndex; + output.timestampNs = input.timestampNs; + output.bytes.resize(output.stride * output.height); + + for (int y = 0; y < input.height; ++y) + { + auto* row = reinterpret_cast(output.bytes.data() + + y * output.stride); + for (int x = 0; x < input.width; ++x) + { + float phase = std::arg(field[static_cast(y) * width + x]); + phase = (phase + Pi) / (2.0f * Pi); + row[x] = static_cast(std::clamp(phase, 0.0f, 1.0f) + * 65535.0f); + } + } + return output; + } +} + +namespace scopeone::dhm +{ + scopeone::core::ImageFrame reconstructPhase( + const scopeone::core::ImageFrame& input, + int sidebandX, + int sidebandY, + int radius, + const std::atomic_bool& cancel, + const std::function& progress) + { + if (!input.isValid() || (!input.isMono8() && !input.isMono16())) + { + return {}; + } + + const int width = nextPowerOfTwo(input.width); + const int height = nextPowerOfTwo(input.height); + const int safeRadius = std::clamp(radius, 1, std::min(width, height) / 2); + const int effectiveSidebandX = std::clamp(sidebandX, -width / 2, width / 2 - 1); + const int effectiveSidebandY = std::clamp(sidebandY, -height / 2, height / 2 - 1); + const int centerX = width / 2 + effectiveSidebandX; + const int centerY = height / 2 + effectiveSidebandY; + auto field = toComplex(input, width, height); + progress(5); + + fft2d(field, width, height, false, cancel, progress, 5, 45); + if (cancel) + { + return {}; + } + + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + const int shiftedX = (x + width / 2) % width; + const int shiftedY = (y + height / 2) % height; + const int dx = shiftedX - centerX; + const int dy = shiftedY - centerY; + if (dx * dx + dy * dy > safeRadius * safeRadius) + { + field[static_cast(y) * width + x] = Complex(0.0f, 0.0f); + } + } + } + + // Move the selected carrier sideband to the zero-frequency origin. + std::vector demodulated(field.size(), Complex(0.0f, 0.0f)); + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + const int shiftedX = (x + width / 2) % width; + const int shiftedY = (y + height / 2) % height; + const int dx = shiftedX - centerX; + const int dy = shiftedY - centerY; + if (dx * dx + dy * dy > safeRadius * safeRadius) + { + continue; + } + const int targetX = wrapIndex(x - effectiveSidebandX, width); + const int targetY = wrapIndex(y - effectiveSidebandY, height); + demodulated[static_cast(targetY) * width + targetX] + = field[static_cast(y) * width + x]; + } + } + field.swap(demodulated); + progress(55); + + fft2d(field, width, height, true, cancel, progress, 55, 95); + if (cancel) + { + return {}; + } + progress(100); + return phaseFrame(input, field, width); + } +} diff --git a/plugins/examples/tool/DhmReconstruction.h b/plugins/examples/tool/DhmReconstruction.h new file mode 100644 index 0000000..e3c0009 --- /dev/null +++ b/plugins/examples/tool/DhmReconstruction.h @@ -0,0 +1,17 @@ +#pragma once + +#include "scopeone/ImageFrame.h" + +#include +#include + +namespace scopeone::dhm +{ + scopeone::core::ImageFrame reconstructPhase( + const scopeone::core::ImageFrame& input, + int sidebandX, + int sidebandY, + int radius, + const std::atomic_bool& cancel, + const std::function& progress); +} diff --git a/plugins/examples/tool/DhmToolPlugin.cpp b/plugins/examples/tool/DhmToolPlugin.cpp new file mode 100644 index 0000000..2dd20b7 --- /dev/null +++ b/plugins/examples/tool/DhmToolPlugin.cpp @@ -0,0 +1,271 @@ +#include "DhmReconstruction.h" +#include "scopeone/ToolFrameStream.h" +#include "scopeone/ToolPlugin.h" +#include "scopeone/ToolTask.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + class DhmTool final : public QWidget + { + public: + DhmTool(scopeone::ui::ScopeOneToolContext& context, QWidget* parent) + : QWidget(parent), m_context(context) + { + setWindowFlag(Qt::Window, true); + setWindowTitle(QStringLiteral("DHM Reconstruction")); + resize(320, 190); + + auto* layout = new QVBoxLayout(this); + auto* form = new QFormLayout(); + m_offsetX = createSpinBox(-10000, 10000, 48); + m_offsetY = createSpinBox(-10000, 10000, 32); + m_radius = createSpinBox(1, 10000, 24); + form->addRow(QStringLiteral("Sideband X"), m_offsetX); + form->addRow(QStringLiteral("Sideband Y"), m_offsetY); + form->addRow(QStringLiteral("Radius"), m_radius); + layout->addLayout(form); + + m_liveCheckBox = new QCheckBox(QStringLiteral("Live reconstruction"), this); + m_liveCheckBox->setChecked(true); + layout->addWidget(m_liveCheckBox); + + auto* buttons = new QHBoxLayout(); + m_reconstructButton = new QPushButton(QStringLiteral("Reconstruct once"), this); + m_cancelButton = new QPushButton(QStringLiteral("Cancel"), this); + m_cancelButton->setEnabled(false); + buttons->addWidget(m_reconstructButton); + buttons->addWidget(m_cancelButton); + layout->addLayout(buttons); + + m_status = new QLabel(QStringLiteral("Ready"), this); + layout->addWidget(m_status); + + m_stream = new scopeone::ui::ScopeOneToolFrameStream(m_context.core(), this); + + connect(m_reconstructButton, &QPushButton::clicked, this, + [this]() { startReconstruction(); }); + connect(m_cancelButton, &QPushButton::clicked, this, + [this]() + { + m_liveCheckBox->setChecked(false); + if (m_task) + { + m_task->cancel(); + } + }); + connect(m_stream, &scopeone::ui::ScopeOneToolFrameStream::frameReady, + this, [this](const scopeone::core::ImageFrame& frame) + { + if (!m_liveCheckBox->isChecked() || !frame.isValid()) + { + return; + } + if (m_sourceId.isEmpty()) + { + m_sourceId = frame.cameraId; + m_stream->setSourceId(m_sourceId); + } + queueFrame(frame); + }); + connect(m_liveCheckBox, &QCheckBox::toggled, this, [this](bool enabled) + { + m_stream->setEnabled(enabled); + if (!enabled) + { + m_status->setText(m_task ? QStringLiteral("Finishing current frame") + : QStringLiteral("Ready")); + return; + } + const auto frame = m_context.currentFrame(); + if (frame.isValid() + && scopeone::core::ScopeOneCore::isRawLayerKey( + m_context.currentLayerKey())) + { + m_sourceId = frame.cameraId; + m_stream->setSourceId(m_sourceId); + queueFrame(frame); + } + }); + + const auto frame = m_context.currentFrame(); + if (frame.isValid() + && scopeone::core::ScopeOneCore::isRawLayerKey(m_context.currentLayerKey())) + { + m_sourceId = frame.cameraId; + m_stream->setSourceId(m_sourceId); + queueFrame(frame); + } + } + + private: + static QSpinBox* createSpinBox(int minimum, int maximum, int value) + { + auto* spinBox = new QSpinBox(); + spinBox->setRange(minimum, maximum); + spinBox->setValue(value); + return spinBox; + } + + void startReconstruction() + { + const auto input = m_context.currentFrame(); + if (!input.isValid()) + { + m_status->setText(QStringLiteral("No image loaded")); + return; + } + + m_sourceId = input.cameraId; + m_stream->setSourceId(m_sourceId); + queueFrame(input); + } + + void queueFrame(const scopeone::core::ImageFrame& input) + { + m_stream->setProcessing(true); + const int offsetX = m_offsetX->value(); + const int offsetY = m_offsetY->value(); + const int radius = std::min(m_radius->value(), + std::max(1, std::min(input.width, input.height) / 2)); + m_result.reset(); + + m_task = new scopeone::ui::ScopeOneToolTask( + [this, input, offsetX, offsetY, radius](const std::atomic_bool& cancel, + const std::function& progress) + { + auto result = scopeone::dhm::reconstructPhase(input, + offsetX, + offsetY, + radius, + cancel, + progress); + if (result.isValid()) + { + m_result = std::make_shared(std::move(result)); + } + }, + this); + + connect(m_task, &scopeone::ui::ScopeOneToolTask::progressChanged, + this, [this](int percent) + { + m_status->setText(QStringLiteral("Reconstructing %1%%").arg(percent)); + }); + connect(m_task, &scopeone::ui::ScopeOneToolTask::finished, this, + [this]() { publishResult(); }); + connect(m_task, &scopeone::ui::ScopeOneToolTask::canceled, this, + [this]() { finishTask(QStringLiteral("Canceled")); }); + connect(m_task, &scopeone::ui::ScopeOneToolTask::failed, this, + [this](const QString& message) { finishTask(message); }); + + m_reconstructButton->setEnabled(false); + m_cancelButton->setEnabled(true); + m_status->setText(QStringLiteral("Reconstructing 0%")); + m_task->start(); + } + + void publishResult() + { + if (!m_result) + { + finishTask(QStringLiteral("Reconstruction failed")); + return; + } + const auto stored = m_context.publishToolStreamFrame( + QStringLiteral("dhm.phase"), *m_result, QStringLiteral("DHM Phase")); + if (stored.isValid()) + { + if (!m_layersPresented) + { + QStringList layers; + const QString rawLayer = scopeone::core::ScopeOneCore::rawLayerKey(m_sourceId); + if (m_context.core().graphFrame(rawLayer).isValid()) + { + layers.append(rawLayer); + } + layers.append(scopeone::core::ScopeOneCore::toolLayerKey(stored.cameraId)); + m_context.showLayers(layers, layers.size() > 1); + m_layersPresented = true; + } + finishTask(QStringLiteral("DHM phase ready")); + } + else + { + finishTask(QStringLiteral("Failed to publish DHM phase")); + } + } + + void finishTask(const QString& status) + { + m_status->setText(status); + m_reconstructButton->setEnabled(true); + m_cancelButton->setEnabled(false); + if (m_task) + { + m_task->deleteLater(); + m_task = nullptr; + } + if (!m_liveCheckBox->isChecked()) + { + m_stream->clearPendingFrame(); + } + m_stream->setProcessing(false); + } + + scopeone::ui::ScopeOneToolContext& m_context; + QSpinBox* m_offsetX{nullptr}; + QSpinBox* m_offsetY{nullptr}; + QSpinBox* m_radius{nullptr}; + QCheckBox* m_liveCheckBox{nullptr}; + QPushButton* m_reconstructButton{nullptr}; + QPushButton* m_cancelButton{nullptr}; + QLabel* m_status{nullptr}; + scopeone::ui::ScopeOneToolTask* m_task{nullptr}; + scopeone::ui::ScopeOneToolFrameStream* m_stream{nullptr}; + std::shared_ptr m_result; + QString m_sourceId; + bool m_layersPresented{false}; + }; + + class DhmToolPlugin final : public QObject, + public scopeone::ui::ScopeOneToolPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneToolPlugin_iid FILE "dhm_plugin.json") + Q_INTERFACES(scopeone::ui::ScopeOneToolPlugin) + + public: + QList tools() const override + { + return {{QStringLiteral("scopeone.dhm_reconstruction"), + QStringLiteral("DHM Reconstruction"), + QStringLiteral("Reconstruction"), + scopeone::ui::ToolWindowMode::ModelessSingleton, + false}}; + } + + QWidget* createTool(const QString& toolId, + scopeone::ui::ScopeOneToolContext& context, + QWidget* parent) override + { + return toolId == QStringLiteral("scopeone.dhm_reconstruction") + ? new DhmTool(context, parent) + : nullptr; + } + }; +} + +#include "DhmToolPlugin.moc" diff --git a/plugins/examples/tool/dhm_plugin.json b/plugins/examples/tool/dhm_plugin.json new file mode 100644 index 0000000..966d58e --- /dev/null +++ b/plugins/examples/tool/dhm_plugin.json @@ -0,0 +1,7 @@ +{ + "id": "scopeone.dhm.reconstruction", + "name": "DHM Reconstruction", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "tool" +} diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 2a36d7b..5fb2e31 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -243,7 +243,6 @@ $config = "Release" $coreCachePath = Join-Path $coreBuildDir "CMakeCache.txt" $guiCachePath = Join-Path $guiBuildDir "CMakeCache.txt" $pluginCachePath = Join-Path $pluginBuildDir "CMakeCache.txt" -$qtPrefixPath = "C:/Qt/6.11.0/msvc2022_64" if ($clean) { if ($target -eq "scopewriter") { @@ -428,16 +427,21 @@ if ($target -in @("all", "plugins")) { } if ($needPluginConfigure) { + $pluginConfigureArgs = @( + "-S", $pluginSourceDir, + "-B", $pluginBuildDir, + "-DScopeOneCore_ROOT=$coreInstallDir", + "-DCMAKE_PREFIX_PATH=$coreInstallDir", + "-DCMAKE_INSTALL_PREFIX=$pluginInstallDir" + ) + $coreQt6Dir = Get-CMakeCacheValue -CachePath $coreCachePath -Key "Qt6_DIR" + if ($coreQt6Dir) { + $pluginConfigureArgs += "-DQt6_DIR=$coreQt6Dir" + } Invoke-Step ` -Label "Configuring ScopeOne plugin examples" ` -FilePath $cmake ` - -Arguments @( - "-S", $pluginSourceDir, - "-B", $pluginBuildDir, - "-DScopeOneCore_ROOT=$coreInstallDir", - "-DCMAKE_PREFIX_PATH=$qtPrefixPath;$coreInstallDir", - "-DCMAKE_INSTALL_PREFIX=$pluginInstallDir" - ) ` + -Arguments $pluginConfigureArgs ` -WorkingDirectory $repoRoot } diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 2ab2b63..d52818b 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -138,10 +138,6 @@ namespace scopeone::ui void DeviceControlWidget::setImageWorkspace(ImageWorkspace* workspace) { - if (!workspace) - { - qFatal("DeviceControlWidget requires ImageWorkspace"); - } m_workspace = workspace; connect(m_workspace, &ImageWorkspace::activeLayerChanged, this, [this](const QString&) @@ -176,10 +172,6 @@ namespace scopeone::ui // Connects the preview widget to control panel state void DeviceControlWidget::setPreviewWidget(PreviewWidget* preview) { - if (!preview) - { - qFatal("DeviceControlWidget requires PreviewWidget"); - } if (m_previewWidget) { disconnect(m_previewWidget, nullptr, this, nullptr); diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index 9fcb3d9..71f8104 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -389,10 +389,6 @@ namespace scopeone::ui QWidget* parent) : QWidget(parent), m_scopeonecore(core), m_workspace(workspace) { - if (!core || !workspace) - { - qFatal("ImageProcessingWidget requires ScopeOneCore and ImageWorkspace"); - } setAutoFillBackground(true); setBackgroundRole(QPalette::Window); m_processingRunning = core->isRealTimeProcessingEnabled(); diff --git a/src/ImageWorkspace.cpp b/src/ImageWorkspace.cpp index d5b7c6e..9487439 100644 --- a/src/ImageWorkspace.cpp +++ b/src/ImageWorkspace.cpp @@ -235,11 +235,6 @@ namespace scopeone::ui QObject* parent) : QObject(parent), m_core(core) { - if (!core) - { - qFatal("ImageWorkspace requires ScopeOneCore"); - } - m_viewerHost = new QWidget(windowParent); auto* hostLayout = new QVBoxLayout(m_viewerHost); hostLayout->setContentsMargins(0, 0, 0, 0); diff --git a/src/InspectWidget.cpp b/src/InspectWidget.cpp index cbbe43e..a98bf8e 100644 --- a/src/InspectWidget.cpp +++ b/src/InspectWidget.cpp @@ -36,6 +36,10 @@ namespace scopeone::ui { return QStringLiteral("static"); } + if (scopeone::core::ScopeOneCore::isToolLayerKey(layerKey)) + { + return QStringLiteral("tool"); + } return scopeone::core::ScopeOneCore::isProcessedLayerKey(layerKey) ? QStringLiteral("proc") : QStringLiteral("raw"); @@ -461,11 +465,6 @@ namespace scopeone::ui , m_scopeonecore(core) , m_workspace(workspace) { - if (!core || !workspace) - { - qFatal("InspectWidget requires ScopeOneCore and ImageWorkspace"); - } - setWindowTitle(QStringLiteral("Inspect")); setupUI(); updateControlsState(); @@ -1195,10 +1194,14 @@ namespace scopeone::ui const bool liveCrossSectionEnabled = m_cameraInitialized && isLiveLayerKey(layerKey) && m_availableCameraIds.contains(currentLayerCameraId()); + const bool toolCrossSectionEnabled = scopeone::core::ScopeOneCore::isToolLayerKey(layerKey) + && currentLayerHasStats; const bool staticCrossSectionEnabled = scopeone::core::ScopeOneCore::isStaticLayerKey(layerKey) && currentLayerHasStats; const bool crossSectionEnabled = !layerKey.isEmpty() - && (liveCrossSectionEnabled || staticCrossSectionEnabled); + && (liveCrossSectionEnabled + || toolCrossSectionEnabled + || staticCrossSectionEnabled); m_drawCrossSectionButton->setEnabled(crossSectionEnabled); m_clearCrossSectionButton->setEnabled(m_cameraInitialized || !layerKey.isEmpty()); const bool annotationEnabled = !layerKey.isEmpty() diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 5c03f4c..14f247b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -379,6 +380,14 @@ namespace scopeone::ui const QString layerKey = scopeone::core::ScopeOneCore::staticLayerKey(sourceId); m_previewWidget->removeStaticLayer(layerKey); }); + connect(m_scopeonecore, &scopeone::core::ScopeOneCore::toolStreamFramePublished, + this, [this](const QString& sourceId, + const QString&, + const scopeone::core::ImageFrame& frame) + { + m_previewWidget->setGraphToolLayerFrame(sourceId, frame); + schedulePreviewCursorStatusRefresh(); + }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::staticFramesCleared, this, [this]() { @@ -819,8 +828,31 @@ namespace scopeone::ui return m_imageWorkspace->activeLayerKey(); } + scopeone::core::ImageFrame MainWindow::currentFrame() const + { + return m_scopeonecore->graphFrame(currentLayerKey()); + } + + scopeone::core::ImageFrame MainWindow::publishToolStreamFrame( + const QString& sourceId, + const scopeone::core::ImageFrame& frame, + const QString& displayName) + { + return m_scopeonecore->publishToolStreamFrame(sourceId, frame, displayName); + } + void MainWindow::showLayers(const QStringList& layerKeys, bool sideBySide) { + auto* activeScene = m_imageWorkspace->activeSceneModel(); + const bool belongsToLiveScene = std::all_of( + layerKeys.cbegin(), layerKeys.cend(), [this](const QString& layerKey) + { + return m_imageSceneModel->layerIds().contains(layerKey); + }); + if (belongsToLiveScene && activeScene != m_imageSceneModel) + { + m_imageWorkspace->activateLiveViewer(); + } m_imageWorkspace->setVisibleLayers(layerKeys, sideBySide); } diff --git a/src/MainWindow.h b/src/MainWindow.h index 98711de..ea8e654 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -46,6 +46,11 @@ namespace scopeone::ui scopeone::core::ScopeOneCore& core() const override; QString currentLayerKey() const override; + scopeone::core::ImageFrame currentFrame() const override; + scopeone::core::ImageFrame publishToolStreamFrame( + const QString& sourceId, + const scopeone::core::ImageFrame& frame, + const QString& displayName = QString()) override; void showLayers(const QStringList& layerKeys, bool sideBySide = false) override; void showToolStatus(const QString& message, int timeoutMs = 5000) override; void presentSession( diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index a7334a9..e245e38 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -486,6 +486,39 @@ namespace scopeone::ui return layerKey; } + // Adds or updates one realtime tool layer + QString PreviewWidget::setGraphToolLayerFrame(const QString& layerId, + const ImageFrame& frame) + { + const QString normalizedId = layerId.trimmed(); + if (normalizedId.isEmpty() || !frame.isValid()) + { + return {}; + } + const QString layerKey = ScopeOneCore::toolLayerKey(normalizedId); + const QString sourceId = ScopeOneCore::sourceIdFromLayerKey(layerKey); + if (normalizedSourceId(frame.cameraId) != sourceId) + { + return {}; + } + const bool newLayer = !m_toolSourceIds.contains(sourceId); + m_toolSourceIds.insert(sourceId); + if (!storeSourceFrame(sourceId, FrameRole::Raw, frame)) + { + return {}; + } + initializeLayerInfo(layerKey); + updateLayerFps(layerKey, 1); + updateLayerInfoDisplay(); + updateImageDisplay(); + if (newLayer) + { + emit availableLayerKeysChanged(availableLayerKeys()); + emit visibleLayerKeysChanged(visibleLayerKeys()); + } + return layerKey; + } + // Removes one static image layer from the preview bool PreviewWidget::removeStaticLayer(const QString& layerKey) { @@ -564,6 +597,17 @@ namespace scopeone::ui availableKeys.append(layerKey); availableSet.insert(layerKey); } + for (const QString& sourceId : m_toolSourceIds) + { + const QString layerKey = ScopeOneCore::toolLayerKey(sourceId); + scopeone::core::DocumentLayer layer; + if (!m_sceneModel->findLayer(layerKey, layer)) + { + continue; + } + availableKeys.append(layerKey); + availableSet.insert(layerKey); + } QStringList layerKeys; layerKeys.reserve(availableKeys.size()); @@ -818,6 +862,10 @@ namespace scopeone::ui { keys.insert(ScopeOneCore::staticLayerKey(sourceId)); } + for (const QString& sourceId : m_toolSourceIds) + { + keys.insert(ScopeOneCore::toolLayerKey(sourceId)); + } return keys; } @@ -1997,7 +2045,6 @@ namespace scopeone::ui showPlaceholder(m_placeholderText); return; } - if (canGpu) { if (renderItems.empty()) diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index 3038841..cd915fe 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -62,6 +62,8 @@ namespace scopeone::ui void setAvailableCameraIds(const QStringList& cameraIds); QString setGraphStaticLayerFrame(const QString& layerId, const scopeone::core::ImageFrame& frame); + QString setGraphToolLayerFrame(const QString& layerId, + const scopeone::core::ImageFrame& frame); bool removeStaticLayer(const QString& layerKey); void clearStaticLayers(); QStringList availableCameraIds() const; @@ -195,6 +197,7 @@ namespace scopeone::ui private: QStringList m_availableCameraIds; QSet m_staticSourceIds; + QSet m_toolSourceIds; LayerLayoutMode m_layerLayoutMode{LayerLayoutMode::SideBySide}; QMap m_layerFps; QString m_layerInfoText{QStringLiteral("No image loaded")}; diff --git a/src/ScopeOneLocalApiServer.cpp b/src/ScopeOneLocalApiServer.cpp index 75df87a..0f0ac78 100644 --- a/src/ScopeOneLocalApiServer.cpp +++ b/src/ScopeOneLocalApiServer.cpp @@ -1124,13 +1124,9 @@ namespace scopeone::ui , m_scopeonecore(core) , m_previewWidget(previewWidget) , m_imageWorkspace(imageWorkspace) - , m_sceneModel(core ? core->imageSceneModel() : nullptr) + , m_sceneModel(core->imageSceneModel()) , m_server(new QLocalServer(this)) { - if (!core || !previewWidget || !imageWorkspace) - { - qFatal("ScopeOneLocalApiServer requires Core, PreviewWidget and ImageWorkspace"); - } m_taskPool.setMaxThreadCount(1); connect(m_imageWorkspace, &ImageWorkspace::activeViewerChanged, this, [this]() diff --git a/src/ScopeOneToolPlugin.cpp b/src/ScopeOneToolPlugin.cpp index 41660e5..ffa7054 100644 --- a/src/ScopeOneToolPlugin.cpp +++ b/src/ScopeOneToolPlugin.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace scopeone::ui { @@ -108,34 +109,27 @@ namespace scopeone::ui continue; } + const size_t entryCount = m_entries.size(); for (const ToolDescriptor& descriptor : descriptors) { - for (const auto& entry : m_entries) + const QString id = descriptor.id.trimmed(); + if (!registerTool(descriptor, [plugin, id](ScopeOneToolContext& context, + QWidget* parent) + { + return plugin->createTool(id, context, parent); + })) { - if (entry->descriptor.id == descriptor.id.trimmed()) - { - valid = false; - break; - } + m_entries.resize(entryCount); + errors.append(QStringLiteral("%1: tool id conflicts with an existing tool") + .arg(file.fileName())); + loader->unload(); + break; } } - if (!valid) + if (m_entries.size() == entryCount) { - errors.append(QStringLiteral("%1: tool id conflicts with an existing tool") - .arg(file.fileName())); - loader->unload(); continue; } - - for (const ToolDescriptor& descriptor : descriptors) - { - const QString id = descriptor.id.trimmed(); - registerTool(descriptor, [plugin, id](ScopeOneToolContext& context, - QWidget* parent) - { - return plugin->createTool(id, context, parent); - }); - } m_pluginLoaders.push_back(std::move(loader)); } return errors; @@ -221,9 +215,21 @@ namespace scopeone::ui delete tool; return; } + tool->setWindowFlag(Qt::Window, true); tool->setAttribute(Qt::WA_DeleteOnClose); entry->instance = tool; tool->show(); + QTimer::singleShot(0, tool, [tool, parent]() + { + QWidget* host = parent ? parent->window() : nullptr; + if (!host) + { + return; + } + const QPoint position = host->frameGeometry().center() + - tool->frameGeometry().center(); + tool->move(position); + }); } } } From 710f6d2a5f5349789a1e245d1398f4acc2fbf05a Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:09:25 +0200 Subject: [PATCH 09/37] Fix --- ScopeOneCore/CMakeLists.txt | 1 + ScopeOneCore/include/scopeone/PluginSDK.h | 12 + plugins/examples/README.md | 4 +- src/DeviceControlWidget.cpp | 208 ++++++++++- src/DeviceControlWidget.h | 9 + src/InspectWidget.cpp | 427 +--------------------- src/InspectWidget.h | 13 - 7 files changed, 224 insertions(+), 450 deletions(-) create mode 100644 ScopeOneCore/include/scopeone/PluginSDK.h diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 34b9dee..1239b57 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -121,6 +121,7 @@ set(CORE_HEADERS include/scopeone/HardwareTypes.h include/scopeone/HardwareProvider.h include/scopeone/PluginManifest.h + include/scopeone/PluginSDK.h include/scopeone/SimulatorProvider.h include/scopeone/ImageSceneModel.h internal/HardwareRuntime.h diff --git a/ScopeOneCore/include/scopeone/PluginSDK.h b/ScopeOneCore/include/scopeone/PluginSDK.h new file mode 100644 index 0000000..a8499cd --- /dev/null +++ b/ScopeOneCore/include/scopeone/PluginSDK.h @@ -0,0 +1,12 @@ +#pragma once + +#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/ProcessingPipeline.h" +#include "scopeone/ProcessingPlugin.h" +#include "scopeone/ToolFrameStream.h" +#include "scopeone/ToolPlugin.h" diff --git a/plugins/examples/README.md b/plugins/examples/README.md index ab25b3e..a4cd7b0 100644 --- a/plugins/examples/README.md +++ b/plugins/examples/README.md @@ -1,6 +1,6 @@ # ScopeOne plugin examples -These projects use the installed `scopeone::PluginSDK` target and do not depend on ScopeOne source files. +These projects use the installed `scopeone::PluginSDK` target and do not depend on ScopeOne source files. Include `scopeone/PluginSDK.h` as the public SDK entry point. The normal ScopeOne build script builds and installs these examples automatically during a full build: @@ -14,7 +14,7 @@ To build only the examples: .\scripts\build.ps1 --target plugins ``` -Each example plugin declares `id`, `name`, `version`, `scopeOneApi`, and `kind` in `plugin.json`. Hardware providers use the public `DriverHostProviderPlugin` contract and run in `ScopeOne_DriverHost`. `ExampleHardwarePlugin` wraps the public `SimulatorProvider` to demonstrate a complete external hardware plugin. +Each example plugin declares `id`, `name`, `version`, `scopeOneApi`, and `kind` in `plugin.json`. Processing plugins implement `ProcessingPlugin` and add modules to the shared pipeline. Hardware providers implement `DriverHostProviderPlugin` and run in `ScopeOne_DriverHost`. Tool plugins implement `ScopeOneToolPlugin` and own an independent workflow window. `ExampleHardwarePlugin` wraps the public `SimulatorProvider` to demonstrate a complete external hardware plugin. Install processing plugins under `plugins/processing`, tool plugins under `plugins/tools`, and hardware plugins under `plugins/hardware`. diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index d52818b..cf0b22b 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -28,6 +32,7 @@ #include #include +#include namespace scopeone::ui { @@ -49,6 +54,125 @@ namespace scopeone::ui } } // namespace + class LayerHistogramWidget : public QWidget + { + public: + explicit LayerHistogramWidget(QWidget* parent = nullptr) + : QWidget(parent) + { + setMinimumHeight(150); + } + + void setStats(const scopeone::core::ScopeOneCore::HistogramStats& stats) + { + m_stats = stats; + update(); + } + + void clear() + { + m_stats = {}; + update(); + } + + void setLogScale(bool enabled) + { + m_logScale = enabled; + update(); + } + + protected: + void paintEvent(QPaintEvent*) override + { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + const QPalette& colors = palette(); + const QFontMetrics metrics = painter.fontMetrics(); + const int labelHeight = metrics.height() + 4; + const int xLabelWidth = qMax(50, metrics.horizontalAdvance(QStringLiteral("65535")) + 12); + const int yLabelWidth = qMax(40, metrics.horizontalAdvance(QStringLiteral("999.9M")) + 8); + const QRect plot = rect().adjusted( + yLabelWidth + 6, + labelHeight + 2, + -(xLabelWidth / 2 + 4), + -(2 * labelHeight + 8)); + + painter.fillRect(plot, colors.brush(QPalette::Base)); + painter.setPen(QPen(colors.color(QPalette::Mid), 1)); + painter.drawRect(plot); + + if (!m_stats.hasData() || m_stats.histogram.empty()) + { + painter.setPen(colors.color(QPalette::PlaceholderText)); + painter.drawText(plot, Qt::AlignCenter, QStringLiteral("No Histogram Data")); + return; + } + + int maxCount = 0; + for (const int count : m_stats.histogram) + { + maxCount = qMax(maxCount, count); + } + if (maxCount == 0) + { + painter.setPen(colors.color(QPalette::PlaceholderText)); + painter.drawText(plot, Qt::AlignCenter, QStringLiteral("No Histogram Data")); + return; + } + + painter.setPen(QPen(colors.color(QPalette::Highlight), 1)); + const int histogramSize = static_cast(m_stats.histogram.size()); + for (int i = 0; i < histogramSize; ++i) + { + const int count = m_stats.histogram[static_cast(i)]; + const double normalized = m_logScale && count > 0 + ? log10(count + 1.0) / log10(maxCount + 1.0) + : static_cast(count) / maxCount; + const int x = plot.left() + (i * plot.width()) / histogramSize; + const int height = static_cast(normalized * plot.height()); + painter.drawLine(x, plot.bottom(), x, plot.bottom() - height); + } + + painter.setPen(QPen(colors.color(QPalette::Mid), 1)); + painter.drawLine(plot.left(), plot.top(), plot.left(), plot.bottom()); + painter.drawLine(plot.left(), plot.bottom(), plot.right(), plot.bottom()); + + const int maxValue = qMax(1, m_stats.maxValue); + for (int i = 0; i <= 4; ++i) + { + const int x = plot.left() + (i * plot.width()) / 4; + const int value = (i * maxValue) / 4; + painter.drawLine(x, plot.bottom(), x, plot.bottom() + 5); + painter.setPen(colors.color(QPalette::Text)); + painter.drawText(QRect(x - xLabelWidth / 2, + plot.bottom() + 5, + xLabelWidth, + labelHeight), + Qt::AlignCenter, + QString::number(value)); + painter.setPen(QPen(colors.color(QPalette::Mid), 1)); + } + + painter.setPen(colors.color(QPalette::Text)); + painter.drawText(QRect(plot.left(), + plot.bottom() + labelHeight + 5, + plot.width(), + labelHeight), + Qt::AlignCenter, + QStringLiteral("Intensity")); + painter.drawText(QRect(0, + plot.top() - labelHeight, + plot.left() - 8, + labelHeight), + Qt::AlignRight | Qt::AlignVCenter, + QStringLiteral("Count")); + } + + private: + scopeone::core::ScopeOneCore::HistogramStats m_stats; + bool m_logScale{false}; + }; + // Creates the device control widget and initializes controls DeviceControlWidget::DeviceControlWidget(scopeone::core::ScopeOneCore* core, QWidget* parent) : QWidget(parent) @@ -74,6 +198,16 @@ namespace scopeone::ui } }, Qt::QueuedConnection); + connect(m_scopeonecore, &scopeone::core::ScopeOneCore::layerHistogramReady, + this, &DeviceControlWidget::onLayerHistogramReady); + connect(m_scopeonecore, &scopeone::core::ScopeOneCore::layerAnalysisCleared, + this, [this](const QString& layerKey) + { + if (layerKey == currentLayerKey()) + { + m_layerHistogramWidget->clear(); + } + }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::stagePositionChanged, this, [this]() { @@ -144,13 +278,21 @@ namespace scopeone::ui { syncLayerSelection(); refreshPreviewLayerSettings(); + refreshLayerHistogram(); updateControlsState(); if (m_liveViewerContext) { syncControlTargetToSelectedRawLayer(); } }); + connect(m_workspace, &ImageWorkspace::activeFrameChanged, + this, &DeviceControlWidget::refreshLayerHistogram); + connect(m_workspace, &ImageWorkspace::activeViewerChanged, + this, &DeviceControlWidget::refreshLayerHistogram); + connect(m_workspace, &ImageWorkspace::histogramReady, + this, &DeviceControlWidget::onLayerHistogramReady); syncLayerSelection(); + refreshLayerHistogram(); } QWidget* DeviceControlWidget::imageControlsWidget() const @@ -264,6 +406,16 @@ namespace scopeone::ui m_layerTable->setMinimumHeight(94); m_layerTable->setMaximumHeight(150); + m_layerHistogramGroup = new QGroupBox(QStringLiteral("Histogram"), m_previewControlsGroup); + auto* histogramLayout = new QVBoxLayout(m_layerHistogramGroup); + histogramLayout->setContentsMargins(6, 6, 6, 6); + m_layerHistogramWidget = new LayerHistogramWidget(m_layerHistogramGroup); + histogramLayout->addWidget(m_layerHistogramWidget); + m_layerHistogramLogCheckBox = new QCheckBox(QStringLiteral("Log scale"), m_layerHistogramGroup); + histogramLayout->addWidget(m_layerHistogramLogCheckBox); + connect(m_layerHistogramLogCheckBox, &QCheckBox::toggled, + m_layerHistogramWidget, &LayerHistogramWidget::setLogScale); + m_layerSettingsGroup = new QGroupBox("Layer Settings", this); QGridLayout* layerSettingsLayout = new QGridLayout(m_layerSettingsGroup); layerSettingsLayout->setContentsMargins(6, 6, 6, 6); @@ -364,8 +516,9 @@ namespace scopeone::ui m_alignYLabel->setMinimumWidth(20); m_alignZoomLabel->setMinimumWidth(60); controlLayout->addWidget(m_layerTable, 0, 0, 1, 6); - controlLayout->addWidget(m_layerSettingsGroup, 1, 0, 1, 6); - controlLayout->addWidget(transformGroup, 2, 0, 1, 6); + controlLayout->addWidget(m_layerHistogramGroup, 1, 0, 1, 6); + controlLayout->addWidget(m_layerSettingsGroup, 2, 0, 1, 6); + controlLayout->addWidget(transformGroup, 3, 0, 1, 6); controlLayout->setColumnStretch(5, 1); @@ -650,6 +803,7 @@ namespace scopeone::ui { rebuildPreviewLayerTable(layerKeys); applyPreviewVisibility(m_previewWidget->visibleLayerKeys(), false); + refreshLayerHistogram(); updateControlsState(); } @@ -721,6 +875,7 @@ namespace scopeone::ui m_workspace->setActiveLayerKey(layerKey); } refreshPreviewLayerSettings(); + refreshLayerHistogram(); updateControlsState(); } @@ -740,6 +895,44 @@ namespace scopeone::ui scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey())); } + void DeviceControlWidget::onLayerHistogramReady( + const QString& layerKey, + const scopeone::core::ScopeOneCore::HistogramStats& stats) + { + if (layerKey == currentLayerKey()) + { + m_layerHistogramWidget->setStats(stats); + } + } + + void DeviceControlWidget::refreshLayerHistogram() + { + const QString layerKey = currentLayerKey(); + if (layerKey.isEmpty()) + { + m_layerHistogramWidget->clear(); + return; + } + + if (m_workspace->isLiveViewerActive()) + { + scopeone::core::ScopeOneCore::HistogramStats stats; + if (m_scopeonecore->getLayerHistogram(layerKey, stats)) + { + m_layerHistogramWidget->setStats(stats); + } + else + { + m_layerHistogramWidget->clear(); + } + m_scopeonecore->setActiveHistogramLayer(layerKey); + return; + } + + m_layerHistogramWidget->clear(); + m_workspace->requestHistogram(layerKey); + } + // Use the selected raw camera layer as the hardware control target void DeviceControlWidget::syncControlTargetToSelectedRawLayer() { @@ -1321,11 +1514,7 @@ namespace scopeone::ui m_minExposureMs = lower; m_maxExposureMs = upper; } - m_exposureLabel->setText( - tr("Exposure (ms)\n%1: %2 to %3") - .arg(m_currentTarget, - formatExposureMs(m_minExposureMs), - formatExposureMs(m_maxExposureMs))); + m_exposureLabel->setText(tr("Exposure (ms):")); return; } @@ -1362,10 +1551,7 @@ namespace scopeone::ui m_minExposureMs = commonLower; m_maxExposureMs = commonUpper; } - m_exposureLabel->setText( - tr("Exposure (ms)\nAll cameras: %1 to %2") - .arg(formatExposureMs(m_minExposureMs), - formatExposureMs(m_maxExposureMs))); + m_exposureLabel->setText(tr("Exposure (ms):")); } // Updates preview running state for the control button diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index a994ffb..adad541 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -1,5 +1,7 @@ #pragma once +#include "scopeone/ScopeOneCore.h" + #include #include #include @@ -26,6 +28,7 @@ class QTableWidget; namespace scopeone::ui { class ImageWorkspace; + class LayerHistogramWidget; class PreviewWidget; class DeviceControlWidget : public QWidget @@ -104,6 +107,9 @@ namespace scopeone::ui void onPreviewLayerMoveUpClicked(); void onPreviewLayerMoveDownClicked(); void onPreviewLayerRemoveClicked(); + void onLayerHistogramReady(const QString& layerKey, + const scopeone::core::ScopeOneCore::HistogramStats& stats); + void refreshLayerHistogram(); void syncControlTargetToSelectedRawLayer(); void resetSelectedLayerTransform(); void syncLayerSelection(); @@ -140,6 +146,9 @@ namespace scopeone::ui QPushButton* m_layerAutoButton{nullptr}; QPushButton* m_layerFullRangeButton{nullptr}; QCheckBox* m_layerAutoStretchCheckBox{nullptr}; + LayerHistogramWidget* m_layerHistogramWidget{nullptr}; + QCheckBox* m_layerHistogramLogCheckBox{nullptr}; + QGroupBox* m_layerHistogramGroup{nullptr}; QLabel* m_alignXLabel{nullptr}; QSpinBox* m_alignXSpinBox{nullptr}; QLabel* m_alignYLabel{nullptr}; diff --git a/src/InspectWidget.cpp b/src/InspectWidget.cpp index a98bf8e..ca2ec8e 100644 --- a/src/InspectWidget.cpp +++ b/src/InspectWidget.cpp @@ -2,12 +2,7 @@ #include "ImageWorkspace.h" #include "scopeone/ImageSceneModel.h" -#include -#include #include -#include -#include -#include #include #include #include @@ -20,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -185,278 +179,6 @@ namespace scopeone::ui QVector m_values; }; - struct LayerHistogramData - { - QString layerKey; - scopeone::core::ScopeOneCore::HistogramStats stats; - QColor color{Qt::blue}; - }; - - class InspectHistogramWidget : public QWidget - { - public: - explicit InspectHistogramWidget(QWidget* parent = nullptr) - : QWidget(parent) - { - setMinimumHeight(150); - } - - // Store histogram data for one layer and repaint - void updateLayerHistogram(const QString& layerKey, - const scopeone::core::ScopeOneCore::HistogramStats& stats, - const QColor& color) - { - LayerHistogramData data; - data.layerKey = layerKey; - data.stats = stats; - data.color = color; - m_layerData[layerKey] = data; - update(); - } - - // Toggle logarithmic histogram display - void setLogScale(bool logScale) - { - m_logScale = logScale; - update(); - } - - protected: - // Paint all tracked camera histograms in one chart - void paintEvent(QPaintEvent*) override - { - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - const QPalette& colors = palette(); - const QFontMetrics metrics = painter.fontMetrics(); - const int labelHeight = metrics.height() + 4; - const int xLabelWidth = qMax(50, metrics.horizontalAdvance(QStringLiteral("65535")) + 12); - const int yLabelWidth = qMax(40, metrics.horizontalAdvance(QStringLiteral("999.9M")) + 8); - - const QRect rect = this->rect().adjusted( - yLabelWidth + 6, - labelHeight + 2, - -(xLabelWidth / 2 + 4), - -(2 * labelHeight + 8)); - - painter.fillRect(rect, colors.brush(QPalette::Base)); - painter.setPen(QPen(colors.color(QPalette::Mid), 1)); - painter.drawRect(rect); - - if (m_layerData.isEmpty()) - { - painter.setPen(colors.color(QPalette::PlaceholderText)); - painter.drawText(rect, Qt::AlignCenter, QStringLiteral("No Layer Data")); - return; - } - - int globalMaxValue = 255; - int globalMaxCount = 0; - for (const LayerHistogramData& layerData : m_layerData) - { - if (!layerData.stats.hasData() || layerData.stats.histogram.empty()) - { - continue; - } - globalMaxValue = qMax(globalMaxValue, layerData.stats.maxValue); - for (int count : layerData.stats.histogram) - { - globalMaxCount = qMax(globalMaxCount, count); - } - } - - if (globalMaxCount == 0) - { - painter.setPen(colors.color(QPalette::PlaceholderText)); - painter.drawText(rect, Qt::AlignCenter, QStringLiteral("No Histogram Data")); - return; - } - - for (const LayerHistogramData& layerData : m_layerData) - { - if (!layerData.stats.hasData() || layerData.stats.histogram.empty()) - { - continue; - } - - const int histSize = static_cast(layerData.stats.histogram.size()); - QColor histColor = layerData.color; - histColor.setAlpha(180); - painter.setPen(QPen(histColor, 1)); - - for (int i = 0; i < histSize; ++i) - { - const int x = rect.left() + (i * rect.width()) / histSize; - const int count = layerData.stats.histogram[static_cast(i)]; - - double normalizedCount = 0.0; - if (m_logScale && count > 0) - { - normalizedCount = log10(count + 1.0) / log10(globalMaxCount + 1.0); - } - else - { - normalizedCount = static_cast(count) / globalMaxCount; - } - - const int height = static_cast(normalizedCount * rect.height()); - if (height > 0) - { - painter.drawLine(x, rect.bottom(), x, rect.bottom() - height); - } - } - } - - drawAxes(painter, rect, globalMaxValue, xLabelWidth, labelHeight); - } - - private: - // Draw intensity and count axes for the histogram plot - void drawAxes(QPainter& painter, - const QRect& rect, - int maxValue, - int xLabelWidth, - int labelHeight) - { - const QColor axisColor = palette().color(QPalette::Mid); - const QColor textColor = palette().color(QPalette::Text); - painter.setPen(QPen(axisColor, 1)); - - QList xTicks; - xTicks << 0 << maxValue / 4 << maxValue / 2 << (maxValue * 3) / 4 << maxValue; - - for (int i = 0; i < xTicks.size(); ++i) - { - const int x = rect.left() + (i * rect.width()) / (xTicks.size() - 1); - painter.drawLine(x, rect.bottom(), x, rect.bottom() + 5); - - const QString label = QString::number(xTicks[i]); - const QRect textRect( - x - xLabelWidth / 2, - rect.bottom() + 5, - xLabelWidth, - labelHeight); - painter.setPen(textColor); - painter.drawText(textRect, Qt::AlignCenter, label); - painter.setPen(QPen(axisColor, 1)); - } - - painter.drawLine(rect.left(), rect.top(), rect.left(), rect.bottom()); - - int maxCount = 0; - for (const LayerHistogramData& layerData : m_layerData) - { - if (!layerData.stats.hasData()) - { - continue; - } - for (int count : layerData.stats.histogram) - { - maxCount = qMax(maxCount, count); - } - } - - if (maxCount <= 0) - { - return; - } - - QList yTicks; - if (m_logScale) - { - yTicks = {1, 10, 100, 1000, 10000}; - } - else - { - int step = maxCount / 4; - if (step == 0) - { - step = 1; - } - - int magnitude = 1; - while (step > magnitude * 10) - { - magnitude *= 10; - } - step = ((step / magnitude) + 1) * magnitude; - - for (int i = 0; i <= 4; ++i) - { - const int value = i * step; - if (value <= maxCount) - { - yTicks.append(value); - } - } - } - - for (int count : yTicks) - { - if (count > maxCount) - { - continue; - } - - double normalizedCount = 0.0; - if (m_logScale && count > 0) - { - normalizedCount = log10(count + 1.0) / log10(maxCount + 1.0); - } - else - { - normalizedCount = static_cast(count) / maxCount; - } - - const int y = rect.bottom() - static_cast(normalizedCount * rect.height()); - painter.drawLine(rect.left() - 5, y, rect.left(), y); - - QString label; - if (count >= 1000000000) - { - label = QStringLiteral("%1G").arg(count / 1000000000.0, 0, 'f', 1); - } - else if (count >= 1000000) - { - label = QStringLiteral("%1M").arg(count / 1000000.0, 0, 'f', 1); - } - else if (count >= 1000) - { - label = QStringLiteral("%1k").arg(count / 1000.0, 0, 'f', 1); - } - else - { - label = QString::number(count); - } - const QRect textRect( - 0, - y - labelHeight / 2, - rect.left() - 8, - labelHeight); - painter.setPen(textColor); - painter.drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, label); - painter.setPen(QPen(axisColor, 1)); - } - - painter.setPen(textColor); - painter.drawText(QRect(rect.left(), - rect.bottom() + labelHeight + 5, - rect.width(), - labelHeight), - Qt::AlignCenter, - QStringLiteral("Intensity")); - painter.drawText(QRect(0, - rect.top() - labelHeight, - rect.left() - 8, - labelHeight), - Qt::AlignRight | Qt::AlignVCenter, - QStringLiteral("Count")); - } - - QHash m_layerData; - bool m_logScale{false}; - }; - // Create the inspection panel and subscribe to core analysis signals InspectWidget::InspectWidget(scopeone::core::ScopeOneCore* core, ImageWorkspace* workspace, @@ -487,7 +209,6 @@ namespace scopeone::ui return; } m_workspace->requestHistogram(currentLayerKey()); - requestVisibleHistograms(); }); connect(m_workspace, &ImageWorkspace::activeLayerChanged, this, [this](const QString&) @@ -501,7 +222,6 @@ namespace scopeone::ui { m_workspace->requestHistogram(layerKey); } - requestVisibleHistograms(); updateLayerVisibility(); updateControlsState(); }); @@ -591,7 +311,6 @@ namespace scopeone::ui updateLayerInspect(it.key(), it->stats); } } - requestVisibleHistograms(); updateControlsState(); } @@ -894,20 +613,6 @@ namespace scopeone::ui crossSectionLayout->addWidget(m_crossSectionWidget); contentLayout->addWidget(m_crossSectionGroup); - m_histogramContainerLayout = new QVBoxLayout(); - m_histogramContainerLayout->setSpacing(10); - m_compareLayersCheckBox = new QCheckBox(QStringLiteral("Show all visible layers"), contentContainer); - connect(m_compareLayersCheckBox, &QCheckBox::toggled, - this, [this](bool enabled) - { - updateLayerVisibility(); - if (enabled) - { - requestVisibleHistograms(); - } - }); - contentLayout->addWidget(m_compareLayersCheckBox); - contentLayout->addLayout(m_histogramContainerLayout); contentLayout->addStretch(); connect(m_drawCrossSectionButton, &QPushButton::clicked, this, [this]() @@ -938,7 +643,7 @@ namespace scopeone::ui mainLayout->addWidget(scrollArea); } - // Create histogram controls for one layer + // Create statistics controls for one layer QWidget* InspectWidget::createLayerInfoGroup(const QString& layerKey) { const QString normalizedLayerKey = layerKey.trimmed(); @@ -948,16 +653,6 @@ namespace scopeone::ui infoGroup.layerKey = normalizedLayerKey; infoGroup.groupBox = group; - auto* histLabel = new QLabel(QStringLiteral("Histogram"), group); - QFont boldFont = histLabel->font(); - boldFont.setBold(true); - histLabel->setFont(boldFont); - layout->addWidget(histLabel); - - auto* histogramWidget = new InspectHistogramWidget(group); - layout->addWidget(histogramWidget); - infoGroup.histogramWidget = histogramWidget; - auto* slidersLayout = new QHBoxLayout(); auto* minLabel = new QLabel(QStringLiteral("Min:"), group); @@ -986,13 +681,6 @@ namespace scopeone::ui slidersLayout->addWidget(maxSliderValueLabel); layout->addLayout(slidersLayout); - auto* histControlLayout = new QHBoxLayout(); - auto* logScaleCheckBox = new QCheckBox(QStringLiteral("Log hist"), group); - histControlLayout->addWidget(logScaleCheckBox); - histControlLayout->addStretch(); - layout->addLayout(histControlLayout); - - infoGroup.logScaleCheckBox = logScaleCheckBox; infoGroup.minSlider = minSlider; infoGroup.maxSlider = maxSlider; infoGroup.minSliderValueLabel = minSliderValueLabel; @@ -1000,10 +688,6 @@ namespace scopeone::ui layout->addWidget(createStatisticsGroup(infoGroup)); m_layerInfoGroups.insert(normalizedLayerKey, infoGroup); - connect(logScaleCheckBox, &QCheckBox::toggled, this, [this, normalizedLayerKey](bool checked) - { - onLogScaleChanged(normalizedLayerKey, checked); - }); connect(minSlider, &QSlider::valueChanged, this, [this, normalizedLayerKey, minSlider, maxSlider, minSliderValueLabel](int value) { @@ -1076,8 +760,7 @@ namespace scopeone::ui return; } - QWidget* histogramGroup = createLayerInfoGroup(normalizedLayerKey); - m_histogramContainerLayout->addWidget(histogramGroup); + createLayerInfoGroup(normalizedLayerKey); updateLayerVisibility(); updateControlsState(); } @@ -1092,7 +775,6 @@ namespace scopeone::ui } LayerInfoGroup& infoGroup = it.value(); - m_histogramContainerLayout->removeWidget(infoGroup.groupBox); infoGroup.groupBox->deleteLater(); m_layerInfoGroups.erase(it); } @@ -1124,9 +806,6 @@ namespace scopeone::ui return; } - const QColor layerColor = getLayerColor(normalizedLayerKey); - infoGroup.histogramWidget->updateLayerHistogram(normalizedLayerKey, stats, layerColor); - const int maxValue = qMax(1, layer.display.levelDomainMax); const int displayMin = qBound(0, layer.display.levelMin, maxValue - 1); const int displayMax = qBound(displayMin + 1, layer.display.levelMax, maxValue); @@ -1144,17 +823,6 @@ namespace scopeone::ui updateControlsState(); } - // Toggle logarithmic histogram scaling for one layer - void InspectWidget::onLogScaleChanged(const QString& layerKey, bool checked) - { - auto it = m_layerInfoGroups.find(layerKey); - if (it == m_layerInfoGroups.end()) - { - return; - } - it.value().histogramWidget->setLogScale(checked); - } - // Update numeric statistics labels for one layer void InspectWidget::updateStatisticsDisplay( const QString& layerKey, @@ -1217,7 +885,6 @@ namespace scopeone::ui const bool isActiveLayer = infoGroup.layerKey == layerKey; infoGroup.minSlider->setEnabled(hasStats && isActiveLayer); infoGroup.maxSlider->setEnabled(hasStats && isActiveLayer); - infoGroup.logScaleCheckBox->setEnabled(hasStats); } } @@ -1228,89 +895,15 @@ namespace scopeone::ui const QStringList visibleLayerKeys = m_sceneModel ? m_sceneModel->visibleLayerIds() : QStringList{}; - const bool visibleLayersChanged = visibleLayerKeys != m_visibleLayerKeys; - m_visibleLayerKeys = visibleLayerKeys; for (auto it = m_layerInfoGroups.begin(); it != m_layerInfoGroups.end(); ++it) { LayerInfoGroup& infoGroup = it.value(); const bool showLayer = visibleLayerKeys.contains(infoGroup.layerKey) - && (m_compareLayersCheckBox->isChecked() - || infoGroup.layerKey == layerKey); + && infoGroup.layerKey == layerKey; infoGroup.groupBox->setVisible(showLayer); infoGroup.groupBox->setTitle( inspectLayerTitle(infoGroup.layerKey, infoGroup.layerKey == layerKey)); } - if (visibleLayersChanged) - { - requestVisibleHistograms(); - } - } - - void InspectWidget::requestVisibleHistograms() - { - const quint64 generation = ++m_histogramRequestGeneration; - if (!m_compareLayersCheckBox->isChecked() - || !m_sceneModel - || m_visibleLayerKeys.isEmpty()) - { - return; - } - - const QString viewerId = m_activeViewerStateId; - const QString activeLayerKey = currentLayerKey(); - QHash frames; - for (const QString& layerKey : m_visibleLayerKeys) - { - if (layerKey == activeLayerKey) - { - continue; - } - const scopeone::core::ImageFrame frame = m_workspace->isLiveViewerActive() - ? m_scopeonecore->graphFrame(layerKey) - : m_workspace->frameForLayer(layerKey); - if (!frame.isValid()) - { - continue; - } - frames.insert(layerKey, frame); - } - if (frames.isEmpty()) - { - return; - } - - auto* watcher = new QFutureWatcher>(this); - connect(watcher, &QFutureWatcherBase::finished, this, - [this, watcher, generation, viewerId]() - { - if (generation == m_histogramRequestGeneration - && viewerId == m_activeViewerStateId) - { - const auto results = watcher->result(); - for (auto it = results.cbegin(); it != results.cend(); ++it) - { - if (m_visibleLayerKeys.contains(it.key())) - { - setLayerInspect(it.key(), it.value()); - } - } - } - watcher->deleteLater(); - }); - watcher->setFuture(QtConcurrent::run([frames]() - { - QHash results; - for (auto it = frames.cbegin(); it != frames.cend(); ++it) - { - scopeone::core::ScopeOneCore::HistogramStats stats; - if (scopeone::core::ScopeOneCore::computeHistogramStats(it.value(), stats)) - { - results.insert(it.key(), stats); - } - } - return results; - })); } // Return persistent inspect state for one preview layer @@ -1326,20 +919,6 @@ namespace scopeone::ui return it.value(); } - // Pick a stable display color from the layer key - QColor InspectWidget::getLayerColor(const QString& layerKey) const - { - static const QList layerColors = { - QColor(0, 120, 215), - QColor(232, 17, 35), - QColor(16, 124, 16), - QColor(247, 99, 12) - }; - - const int index = qHash(layerKey) % layerColors.size(); - return layerColors[index]; - } - // Apply manual display range changes from layer sliders void InspectWidget::onLayerSliderChanged(const QString& layerKey, int minValue, int maxValue) { diff --git a/src/InspectWidget.h b/src/InspectWidget.h index 05877bb..17133c3 100644 --- a/src/InspectWidget.h +++ b/src/InspectWidget.h @@ -9,19 +9,15 @@ #include #include -class QCheckBox; -class QColor; class QGroupBox; class QLabel; class QPushButton; class QSlider; -class QVBoxLayout; namespace scopeone::ui { class ImageWorkspace; class InspectCrossSectionWidget; - class InspectHistogramWidget; class InspectWidget : public QWidget { @@ -66,8 +62,6 @@ namespace scopeone::ui { QString layerKey; QGroupBox* groupBox{nullptr}; - InspectHistogramWidget* histogramWidget{nullptr}; - QCheckBox* logScaleCheckBox{nullptr}; QSlider* minSlider{nullptr}; QSlider* maxSlider{nullptr}; QLabel* minSliderValueLabel{nullptr}; @@ -95,17 +89,14 @@ namespace scopeone::ui void removeLayerInfo(const QString& layerKey); void updateLayerInspect(const QString& layerKey, const scopeone::core::ScopeOneCore::HistogramStats& stats); - void onLogScaleChanged(const QString& layerKey, bool checked); void updateStatisticsDisplay(const QString& layerKey, const scopeone::core::ScopeOneCore::HistogramStats& stats); void updateControlsState(); void updateLayerVisibility(); - void requestVisibleHistograms(); void saveViewerState(); void restoreViewerState(); QString currentLayerKey() const; LayerInspectState& getOrCreateLayerState(const QString& layerKey); - QColor getLayerColor(const QString& layerKey) const; void onLayerSliderChanged(const QString& layerKey, int minValue, int maxValue); QString currentLayerCameraId() const; @@ -116,8 +107,6 @@ namespace scopeone::ui QHash m_layerStates; QStringList m_availableLayerKeys; QStringList m_availableCameraIds; - QVBoxLayout* m_histogramContainerLayout{nullptr}; - QCheckBox* m_compareLayersCheckBox{nullptr}; QPushButton* m_drawMeasurementLineButton{nullptr}; QPushButton* m_clearMeasurementLinesButton{nullptr}; QLabel* m_measurementInfoLabel{nullptr}; @@ -130,8 +119,6 @@ namespace scopeone::ui QString m_crossSectionLayerKey; QHash m_viewerStates; QString m_activeViewerStateId; - QStringList m_visibleLayerKeys; - quint64 m_histogramRequestGeneration{0}; bool m_inspectingLive{true}; }; } From 745ee3ee75642c6e8e8774fef641a0817b7d8596 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:52:41 +0200 Subject: [PATCH 10/37] Add TIFF input to DHM tool --- plugins/examples/CMakeLists.txt | 1 + plugins/examples/tool/CMakeLists.txt | 2 + plugins/examples/tool/DhmToolPlugin.cpp | 89 ++++++++++++++++++++++++- scripts/build.ps1 | 7 ++ 4 files changed, 97 insertions(+), 2 deletions(-) diff --git a/plugins/examples/CMakeLists.txt b/plugins/examples/CMakeLists.txt index 6584669..fdde06f 100644 --- a/plugins/examples/CMakeLists.txt +++ b/plugins/examples/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_AUTOMOC ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) find_package(ScopeOneCore CONFIG REQUIRED) +find_package(OpenCV REQUIRED COMPONENTS imgcodecs imgproc) function(scopeone_example_plugin target kind) set_target_properties(${target} PROPERTIES diff --git a/plugins/examples/tool/CMakeLists.txt b/plugins/examples/tool/CMakeLists.txt index 320236c..020f40d 100644 --- a/plugins/examples/tool/CMakeLists.txt +++ b/plugins/examples/tool/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(ScopeOneDhmTool MODULE target_compile_features(ScopeOneDhmTool PRIVATE cxx_std_20) target_link_libraries(ScopeOneDhmTool PRIVATE scopeone::PluginSDK + opencv_imgcodecs + opencv_imgproc Qt6::Widgets ) scopeone_example_plugin(ScopeOneDhmTool tools) diff --git a/plugins/examples/tool/DhmToolPlugin.cpp b/plugins/examples/tool/DhmToolPlugin.cpp index 2dd20b7..af48da0 100644 --- a/plugins/examples/tool/DhmToolPlugin.cpp +++ b/plugins/examples/tool/DhmToolPlugin.cpp @@ -5,14 +5,19 @@ #include #include +#include #include #include #include #include +#include #include #include #include +#include +#include + #include #include @@ -26,9 +31,16 @@ namespace { setWindowFlag(Qt::Window, true); setWindowTitle(QStringLiteral("DHM Reconstruction")); - resize(320, 190); + resize(640, 430); auto* layout = new QVBoxLayout(this); + auto* previews = new QHBoxLayout(); + m_inputPreview = createPreview(QStringLiteral("Hologram input")); + m_resultPreview = createPreview(QStringLiteral("Reconstructed phase")); + previews->addWidget(m_inputPreview); + previews->addWidget(m_resultPreview); + layout->addLayout(previews); + auto* form = new QFormLayout(); m_offsetX = createSpinBox(-10000, 10000, 48); m_offsetY = createSpinBox(-10000, 10000, 32); @@ -43,9 +55,11 @@ namespace layout->addWidget(m_liveCheckBox); auto* buttons = new QHBoxLayout(); + m_loadButton = new QPushButton(QStringLiteral("Load hologram"), this); m_reconstructButton = new QPushButton(QStringLiteral("Reconstruct once"), this); m_cancelButton = new QPushButton(QStringLiteral("Cancel"), this); m_cancelButton->setEnabled(false); + buttons->addWidget(m_loadButton); buttons->addWidget(m_reconstructButton); buttons->addWidget(m_cancelButton); layout->addLayout(buttons); @@ -57,6 +71,20 @@ namespace connect(m_reconstructButton, &QPushButton::clicked, this, [this]() { startReconstruction(); }); + connect(m_loadButton, &QPushButton::clicked, this, [this]() + { + const QString path = QFileDialog::getOpenFileName( + this, QStringLiteral("Load hologram"), QString(), + QStringLiteral("Images (*.png *.jpg *.jpeg *.bmp *.tif *.tiff)")); + if (path.isEmpty()) + { + return; + } + m_fileInput = imageFrame(path); + m_liveCheckBox->setChecked(false); + m_sourceId = m_fileInput.cameraId; + startReconstruction(); + }); connect(m_cancelButton, &QPushButton::clicked, this, [this]() { @@ -89,6 +117,7 @@ namespace : QStringLiteral("Ready")); return; } + m_fileInput = {}; const auto frame = m_context.currentFrame(); if (frame.isValid() && scopeone::core::ScopeOneCore::isRawLayerKey( @@ -119,9 +148,59 @@ namespace return spinBox; } + static QLabel* createPreview(const QString& title) + { + auto* preview = new QLabel(title); + preview->setAlignment(Qt::AlignCenter); + preview->setMinimumSize(280, 180); + preview->setFrameShape(QFrame::Box); + preview->setStyleSheet(QStringLiteral("background: #202020; color: #aaaaaa;")); + return preview; + } + + static scopeone::core::ImageFrame imageFrame(const QString& source) + { + const cv::Mat image = cv::imread(source.toStdString(), cv::IMREAD_UNCHANGED); + cv::Mat gray; + if (image.channels() == 1) + { + gray = image; + } + else + { + cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); + } + if (gray.depth() != CV_16U) + { + gray.convertTo(gray, CV_16U, 257.0); + } + gray = gray.clone(); + scopeone::core::ImageFrame frame; + frame.cameraId = source; + frame.width = gray.cols; + frame.height = gray.rows; + frame.stride = static_cast(gray.step); + frame.bitsPerSample = 16; + frame.pixelFormat = scopeone::core::ImagePixelFormat::Mono16; + frame.bytes = QByteArray(reinterpret_cast(gray.data), + static_cast(gray.total() * gray.elemSize())); + return frame; + } + + static void showPreview(QLabel* preview, + const scopeone::core::ImageFrame& frame) + { + const QImage image(reinterpret_cast(frame.bytes.constData()), + frame.width, frame.height, + static_cast(frame.stride), + QImage::Format_Grayscale16); + preview->setPixmap(QPixmap::fromImage(image).scaled( + preview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + } + void startReconstruction() { - const auto input = m_context.currentFrame(); + const auto input = m_fileInput.isValid() ? m_fileInput : m_context.currentFrame(); if (!input.isValid()) { m_status->setText(QStringLiteral("No image loaded")); @@ -135,6 +214,7 @@ namespace void queueFrame(const scopeone::core::ImageFrame& input) { + showPreview(m_inputPreview, input); m_stream->setProcessing(true); const int offsetX = m_offsetX->value(); const int offsetY = m_offsetY->value(); @@ -184,6 +264,7 @@ namespace finishTask(QStringLiteral("Reconstruction failed")); return; } + showPreview(m_resultPreview, *m_result); const auto stored = m_context.publishToolStreamFrame( QStringLiteral("dhm.phase"), *m_result, QStringLiteral("DHM Phase")); if (stored.isValid()) @@ -229,7 +310,10 @@ namespace QSpinBox* m_offsetX{nullptr}; QSpinBox* m_offsetY{nullptr}; QSpinBox* m_radius{nullptr}; + QLabel* m_inputPreview{nullptr}; + QLabel* m_resultPreview{nullptr}; QCheckBox* m_liveCheckBox{nullptr}; + QPushButton* m_loadButton{nullptr}; QPushButton* m_reconstructButton{nullptr}; QPushButton* m_cancelButton{nullptr}; QLabel* m_status{nullptr}; @@ -237,6 +321,7 @@ namespace scopeone::ui::ScopeOneToolFrameStream* m_stream{nullptr}; std::shared_ptr m_result; QString m_sourceId; + scopeone::core::ImageFrame m_fileInput; bool m_layersPresented{false}; }; diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 5fb2e31..80d61de 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -438,6 +438,13 @@ if ($target -in @("all", "plugins")) { if ($coreQt6Dir) { $pluginConfigureArgs += "-DQt6_DIR=$coreQt6Dir" } + $coreOpenCvDir = Get-CMakeCacheValue -CachePath $coreCachePath -Key "OpenCV_DIR" + if (-not $coreOpenCvDir) { + $coreOpenCvDir = Join-Path $coreSourceDir "external\opencv-4.12.0\build" + } + if ($coreOpenCvDir) { + $pluginConfigureArgs += "-DOpenCV_DIR=$coreOpenCvDir" + } Invoke-Step ` -Label "Configuring ScopeOne plugin examples" ` -FilePath $cmake ` From f1c585697b2c8f0622d555574aa4fdf5dd89d138 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 02:47:39 +0200 Subject: [PATCH 11/37] Fix multi-camera live processing preview --- ScopeOneCore/external/ScopeWriter | 2 +- src/DeviceControlWidget.cpp | 297 +++++++++++++++++++++++++++--- src/DeviceControlWidget.h | 7 +- src/ImageWorkspace.cpp | 7 + src/MainWindow.cpp | 160 +++++++++++----- src/MainWindow.h | 10 + src/PreviewWidget.cpp | 215 ++++++++++++++++++++- src/PreviewWidget.h | 16 +- 8 files changed, 636 insertions(+), 78 deletions(-) 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/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index cf0b22b..2745c77 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -57,10 +57,14 @@ namespace scopeone::ui class LayerHistogramWidget : public QWidget { public: + using LevelsCallback = std::function; + using AutoLevelsCallback = std::function; + explicit LayerHistogramWidget(QWidget* parent = nullptr) : QWidget(parent) { setMinimumHeight(150); + setMouseTracking(true); } void setStats(const scopeone::core::ScopeOneCore::HistogramStats& stats) @@ -69,6 +73,14 @@ namespace scopeone::ui update(); } + void setLevels(int minLevel, int maxLevel, int domainMax) + { + m_minLevel = minLevel; + m_maxLevel = maxLevel; + m_domainMax = qMax(1, domainMax); + update(); + } + void clear() { m_stats = {}; @@ -81,6 +93,29 @@ namespace scopeone::ui update(); } + void setOnLevelsChanged(LevelsCallback callback) + { + m_levelsCallback = std::move(callback); + } + + void setOnAutoLevelsRequested(AutoLevelsCallback callback) + { + m_autoLevelsCallback = std::move(callback); + } + + QRect plotRect() const + { + const QFontMetrics metrics = fontMetrics(); + const int labelHeight = metrics.height() + 4; + const int xLabelWidth = qMax(50, metrics.horizontalAdvance(QStringLiteral("65535")) + 12); + const int yLabelWidth = qMax(40, metrics.horizontalAdvance(QStringLiteral("999.9M")) + 8); + return rect().adjusted( + yLabelWidth + 6, + labelHeight + 2, + -(xLabelWidth / 2 + 4), + -(2 * labelHeight + 8)); + } + protected: void paintEvent(QPaintEvent*) override { @@ -90,12 +125,7 @@ namespace scopeone::ui const QFontMetrics metrics = painter.fontMetrics(); const int labelHeight = metrics.height() + 4; const int xLabelWidth = qMax(50, metrics.horizontalAdvance(QStringLiteral("65535")) + 12); - const int yLabelWidth = qMax(40, metrics.horizontalAdvance(QStringLiteral("999.9M")) + 8); - const QRect plot = rect().adjusted( - yLabelWidth + 6, - labelHeight + 2, - -(xLabelWidth / 2 + 4), - -(2 * labelHeight + 8)); + const QRect plot = plotRect(); painter.fillRect(plot, colors.brush(QPalette::Base)); painter.setPen(QPen(colors.color(QPalette::Mid), 1)); @@ -133,6 +163,33 @@ namespace scopeone::ui painter.drawLine(x, plot.bottom(), x, plot.bottom() - height); } + const int domain = qMax(1, m_domainMax); + const int xMin = qBound(plot.left(), plot.left() + static_cast(static_cast(m_minLevel) * plot.width() / domain), plot.right()); + const int xMax = qBound(plot.left(), plot.left() + static_cast(static_cast(m_maxLevel) * plot.width() / domain), plot.right()); + + if (xMin > plot.left()) + { + painter.fillRect(QRect(plot.left(), plot.top(), xMin - plot.left(), plot.height()), QColor(0, 0, 0, 70)); + } + if (xMax < plot.right()) + { + painter.fillRect(QRect(xMax, plot.top(), plot.right() - xMax, plot.height()), QColor(0, 0, 0, 70)); + } + + painter.setPen(QPen(QColor(0, 200, 255), 2)); + painter.drawLine(xMin, plot.top(), xMin, plot.bottom()); + QPolygon minHandle; + minHandle << QPoint(xMin - 4, plot.top()) << QPoint(xMin + 4, plot.top()) << QPoint(xMin, plot.top() + 6); + painter.setBrush(QColor(0, 200, 255)); + painter.drawPolygon(minHandle); + + painter.setPen(QPen(QColor(255, 180, 0), 2)); + painter.drawLine(xMax, plot.top(), xMax, plot.bottom()); + QPolygon maxHandle; + maxHandle << QPoint(xMax - 4, plot.top()) << QPoint(xMax + 4, plot.top()) << QPoint(xMax, plot.top() + 6); + painter.setBrush(QColor(255, 180, 0)); + painter.drawPolygon(maxHandle); + painter.setPen(QPen(colors.color(QPalette::Mid), 1)); painter.drawLine(plot.left(), plot.top(), plot.left(), plot.bottom()); painter.drawLine(plot.left(), plot.bottom(), plot.right(), plot.bottom()); @@ -166,11 +223,142 @@ namespace scopeone::ui labelHeight), Qt::AlignRight | Qt::AlignVCenter, QStringLiteral("Count")); + + const QString readout = QStringLiteral("Min: %1 Max: %2").arg(m_minLevel).arg(m_maxLevel); + painter.drawText(QRect(plot.left(), 0, plot.width(), labelHeight), Qt::AlignRight | Qt::AlignVCenter, readout); + } + + void mousePressEvent(QMouseEvent* event) override + { + if (event->button() != Qt::LeftButton) + { + QWidget::mousePressEvent(event); + return; + } + const QRect plot = plotRect(); + if (plot.width() <= 0) return; + const int domain = qMax(1, m_domainMax); + const int xMin = plot.left() + static_cast(static_cast(m_minLevel) * plot.width() / domain); + const int xMax = plot.left() + static_cast(static_cast(m_maxLevel) * plot.width() / domain); + const int mx = event->pos().x(); + if (std::abs(mx - xMin) <= 8) + { + m_dragMode = DragMode::MinLevel; + } + else if (std::abs(mx - xMax) <= 8) + { + m_dragMode = DragMode::MaxLevel; + } + else if (std::abs(mx - xMin) < std::abs(mx - xMax)) + { + m_dragMode = DragMode::MinLevel; + updateLevelFromMouse(mx); + } + else + { + m_dragMode = DragMode::MaxLevel; + updateLevelFromMouse(mx); + } + event->accept(); + } + + void mouseMoveEvent(QMouseEvent* event) override + { + const QRect plot = plotRect(); + if (m_dragMode != DragMode::None) + { + updateLevelFromMouse(event->pos().x()); + event->accept(); + return; + } + if (plot.width() > 0) + { + const int domain = qMax(1, m_domainMax); + const int xMin = plot.left() + static_cast(static_cast(m_minLevel) * plot.width() / domain); + const int xMax = plot.left() + static_cast(static_cast(m_maxLevel) * plot.width() / domain); + const int mx = event->pos().x(); + if (std::abs(mx - xMin) <= 8 || std::abs(mx - xMax) <= 8) + { + setCursor(Qt::SizeHorCursor); + } + else + { + setCursor(Qt::ArrowCursor); + } + } + QWidget::mouseMoveEvent(event); + } + + void mouseReleaseEvent(QMouseEvent* event) override + { + if (m_dragMode != DragMode::None) + { + m_dragMode = DragMode::None; + event->accept(); + return; + } + QWidget::mouseReleaseEvent(event); + } + + void mouseDoubleClickEvent(QMouseEvent* event) override + { + if (event->button() == Qt::LeftButton) + { + if (m_autoLevelsCallback) + { + m_autoLevelsCallback(); + } + event->accept(); + return; + } + QWidget::mouseDoubleClickEvent(event); } private: + enum class DragMode { None, MinLevel, MaxLevel }; + + void updateLevelFromMouse(int mouseX) + { + const QRect plot = plotRect(); + if (plot.width() <= 0) return; + const int domain = qMax(1, m_domainMax); + const int rawVal = static_cast(static_cast(mouseX - plot.left()) * domain / plot.width()); + if (m_dragMode == DragMode::MinLevel) + { + const int newMin = qBound(0, rawVal, m_maxLevel - 1); + if (newMin != m_minLevel) + { + m_minLevel = newMin; + if (m_levelsCallback) + { + m_levelsCallback(m_minLevel, m_maxLevel); + } + update(); + } + } + else if (m_dragMode == DragMode::MaxLevel) + { + const int newMax = qBound(m_minLevel + 1, rawVal, domain); + if (newMax != m_maxLevel) + { + m_maxLevel = newMax; + if (m_levelsCallback) + { + m_levelsCallback(m_minLevel, m_maxLevel); + } + update(); + } + } + } + scopeone::core::ScopeOneCore::HistogramStats m_stats; bool m_logScale{false}; + int m_minLevel{0}; + int m_maxLevel{255}; + int m_domainMax{255}; + DragMode m_dragMode{DragMode::None}; + LevelsCallback m_levelsCallback; + AutoLevelsCallback m_autoLevelsCallback; }; // Creates the device control widget and initializes controls @@ -377,6 +565,23 @@ namespace scopeone::ui onPreviewAvailableLayerKeysChanged(m_previewWidget->availableLayerKeys()); applyPreviewVisibility(m_previewWidget->visibleLayerKeys(), false); onPreviewLayerInfoTextChanged(m_previewWidget->layerInfoSummaryText()); + + if (m_clippingCheckBox) + { + m_clippingCheckBox->setChecked(m_previewWidget->isClippingWarningEnabled()); + connect(m_clippingCheckBox, &QCheckBox::toggled, + m_previewWidget, &PreviewWidget::setClippingWarningEnabled); + connect(m_previewWidget, &PreviewWidget::clippingWarningChanged, + m_clippingCheckBox, &QCheckBox::setChecked); + } + if (m_scaleBarCheckBox) + { + m_scaleBarCheckBox->setChecked(m_previewWidget->isScaleBarVisible()); + connect(m_scaleBarCheckBox, &QCheckBox::toggled, + m_previewWidget, &PreviewWidget::setScaleBarVisible); + connect(m_previewWidget, &PreviewWidget::scaleBarVisibilityChanged, + m_scaleBarCheckBox, &QCheckBox::setChecked); + } } // Builds layer and alignment controls @@ -452,6 +657,11 @@ namespace scopeone::ui m_layerAutoStretchCheckBox = new QCheckBox(QStringLiteral("Continuous Auto"), m_layerSettingsGroup); m_layerAutoStretchCheckBox->setToolTip( QStringLiteral("Update display levels continuously as images arrive")); + m_clippingCheckBox = new QCheckBox(QStringLiteral("Hi-Lo Warn"), m_layerSettingsGroup); + m_clippingCheckBox->setToolTip(QStringLiteral("Highlight saturated pixels in red and zero pixels in blue (Hotkey: C)")); + m_scaleBarCheckBox = new QCheckBox(QStringLiteral("Scale Bar"), m_layerSettingsGroup); + m_scaleBarCheckBox->setToolTip(QStringLiteral("Display calibrated scale bar in viewport")); + m_scaleBarCheckBox->setChecked(true); layerSettingsLayout->addWidget(m_selectedLayerLabel, 0, 0, 1, 6); layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Order:"), m_layerSettingsGroup), 1, 0); @@ -469,6 +679,8 @@ namespace scopeone::ui layerSettingsLayout->addWidget(m_layerAutoButton, 4, 0, 1, 2); layerSettingsLayout->addWidget(m_layerFullRangeButton, 4, 2, 1, 2); layerSettingsLayout->addWidget(m_layerAutoStretchCheckBox, 4, 4, 1, 2); + layerSettingsLayout->addWidget(m_clippingCheckBox, 5, 0, 1, 3); + layerSettingsLayout->addWidget(m_scaleBarCheckBox, 5, 3, 1, 3); auto* transformGroup = new QGroupBox(QStringLiteral("Transform"), this); auto* transformLayout = new QGridLayout(transformGroup); @@ -544,6 +756,26 @@ namespace scopeone::ui this, &DeviceControlWidget::onPreviewLayerFullRangeClicked); connect(m_layerAutoStretchCheckBox, &QCheckBox::toggled, this, &DeviceControlWidget::onPreviewLayerAutoStretchToggled); + m_layerHistogramWidget->setOnLevelsChanged([this](int minLevel, int maxLevel) + { + const QString layerKey = currentLayerKey(); + if (layerKey.isEmpty() || !m_sceneModel) return; + scopeone::core::DocumentLayer layer; + if (m_sceneModel->findLayer(layerKey, layer)) + { + const int domainMax = layer.display.levelDomainMax > 0 ? layer.display.levelDomainMax : 255; + if (m_workspace) + { + m_workspace->setLayerAutoStretchEnabled(layerKey, false); + } + m_layerAutoStretchCheckBox->setChecked(false); + m_sceneModel->setLayerDisplayLevels(layerKey, minLevel, maxLevel, domainMax); + } + }); + m_layerHistogramWidget->setOnAutoLevelsRequested([this]() + { + onPreviewLayerAutoClicked(); + }); const auto applySourceTransform = [this]() { const QString sourceId = selectedLayerSourceId(); @@ -731,6 +963,9 @@ namespace scopeone::ui m_workspace->layerAutoStretchEnabled(layerKey)); } + m_layerHistogramWidget->setLevels( + layer.display.levelMin, layer.display.levelMax, layer.display.levelDomainMax); + const int row = m_layerTable->currentRow(); m_layerMoveUpButton->setEnabled(row > 0); m_layerMoveDownButton->setEnabled(row < m_layerTable->rowCount() - 1); @@ -936,7 +1171,8 @@ namespace scopeone::ui // Use the selected raw camera layer as the hardware control target void DeviceControlWidget::syncControlTargetToSelectedRawLayer() { - if (!scopeone::core::ScopeOneCore::isRawLayerKey(currentLayerKey())) + if (isAllTarget(m_currentTarget) + || !scopeone::core::ScopeOneCore::isRawLayerKey(currentLayerKey())) { return; } @@ -1422,6 +1658,26 @@ namespace scopeone::ui emit stageMoveFailed(tr("Failed to queue Z stage move: %1").arg(zLabel)); } + // Moves XY stage with step factor + void DeviceControlWidget::moveXYStep(double dxScale, double dyScale, bool big) + { + const QLineEdit* lineEdit = big ? m_xyBigStepLineEdit : m_xyStepLineEdit; + if (!lineEdit) return; + const double stepValue = lineEdit->text().toDouble(); + if (stepValue <= 0.0) return; + moveXYStage(dxScale * stepValue, dyScale * stepValue); + } + + // Moves Z stage with step factor + void DeviceControlWidget::moveZStep(double dzScale, bool big) + { + const QLineEdit* lineEdit = big ? m_zBigStepLineEdit : m_zStepLineEdit; + if (!lineEdit) return; + const double stepValue = lineEdit->text().toDouble(); + if (stepValue <= 0.0) return; + moveZStage(dzScale * stepValue); + } + // Updates control state when cameras initialize void DeviceControlWidget::onCameraInitialized(bool initialized) { @@ -1581,8 +1837,6 @@ namespace scopeone::ui // Rebuilds available camera control targets void DeviceControlWidget::setControlTargets(const QStringList& cameraIds) { - QString current = m_cameraSelectCombo->currentText(); - { QSignalBlocker blocker(m_cameraSelectCombo); m_cameraSelectCombo->clear(); @@ -1592,26 +1846,13 @@ namespace scopeone::ui m_cameraSelectCombo->addItem(id); } - int idx = m_cameraSelectCombo->findText(current); - const bool currentIsAll = isAllTarget(current); - if (!cameraIds.isEmpty()) + if (cameraIds.size() > 1) { - if (currentIsAll && cameraIds.size() > 1) - { - m_cameraSelectCombo->setCurrentIndex(0); - } - else if (currentIsAll) - { - m_cameraSelectCombo->setCurrentIndex(1); - } - else if (idx >= 0) - { - m_cameraSelectCombo->setCurrentIndex(idx); - } - else - { - m_cameraSelectCombo->setCurrentIndex(1); - } + m_cameraSelectCombo->setCurrentIndex(0); + } + else if (cameraIds.size() == 1) + { + m_cameraSelectCombo->setCurrentIndex(1); } else { diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index adad541..a3c7b40 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -53,9 +53,10 @@ namespace scopeone::ui void refreshStageDevices(); void refreshCameraParameters(); - void onCameraInitialized(bool initialized); - void setPreviewRunning(bool running); + void onCameraInitialized(bool initialized); + void moveXYStep(double dxScale, double dyScale, bool big = false); + void moveZStep(double dzScale, bool big = false); signals : void startPreviewRequested(); @@ -146,6 +147,8 @@ namespace scopeone::ui QPushButton* m_layerAutoButton{nullptr}; QPushButton* m_layerFullRangeButton{nullptr}; QCheckBox* m_layerAutoStretchCheckBox{nullptr}; + QCheckBox* m_clippingCheckBox{nullptr}; + QCheckBox* m_scaleBarCheckBox{nullptr}; LayerHistogramWidget* m_layerHistogramWidget{nullptr}; QCheckBox* m_layerHistogramLogCheckBox{nullptr}; QGroupBox* m_layerHistogramGroup{nullptr}; diff --git a/src/ImageWorkspace.cpp b/src/ImageWorkspace.cpp index 9487439..38b71f8 100644 --- a/src/ImageWorkspace.cpp +++ b/src/ImageWorkspace.cpp @@ -46,6 +46,7 @@ namespace scopeone::ui ImageDocumentPage(const QString& documentId, const QString& title, const QString& cameraId, + const std::shared_ptr& session, const scopeone::core::ExperimentDocument& presentation, int frameCount, QWidget* parent) @@ -107,6 +108,10 @@ namespace scopeone::ui auto* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); m_preview = new PreviewWidget(scene, this); + m_preview->setPixelSizeCallback([session, cameraId](const QString&) + { + return session ? session->cameraPixelSizeUm(cameraId) : 0.0; + }); layout->addWidget(m_preview, 1); auto* navigation = new QWidget(this); @@ -708,6 +713,7 @@ namespace scopeone::ui document->page = new ImageDocumentPage(document->id, document->title, camera, + session, session->experimentDocument(), document->frameCount, m_viewerTabs); @@ -1441,6 +1447,7 @@ namespace scopeone::ui document->page = new ImageDocumentPage(document->id, document->title, document->cameraId, + document->session, document->session->experimentDocument(), document->frameCount, m_viewerTabs); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 14f247b..5021d5e 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -66,21 +69,6 @@ namespace scopeone::ui return layerKeys; } - // Keep only raw layers from the current preview selection - QStringList rawOnlyLayerKeys(const QStringList& layerKeys) - { - QStringList rawKeys; - rawKeys.reserve(layerKeys.size()); - for (const QString& layerKey : layerKeys) - { - if (scopeone::core::ScopeOneCore::isRawLayerKey(layerKey)) - { - rawKeys.append(layerKey); - } - } - return rawKeys; - } - // Apply common status label presentation void configureStatusLabel(QLabel* label, int minWidth, int maxWidth, const QString& tooltip) { @@ -332,6 +320,7 @@ namespace scopeone::ui connect(m_scopeonecore, &scopeone::core::ScopeOneCore::previewStateChanged, this, [this](bool running) { + m_previewRunning = running; m_previewWidget->resetLiveFrameRates(); m_deviceControlWidget->setPreviewRunning(running); m_deviceControlWidget->setControlTargetEnabled(!running); @@ -471,6 +460,11 @@ namespace scopeone::ui } }); + connect(m_previewWidget, &PreviewWidget::stageStepRequested, + m_deviceControlWidget, &DeviceControlWidget::moveXYStep); + connect(m_previewWidget, &PreviewWidget::stageZStepRequested, + m_deviceControlWidget, &DeviceControlWidget::moveZStep); + connect(m_deviceControlWidget, &DeviceControlWidget::controlTargetChanged, this, &MainWindow::updateControlTarget); connect(m_deviceControlWidget, &DeviceControlWidget::exposureValueChanged, @@ -541,33 +535,12 @@ namespace scopeone::ui tr("Processing: Live"), tr("Processing is running")); showStatusMessage(tr("Image processing started"), 3000); - QStringList visibleLayerKeys = m_previewWidget->visibleLayerKeys(); const QStringList availableCameraIds = m_previewWidget->availableCameraIds(); - - for (const QString& layerKey : std::as_const(visibleLayerKeys)) + QStringList visibleLayerKeys; + for (const QString& cameraId : availableCameraIds) { - if (!scopeone::core::ScopeOneCore::isRawLayerKey(layerKey)) - { - continue; - } - const QString cameraId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey); - if (cameraId.isEmpty() || !availableCameraIds.contains(cameraId)) - { - continue; - } - const QString processedLayerKey = scopeone::core::ScopeOneCore::processedLayerKey(cameraId); - if (!visibleLayerKeys.contains(processedLayerKey)) - { - visibleLayerKeys.append(processedLayerKey); - } - } - - if (visibleLayerKeys.isEmpty()) - { - for (const QString& cameraId : availableCameraIds) - { - visibleLayerKeys.append(scopeone::core::ScopeOneCore::processedLayerKey(cameraId)); - } + visibleLayerKeys.append(scopeone::core::ScopeOneCore::rawLayerKey(cameraId)); + visibleLayerKeys.append(scopeone::core::ScopeOneCore::processedLayerKey(cameraId)); } m_imageWorkspace->setVisibleLayers(visibleLayerKeys, true); @@ -579,11 +552,8 @@ namespace scopeone::ui tr("Processing: Off"), tr("Processing is off")); showStatusMessage(tr("Image processing stopped"), 3000); - QStringList visibleLayerKeys = rawOnlyLayerKeys(m_previewWidget->visibleLayerKeys()); - if (visibleLayerKeys.isEmpty()) - { - visibleLayerKeys = rawLayerKeys(m_previewWidget->availableCameraIds()); - } + const QStringList visibleLayerKeys = + rawLayerKeys(m_previewWidget->availableCameraIds()); m_imageWorkspace->setVisibleLayers(visibleLayerKeys); }); connect(m_imageProcessingWidget, &ImageProcessingWidget::processedLayerReady, @@ -763,6 +733,10 @@ namespace scopeone::ui void MainWindow::setupUI() { m_previewWidget = new PreviewWidget(m_imageSceneModel, this); + m_previewWidget->setPixelSizeCallback([this](const QString& layerKey) + { + return m_imageWorkspace->pixelSizeUm(layerKey); + }); setCentralWidget(m_imageWorkspace->viewerHost()); setupTools(); @@ -936,9 +910,105 @@ namespace scopeone::ui m_viewMenu = menuBar()->addMenu(tr("&View")); m_fullScreenAction = m_viewMenu->addAction(tr("&Full Screen")); m_fullScreenAction->setCheckable(true); + m_fullScreenAction->setShortcut(QKeySequence::FullScreen); + + m_fitToWindowAction = m_viewMenu->addAction(tr("Fit to &Window")); + m_fitToWindowAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+0"))); + connect(m_fitToWindowAction, &QAction::triggered, this, [this]() + { + m_previewWidget->setFitToWindow(true); + }); + + m_actualSizeAction = m_viewMenu->addAction(tr("&Actual Size (100%)")); + m_actualSizeAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+1"))); + connect(m_actualSizeAction, &QAction::triggered, this, [this]() + { + m_previewWidget->setFitToWindow(false); + m_previewWidget->setZoomPercent(100); + }); + + m_zoomInAction = m_viewMenu->addAction(tr("Zoom &In")); + m_zoomInAction->setShortcuts({QKeySequence::ZoomIn, QKeySequence(QStringLiteral("Ctrl+=")), QKeySequence(QStringLiteral("Ctrl++"))}); + connect(m_zoomInAction, &QAction::triggered, this, [this]() + { + m_previewWidget->setFitToWindow(false); + m_previewWidget->setZoomPercent(m_previewWidget->zoomPercent() + 20); + }); + + m_zoomOutAction = m_viewMenu->addAction(tr("Zoom &Out")); + m_zoomOutAction->setShortcut(QKeySequence::ZoomOut); + connect(m_zoomOutAction, &QAction::triggered, this, [this]() + { + m_previewWidget->setFitToWindow(false); + m_previewWidget->setZoomPercent(m_previewWidget->zoomPercent() - 20); + }); + + m_viewMenu->addSeparator(); + + m_scaleBarAction = m_viewMenu->addAction(tr("Show &Scale Bar")); + m_scaleBarAction->setCheckable(true); + m_scaleBarAction->setChecked(m_previewWidget->isScaleBarVisible()); + connect(m_scaleBarAction, &QAction::toggled, m_previewWidget, &PreviewWidget::setScaleBarVisible); + connect(m_previewWidget, &PreviewWidget::scaleBarVisibilityChanged, m_scaleBarAction, &QAction::setChecked); + + m_clippingAction = m_viewMenu->addAction(tr("Show &Saturation Warning (Hi-Lo)")); + m_clippingAction->setCheckable(true); + m_clippingAction->setShortcut(QKeySequence(Qt::Key_C)); + m_clippingAction->setChecked(m_previewWidget->isClippingWarningEnabled()); + connect(m_clippingAction, &QAction::toggled, m_previewWidget, &PreviewWidget::setClippingWarningEnabled); + connect(m_previewWidget, &PreviewWidget::clippingWarningChanged, m_clippingAction, &QAction::setChecked); + m_viewMenu->addSeparator(); m_dockWidgetsMenu = m_viewMenu->addMenu(tr("&Dock Widgets")); + m_togglePreviewAction = new QAction(tr("Toggle Live Preview"), this); + m_togglePreviewAction->setShortcut(QKeySequence(Qt::Key_Space)); + m_togglePreviewAction->setShortcutContext(Qt::ApplicationShortcut); + connect(m_togglePreviewAction, &QAction::triggered, this, [this]() + { + QWidget* focus = focusWidget(); + if (focus && (qobject_cast(focus) || qobject_cast(focus) || qobject_cast(focus))) + { + return; + } + if (m_previewRunning) + { + m_scopeonecore->stopPreview(m_currentControlTarget); + } + else + { + m_scopeonecore->startPreview(m_currentControlTarget); + } + }); + addAction(m_togglePreviewAction); + + m_snapAction = new QAction(tr("Snap"), this); + m_snapAction->setShortcuts({QKeySequence(Qt::CTRL | Qt::Key_Return), QKeySequence(Qt::CTRL | Qt::Key_Enter)}); + m_snapAction->setShortcutContext(Qt::ApplicationShortcut); + connect(m_snapAction, &QAction::triggered, this, [this]() + { + m_recordingWidget->snapToGallery(m_currentControlTarget); + }); + addAction(m_snapAction); + + m_autoContrastAction = new QAction(tr("Auto Contrast"), this); + m_autoContrastAction->setShortcut(QKeySequence(Qt::Key_A)); + m_autoContrastAction->setShortcutContext(Qt::ApplicationShortcut); + connect(m_autoContrastAction, &QAction::triggered, this, [this]() + { + QWidget* focus = focusWidget(); + if (focus && (qobject_cast(focus) || qobject_cast(focus) || qobject_cast(focus))) + { + return; + } + const QString activeLayer = m_imageWorkspace->activeLayerKey(); + if (!activeLayer.isEmpty()) + { + m_imageWorkspace->autoLayerLevels(activeLayer); + } + }); + addAction(m_autoContrastAction); + m_toolsMenu = menuBar()->addMenu(tr("&Tools")); m_toolRegistry->populateMenu(m_toolsMenu, this); m_toolsMenu->addSeparator(); diff --git a/src/MainWindow.h b/src/MainWindow.h index ea8e654..18134f8 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -155,6 +155,15 @@ namespace scopeone::ui QAction* m_exitAction{nullptr}; QAction* m_fullScreenAction{nullptr}; + QAction* m_fitToWindowAction{nullptr}; + QAction* m_actualSizeAction{nullptr}; + QAction* m_zoomInAction{nullptr}; + QAction* m_zoomOutAction{nullptr}; + QAction* m_scaleBarAction{nullptr}; + QAction* m_clippingAction{nullptr}; + QAction* m_togglePreviewAction{nullptr}; + QAction* m_snapAction{nullptr}; + QAction* m_autoContrastAction{nullptr}; QAction* m_loadConfigurationAction{nullptr}; QAction* m_unloadConfigurationAction{nullptr}; QAction* m_saveImageAsAction{nullptr}; @@ -179,5 +188,6 @@ namespace scopeone::ui int m_closeSaveTotal{0}; bool m_closeSaveInProgress{false}; bool m_closeAfterSave{false}; + bool m_previewRunning{false}; }; } diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index e245e38..28c95d9 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -908,6 +908,47 @@ namespace scopeone::ui return m_fitToWindow; } + // Sets whether the calibrated scale bar is drawn on preview + void PreviewWidget::setScaleBarVisible(bool visible) + { + if (m_scaleBarVisible == visible) + { + return; + } + m_scaleBarVisible = visible; + emit scaleBarVisibilityChanged(m_scaleBarVisible); + update(); + } + + bool PreviewWidget::isScaleBarVisible() const + { + return m_scaleBarVisible; + } + + // Sets whether overexposure and underexposure clipping warning is active + void PreviewWidget::setClippingWarningEnabled(bool enabled) + { + if (m_clippingWarning == enabled) + { + return; + } + m_clippingWarning = enabled; + emit clippingWarningChanged(m_clippingWarning); + update(); + } + + bool PreviewWidget::isClippingWarningEnabled() const + { + return m_clippingWarning; + } + + // Sets the callback providing pixel size in micrometers per layer + void PreviewWidget::setPixelSizeCallback(std::function callback) + { + m_pixelSizeCallback = std::move(callback); + update(); + } + // Refreshes placeholder state and schedules repaint void PreviewWidget::updateImageDisplay() { @@ -1483,6 +1524,122 @@ namespace scopeone::ui } } + // Draws a calibrated scale bar overlay in the corner of visible image areas + void PreviewWidget::drawScaleBar(QPainter& painter, const std::vector& renderItems) const + { + if (renderItems.empty()) + { + return; + } + + QSet drawnAreas; + for (const RenderItem& item : renderItems) + { + if (!item.info || !item.info->frameState || drawnAreas.contains(item.area)) + { + continue; + } + + QRect displayRect; + QSize imageSize; + if (!resolveDisplayGeometry(*item.info->frameState, item.processed, item.area, displayRect, imageSize)) + { + continue; + } + + if (imageSize.width() <= 0 || displayRect.width() <= 0) + { + continue; + } + + const double pixelSize = m_pixelSizeCallback ? m_pixelSizeCallback(item.layerKey) : 0.0; + if (pixelSize <= 0.0) + { + continue; + } + + const double pixelsPerImagePixel = static_cast(displayRect.width()) / static_cast(imageSize.width()); + const double screenPixelsPerUm = pixelsPerImagePixel / pixelSize; + if (screenPixelsPerUm <= 1e-6) + { + continue; + } + + const double targetUm = 80.0 / screenPixelsPerUm; + static const double niceSteps[] = { + 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 25.0, 50.0, + 100.0, 200.0, 250.0, 500.0, 1000.0, 2000.0, 5000.0, 10000.0, 25000.0, 50000.0 + }; + + double bestUm = niceSteps[0]; + double minDiff = std::abs(targetUm - bestUm); + for (double step : niceSteps) + { + const double diff = std::abs(targetUm - step); + if (diff < minDiff) + { + minDiff = diff; + bestUm = step; + } + } + + const int barWidthPx = static_cast(std::round(bestUm * screenPixelsPerUm)); + if (barWidthPx < 10 || barWidthPx > displayRect.width() - 20) + { + continue; + } + + QString labelText; + if (bestUm >= 1000.0) + { + labelText = QString::number(bestUm / 1000.0, 'g', 3) + QStringLiteral(" mm"); + } + else if (bestUm >= 1.0) + { + labelText = QString::number(bestUm, 'g', 3) + QStringLiteral(" um"); + } + else + { + labelText = QString::number(bestUm * 1000.0, 'g', 3) + QStringLiteral(" nm"); + } + + painter.save(); + painter.setRenderHint(QPainter::Antialiasing, true); + + QFont font = painter.font(); + font.setPointSize(9); + font.setBold(true); + painter.setFont(font); + + const QFontMetrics fm(font); + const int textWidth = fm.horizontalAdvance(labelText); + const int boxWidth = std::max(barWidthPx, textWidth) + 16; + const int boxHeight = fm.height() + 14; + + const int margin = 12; + const int boxX = displayRect.right() - boxWidth - margin; + const int boxY = displayRect.bottom() - boxHeight - margin; + const QRect boxRect(boxX, boxY, boxWidth, boxHeight); + + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0, 0, 0, 160)); + painter.drawRoundedRect(boxRect, 4, 4); + + painter.setPen(Qt::white); + const QRect textRect(boxX, boxY + 2, boxWidth, fm.height()); + painter.drawText(textRect, Qt::AlignCenter, labelText); + + const int barX = boxX + (boxWidth - barWidthPx) / 2; + const int barY = boxY + fm.height() + 6; + QPen linePen(Qt::white, 3, Qt::SolidLine, Qt::RoundCap); + painter.setPen(linePen); + painter.drawLine(barX, barY, barX + barWidthPx, barY); + + painter.restore(); + drawnAreas.insert(item.area); + } + } + bool PreviewWidget::markupAtWidgetPosition(const QPoint& widgetPos, ImageSceneModel::Markup& outMarkup, PreviewInteractionTarget& outTarget, @@ -2061,11 +2218,16 @@ namespace scopeone::ui if (m_sceneModel->hasMarkups() || (m_roiDrawingMode && m_roiDragging) - || (m_crossSectionDrawingMode && m_crossSectionDragging)) + || (m_crossSectionDrawingMode && m_crossSectionDragging) + || m_scaleBarVisible) { QPainter p(this); drawMarkups(p, renderItems); drawActiveInteractionMarkup(p, renderItems); + if (m_scaleBarVisible) + { + drawScaleBar(p, renderItems); + } } return; } @@ -2122,6 +2284,7 @@ namespace scopeone::ui uniform float uAlpha; uniform float uGamma; uniform int uColormap; + uniform int uShowClipping; uniform sampler2D uColormapLut; vec3 applyColormap(float t, int map) { vec2 lutSize = vec2(textureSize(uColormapLut, 0)); @@ -2132,6 +2295,16 @@ namespace scopeone::ui void main(){ vec4 s = texture(uTex, vUV); float t0 = s.r * uTexNormScale; + if (uShowClipping == 1) { + if (t0 >= 0.999) { + FragColor = vec4(1.0, 0.0, 0.0, uAlpha); + return; + } + if (t0 <= 0.0001) { + FragColor = vec4(0.0, 0.2, 1.0, uAlpha); + return; + } + } float t = clamp((t0 - uMinNorm) / max(uMaxNorm - uMinNorm, 1e-6), 0.0, 1.0); t = pow(t, 1.0 / max(uGamma, 1e-3)); FragColor = vec4(applyColormap(t, uColormap), uAlpha); @@ -2170,6 +2343,7 @@ namespace scopeone::ui m_uColormapLut = m_prog.uniformLocation("uColormapLut"); m_uUvScale = m_prog.uniformLocation("uUvScale"); m_uUvOffset = m_prog.uniformLocation("uUvOffset"); + m_uShowClipping = m_prog.uniformLocation("uShowClipping"); // Uploads all colormaps once for shader lookup glGenTextures(1, &m_colormapTexture); @@ -2276,6 +2450,7 @@ namespace scopeone::ui m_prog.setUniformValue(m_uAlpha, opacity); m_prog.setUniformValue(m_uGamma, static_cast(std::clamp(display.gamma, 0.2, 2.0))); m_prog.setUniformValue(m_uColormap, display.colormapIndex); + m_prog.setUniformValue(m_uShowClipping, m_clippingWarning ? 1 : 0); setUvTransform(flipX, flipY); if (display.blending == Blending::Opaque) @@ -3037,6 +3212,44 @@ namespace scopeone::ui return; } + const bool bigStep = (event->modifiers() & Qt::ShiftModifier); + if (event->key() == Qt::Key_Up) + { + emit stageStepRequested(0.0, 1.0, bigStep); + event->accept(); + return; + } + if (event->key() == Qt::Key_Down) + { + emit stageStepRequested(0.0, -1.0, bigStep); + event->accept(); + return; + } + if (event->key() == Qt::Key_Left) + { + emit stageStepRequested(-1.0, 0.0, bigStep); + event->accept(); + return; + } + if (event->key() == Qt::Key_Right) + { + emit stageStepRequested(1.0, 0.0, bigStep); + event->accept(); + return; + } + if (event->key() == Qt::Key_PageUp) + { + emit stageZStepRequested(1.0, bigStep); + event->accept(); + return; + } + if (event->key() == Qt::Key_PageDown) + { + emit stageZStepRequested(-1.0, bigStep); + event->accept(); + return; + } + QOpenGLWidget::keyPressEvent(event); } } // namespace scopeone::ui diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index cd915fe..787c983 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "scopeone/ImageSceneModel.h" #include "scopeone/ImageFrame.h" @@ -80,6 +81,11 @@ namespace scopeone::ui int zoomPercent() const; void setFitToWindow(bool enabled); bool isFitToWindow() const; + void setScaleBarVisible(bool visible); + bool isScaleBarVisible() const; + void setClippingWarningEnabled(bool enabled); + bool isClippingWarningEnabled() const; + void setPixelSizeCallback(std::function callback); void startROIDrawing(const QString& cameraId); void startMeasurementLineDrawingForLayer(const QString& layerKey); void startCrossSectionDrawingForLayer(const QString& layerKey); @@ -97,6 +103,10 @@ namespace scopeone::ui void layerInfoTextChanged(const QString& text); void zoomLevelChanged(int zoomPercent); void fitToWindowChanged(bool enabled); + void scaleBarVisibilityChanged(bool visible); + void clippingWarningChanged(bool enabled); + void stageStepRequested(double dxScale, double dyScale, bool big); + void stageZStepRequested(double dzScale, bool big); void activated(); void mousePositionChanged(const QPoint& widgetPos); void roiDrawn(const QString& cameraId, @@ -221,7 +231,10 @@ namespace scopeone::ui QOpenGLShaderProgram m_prog; GLint m_uTex{-1}, m_uMinNorm{-1}, m_uMaxNorm{-1}, m_uTexNormScale{-1}, m_uAlpha{-1}; GLint m_uGamma{-1}, m_uColormap{-1}, m_uColormapLut{-1}; - GLint m_uUvScale{-1}, m_uUvOffset{-1}; + GLint m_uUvScale{-1}, m_uUvOffset{-1}, m_uShowClipping{-1}; + bool m_scaleBarVisible{true}; + bool m_clippingWarning{false}; + std::function m_pixelSizeCallback; struct CachedTexture { @@ -315,6 +328,7 @@ namespace scopeone::ui const RenderItem& item) const; void drawMarkups(QPainter& painter, const std::vector& renderItems) const; void drawActiveInteractionMarkup(QPainter& painter, const std::vector& renderItems) const; + void drawScaleBar(QPainter& painter, const std::vector& renderItems) const; bool markupAtWidgetPosition(const QPoint& widgetPos, ImageSceneModel::Markup& outMarkup, PreviewInteractionTarget& outTarget, From 4f1297fedc67f4c7688f2211099d88e8466baeb2 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:54:26 +0200 Subject: [PATCH 12/37] Add cursor HUD readout, tile maximize toggle, and preview layout improvements --- ScopeOneCore/src/DriverHostProviderProxy.cpp | 6 +- VERSION | 2 +- src/DeviceControlWidget.cpp | 20 +- src/ImageProcessingWidget.cpp | 66 +++---- src/ImageWorkspace.cpp | 6 + src/MainWindow.cpp | 75 ++++--- src/MainWindow.h | 2 + src/PreviewWidget.cpp | 198 ++++++++++++++++++- src/PreviewWidget.h | 9 + 9 files changed, 310 insertions(+), 74 deletions(-) diff --git a/ScopeOneCore/src/DriverHostProviderProxy.cpp b/ScopeOneCore/src/DriverHostProviderProxy.cpp index e2459a8..4b789e8 100644 --- a/ScopeOneCore/src/DriverHostProviderProxy.cpp +++ b/ScopeOneCore/src/DriverHostProviderProxy.cpp @@ -321,12 +321,16 @@ namespace scopeone::core::internal QTimer timer; timer.setSingleShot(true); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + struct LoopGuard + { + QEventLoop*& target; + ~LoopGuard() { target = nullptr; } + } loopGuard{m_waitingLoop}; m_waitingLoop = &loop; m_socket->write(driverhost::encodeMessage(request)); m_socket->flush(); timer.start((std::max)(1, timeoutMs)); loop.exec(); - m_waitingLoop = nullptr; if (m_waitingResponse.isEmpty()) { diff --git a/VERSION b/VERSION index 03a70cf..e7985f3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.025 +1.5.901 diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 2745c77..a8143fa 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -1661,21 +1661,21 @@ namespace scopeone::ui // Moves XY stage with step factor void DeviceControlWidget::moveXYStep(double dxScale, double dyScale, bool big) { - const QLineEdit* lineEdit = big ? m_xyBigStepLineEdit : m_xyStepLineEdit; - if (!lineEdit) return; - const double stepValue = lineEdit->text().toDouble(); - if (stepValue <= 0.0) return; - moveXYStage(dxScale * stepValue, dyScale * stepValue); + const double stepValue = (big ? m_xyBigStepLineEdit : m_xyStepLineEdit)->text().toDouble(); + if (stepValue > 0.0) + { + moveXYStage(dxScale * stepValue, dyScale * stepValue); + } } // Moves Z stage with step factor void DeviceControlWidget::moveZStep(double dzScale, bool big) { - const QLineEdit* lineEdit = big ? m_zBigStepLineEdit : m_zStepLineEdit; - if (!lineEdit) return; - const double stepValue = lineEdit->text().toDouble(); - if (stepValue <= 0.0) return; - moveZStage(dzScale * stepValue); + const double stepValue = (big ? m_zBigStepLineEdit : m_zStepLineEdit)->text().toDouble(); + if (stepValue > 0.0) + { + moveZStage(dzScale * stepValue); + } } // Updates control state when cameras initialize diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index 71f8104..b3f4eaa 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -76,12 +76,12 @@ namespace scopeone::ui painter.drawLine(area.center().x(), area.top(), area.center().x(), area.bottom()); painter.drawLine(area.left(), area.center().y(), area.right(), area.center().y()); - const QPointF center = toWidget(m_parameters.value("center_x").toDouble(), - m_parameters.value("center_y").toDouble()); - const double sizeX = m_parameters.value("size_x", 0.1).toDouble() * area.width(); - const double sizeY = m_parameters.value("size_y", 0.1).toDouble() * area.height(); - const double rotation = m_parameters.value("rotation").toDouble(); - const int shape = m_parameters.value("shape").toInt(); + const QPointF center = toWidget(m_parameters.value(QStringLiteral("center_x")).toDouble(), + m_parameters.value(QStringLiteral("center_y")).toDouble()); + const double sizeX = m_parameters.value(QStringLiteral("size_x"), 0.1).toDouble() * area.width(); + const double sizeY = m_parameters.value(QStringLiteral("size_y"), 0.1).toDouble() * area.height(); + const double rotation = m_parameters.value(QStringLiteral("rotation")).toDouble(); + const int shape = m_parameters.value(QStringLiteral("shape")).toInt(); painter.save(); painter.translate(center); painter.rotate(-rotation); @@ -96,7 +96,7 @@ namespace scopeone::ui painter.drawEllipse(QRectF(-sizeX / 2.0, -sizeY / 2.0, sizeX, sizeY)); if (shape == 2) { - const double inner = m_parameters.value("inner_size").toDouble() + const double inner = m_parameters.value(QStringLiteral("inner_size")).toDouble() * area.width(); painter.setBrush(QColor(24, 27, 31)); painter.drawEllipse(QRectF(-inner / 2.0, -inner / 2.0, inner, inner)); @@ -108,19 +108,19 @@ namespace scopeone::ui painter.setBrush(QColor(255, 120, 90)); painter.drawEllipse(center, 4, 4); painter.setBrush(QColor(90, 210, 255)); - painter.drawEllipse(toWidget(m_parameters.value("center_x").toDouble() - + m_parameters.value("size_x", 0.1).toDouble() / 2.0, - m_parameters.value("center_y").toDouble()), + painter.drawEllipse(toWidget(m_parameters.value(QStringLiteral("center_x")).toDouble() + + m_parameters.value(QStringLiteral("size_x"), 0.1).toDouble() / 2.0, + m_parameters.value(QStringLiteral("center_y")).toDouble()), 5, 5); } void mousePressEvent(QMouseEvent* event) override { - const QPointF center = toWidget(m_parameters.value("center_x").toDouble(), - m_parameters.value("center_y").toDouble()); - const QPointF handle = toWidget(m_parameters.value("center_x").toDouble() - + m_parameters.value("size_x", 0.1).toDouble() / 2.0, - m_parameters.value("center_y").toDouble()); + const QPointF center = toWidget(m_parameters.value(QStringLiteral("center_x")).toDouble(), + m_parameters.value(QStringLiteral("center_y")).toDouble()); + const QPointF handle = toWidget(m_parameters.value(QStringLiteral("center_x")).toDouble() + + m_parameters.value(QStringLiteral("size_x"), 0.1).toDouble() / 2.0, + m_parameters.value(QStringLiteral("center_y")).toDouble()); if (QLineF(event->position(), center).length() < 14.0) { m_dragMode = DragMode::Move; @@ -146,27 +146,27 @@ namespace scopeone::ui if (m_dragMode == DragMode::Move) { const QPointF delta = event->position() - m_lastPosition; - parameters["center_x"] = qBound(-0.5, - parameters.value("center_x").toDouble() - + delta.x() / plotArea().width(), - 0.5); - parameters["center_y"] = qBound(-0.5, - parameters.value("center_y").toDouble() - + delta.y() / plotArea().height(), - 0.5); + parameters[QStringLiteral("center_x")] = qBound(-0.5, + parameters.value(QStringLiteral("center_x")).toDouble() + + delta.x() / plotArea().width(), + 0.5); + parameters[QStringLiteral("center_y")] = qBound(-0.5, + parameters.value(QStringLiteral("center_y")).toDouble() + + delta.y() / plotArea().height(), + 0.5); } else { - const QPointF center = toWidget(parameters.value("center_x").toDouble(), - parameters.value("center_y").toDouble()); - parameters["size_x"] = qBound(0.001, - 2.0 * std::abs(event->position().x() - center.x()) - / plotArea().width(), - 1.0); - parameters["size_y"] = qBound(0.001, - 2.0 * std::abs(event->position().y() - center.y()) - / plotArea().height(), - 1.0); + const QPointF center = toWidget(parameters.value(QStringLiteral("center_x")).toDouble(), + parameters.value(QStringLiteral("center_y")).toDouble()); + parameters[QStringLiteral("size_x")] = qBound(0.001, + 2.0 * std::abs(event->position().x() - center.x()) + / plotArea().width(), + 1.0); + parameters[QStringLiteral("size_y")] = qBound(0.001, + 2.0 * std::abs(event->position().y() - center.y()) + / plotArea().height(), + 1.0); } m_lastPosition = event->position(); if (m_parametersChanged) diff --git a/src/ImageWorkspace.cpp b/src/ImageWorkspace.cpp index 38b71f8..d7c87ac 100644 --- a/src/ImageWorkspace.cpp +++ b/src/ImageWorkspace.cpp @@ -1086,6 +1086,10 @@ namespace scopeone::ui } document->activeLayerKey = normalizedLayerKey; } + if (PreviewWidget* preview = activePreviewWidget()) + { + preview->setActiveLayerKey(normalizedLayerKey); + } if (previousLayerKey != normalizedLayerKey) { emit activeLayerChanged(normalizedLayerKey); @@ -1581,6 +1585,8 @@ namespace scopeone::ui this, &ImageWorkspace::measurementLineInspected); connect(preview, &PreviewWidget::measurementLineCleared, this, &ImageWorkspace::measurementLineCleared); + connect(preview, &PreviewWidget::layerClicked, + this, [this](const QString& layerKey) { setActiveLayerKey(layerKey); }); connect(preview, &PreviewWidget::mousePositionChanged, this, [this, documentId](const QPoint& position) { diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 5021d5e..ca728de 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -255,18 +255,11 @@ namespace scopeone::ui m_deviceControlWidget->setControlsEnabled(!configurationRunning); m_toolRegistry->setEnabled(!configurationRunning); - const QStringList cameraIds = m_scopeonecore->cameraIds(); - if (cameraIds.isEmpty()) - { - applyNoCameraState(); - } - else - { - applyLoadedCameraState(cameraIds); - } + syncCameraState(); if (!configurationRunning) { refreshDevicePanels(false); + const QStringList cameraIds = m_scopeonecore->cameraIds(); if (m_scopeonecore->loadedConfigurationPath().isEmpty()) { showStatusMessage(tr("Configuration unloaded"), 3000); @@ -285,9 +278,7 @@ namespace scopeone::ui this, [this]() { if (m_scopeonecore->configurationOperationRunning()) return; - const QStringList cameraIds = m_scopeonecore->cameraIds(); - if (cameraIds.isEmpty()) applyNoCameraState(); - else applyLoadedCameraState(cameraIds); + syncCameraState(); refreshDevicePanels(false); }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::configurationLoadFinished, @@ -609,6 +600,8 @@ namespace scopeone::ui { updateSessionPresentation(*m_scopeonecore, *m_imageSceneModel, session); m_imageGalleryWidget->addSession(session); + m_imageGalleryDockWidget->show(); + m_imageGalleryDockWidget->raise(); }); connect(m_deviceControlWidget, &DeviceControlWidget::snapRequested, this, [this](const QString& target) @@ -627,6 +620,8 @@ namespace scopeone::ui [this](const std::shared_ptr& session) { m_imageGalleryWidget->addSession(session); + m_imageGalleryDockWidget->show(); + m_imageGalleryDockWidget->raise(); QTimer::singleShot(0, m_deviceControlWidget, [this]() { m_deviceControlWidget->refreshCameraParameters(); }); const QString result = session @@ -958,6 +953,29 @@ namespace scopeone::ui connect(m_clippingAction, &QAction::toggled, m_previewWidget, &PreviewWidget::setClippingWarningEnabled); connect(m_previewWidget, &PreviewWidget::clippingWarningChanged, m_clippingAction, &QAction::setChecked); + m_toggleLayoutAction = m_viewMenu->addAction(tr("Toggle &Grid / Overlay Layout")); + m_toggleLayoutAction->setShortcut(QKeySequence(Qt::Key_G)); + m_toggleLayoutAction->setShortcutContext(Qt::ApplicationShortcut); + connect(m_toggleLayoutAction, &QAction::triggered, this, [this]() + { + QWidget* focus = focusWidget(); + if (focus && (qobject_cast(focus) || qobject_cast(focus) || qobject_cast(focus))) + { + return; + } + if (m_previewWidget->layerLayoutMode() == PreviewWidget::LayerLayoutMode::SideBySide) + { + m_previewWidget->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::Overlay); + showStatusMessage(tr("Layout: Overlay Blended"), 2000); + } + else + { + m_previewWidget->setLayerLayoutMode(PreviewWidget::LayerLayoutMode::SideBySide); + showStatusMessage(tr("Layout: Grid Split View"), 2000); + } + }); + addAction(m_toggleLayoutAction); + m_viewMenu->addSeparator(); m_dockWidgetsMenu = m_viewMenu->addMenu(tr("&Dock Widgets")); @@ -1093,40 +1111,37 @@ namespace scopeone::ui void MainWindow::setupPropertyBrowser() { m_propertyDockWidget = new QDockWidget(tr("Properties"), this); - m_propertyDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + m_propertyDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea); m_propertyBrowser = new DevicePropertyWidget(m_scopeonecore, this); m_configPresetWidget = new ConfigPresetWidget(m_scopeonecore, this); m_propertyDockWidget->setWidget(m_propertyBrowser); - m_propertyDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::LeftDockWidgetArea, m_propertyDockWidget); m_configPresetDockWidget = new QDockWidget(tr("Configs"), this); m_configPresetDockWidget->setWidget(m_configPresetWidget); - m_configPresetDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); - tabifyDockWidget(m_propertyDockWidget, m_configPresetDockWidget); + m_configPresetDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea); } // Create the recording control dock void MainWindow::setupRecording() { m_recordingDockWidget = new QDockWidget(tr("Recording"), this); - m_recordingDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); + m_recordingDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea); m_recordingWidget = new RecordingWidget(m_scopeonecore, this); m_recordingDockWidget->setWidget(m_recordingWidget); - addDockWidget(Qt::RightDockWidgetArea, m_recordingDockWidget); - tabifyDockWidget(m_controlDockWidget, m_recordingDockWidget); - m_controlDockWidget->raise(); + addDockWidget(Qt::LeftDockWidgetArea, m_recordingDockWidget); + splitDockWidget(m_propertyDockWidget, m_recordingDockWidget, Qt::Vertical); } // Create the image gallery dock void MainWindow::setupImageGallery() { m_imageGalleryDockWidget = new QDockWidget(tr("Image Gallery"), this); - m_imageGalleryDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + m_imageGalleryDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea); m_imageGalleryDockWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); @@ -1134,9 +1149,11 @@ namespace scopeone::ui m_imageGalleryWidget = new ImageGalleryWidget(m_scopeonecore, this); m_imageGalleryDockWidget->setWidget(m_imageGalleryWidget); + addDockWidget(Qt::LeftDockWidgetArea, m_configPresetDockWidget); + tabifyDockWidget(m_propertyDockWidget, m_configPresetDockWidget); addDockWidget(Qt::LeftDockWidgetArea, m_imageGalleryDockWidget); tabifyDockWidget(m_propertyDockWidget, m_imageGalleryDockWidget); - m_imageGalleryDockWidget->raise(); + m_propertyDockWidget->raise(); } // Close the modal configuration progress dialog if present @@ -1259,6 +1276,20 @@ namespace scopeone::ui m_toolRegistry->updateActions(); } + // Synchronizes camera lists and UI state based on available devices + void MainWindow::syncCameraState() + { + const QStringList cameraIds = m_scopeonecore->cameraIds(); + if (cameraIds.isEmpty()) + { + applyNoCameraState(); + } + else + { + applyLoadedCameraState(cameraIds); + } + } + // Clear preview and panel state when no camera is available void MainWindow::applyNoCameraState() { diff --git a/src/MainWindow.h b/src/MainWindow.h index 18134f8..513a9ac 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -79,6 +79,7 @@ namespace scopeone::ui void showLivePreview(); void updateControlTarget(const QString& target); void updateDockWidgetMenu(); + void syncCameraState(); void applyLoadedCameraState(const QStringList& cameraIds); void applyNoCameraState(); void refreshDevicePanels(bool fromCache = false); @@ -161,6 +162,7 @@ namespace scopeone::ui QAction* m_zoomOutAction{nullptr}; QAction* m_scaleBarAction{nullptr}; QAction* m_clippingAction{nullptr}; + QAction* m_toggleLayoutAction{nullptr}; QAction* m_togglePreviewAction{nullptr}; QAction* m_snapAction{nullptr}; QAction* m_autoContrastAction{nullptr}; diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index 28c95d9..fe39b9a 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -942,6 +942,16 @@ namespace scopeone::ui return m_clippingWarning; } + void PreviewWidget::setActiveLayerKey(const QString& key) + { + if (m_activeLayerKey == key) + { + return; + } + m_activeLayerKey = key; + update(); + } + // Sets the callback providing pixel size in micrometers per layer void PreviewWidget::setPixelSizeCallback(std::function callback) { @@ -1640,6 +1650,135 @@ namespace scopeone::ui } } + // Draws tile names and active border outlines in Grid View + void PreviewWidget::drawTileLabelsAndBadges(QPainter& painter, const std::vector& renderItems) const + { + if (renderItems.empty()) + { + return; + } + painter.save(); + painter.setRenderHint(QPainter::Antialiasing); + + const QFont font(QStringLiteral("Segoe UI"), 9); + painter.setFont(font); + const QFontMetrics fm(font); + const bool overlay = m_layerLayoutMode == LayerLayoutMode::Overlay + && renderItems.size() > 1; + bool overlayBadgeDrawn = false; + + for (const auto& item : renderItems) + { + if (!item.info || !item.info->frameState) + { + continue; + } + + if (overlay) + { + if (!m_activeLayerKey.isEmpty() && item.layerKey != m_activeLayerKey) + { + continue; + } + if (overlayBadgeDrawn) + { + continue; + } + } + + if (!m_activeLayerKey.isEmpty() && item.layerKey == m_activeLayerKey && renderItems.size() > 1) + { + painter.setPen(QPen(QColor(0, 200, 255, 200), 2)); + painter.setBrush(Qt::NoBrush); + painter.drawRect(item.area.adjusted(1, 1, -1, -1)); + } + + if (m_layerLayoutMode == LayerLayoutMode::SideBySide || renderItems.size() > 1) + { + const QString name = layerName(item.layerKey); + const int frameW = item.processed ? item.info->frameState->processedFrame.width : item.info->frameState->rawFrame.width; + const int frameH = item.processed ? item.info->frameState->processedFrame.height : item.info->frameState->rawFrame.height; + const QString labelText = (frameW > 0 && frameH > 0) + ? QString("%1 (%2x%3)").arg(name).arg(frameW).arg(frameH) + : name; + const int textWidth = fm.horizontalAdvance(labelText); + const QRect badgeRect(item.area.left() + 8, item.area.top() + 8, textWidth + 14, fm.height() + 6); + + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(15, 18, 22, 180)); + painter.drawRoundedRect(badgeRect, 4, 4); + + painter.setPen(QColor(230, 235, 240)); + painter.drawText(badgeRect, Qt::AlignCenter, labelText); + overlayBadgeDrawn = true; + } + } + painter.restore(); + } + + // Draws live cursor coordinates and pixel value HUD badge in bottom-left + void PreviewWidget::drawCursorHud(QPainter& painter, const std::vector& renderItems) const + { + if (m_hoverWidgetPos.x() < 0 || m_hoverWidgetPos.y() < 0) + { + return; + } + + for (const auto& item : renderItems) + { + if (!item.info || !item.info->frameState || !item.area.contains(m_hoverWidgetPos)) + { + continue; + } + + QPoint imagePos; + if (mapWidgetPositionToImage(*item.info->frameState, item.processed, item.area, m_hoverWidgetPos, imagePos)) + { + const auto& frame = item.processed ? item.info->frameState->processedFrame : item.info->frameState->rawFrame; + int pixelVal = 0; + bool hasVal = false; + if (frame.isValid() && imagePos.x() >= 0 && imagePos.x() < frame.width + && imagePos.y() >= 0 && imagePos.y() < frame.height) + { + if (frame.isMono8()) + { + const uchar* ptr = reinterpret_cast(frame.bytes.constData()); + pixelVal = ptr[imagePos.y() * frame.stride + imagePos.x()]; + hasVal = true; + } + else if (frame.isMono16()) + { + const quint16* ptr = reinterpret_cast( + frame.bytes.constData() + imagePos.y() * frame.stride); + pixelVal = ptr[imagePos.x()]; + hasVal = true; + } + } + + painter.save(); + painter.setRenderHint(QPainter::Antialiasing); + const QFont font(QStringLiteral("Segoe UI"), 9); + painter.setFont(font); + const QFontMetrics fm(font); + + const QString hudText = hasVal + ? QString("X: %1 Y: %2 | Val: %3").arg(imagePos.x()).arg(imagePos.y()).arg(pixelVal) + : QString("X: %1 Y: %2").arg(imagePos.x()).arg(imagePos.y()); + const int textWidth = fm.horizontalAdvance(hudText); + const QRect hudRect(item.area.left() + 8, item.area.bottom() - fm.height() - 14, textWidth + 14, fm.height() + 6); + + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(15, 18, 22, 200)); + painter.drawRoundedRect(hudRect, 4, 4); + + painter.setPen(QColor(0, 200, 255)); + painter.drawText(hudRect, Qt::AlignCenter, hudText); + painter.restore(); + break; + } + } + } + bool PreviewWidget::markupAtWidgetPosition(const QPoint& widgetPos, ImageSceneModel::Markup& outMarkup, PreviewInteractionTarget& outTarget, @@ -2216,19 +2355,20 @@ namespace scopeone::ui drawRenderItem(item); } + QPainter p(this); if (m_sceneModel->hasMarkups() || (m_roiDrawingMode && m_roiDragging) - || (m_crossSectionDrawingMode && m_crossSectionDragging) - || m_scaleBarVisible) + || (m_crossSectionDrawingMode && m_crossSectionDragging)) { - QPainter p(this); drawMarkups(p, renderItems); drawActiveInteractionMarkup(p, renderItems); - if (m_scaleBarVisible) - { - drawScaleBar(p, renderItems); - } } + if (m_scaleBarVisible) + { + drawScaleBar(p, renderItems); + } + drawTileLabelsAndBadges(p, renderItems); + drawCursorHud(p, renderItems); return; } @@ -2357,6 +2497,7 @@ namespace scopeone::ui glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, kColormapSize, colormaps.size(), 0, GL_RGB, GL_UNSIGNED_BYTE, atlas.constData()); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glActiveTexture(GL_TEXTURE0); m_prog.release(); m_vao.release(); @@ -2426,6 +2567,7 @@ namespace scopeone::ui frame.width, frame.height, GL_RED, uploadType, frame.bytes.constData()); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); cachedTexture.uploadedRevision = frameRevision; } @@ -2793,15 +2935,55 @@ namespace scopeone::ui return; } m_sceneModel->selectOnly(QString()); + + if (interactionTargetAt(event->pos(), target) && !target.layerKey.isEmpty()) + { + setActiveLayerKey(target.layerKey); + emit layerClicked(target.layerKey); + } } QOpenGLWidget::mousePressEvent(event); } + // Handles double click to maximize or restore grid tile + void PreviewWidget::mouseDoubleClickEvent(QMouseEvent* event) + { + if (event->button() == Qt::LeftButton) + { + PreviewInteractionTarget target; + if (interactionTargetAt(event->pos(), target) && !target.layerKey.isEmpty()) + { + if (m_savedVisibleLayerKeys.isEmpty()) + { + m_savedVisibleLayerKeys = m_sceneModel->visibleLayerIds(); + m_sceneModel->setVisibleLayers({target.layerKey}); + } + else + { + m_sceneModel->setVisibleLayers(m_savedVisibleLayerKeys); + m_savedVisibleLayerKeys.clear(); + } + update(); + return; + } + if (!m_savedVisibleLayerKeys.isEmpty()) + { + m_sceneModel->setVisibleLayers(m_savedVisibleLayerKeys); + m_savedVisibleLayerKeys.clear(); + update(); + return; + } + } + QOpenGLWidget::mouseDoubleClickEvent(event); + } + // Updates active drawing interactions during mouse move void PreviewWidget::mouseMoveEvent(QMouseEvent* event) { + m_hoverWidgetPos = event->pos(); emit mousePositionChanged(event->pos()); + update(); if (m_measurementLineDrawingMode && m_measurementLineDragging) { m_measurementLineEnd = event->pos(); @@ -3104,7 +3286,9 @@ namespace scopeone::ui // Clears the cursor readout after the pointer leaves the preview void PreviewWidget::leaveEvent(QEvent* event) { + m_hoverWidgetPos = QPoint(-1, -1); emit mousePositionChanged(QPoint(-1, -1)); + update(); QOpenGLWidget::leaveEvent(event); } diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index 787c983..e21183d 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -85,6 +85,8 @@ namespace scopeone::ui bool isScaleBarVisible() const; void setClippingWarningEnabled(bool enabled); bool isClippingWarningEnabled() const; + void setActiveLayerKey(const QString& key); + QString activeLayerKey() const { return m_activeLayerKey; } void setPixelSizeCallback(std::function callback); void startROIDrawing(const QString& cameraId); void startMeasurementLineDrawingForLayer(const QString& layerKey); @@ -107,6 +109,7 @@ namespace scopeone::ui void clippingWarningChanged(bool enabled); void stageStepRequested(double dxScale, double dyScale, bool big); void stageZStepRequested(double dzScale, bool big); + void layerClicked(const QString& layerKey); void activated(); void mousePositionChanged(const QPoint& widgetPos); void roiDrawn(const QString& cameraId, @@ -127,6 +130,7 @@ namespace scopeone::ui void resizeGL(int, int) override; void paintGL() override; void mousePressEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void leaveEvent(QEvent* event) override; @@ -234,6 +238,9 @@ namespace scopeone::ui GLint m_uUvScale{-1}, m_uUvOffset{-1}, m_uShowClipping{-1}; bool m_scaleBarVisible{true}; bool m_clippingWarning{false}; + QString m_activeLayerKey; + QStringList m_savedVisibleLayerKeys; + QPoint m_hoverWidgetPos{-1, -1}; std::function m_pixelSizeCallback; struct CachedTexture @@ -329,6 +336,8 @@ namespace scopeone::ui void drawMarkups(QPainter& painter, const std::vector& renderItems) const; void drawActiveInteractionMarkup(QPainter& painter, const std::vector& renderItems) const; void drawScaleBar(QPainter& painter, const std::vector& renderItems) const; + void drawTileLabelsAndBadges(QPainter& painter, const std::vector& renderItems) const; + void drawCursorHud(QPainter& painter, const std::vector& renderItems) const; bool markupAtWidgetPosition(const QPoint& widgetPos, ImageSceneModel::Markup& outMarkup, PreviewInteractionTarget& outTarget, From 79c897c4c888db733bf4f40cec746ecac849c4a3 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 13:01:13 +0200 Subject: [PATCH 13/37] Fix Analyze layer panel layout --- src/InspectWidget.cpp | 6 +++++- src/InspectWidget.h | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/InspectWidget.cpp b/src/InspectWidget.cpp index ca2ec8e..3558876 100644 --- a/src/InspectWidget.cpp +++ b/src/InspectWidget.cpp @@ -583,7 +583,9 @@ namespace scopeone::ui scrollArea->setFrameShape(QFrame::NoFrame); auto* contentContainer = new QWidget(scrollArea); + m_contentContainer = contentContainer; auto* contentLayout = new QVBoxLayout(contentContainer); + m_contentLayout = contentLayout; contentLayout->setSpacing(8); contentLayout->setContentsMargins(5, 5, 5, 5); @@ -647,7 +649,7 @@ namespace scopeone::ui QWidget* InspectWidget::createLayerInfoGroup(const QString& layerKey) { const QString normalizedLayerKey = layerKey.trimmed(); - auto* group = new QGroupBox(inspectLayerTitle(normalizedLayerKey, false), this); + auto* group = new QGroupBox(inspectLayerTitle(normalizedLayerKey, false), m_contentContainer); auto* layout = new QVBoxLayout(group); LayerInfoGroup infoGroup; infoGroup.layerKey = normalizedLayerKey; @@ -687,6 +689,7 @@ namespace scopeone::ui infoGroup.maxSliderValueLabel = maxSliderValueLabel; layout->addWidget(createStatisticsGroup(infoGroup)); m_layerInfoGroups.insert(normalizedLayerKey, infoGroup); + m_contentLayout->insertWidget(m_contentLayout->count() - 1, group); connect(minSlider, &QSlider::valueChanged, this, [this, normalizedLayerKey, minSlider, maxSlider, minSliderValueLabel](int value) @@ -775,6 +778,7 @@ namespace scopeone::ui } LayerInfoGroup& infoGroup = it.value(); + m_contentLayout->removeWidget(infoGroup.groupBox); infoGroup.groupBox->deleteLater(); m_layerInfoGroups.erase(it); } diff --git a/src/InspectWidget.h b/src/InspectWidget.h index 17133c3..a1fc1c1 100644 --- a/src/InspectWidget.h +++ b/src/InspectWidget.h @@ -13,6 +13,7 @@ class QGroupBox; class QLabel; class QPushButton; class QSlider; +class QVBoxLayout; namespace scopeone::ui { @@ -103,6 +104,8 @@ namespace scopeone::ui scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; ImageWorkspace* m_workspace{nullptr}; scopeone::core::ImageSceneModel* m_sceneModel{nullptr}; + QWidget* m_contentContainer{nullptr}; + QVBoxLayout* m_contentLayout{nullptr}; QHash m_layerInfoGroups; QHash m_layerStates; QStringList m_availableLayerKeys; From 9d3cea6a3c1831cbdbf334a67121cb7ce4de131b Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:02:58 +0200 Subject: [PATCH 14/37] Refactor preview viewport transforms and unify cursor readout --- resources/Screenshot 2026-09-01 140040.png | Bin 0 -> 2570481 bytes src/MainWindow.cpp | 52 ++- src/PreviewWidget.cpp | 455 ++++++++++++++------- src/PreviewWidget.h | 31 +- 4 files changed, 383 insertions(+), 155 deletions(-) create mode 100644 resources/Screenshot 2026-09-01 140040.png diff --git a/resources/Screenshot 2026-09-01 140040.png b/resources/Screenshot 2026-09-01 140040.png new file mode 100644 index 0000000000000000000000000000000000000000..66e733bad2537ab7c6bb5d7862d84f4875aed4fa GIT binary patch literal 2570481 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

64j$dx@q!$ro2D7wwpJy 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ΠQ!w*HEFVdZ~y1YZh71}{OURy`dAOR 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%0AuH@S`-Fw+?`kzxaUax0u>$yN`6IE+d^)0Jb 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

rkY8tSFowq=H5&P?e`o= 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%0AAh|#wDM4Ft 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 z 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

0j<3#tF==bKm z50~uu4vcDyQFAnhOR4}%U!qY?h+lyzj#tq^c?ED5?U+DXE|Tf2 z@qI#5;7Iw|vr-`X#M5e>3NF6NA}Yw<0{dp&GmLRQw0`kSV8KXYqW&n?T)fq({Lu;K z)sh@j32TE;e%Cgj(eMxEx@eiHuwkmq_`Yn3((+JNo8ETo9!hc-7I47a&0SdR1($}H ztn?zJQwOSZ@P*E{+~31CesH!j+tAdzB0dw?Uu5scP;Oi~Vui;E$}eIC@+f4NA}wN@ zTUe>Y_VGeAj?J*og2IV+u7(4xe1QRtRJ4q=nH~h|DTnqg6x~M6Q^up7!~#2a1C4u0 zBMN5KmcMP8N!}m$eQ}bwALk(W@77%hor&+wQKM#}ddWg!`wWAd` z$mzHtuEiKa5%$b-5mYqOv|iK}8?vc3mz-|e(8;VWo!4Ay*k7@3ZUzhdnwp!+im?9J ztz1x;^oQ)!4u}b7gPUO-mfb{H%r#e^VnyMFTNgSRb+UeT=f8@hMCG!6>E}C&gsmC> zd+LwV(uvlpzYCVjnufsp`%hY?Ikiav?HSrsN|*b^Vu3r=|oZar|(4jb9S9f9whpzi!P_oUYZQ}!&%NZn$o_xasL z;?n!0m)@OGmqu;)z5YJkA4mZ{m`DjdnI3I(&tW73!z8q#^f0+<%%0DengW&bF*pL! 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 S6b!x_u`d*I$2FLKkyP@qPbtF9*H;*xNmcwp-FB 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

(}wjV3b?%7rIMXFii0iR(SuIIV6xU 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%0D?FdpaBeFb}-FT>tS7{ouDptv1pKj!Gh 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 vht1lZ^GJbz z!Rk^@1kv|#m@K=&lSE2}=hpU#eezi8eA}kbpGNy_%*6#=|G)e34We}Uf6QC1cL+=+ zFNx3G;%%!|oA?_|ogj5H=oFy%WY(xh66cxc{L++NOdCzN4H2IZ30#oHLy=6-@-Jcs zAQ)=Yru)3~epRf6JwMJ;S;l%nc>;cBkpQfv$E%=r-JLbzRnQR}67XdK2`N)P!bUtM zpQT0UU-&p3k4`+`1+{>oi;YL9bZmNHqb{%kue-1Ye2)2RER}wybh)MiWV6tUGlo3; zC#>57`UPmBxu|{!1=o!CdTY*dFnV{yuQ@PIy(FtJG*OX2*xK z+Tc$FpK>JIuA*(+)&U|L8p;R{tS5=uZaf6iq#2MI+{OME$XqEi(bUL$_NTNC!i{I+j|C5i&`U{d!KCsenKy?suVVN!WXuL9t@>RK*)`=i;~>e~m?7dN97(%!L1lXh}*X^+GDs<7t#e^`od%j8eA z2z5O;vt`WhiGgA1XY;1NKcwft8)9pBg#%%#=D%B1Dw9p*2gP8D+V$cV0E ztC7Fzf@Fw4vgdXRP884GWyWYGu3m}BQy)AziS#ccbv}W{*G}I}-VP|X5xGWIeAB>X zYc!8zU%ESnmRyL9T$wl4e+1o$rXSEgkrEa~N- z@BOlTIPJ=*&2K19uG7C^xhRBADXYHYsGi zRw?tgXK91p5$&5Yw=d~*GKIiK&+9uGpI#?kn0EJ-DXf4{>o8XdDzj-F6MKu}3_x3U zD#xZqU(dL@R@zeV(XK(T3(u{9WPD1Qa%qfeK{W=QbCl(vw%TrFSg03Boiw{PXQV5p zaz6iKrtZXL=a)+g-}T0g#>Y&9(%b@SK@h%7;*+?f^jsDhcxA|=6}cuOb0r%k1=`? zVRD*fR6%>w{RQWdM-dc*>eH+F_XxJUN~5oWBxxxAVE2c(`P*OrW6AfSo)`XUR{(@f z&&5w(#KxL%)fej)JFP4&B?l%}{+)6%>|Zt47#heeYSWoUI0>}~J8tZ<-Nb)@&;3k# z1gAArTN*ivWTe?3VNX&*HJZD=_yO(MPL)CY0>fz39a3`@;#>+|R*!){_%Z6L6Aft8lALj#Yr@&ea%(!BII>wH=A3+d=>aN6^v+79cW(C|( zMx?xP@<$t$qiMlTc4h)Y(EgYfJF|);mbKut`kHOGb#Zp@xs#^Le}+B*{aKi&(5x?$ zr$1qMI-MZ$qT2e^4}Wu+u`WIWRYe1?G_(E1Id8Jn^Bq2vr_*W0PpRLWA+#TV!1~&} z@}N7-fF7G0L)$L&;)a5B<)#n&k%=Cvo53VoaSd7cP-JvKJJX zE}O}d+p^KeTrIb6ZwVC{Ro{?3&AD!gzy7>e)B%x6cjpHz`hBar8C^Adx(3E+pco^| z+C#=tcb6o^Po5_0CZ?wll{``I--x`zW!}fry+cVRN>w=5!3?Q@w5Hk7LeR|whXk_BS+?PRlxTc;57c zeS-b;IUC9nwTaPoH7*n1Ttxq0(IJ+YBVQu^7ylN?wb?EMZ9RzCsE!y>Yfk*vz*fyr z#_<0Me!h?*4ci*e#v&m(Al>|R(naUm87#I}JRL7tOwUbP_C}ZtmHQ0NB@0F#V$=UI z+1r}&`(xdN4r$mft8wx_QFCTxW{iFO-||c-%>UR%)fuCm%T!;Dg*g{gLNAN}67l>o zZ$oWNUK?yK({Vd-ZOfedPFA)|=wMCM>>507`Sj1)H)}q__x@$~^{=}o{|Lt4d;K@N z?$=JQjhAMxUz@p@`GhKq2$kdkn#qf-4DWL@)|X?Jx+_&b4D9~CDF3hOk0ZZbzjD;N zIwV^g+}P#tL1Yvy3ZlsujbW&6I%}bT&Mlb}2LVYTj_mF2jqR;%@!`NSe^>!5B+sM) zjh}ytd-;MKkb%KFz_QYJ6y!{h};XfKwOx9Ux7r#!FJnZVYnwvV+0(A9F zy!8wtg@jFuW^#<1e`7MDUUk$0i{SniFO>P99nb-gVX_G1S0D%+zJUWi`XTL*;4qb& zgW6oxf=@(;()mKE80m#ZT0)c5?BjWF~^weu9czW6weZN=bKaHuHN3)LO+ z2iO(50xMR0aiGD^ebRuAg+%z-JOEoDB%D?Qw|#}flM&&N>7sB}*j$De+cKr&is$e+pBP55KUdRXfX#8jGB{>8ao0DOC?`Yqdb353G^=SQmI@sI9Bi`iLih9u za|g2uI#= zVU7}NhrZIO{Q>~U7k$~}>FhBFnb5|jSu0Zd`WnTgK|OH(cetTWvs+?aD9101e{OEv zV_hu?3Gmbpl!dOgn?8usz}@g!Qk_ZRyi-kFx?<^Q1C$aQo<)~{+#GYTbeT@R?2vYb zgO|RV`ycGj$@HTSu5McD8b;dRg`v?06AGZ+qDS3x$Zo0X{Z zSrfo&!t%k{4$#qsysAnBY5mkrd)OAI#2{=Y%&4#2DI5_Mghb38^O91@B*T7Dz4)G# zCBJ5&YJYi!iN#$!A1L^OI$MJ^{PN;ySHlI6Lymon^{uh=vl{+{(0eIcYC3Bu-Rlf2 zP{H{H>y*ovQNhvOS>&Mf4F<(k@pK&yo4=yv#PgB1zomP^4BIs~%g%=5r~4g0jpX#X zU)i+vLxm0+1~~gz%6h+xAw=I*tvpF4aezN8C_VB&$?5qhZ=VQ5f4PUo^Oi;*jfIuy z@=p!*Jule3wYT=R1$OFFV3)_;R1TJpWRD5mKj3%5I$skBu4s`n|vL-kyT?7Z@ zGPSF+K&+0>4_ab&Yv9S$k(1Nu*!+aCL}Jq(ECXjg*oK2Z!-RLyczRugSc>>qEzKla z){i@_`-z9z>c$OoN6HE->p;DYC=9<2Zo#$CvEb zqlrX9-nQC)Lv9i#NX9W-pKgS=9w>-Bpnqz5q}crGOZ1+#Cqe{j_r_$_k5P_s)+(t=}1T}%y)&K1rEGhT5nf%#H;vPM~SkbKypJ0XNZLQ0i2M2lQ- zjO<$mwkE*h6N(i-luubtYJd0aC#JqYV$WL%R<6Bke2{xcC6jV^iOsw=1c1(XHpV0-Ig{k|B({5()eVIfseDFGeiiKdemyj*|`wNq*hr%54ct*yySkMdQ97 zdOm4r)Abyr#m`ju#Dpn(Xqi2Ab{-wvVgx2v%i9`)Um5)4D+LW^S*1T>=YxO$#MF3m zVZ{SVh{9x2jNEi)tc-1dj9~9Bvd+nnR0>=ira<8zqIGE~La2`;{T3A-_8FWl|AAti zjiM>KcbX!ZS>>Lrwu$-S2gM55=`x&=6GiY9A^MPW#@v8owYg%aS#1{-ify|0Me{obdxj0I^<^_u%^?>0OWsJMhcBGv>mWvj2kT#)HyZ5#I5Trad?u?S3)B->%BXGovFMPk z@KG`*KDXZ*$6WjUIf+&)tH5?$pmuYv=0&HrLlspN-j}+Bwq66Lt~>!JCB+O1`&5L; z8SJAKaQbT1KH17ooJ&irk|g{rjs(h9k9CObyJ(LB2Bwaz#}1rhVx70RfR$)vWq+&m zhcniw={K`vcRg@Q5X8rE!DqX6;5(#n&cuSZF!KyM*XqHdDJj)-`?Ju++haw7jcA(Z8S6U%!+$`@NPWkP*>)HyxC_ z+Uvy#-8=2e_!AxH5UL1v^Sa)dd3>`QrTnU)PIGe%+b!I=nS;s;gACd6N+|Wz2vit7 zlOf6eCT=3UZBXs7^6%?J>tz=lVmLoy=|0A3@7~5AqoE`*R~dH0QK+fe?ZoR+s?r%~AV9ppHBT8XybIuuV@04Nyh(+WYEpI$@I1G*Zu|(fyEJu)swdTwrS^!%Z4LiA6XavMI-$fPO#`m z_-))pweoSX8Zu{{vftxSq7Yq!eeb;@KFlEp%#H=QW|zS=TAFX&_p|DyKX+-^Mo*A$2D z_f*QLBDVbHDCf)5t}Mb8qXbvS_|H2jb^|Lbk2MNZKP}8fWKb-{IfHnZ*jgd_gMa;N z_<)DL_jHeA^$_bSxFE9UH|?>xaRLM$Bo9OR~cSIa*qQMor_%GT&Ka3M_Z!7I!5S1PPjanXlP}ln)<_^ zmJlxii_4P`^>oqY{Lb`zg_3$x{<~E|J%mv8V&gXwXP?$ewtFRAr1tT0MMRy@9kXrU zS>I-5x)3b)wRVHLHZ$XjJDwII5O@xsp7VOF4uhLW=3Ql|f>#$d1trg%>aJ;rDLa2N zw(fg$+qtfUkQ4bi%_zY!^p@EKi(`m$wQ+((uXq$k^#nn(bm{o6!vBIbxU6#jj)xoIFYb!!aH2BniP67 zLzBKq^T2|rPn7*D_V28aIkt*im%1H9c426b?~Jh6eRS?MjBMK{@P)ofO1QIS{qzc@ zZYf6D71$+z<<*^83-0(O2v+#17KLZE<)NklzQ(V6^1g>ux?6|y#SW^HB@Gtr25fz& zcnkTRpV)imK2&Fg!{0}Iz;|~|Ha-yj#LNn>=x(zP&$q9sZZEtPSP1w#b-Njh!+0Qm zociKH=O(+n2!b!`ex|k3Spl(s3`&oL;0M-DAQ*1 zF4zfrO%EY?KrQ?1V2vBK@M;eIZn5eoyI|Q`Mw^eXCFg^k?fTw}&Xt&t`2^IjO`LD= znV(3<8*`+F#C4eYaM;84*?POEZgw4Tf8s^=mqTZE1cttahXL8RT`(uh=>e;q7 zNc7$@`snlqnkst6f->X(Flh{RtHJibT8ei-kLch;ba*L!!WSvcbk~3%fmVjJ@EuoLKDjwDS=RV~V3k8q$`zq_OM22BHg|JeMVo$3c-9^pj>aWN9Mp3B4X#Z??O*lJBwfV7w^L%TPVTbuJ!l)JGt2OHEs2JcyBC_fw+mv-xSzswZp19F_k zyziiSZFLcdTK&-4d!~(-5d_0+yAD-d7W%3a1sNgmS=LsHnBBW`)$8s09rnEX?c)U% zX(%MVV>9Uyt_;Vp{-T^4*|kNit4}*wibV@L6T7hokcoaVdRZ&&SEv!0xNW7}K5G>> z%BuO4sK>8gw&x$G$gyZW_ux|_r=6UDF%=EWhZe?HKkVL^a#(~+hL>)dD_i0^C5~Lj z1?t**6wR&h0{hJRXzx&Sb2CPXM9cMXwG2F4o^3`Piqq!+Wsj2a86P5hsa;(xJ=~jdDN^;Kr4MUP#Be3mPLL zmaR@`8UOe__@3Uk3^fP5`b5hdqKZs?0WB`-meN~13Bl|0|EXNbMaezRuSI)m^WceB zy%sV)fk8%@3aU1Ro1usu?7UCIIP*A;VAXWEFyHR|p8aIY;i>O6ksX4SotsPgflJiY z4yr_->HWq|_qOfSagf|PkyBB3*#b!FEf&z@ey7O){n#Ms3_5I}@64SmP`|ksTcTg~ z%9~q?a2+*WKjrv-P9Yul+A>V+Ve=lD=@QWsp_@q?S~g|@>C_9dZBYqbS4MqeAnQD@ zQ@_l4pCc6Vq-XKq;uXPj5BzY~;l#@H2>;+^MDr&vXrihs$t0Bo0)=CgJ9MjAgH?3y zmkz!UOlbXOzRQ@5Ks}H5vYOWz1xFE-ln{U7)~9T!ku40;Qy=8jZ|ZM3^UB#(CgB9r z$7kRcQ3q1YCZ3at5LWzyZlR>G1yb>h7hje}F?lGpZqs4>ZJ$#vfs zc#@QTzxx=@p?sSppV&DQxjIu8LAa`{zQ=H1S;dW$Hqu+@N-JG8_2i08(GuRVT-lj! z-hASL-Hd~drH#GSqUFFSItxWvgAd#j0lAdtQc=;M^9gBKezVS_B z9Nct?FSzRUzLCJ0`sgn#&pcxx4eTu6NJ2<-~WpSEl$Dm*a^!8w}oZ?@v_^Lr^7;W)tt3W=yd%0kmKhEuAyR9em*2p@mzFGUtJ<%N#n8Bkn6azz z5UQ)x7u}cXqci=0it}?cbWE5+6T4nuh^v)ZLqoCU=35^%%nEb_sk1q>h$SIM;3Oz z`#Obf%eLtfZ+S6RpCq$4$ES@93~Uous_GMDpsnXfqD3+#NL{*|Ua#JZ{c9T-n@|7W z6A8NBFXGGfV`J)w^FB?NY~b4kF$?v|nC!$|!#3i}h^rp}47R1(kLyU^Mf~$j8-Qgy zMP^5cwB>U5cfKp3in&~DG$YMs!;aS&0cOM95cD!}2aaKCv0krtunS6ZW-cNz7ZxEya!C%S3l72X}S66=e5!Q)^fX|4ZxdLJe?%1K z=nLB|S=BnbYunx*z7D*RgK&*lP|NYJmOUO?6qzjfk7w&cEmRIR=84Kd2+HbCPAXcM zPdaFrpjNd3tz``i+V2p%D%f#dV%;06Nw`=&Jkwn47?M?JFxTw1Bop=zcJ2nOZjZ`| zc~O(eBRKF;{E7O(_3|9=s+RKdxRd(1-#v68&l~sBb_%0#=0&!lcOD6(*k1 z|1%}-c_*aeQ8q$CN{={Ws81#-l%LKQbhp2LMib>_iZaX)@uW$;R`tqjFCzM5@A`d@ zjr$%6ypqjAkA4f|%vEf8PbV7(KG$3iV{#v1>N9#>v=Qsiu@H_cG=X_bzq82Bu`8vJ z6qvbvoikXW@8hqU{bL#aAa%?zY_lOI?AGJtge+3s8lVt3Z9Gv(CKuMhi+YQ)qLXoN zvO**Bj18xYdtZUHoG>9Ao?t@m27OWZ`{HW8xi-epf>q2auVxrN+ zM!iq|n^Wm*1CrXef9Crbv2`6c_;O6;Y>auC;Y@350o31e;FkgzeP^KPqw1omxTjHY z3M96LVM64N*!Py7du@umJ3=pqs};h@^ae!zptVyN9(x3t^Pa1Vyba)1)qWR8=?|uL zmDs<@st{IW1vc6!8}}H_6IE|`S+Sh21W7~G438^8LYS|JpYtdXm#tyM-3uKaa5D>A z4JtJL32gQpE(@011(m}UA#EMy_PN>wMf>enomWu>XIuknw`Da&cP-}&n8RlY0oOW0 zk6WldyAVpRSPrz+XUEJp45v?HTQ+}5&UxmQL)hv)iLA!YT7lr@eM`@#q-}=#Qgrdm zsOTl^M@@fXGjI4m%h)XP)u=)?J$}u3Z{)gfP|()q(H)fi)>3JyCU)}h2Y!LgT4+E) zi1=S7%;C3)#np ztO#3+=pc?i6X94k)VaHKPyDPknVZdSNT;$u>MuVau~hfM$Kj~UVe?Ip_CK?U6R1*! zA(A(jeMo9*I-kOHY^iASvAMHL~G z43Ebt-f^NBB#VO`fzi4^(E8acKDf2LH^ig9Ju~D>J(lG-wmoe-d|$uXpyLEDw2*zN zzxYC}bq|jqAkxD_qRk@W>X}Yep#iFAUuRol`n{}T5F<1*`>C+*5*@^Rr{Rf$ydmm0 zW?`K3zOWZsT3`rmWJ%9^%qO7gaqh76^%Kq8p_g>pqD$R!5WX}};GO=wi6`Y72wl5w zUV(CE%jrYSHnT&-Z}=uqy4-v@*5-4~kBn*Hr_PQ%(+YQ9uH2*oRn)qf8~0S(z0l2G znF~|q(huF5gc^^cG&D3uEDL-#I`ZRL&%l(8f4Zyf{Fube>wy!D#0!TF3S@ZYD_Win;EhPk<@wcX1EDM0^H2=tk;E99>mnu~Ih`deZMycnbW{0zUX zox$12C9}yh%lL-td>lmj&ZuEEzwIBqhx5y7^%YCiu$dgBj>s`rC4zLL=2Nlyj7SJy zjqld0p1sy_fKe+LVVzm0)Zl<=yY;v^TF?Ik|M;u&Y8{u_iDL|0CK^AY=1Kmo1KmBUgW>sd;%?4&M`dZSL^$k3t^h5k19{7xSxH;~+_y@3AqI-8j-AdN)-Nzq?Qs2eZ5kmsK&KB)&j%g=|D*ghcLw!x@8l?U|Z(asw|L+;8Q> z5;@~(y$2Q>lyQN8{`dD5!(U9zN8$LPt7lD}>Xi>$JrS}$I^gyz9mN(Kg0Ijar;vtI zEm?j!&-12AEsO-$VnTs`USBuNozET~_PrrYM^y*B(-6}~LO-9WZd0?@xy2W9qmAKO^TKn1vI|@<&d!v+QwgCd;DEOU$67;K17H ze$9^pA?;9fs#iGo4$66LwSV9uZDk~Nu8ym=6+Ce^BZYQow(XL?AVWWbnHY?GWA8q` z0s7A~$e!}x?zb6% zgR*_>sP3eK(BtM#-tX(q_*c#*bKl)MQ&L$Mfbt)>dyCMWz?BqhMM!CESOPwv|HdfG zL756w@BMI_XhCJxOZPu+vhP1nt(^NS97A=OsX`3&<~)g*-wi?z!+4?2H42V-co#12 z;o^+9A1Cz!MiAp?_PMoJ6-fyp7X0ay@ch2(S`==VVWh)5y&Bks2D*teroXU@?x3H} zM66h)ZR}B0_C~&YakNX`f1^9Nn0$y3?c+4(<=1`_^~DYm8D2oL3q4YVZN%z?F4^rP za~nfK&oXPn{2H=@CtJBzOnbZH!-=p1{g<3{&J_C!1GB>|chn3|L@Z8*jDI1=?HE?{ z+abZLFZ*FL(l}IpRXdPn(3xp&Uk}%cdYRl5_uMN_)b-RBk>W@Nn0KXv=lLF9rF71O zVf)??wU|6?Ky?cnD8%h#`DUkNkvumCz1XVeIQc=Q-L5MCFQgy8Znoghnyu5Z>*<{3o2g_3w>zfJ8Bri?rq#rFpRTN?WRIdw9T3dx) z-~!E^{D(?$;+8l8HSU@KxQ!AkhFG^)|0I+eg8>fNt+_~?RXvL^UR2yRrC>zA?L}%% z9bWfg%6+tuZ}{8R!?p%n4`?ivTkQRToUS?F> zivj55>-r48X!8~z#luDVZdc6RW%^So0&@soj3J-uN?)$u#;8`f=fs&KifT-^QgE%~ zIA6u>5IK(6_E)8?Ke^sbCpnl&t95eKyvlnKWjDv+H^3c`hA3Li`20j=pHx7nr ztWCcTUl{tEi6P=;AuNo&7*HKZzvGD>gy&BEbZQtQf$`wGYaZR@$|fE`-Qrgc7w=vf zh@?w2$ip9>>56-9X~P8}Wg#b=Y3eBpb0eCTv$X%#fnf+0mMF=qN%{pkcBJ~axw)Yv z7nPd;X@AR&e)A|o?P2Pu>N{2Kxd`-Jc@LDTCU?^>TU8q-R$B8oPxCh}`ASoGR~>C- zl1EjyMllF4mQhl8qk0ZS&}XXpM@Q>4g;DK`wm|J1Y?$(A%bj-Yw=`&+2{ z@Os$3D|@BCMBEmAW}O2Ud=5frg|rtIEwugSEFHp6qR$^)S=`#~$O_&OIQJ==1}LLC z-cH7l&R=;v*9KB}pwt)rTO;cP~16Vc}b28(||87gDg^dHo`UB}-Rp(Yhxz&%BvB z8MgQp)K1vu7cL8#``HCmH@wn1Jy<7M{a^vwRKS{@nC|o9k*n*Eo=UipL8y$)Mh^$^ z!b<PexcLV-QUiJ z`3L*i0Qyn^zyV`*yH0?&qIy?nubr)7d6`YUelYizOs>yypn$_7tt|vSrAUaS{H(;+ zAvCJQ|9zH^Vb>M5vkt}kQ$BfqE|m5rWW5#d`#VpiI)m^;)TI##^J!FwE*rage63s^t+-nkS`Yj?H_=32rQZE$c5KAHu63 z=||mh-&`qtp%@^bd3rWRIabu1?5=!glJfWeK zReI%cfJ9)oE@z?#z81#3u{duQ+QOm};NYcyh+D;VUA%A{pf*1Z%0>;@WnK>)lo?e! zdunnvS~UwI;F_6<Hq0C~U~O-g6g zXs>l?iT#9^8Ly>Lo_Lr4gL*5}E?{X!VcXd|W!6Ytod?^r=JSB97#`2vtRHW)0Vc}| z5=u|R$gRDz?aNljXVxV`{tszzd)gsW+kX#|hb`S;my=tm)43ZoY;>n9zqOsO`2B z=K^21nf+Q?=?XEOv|XyOD5!8}eCO)|JODTX;t4l_AvQLZAZZTa&Yj|N7_k$_$_QLM z@S8L@|A?1U@bZle8T<&^l*!L@aO#QL3aQ~lcKDDzvnIh@S`s^{1WD?Xg0Ae*B zT;|1%o6UV9PwUqWL` zRpUW6oAnb!71E>rSMQ^IeX-a=TvPW_e{*OqCOW}H?FQ+hXRo05J<6_( zdf~CR-Wq0kA=!XcvOgTWbE?%dKSK%k7NSpG59$_SIhUG-uaX)_-@`eBKcCf`D7iHk ziro(us!Ddg(HUEqX(_%vcMddSJ4n~HUR&MIhMDI?Qe&h^&Lo1BZ{6Y64$m{*5i0p) zbQ%0e3?mamb)p3*vVNOCXQZeoOd-Pbt~iA=;4JE%m11Ts-rLHWF1e7Xl)y9tKD|iz zQ&T9Y?9B)f{4{Tz4S}@Z6TImtI3K6{x*r-k@Ew8(@*^*dos&4m}H zdvUmCo;_>GH+Af3Uhqrk=UkdtZ;_>Zntv%e1q?^X`?1p1fI4Wb=N3W-~)Hc?@9nOs__>^Sqg{u0ovu$AL0 z|Hh~Lrz)k@l5xAM6@vYStDPI_hiHGK2NiacIz9A!_5w}G%R@WW0}y6Trw@_@Laqtk z?xCraliX61`7~y2Gt)bC`XwkOb`=*UrC7?NU8y;%LZnVJOqX)sy7&2^edP$H)T-#U z4#;el_k}hndQrdX8n|A8s1x)!+b~6cm_=6NUY(__#D@2o-$s2q)|G~WFAz*h1`G5! zA7K6xj_qdmv^dYNEqig>7vO>Zd)|y^Ug%a=vwBKY084P7f?`d*JA|ou?I|! z_A9k($~Qzj4NY%7n;W~J>`TRNV!v2*c`F4aIH#4E@fzRcMhU7L(|1d^Ez1b;+-fr)nvrP!J4G*>E@I+Iz{1ok6gHfzOY~_Pwv&qHgi(?^sW!r91IgUz$EO z*;>8iVDIOUogf$Wd$**dj|=9kx;@RKItqIy?Gx5hG}U)>+;{eeY@0+}HW0M% z1AFx{HTq%TnQP^-=wG?zZ@+PHZsZQ8!76UU1nrj z#4#3vJ+-l3I!2reg-I;#73ohAmHnTLV^GQY@rR3B>nQV>YvpAy^&L>~MB(8Mnz5}| zl|DbDsg_qx2kCOEVBc4ZzlYwA9nI$EAxO73Yuh?m@yT-^JonmrDp<%%%}EXW|B+W_ zeJDCLKkXW^8VMeJP)_8u#^f-h4ovApt$VCf7t0o$#FV|ZAORvP1KXX#I(p*hr=YyL zRo@1Tkk#^FLXrVCu%$#sjPK^F6GGw_hP!4(T_Yp(4s)ZS|6Z>F_uE6`DsIivYn{gG z3Ft$hoMNrw?+CAamx|@a3_E?g;;w#6)33|Zn3_zVFP%ce_b)yq zu>AAZ%W6e_r!3wl)D*KcQ}nwfYvq7J8>JYEKTJtnwn*=1?SwSlL+-uZ9TKd!vjQeE zbkfB%kte2Air|^{IDC7V#8R|75o`=;4{hDW4{MVMnn+IQuR61D4RcBz!|n!G{gAmT z(8#p@L!T(Cx%A3@)jh>hhwQpfu=3am;V7+(>S7rWx744dSgpKv_Hh+L?#{<&%3i;SpRkO;uk`Plx3klC*$e(bHaUfQ z{IIq*uRmySVw~;6G$eYcU+}TJF<0X{^;!Ze>{!bQ#0<8phQ2)L{muE-maMEC6ehnR zk<%qIaqU(~R&lxuoF==!$=Dp)YrG7s<%`x0taL*fR<`Fp zkSt`@y14yjdMj)jex&yez`yYEJs&e~6J%+v&Oq?Ok~13w?prBNb4Tq>32Y3@%VjSm z%G+J2Cb@W&Uw@L;px{O4{^0($9pd+N`17j@8O%l?2q)}@`BAc0-w>l%UTSM==z^p# ztpOpHt+ZPquZTVc{hilEwx`yK1Mrzq6Cky$zmr|7DVPB4R09KX*eAxM2&EzQ0sK;^ zYU)T?EZoZT3cY6qhU5&wbU50wC*>-G{vf|8G7`lY%eIas_7@&s`0U;^aAj{jdSA4T zZAf5H2IZ=tiU$%X3XKcWS!X)X1>sF4?yfr4uov3pwz9sddIV~`QSg^DR%9!FeQ8YT z5EEv;@*j($(aC}0<#FF!al}l&Wr!}~ z5z|wtSD1>eSgkYNr2bE2C$RGE&G~Bk9W=qb(96kqGt*Glu#NTr&uI?IQbfPK;b`j< z7)Jd@*X|uwXPW07SsFfjy1n7dvI`%2jbbGSX`O>U(p zbkHf5KJ&KM%kJNDMy2JWbU?)CDL`1bFv4{vC91e{*rO&it-nd&*p%}$H2Luv2g_M* z%C?PH7cdyMk&`K?=f8vt>Yziy(EQK*-pz8ZGRwqh%-#Co>&bpT-hju$DbrjN>95?u z|4@vz!kL(U7h(+p*e6BS{FmZNQ?x>PSN=5!5%_qSGfSY<|L9j6Ihl2PdKT>LS$xNZ z?8;-^68_KKxJg)(IKun`w0HC&Vf)DNPM0~C=%w5U{ZTWV@7)1 z%Fvpd2T-XwwKPHJ`2?D$N)Z(S&!{M? zlHW`@`z5jN`S1yLtz)K?_^5>YTK!|8JP8bZ$Gr77%|@L&hg!u@>3kIUH?*&5{iT^V zWvzicYdXc<5ZW>F#c+@{%-3=2sFN?aMIKLQ8I6sOsisZss8Arb`hG(G?1*VQ{Z>Te zXT5G~6zp^ls5ECGqOWVQcq`Ejp85Nf$HIJqcHKHNvF2oRi&(enbDy*6_5+ZV+BC8gmIc%c*dsfS0<&l3A zgx9X=fa-ueTMt?9XfB)79R;*xqLPz&bEO8VAn=(2ZRelgMf9cQzy35(ksQQ(uV)62A&KoPQ zb2yAKfFOV$*4n;x*Kc5_Xg)U>?sQInl*2eUI}13=i;Hsa>C#9xb3YY5^Cbk#AaW8_ zz%pq^Szljd9>l7Rx$tt+i2SQ~qL}YsjqWi`ICuls$VKVZXjE0`j$2gBQV-+y<&9@X z1^jCM#Y0HP(8^N_NF@8*zY|n{-*DSqb=jihf27ZjtqVYgpXvG~@g`A2-u(?#>$Dn@(0n+Y1UGGRP~EX*P6O2Hy1sPV%L|Y4zFle zAR*>m7dW;H$aA%TSg4|6?G}V{W6PBkS2J*$Zn|w+COpzd+n%UqI}EthOP+6=(}L3y zUm!9jaT~+=$a+4E9lf7avw8T(L)f{|>Td}Em?AZwc4OgiudEYB-+c!+8r_ZaM5&_?f8A5L!xg2sTsMOd1Jfy#zpXQ zS5XnUC8i9r6Ej@}t?-n)GxF8RcX+V+FcseA0&1>v2^pLyKPnP=h6{4ccrh&WxZK#|U@!l{QJ#bxbXg2sXwyRCpdiJ1`Gk}kY zi)yE*K4UnRUrJm;>e!UkcW3%SE()S!<`|%ys#PcSB_`nE_bSgI_qI3Qf*G0tC1w+S z1*nN?=qlH*P>cS8gq;w=GKWV9&0FebhNyi-;_fPC;&@uq%=*TCj_-X~sKqRXTqC>T z4QaQ?;kN_Jcw3v>I)2fK_d&AXZ%i$8-wp#9BAxXn%&r)-E6nIO>gX0)f)YqciZ`rW z*d}Y6G3zb+V{b=zZ7=BZo>{wrtL={K9%jouStZk0m~(U05H1HVHVng{D-MIH4FQi^gF#Xj=up zx)aPK5g8)I>%f9Q6ThBB?;b~^52t&UYBm=)U+s>#wKdso2hXP_$o(XXrgZ>|B-xbi z`UJB`6A$dgsO`3+%&eMrX2Kg-EWj20Bsl8c>`!wj{FNvmeYy_F0~lj#IDJmpP0N|c z3LY$R^AD5rgU3ipWb1dX!1L=UQ!~XdXMQ}DK1aF_@cXPJX&1i!m60;ymcOJpv2OA= zU;nKiTXSS&QPk?Z+IMX2enOzV41ZGCyWaX**b8J9jbHmA-}dj?+aIMFjhSJGZe5FO zX#EoNWJim9M8_+JIsJ{Okmt8w*9_7P-oMMGywq!@mw!5<@_%w=vmdfEiv)h?^bQC2 z0Q_y1@q0tlu3*OT#c-6~Trt8q5qYkKH)zu*buV>z-l+Fecht)W+?N<4^lpvKDRZT5 z53fFV)Ukf?4TK7P{t#%yadq7^Hiw3XhDA~gvFXQ3Z5%><((iB|JCBsrQ)Kt~IcR4> znMG?f+V{yL>cLH)-c&N$Mm3Xt$Vm^?tlKBlIL9#tG7SyzZ$W)odeZ2?o(EPYl9=8{ zR|FMD#`2{_Hwy1v?t2~wT^gzUXS}iU)u$_`Ttxv@?c-kgocq}4sZc#d`pjE+hmMN8GuVjY5_Gt(?BVo&ij5{VMGL;Bt;q$!|obVyl!5ePOC@dS9hI4n8G*<0bIff?(2P~ z-6QLnS9#x1o3iK@0-5*= zVIpn5i)CsG*-`&NwIFG_xKXp$?bIp3C}Uz>O}jS@B}(r|bPp#5cw2sb$O$`l`nW#i z=d^m|;pM2VcEJe~cFW10?{XkNTMwgsCA;72z?>&L`1cP6R5OnEd)oypoy%5+T_c48 zjVSxoCYaZ;LDxJoG?XHKSfX($cfyI`yUcy-pGC(&xZ)G_IAPj5zHGFTTLV@VsuB|0~MFs8WK^HU2AX4 zoK3aoeSJ}2KRK?rg`@XUf#;EP%hG*|TcD-l!RyZOO*R#lZ%ZGuBVGBhpEK|Uk0wFd zj1?2+j7bS&yNN;F-2m@xOmE?uwNqqJGD&;KXfMSn4WN-(j<>)-r>ORYO=SkK#v5dE z+jDxYQnrL2DD^~5wX6Ii*OM5MoN5q|z+tz31#GfrAJ+NhjVJOaN#o;F(AF6}Oz!R+ zLi}@of0`FX$ zX<)PBXF-Ji$2cMQrSO%}R8GKI$&Urw;#3e?cFYF?J?=!!-jOE_#CbFcOYA1Bbk)gH%l2yyOeGz9j zyA0!AsXs{1Di8bZ6f&6Fp$6}_=`$3gE(p>0O#O>?{A}yf)i{L|DuI3z(mk4(H`%-3 z1y-8iR_0Yx$Ly2#?G)RXMx0-#=dcg#{M!HluEH~N3yfy*c?I22>$^pM=Or%YLlv_4 z;{B( zeN}QJ5E}i5laqij|4mlHJo+`N3q~+leyVFpYU9@bL*8Qr7ulBKG4lGH*hceMCj4Cof$K41XMKKKeGb)n6Yl8zV=ObRg${*}JaMp-zce<(WI5DsZMU}C^>o?x)YO~_DQ0jC zWQxIee20jo*M;;;zWlQ7R4WoQTs;%hSzZ;0(7`wDc>d156Y-CVDf{QQ2t#)W%zo9t zcOYM@WMkU_zp1a$$}R&%zk_LdFFp7n%$-mLi7j!Ptd6(FNE$AkuMH^Y7~jEUptwIX zT|~07Jk!1^F9(Fldb@C}1SsEd_15zL;R}OVcqLaDKnoFCX}mRb8q2BiT|W^|?gd## z{RDE3_6SsBGDZhbt6#*oEOn(lP&~erNx9U*|6OvLq`r+1>*R=yK`zj}$CL8Q;m-Y& z$fXsN9${VB?Cdbux|0o`3Lkpm%Ff`q7j-0cAQ)(f~gu>Hd=MloD`GfczQQgvfsq`}>2{vsI%rgX_chD)Mb5 zU4QX3o6eQl!=0#^p5O>k-NBJC7tTGhJgn$=ztX{oQ|lNHTCDc)KEyB1Z|=5;*L0S2 z*~3C2-lk+47heVG@76djb?7V_US1h(NM3k$4v-fZTDMg!7NyZ@4}>r3F#BZ=rR*aH zW`zbjsQr9h_a*DJM7_rhM0rX7cK9H&RV~M|-fh?4WTP~%zcM$dEILOvHaF&7@~|Cy z`5LC|YDXNl;aw-1l~aR{J8ePyBr28t04x8L`?KN#k-tSlFn5k*GF zV*ZaM;qySl$**c``tjkG?Ov<|*{s5Kj|2PqX68C0@gXP^x<^`c^E3Y6w0uJ33doIy zEkp>FK0?UOw88GQ!b-Sd#RUXZpw_u&JwlBM&}P%Tj= z2UYt<9ie3QwzYevPe&}SsqV~By*cak+3o*_Xy6UECgX*GC1CQ`;=K_O=wUYVDf8pz z4XeRM4AZCbkPs&QB=}wYad|Xf-h$|t_v>29@-m72@0e;p6mYtMOOQ)9sFKB&;~qmb z{ne|i40nQNN!TgC8m>*m8}bwVJ^dbHro@2Dbwl_d!DE(Me__jKix#*FI2$5FlXK0V zQVI3gH~BcjEiAM0gSX*mq56o&tivN5^87qiI{JXPZZ&b3;uKbiReDQz946ON7@>n*m_|q$PD}x zZ!>xD6A|>U1lL0!2Fli_Q*Ntti3z%8eQgp47LT836XudC)i1~o0`P0!gRE37+J0eU z&Uh>SDpk)gd|?2QHPWiEMp8x;M_DBB91fG1=8D ztUO35d*&&>;b!+TlTwyBc1vgDu4N{NZfy2NNn_nUxTlT)_@|BdM@vH(bha!_?}v!Z z>lYgx-?ujY$*a|~&)Dz`Y^=HSo#)A(`5OQ=;j(R4rf4xk{N<#|d%@yAiR)s+y+|m_8&B8-JYMS5cEf3PU zfqU(->?aak9unL6(1+IRZvKkLkHg<=aaTigq)e{c0(!vGbhkoJc z20g0`&G3y~cC*92-AN>m3MGh}lpcpjFKI4%-4(9t^oo)&%M7K21{&U9k9Dq*6{SG~ z0r?fgPCG9)Rn1gCSpSGs6UzUGYhq(*Szc@(y{SalP{Khy#OULp4=?MOIEK!k=}+FJ zv?jf(MT?g9M|92ts|)@S%LW@7M<+$=Y&Jx_cRLTWeO00Ut+ZBdfOPjI$U=D|t-Ku@ z*B(jZ1`Wx^jXK8CoV>g@ezqutHF6P#W|KPdU{jZHg>w0Z5s}h(Jhd*~+)TEi6MK;s zkZXc_&Y+0eEUkSswvuI;1d%1@2YcoBx}j)ks}Y=A%E+DRc$(satigpcB6P@!`0l6Ncwq;=k#{~tt4z*|pK zjz?WU@wjn2CgR~5?2jDBmuWUKiUqT~i?>EnEC=bv>`R97o6cCn#!O}FypVykdX3js zIK@x?lF7bL1J4P9YsP?Q4G#&j9^<8+fs1OkO@FwoF->W8=oL0v=uwi&ifZqb248qs z^yo4RHD5~K(9w*%bvv&N0yA@6s)yT&KUIu;%?WEVokS4kJu+hliUY7!{j^0m=*7sq_+{hKKg|6dVo1eLM{-4RXpxt3JN%KuM?#Y$JFc*f3 zdJbJXb`t4i-1%^P(6QXwrDymt7?#AgaBWh~@cpb4)`tQ#qw&=_e>mYO?!trdGaeHF<$ zSqt2fC$W2qi}z460JQ;8FkW-3>TfKpccvZwsuS9n33 za60-Y3+dGi`+bbq8_q2W?~&CdikY@hx*UmjsC_Wf$rvp54fv>H>_0=VMBCRRv_cnzv)i;G}d%J6{3H^W+^zixb z_8v7I&Y2>vH*cAMHF|5&sHEi%E)n6^IB(KvPTZYUtn>6t?jal2Is_$Dq|jz(E@BgI zl4`I%p*R(o2}_QO6`kJgkIwxkMWi)CF^#`s0p5+qy^bBxa-mRr96^CfJ^);=*UW0UN}4x91_oub5R=IE77Jv%WED;Zk0)W={%-@#{`%;ZH*+-f4VSroOehPc^do&erC!|?%(G&OUKW& zwhflhsnqV=sWuA*vf9=|y|`h$P3i zV~|1Q*gXt*giF69dv2svj~-4jWTDv(<4eDK(q)M=ENtz5ulE;S6zMls%vz^0hUrz^ ziwCM6@0+(g7%5r&tRsEb{v$hUSOA1k!&iMQYC~9syo{vacEe7U;C8f8JmHyx6ETaT zb3IEFK^4(mI=p|AWIUu{J!6!&tt(<% ztx)W{C9-=DmN-nU>~ADZU2bVWmB}96_e<8^D;?tq zzQqlg?h~rLp19FeVfo26bDoE_;w&;@?0Z4OgCd82fknoZd7LA9qoLi(+I|IOt?T=& zz@&xwU}t(F3LZxo`3Xd%V2QSQ7#R-*n;yGz8rycu-A9#_JdEFn;eMloXYxVG99_8d zUDN03F7Ev7Mv8o>+@W3^{vd2+aOEW|h5{yMM}O`YUWLkGbe9rFWrI;(qOfzib+b|l zpv~9e{iMeQGUz(`kC^g0j@X&BolB-}0)^ZX)sx_&E1D;v0XiNe#ZZ5?IaIU2@Z(y- z042P=cM|)VGD1|S$+M^pv&P5uqrdix{i=j6royNS+gM0i1ngtuk@c<=H1mNsykYFu zN6AwQHd7J<{fd^}(MA$f5o-Wwmojg%sc6TAIoeIYpViLDxYtYg9a%B4J`>C4YhtFx ztT9n^&V)mW+cmbmP~GylYwDJ!*m&cGoyXW+F_ou_`rJ5a*z&@(0 z2<#DG)>}W8!w9sI{l{~A{s9@wlnLseX?*I%ChJq1b@gO6765?bA!Dj-poN(Tb`d!X z37(aDZK6!m4@ctty(d0T+>lf8>+lBH-sohUz&CkzBPKBdzKU~|DPjV(S$GD7GW`y>R=Xsbq7PjWy)3oOZRTute_Qf(UZ29$EB~TkA z1giGVhq;(|sy}wsLHOfdk*8Etlc{)3xhKlt9(Hw{KSqQxIM z1H=x~g!YMa@$@{F8fIHxn9QqJxA!jW`qFax@$d38G1QJny?AsS%^w^=8P+gE|Lm#7 zne+`KP)W}d!aIjLf6&QaN~wq3p7dgbhx~wcCx>)d@)@97=4g&NGy#YfKm`ybW$HSo z=U1(wn*I9nAYa#;+RD_e!8}5sEvyc(uCbdcF!BY;&8j&O$|GRlpZY*<;OCdgfGO$; z)d)(LrL+5;-!qz~kM~0GfN*o!wAr<<6*cUxuLS~@Ut@#~FC8#?_Zs-0I5|J1Q9^6d zB~*PEyO8f`xUKxe=XZggX*Uhl(d%+pNMV^pH|!EOQNflWN~(@g5h@87K4Lfd-<~8$!r^Bn-mM0F#)YX*e->Pkh z#;fX!qZ2O==m)NP(=dgx4a3jOfR6;?7kV1P{J$^aAE$J>%!=XKM&K7VlaucC zmW5eVJ{c$#Z~T1D7rtBA!~C=^@n5dE7q5zL@vW=~i`k{7!xhRGuks*eH-+_K!q%{u zh0;L^FN#|{vrrh~KKOh!00`g3Q9?@%Y0mRkOsM=^wX%XYYuwYG8QJ*q6o|01%xmg= zrmw7lHoM9OLiJ@Y_j$vNO$7Wu$I3r-i3ZFbPFoDldF{q$NN4xfmcwNpyk8CU`bB?^ z?}W6*(nK$wLF4#m{gl7^p*tb^kjmBf^~M8VViFgSm&eRThxW@Ih5)W1q;)9G0JBv* zB4(TV+^S{9j_y<-*dt{ok;X0pTZiUIGb&D7;ncKP+}l8Tc=iG3gyVX(a(S)#_cNLT z?(@y%h0b~Nr%shc=?0U9dSS0Ky0YsvWvRJ}KMY+RRH#K}k5yrkN)0DCcbAn^zmjzb}inL(sje zZB^=9k@J6Y@_bHMH4IVe zjgfi(=dIJOKJlI8&>rya#TvA|G&I2AYmtNr+DBuqj&M`KQf7X8({*! zhyNGpI9ebnz!)87BGZZqZW2|C+^Q>d{ar+%pG3*&@ffO(G>{5#)8+ToYA7=zUy72B zh?~inj0S;P*TOW;k6S@FxsD)Lwj9#j8a+(5hiW+#@Qfo{ z*~l;kR)*z$z6ZB{Y0TkIGF#tH^)C5V+KKN*gPHiMmR;X}>jG-NR4Vi8!=;uOMfo^$U#r9vUpiKP&8yIo5%{X5V@NoB;!OSI1gqLD1 zT3{`&%pm#u=7i9z7ya0cb-wYN(_Z1YVNLs;RqC38`_$D*Lc#8ehjP(7%h)O_-$w3_ zFuMBPWP$@Sv!;3x_U0Ym^l@;9@ecdN6<3m})?3n0o=)0rkj5_)3aMwT=a9bs){q@S zb8yt)W%!nv55H8|&WA)Sw>h-(i*2566FR;y5moq5LTaw|#8Uql$usbhcjY1E-v=kn z@1u|2vZyIPFyO!_AoYY9XcmfJIlgO!fX_tC)BH@sCMO(@ryj@3#q17qQ z>QvHaNEXG*yQXpK*XSJD*uc4x*bseyenfHwxL^7;yaztUL&1k#BV%h4Uz6|1$8rO4 zlNV{3K=kC?3xvK#qI`ZSuymI)LYHhwvHqMh!mz`Np3|^`uTx1PUb8XR0$%%XyZ&w- z-JPzP>x28Do3~TWY&~Zu1c>r5Wd-Oi-dHL7x>nuvu_SD2r^}K!`-}1m{fZsa>*pa9 z!EI1~bVTI*{mcq>8jMR8T}Ra7BJvBDUe{$9M+Vyd9cYp5)j4|c>vIUA3$^nHFl}(- z4dOCqKX!OBme{p8g{?>#**MBc#1)w*tPj3Nx+{8b0ObIJ&ZAsy^Fg2+@h<0#JN?D> z^9gdifz-$|+~HQ5s;e}!;3mie7TVFC<{tY|zKfeWj;#yU4`EjUsQ#Hcs413`9{uKW zTGI`kxQe^a$%W9+5-JOADsH22^UKL3P3Rqt0i$Yz_jjshQQr64xu4LEquhVhZPyV# zV#V5V@-Vfj0fU){!>Eb_wSToY$iq><^uKEC>u##{-k#l{V%EtW+JSrJngtz4#j8`} z{C(bSH`rZoGxY{UDCZu^VT!z`WpeGUJaiFM+2G~IqC$-?!6 zd)F&ZLO%jO@>;s0Lo7qfSN3|$0|tY!QC zxUzjfLzJ`vgD>>(uhrAz2R<-yXN5x!Q+C21C&+{_2t$yxA{J=T)Tb-?xB^UuJIaT% zEl}5CIclivR|nm2QK|zXBI83?BEY!SV;{I{?_^=#24}57VGnU8TPQ&G=FTkOZ1WSv zdVHdoiR&Xe_E+`Se+X81b8ZHvcYd(ewx<|Ku|fC%lH4_C0OyUK*cx+8AEW(B40DWF zF25O%W^cK~Z@>R=J5D52O{DKazfTbM*!|e3R5&%=1>_Bfwtaut+E8f^#7#@TiwFNM z50si+1?P)44&KXIZ~ZH>#Yhwp8I{pa&KfRIUNS>lMy_I|+BkZFLUY>d4=)!lwP334@@nU=hi~JeS9v^%6a%Mxs zZ$rU`9}@xPe-#ET*H6X#qqdPlcpJ}r_Tb77cx|g)x|DcB;bK~v`BEy-Bo zUkQz)7zZ0HT{>hIa|v{5`H8$}ywPzirc-xhs87(i-Pt;w&)VgG;k2WAe5!{q>{c-! zOxEzdgo+22`mynQi`5Ut24)w=f%n^2;8 zULX_c3k)vCb^5tMT2mDmY13KUt_cz4C&`I|vXfaxzPn*ujiKL8Q$3*b(A_S)&MP$V zhK60XGy`mAoBX~O<`>y8NJu0E+Vz{$3Og-#a?7@Uit45uEZyfTeUC@5shZj6=``gz zdM>g0B1qbg_AJCh?>**tM7ju^@$+9Gen8YleV$eq_U)la;nVQcW=!a6p86bI(%^#V zGaDH2QWE$ffL_YsQSnXwpY~b^RO?+7E-F6xI&CQhUNOlK{{VtHoWRXVP;3+PZu5Lr3Iz(+ zKb2pWW1&~*RLGhw(~I1xNjSs8^9Y_Vy>1ElR8Ae=&D|M2F&2mU9O1hWL+1ySrw6Ud z9-p-v!{)&QwR7$!F5zSmAC=yFb0T2L$cU!Ci0LZPW5s@_1C!n~<6ZTM$~D|wx||k! z`XFBB?AMz7FpY>+hb9c&&ncud7V(;<1NCHgO6l4pdKD&P;WL_^@9yX&+(%*WIY~0p zx!w#~@ck3Ev*l+J7AtpEw~=9&8KmhfYa%-phx=>rg+YB&Iy{F|$?Q8vUY?&x9fo$Nt5HV zF@EneBOsW_OXQKNHLBQ$yE;1a)rrlxP z3_5gXcd-bGgbMs3d-8;PgXQ%jk@LH4p7kRUb{BVtWAfi1_p{0i?Sv22n}8I;KXt-y zPw5959~sv+FOJ)YNLJJB!O#hoZbWznsDW!wu=d$m*ntarD^==`w~}L;9P3+kI4_(Y zsd4Y@C@guwTK=xgb+?BrAWjP0lVwK9*zoO5%--fAo+-hX)VIuA)vt$qIN95ilfd2Q zCJEa^1}2CpI~U^on|8l{0jlP~R7{^syhiTcjY(|M%v7UMSL^iD zm1E#1CIX3{Vsswe@z@5ufM1*fvJooVdkiL;j7fFA*W!mI_v5NY@VAM~>-Svr668m? zy_>cp4Ksr_!k7N}G^1@9VDX4_ZatBiM$2u}Rn_15MhitIj51Hm`HwXHXrF}%5z zc|x6H=!c!ozt6rY+2=LPt#$YGGPyT)&z~y*(~Vna@7aLYMMd0aSE!$ak9>BBVL^uK z41AMDsI~vvxoPek*mUD=&p`w_)rt)Df7OZ_zjnLTjCbFfBz`AOhr$Og1N1dH?0Nxq zy(^GD{33t&IXk1muxb5oEE`FJUe_*_<)tspgcLo{GgJI-SOAHATlgTc;{8>owjiDD z5eN^Y&Z{0{EOcGE$**2G4_`0~=cSM}`}F8t$9jxob|=eAN|uTv!$V4XlK$jecT00b zy2aT70h3qc-|2!JellT0GN8G);_dGR?bM{$Y|iEeV_%<@|07^#8eD?b$eaZEKz#3$ zS9eUPozZ^pOmqvPv(jK#Sy}l(arX&eQMech8}6kHs6-v@-4ORL3Skb?ii^-R$gM%%$7jGk$;n=0vzzS5_vcU=0N^3GZt2X4Res zY$XT(2QYVFKTpdIcv}>^^f1)}aHzOtB~|h;=8G>F4!U(NR7FnegsQfrGtP>(5m;)_ zDN1!SZJN6~`H&5-$k)NEU)6hYtuh?#>Q&0E^MEZn4r;g2QFKz!Nx!m`-eF%bDW`9@ zpnY|cMfXx+)C*ldBO?~hmEgvP-!^!8NtYFQp2@>svzlx(S9O#HsVw`7nXoqGVT~Ca5xt<3FrP1Zt1S))BaAJuuISB`z{uB9N-w|(=1_E}Ek10(2eV#cd|tfhb_$pT9L zdijCzrBCRKRcdp~){18vn24F{I>uy#8U-&5T^5eEnI@@xbL8 ze$}nnfx8~#6BYD?g}5mfZx5i1h(-?iNIn8GA4oHq9qcT=L08S)r=5Wd0%R)=L-y`~ z?&5Hcd2^hKxh(;?B&D(9?j`}#c2kmEtmtc;a7iF0&3;t?q7*tUIkXk`l%UVC1?J88 zI;;9mfyy1YnTafA@s*U8Z{8%6Ee;jCrLBkrCB2!LshHrRRZCEl4bger4Md~+kO)mA zWf9kk58Z{4ut}S#_0dyx2AI2Bs(I?UW_StBVW}xgRUy<=EOwJkxanYKLcsPLBjyF7 zvK;n_AKtzt8RLQQkZ!FFx)4Mui#vf^Yi(w4W>z7`Rkr1!BT>UEkTq>0`Mid zJq({ha~3If49k)=-b==9zeh5^+G$SSS}RH3#_X;9x!KFo_Ht>vi!^i92h=|No4wJd z!?Z-%bY`DRWiJBo$6Rz#x7oVvy#!sbMj*n+Z{v-oFQi$mY#4gm=^6Zg=D&%n=Xbg| zt(yVc%vdI071z%6QwZ;4u1+x^OnC;8kyHJKDVhCa1HgHdm|rw@x>B4C%U6;oXLIai z6`(s=g|Vw5twd7k&H)71{O$c2jfIJaaI^^LJCjBkA{{~V`h8JN-}dao;C?$-+O}zr z9**Tur(~r1ta$jJU8~dbtH1@@{qdg)hfEPB@%WXT=^8Z4wnzRF{isCVFvSV*DX_~2 z7SMyMw-c}vy)(XFuYFh(@XscIXyK6heciQsI=;VceG(u&u;JT;xtMCiZ+)AQ)|#yD zS({f(?O)hb{^q<5IaOyye6qKWU^L|*<=2VI(U<-fDb7!-id($^e$2P%GCe_&elFX>*OW369#S+2 z`Vxt6tbe7;YWfMjtDOs{k1Tjz_G*W)mcF#sulTHvK57w^>+gFpPL#2=X{7VjFqVEqJaVd`}{)KhR z_Wf0s^8J;TrC*wDWMuTL%nBaMS2PEu`k5&kY2hyiUxQGZ&{)@3RC|rLu11}T^;&-q zM?p7!M)ofi7cS4YUm@`tCQzyBB16V4Xjx-Wn!wqcvGgz3vGs;HnmPz2;P1zeO_XxGeVyynu-5p#Hj~KgrEqabCxhC+#E`8_?uN;qv_&SZ_1PjR^O!*--5wkV>sX3{WS&hqNj_^25rY z7>%kj*8Q9rVXlMD(H$FoWeEyg_OdjtP>nWrr+CtEb>agwR4>^#ZsX=5B?i`PV2ky( zBa7N@)A>g;NF{#>b797&5b3S}Nxo^SxM>0bko^M<*gR38U%z_jjzuf}J?bsj_`-wB zhhhEPhv{Z7t;js*NGVr?LrkAz${a>!u9=we?-PbUhpW>q%>QENZ-lNO4IrO+6GM-6 zG@e*GhiS9Q-W40C6~SyqhLGB@;r!;~ZW-O)f%0G=K-b|Haw-zUOi<}-(;`Q|X?`;! z9y^B+=9(vLv_yV1b#JRhGz}kV-WQ?S`KtiOj9Gq}YzSK)7IEhn#}qPnkT)h-{A@9m zGnH@ctPs^l{^B(9(#c%_g9%@5U*uYV2ysn`GcOi<{Jvr)YZh2c=Sjk5AlFiR>eE|T zl6K72XLnG&D!kmhIEG+_$Lq>+lGQm}n)V&-n&0-A;JBTM$QNzDyL-O_pCB4&Yb$S? zDG_$K-tw2LJ#J|UzZhIYm>DDLVH%jd3ts+4F_4KFIZx=HQE%dbs!DDSOFLgc37%DL zH2^$m#5(QIUhK}JMN6X3nhvNid$%gzZEaIB8F>v%60awH3V={S8@j^1wxvB9jTDyy z`T_V%jLCR$JV-m(*kwPaXmJ<(S0n!6^>&-vLa=hfmFZ)T82TVIta3ApA51*jPXQhk z+v5MAPy2*0hn)45Cg1G!6`bDc`}m{T8wG!%Gmstiw)I*;X3w1hJgN1O1e0~^ znJLTcg_aew{K z2JbTRg@}>$H%%nCiLt_IYk%Xx*5`CT9dQ2mul|9#dZ$#t>0hyOH@0GYV$f~DcY5t% zYHNdUR4JT5y%7FMW#Ul!2J}E>U?#UYJ7%C5)G{}fzGd7R(Y@q-x^;mgJmC@BcAA;9 z)f&!0(jH`am}*=6g?5ZZPPRUH3g0NaZZ($+D7e4;L6@jnxC<|JhsMm1IAv83r1)$1 zr0Q>+pB{3t4~I=>rXQ5wsZOzRKfk;2G@g1p92M1nlO8@Y^zwK>`4QI%aaaEIQ%|aX zh?rr(@Cu6&72VKr4!czlwQzAOe0<`c2967c^?G{7i1 z%Tjp>)VvtcuO%-Urt4Flr2DbG{VF_}vm3E&IL^wzq(j|T+f(eTu6^Lso31|wc%{Jl zlgneDRFJwAG8=@P>=k{qVTZ`0qOy$CpymNVvgZ1GE0n z_HyQIw%pm;M7uOu-C~N8ZauliYT#!D?HHH77@O_1I{12293jlL%MXA+HXUv3a*e~^zxJ<|QqdW5_?`67rFjcQzBq}WBP>=Yd zOy;VkT0^j5RZyeb|6q*0N`ZXnN8oh>j2j8I3@a{Fcb z+2RKeU{q{bUUs&cFpMfKcCag*4?0{50kACGD^Al91Pf--yY9u4@0F(>*H31Eh$^25l>@_naa4Ypzq5 z3wK_PGh?>^0gPe^8+S+Z41`ZSG@+|cE~Pb1CTJ8(qwzCv(sD@0L~}`cxnz=Tgqa-N zZZtVpl)jb_>N&Xs+L0~3ID|II+gFQ5#ZxKO?X?}4Q>+axY4mT? zUK-*YVH#fATW&c~U2lSLjSq2Vn-e9+sfBL;+tfLb9BJK+10}^Ik(%sXw8Me*n6k-z zIp*8zKc8DWgCYq3(3>#uNW&*5$2HAMa*D`}^`geb;E11mcl?)xVsS&vJh6GFizY>u zA}W3X5Go6iYuJCgo`Ba?$9yIDm(87{dx?@}@5(RNeE9w7h(b>Ee0Hq=GyB`*2Kx$1G*L zFY^MhPDF5ZdSnN7-PFFHZnEQ#Lgy1ov11NzeshMe7+{YB9ZbR>3yHj4pscajKH>xN zG>MX@qzIf_&aa-10$1WsO*R^JxBfgO{v0edksnPAgre;QPZEzzvNrJTt$Mn~B=9Ky zTEZY{s^w<}n_)IuH~7L{W!5?{74&(FxfS7F1ZR<> zqoFB7-(@+D_62bN4e15~C$eD@Hp3zkOW3CAxdyCP(;=Hn%WwTeme7Z()JznwuK~Ri zji#HAhoLqxRaTC4~H3I>_duUY(s}2SY+(A~rHm3Ja+| z?quTIhojzI_#b%VK=E;U*S}n$-RqA0km|%?O>N4RYcRRv?3+9cVd*U?D^l$R79$U{ zDO_E9TY^^Bc-(VxE;~c)*AMgub>+Y|D-JkoygL@}uC)+4H%(W}%dX5mmXO+ttfM2W zrk^4pRgt#4Uqqs>zt-^XGV=~+-r})iV3 z@}vGF6BVad$ih%4$PX85r4tuNZO9n}NG`|1Nunuxb|Mr9^nKY@jHCUpdE#WIT|J_f zu}V97&=<6g^H*f^ZGu?zKV~oN`J-Bu4y6Xc#@yMls^Fi2B;tOqb)0-6IBZN2`_~~O zSCP0Su|AI1sO>y^T&O8aBN4s6ua;!ukgHJsK1J0EK$tR(T^9bF7{?M9(>!t3l z_(t!Z)nlr$IO((!w>zV9c zs0j^uo!gCy2TzAsIY>mtuI}^aEg?XqLdH_*O~p@kyZ6!&oIqS?i?K;jyI1u|&%>|; zw5`lA)5jGLcv#!U+88q>@1D_cPc32E=xB0J^~VNQ>|-#j^3^3hl_O9t=vUiGvo?*k z0+I(aDx87r)KcH&zbmroT^*&hFp7c9v^FNsj`Y8{degY1^S9k~=6nB_8n{pacr>ra&D4lUb+;A6MvNSWa#>owo8dEYwGZ%1!OvOwQ755Dl1rc{# z0736x*LDBz>w5ipBF}^ee80cX=Qxh{;n5=;!uOucUtND*;x;sWO#|#gOdvlg*0h4J z3zmm%%75MnKEi_*Kh>@W){dv3ww&V*^5Yt-E+kX@#^1PGd2v4<-5ALxjIeLDP+WmgJl>dGf3j+z9dYvWru~^V z(bjX+=Q}N?(Nm)E}5O}|0hQSe?^Du8`c+3uVtXVYoi;A5I`Q^InUr8KWdp2=uAG% z|EmF=3V@@yR{U=kNZJl0$MMX*0MM8(QywPgNJCNk4SZchIoknynf7(_qihj_xNT0v zv9N8^Z5NQ?%&+C;p5Q^{$B2wL)kvLoCzpP&E9RU3%KDSl+REy}o4#SLS!ziv(dR#p z8v8ekF=samF;^D1h`chDuSjG2jp`eqV5RbJNmXzzFcbi9z9YlA-;@D^flagB3;@uB zt)uR5-LKDL6ZZFmZgI>OH{T&$S3>HuI;x2_Z9N;fwrl`qTQT}d4WE~4 zXU0*CJn-nO27bcz%u~-Caut~f@PR%DE&Oassuus-O;U;1`E`!hd-MTtwf~X{OkPaF zkk@!zRGOmYa28M=d~9I;`XV{C9PW2APTC0t@y$nnJ#`FS6SBUDA2_RJ`cE_c-4u=n zMN91x5rgc0dmA^*8n*HAB{X<)j@`jNj)eG?ZL(X_M|J$vKr}zDBJDK4Vft!& zI%{n7?R0MDe908RpAYs04JJKfP_>Sjo6s)^QEn@>^o8@es()NciBqy>gr`bUP;jNSm91*F)z6b%iowkJ?=#){Iu1d>fbe^jUi-qG zl6efrs}q#4YC}U=W}vjl&J@c#NRtv+{u=w#lC?ITV{8=m4f+5s$+Z$8H%0pGuj-i_ zp4R^EQL^J=^sQ+R@2;Ra?%cuTa_+a%)+8gM9#Ic5oyE#cbt+qzEeyKs`uo0R4huwr z*KoW}Ipr9qHu|P3<&td1ztiQQ?fIELl|cJ+Bzwv$ur#W9YQj*%ehd-g)AX|ya*N3g zlAenoufDr!HPZh@GNfPE^1k`{yL+QL&r)$4#KWu+S*M?yzh~L%tDjNRR=J-1`}4R_ zE_m&A65D-iVR86N_UxgSO7-RlPWqddy9fbVtH zVrp{{#pIxDW@hv*3|Rg}B8yv-)n<$g5JH;bwi-~2aP7QHd7dlO10xHrN6RhgU7fWJ zv%PgkDlrh9-KfL1oYkoxD1O)de_vjj@|X&JVoP2^LIxhyHka^Ct#o9)0XC_LZ4HrK zD`sA*P245?60Z7|ZOuFp{N|Z4hv0L*C^4MUW5b$xz2kQzD{9I+z`c^D{u2w~Cd@34 z3e2t2>@yC;eAw6r%UCAyo2I*Tz4uL?3vH1HRXX z(crt2Zj-&h19A!UX&~OOuI`WJ$)2oWn;-b9m^)jIM&TMm}5NCyLR0 z+aLhb*Sux#h;Da?360*VA?!&uADW5Sm=a6V%Z8yuewwIAyCQ16`-9m(dJ4Dve8xmp zQe$Akdc6Ni=d^qJgIm`ywVdPm_%L+ zXch%Pl%fuWRATMH!}+4Iz#aF6kd=8S?Q<-^8Cq0AQPV=a8r-d8_sfMYH8AWk#5)Vw z%EV5yTv`I1F2~OkV56h5m}U)ma)4@puhwa%Uzw*$ZUIIfd2%8{Yeyhp?EWxxKasqh z{yfyznYu-oH-OLS{6B3SD7tY4Mws|a=2hkmKv!Qn0t_>%#uoZ=?FoFj*4DrHOp2y= zaQedadgo7B{3|U;vsm zX-^4j{536eMVE&Um5yKjZCbKk4@U1cCUQZ0ZD%{hvs2CTGHRWRoE?`mq9BUPw*Ki>w_^fzsdd z2X=a6-P^3h0t|<7rX2qAV{-b??6rxset?2_-rmp4X5~CkJR`5o2l7kjwK&Sw=NG|x zPsC33A)q)^OvNm>h|#rynVtldXzDNY=h$F5-j)}uDLBizq7M9C(h}HH-X4e0n4GJ# z=#F<#Nv*|A1;ckea0kXiO+bD#kCNfmlR8cbHgY|%Cb+!8F`83QC-irA>EKxcJGe^=D2c)pk3pR&Gi{4j-o0dU?BD zeF_4SvpOIWmh@L|_mDFGBUN!^v^wUAZdF&mp*wy!3sZg9b3%5zvfpfbVDAwpNc1>w zy^x5GhDe%!PS0j{YUHn*BFhpG4gSG@t2ubi|4AG9YL={Ng0k0jEnv($8`=`JiBgx> ziBWWinF0J-`4l$a#5$z+K7zP2e_~zmqQq2i9AsiE_cQ77xvGq!Rj&noN%rr&xAhnY zSGF{hV6HeYVDEO(a2b25Ui+4w*7Ai5woHwp1;T2(W1O*% zn#wmd(H*l7di*d3q0O%31@XR=*4>Ag1h)<@<2rj9PbPa*7*k`99n6X)!heO;3%_h( zzqBa7seu<3D9i#Qu|?}A_9~vx^@wff9c#q}C!9@Z3r=LYa$&UWkfR_9hT=X@x=~6& zG`vqPr<}l!VfFv0D6!pA(`2`{ImXGSj;5axEco|rwSC=r8Y~yX$|z3TcYr3iHiK}T zpUJs91LXqz#69L2Q&p9BV&XBKC!`C4$Y7mZ&h3We=8x^es-#Tpo!nLARa#GgX%a_DfzxUd< zwH6|7md&_F>L|l{INLRdNUi6-=>Ll*OD2=T8}+=#A*de^=Q9HZDzENm)(3F>q~&bu+ubs_SU6Wk4PN{21@OXQFI z1}gvZSV4!HwVCRHZafH@A7D&a$!Q14MlJ+Nw(i@Q5qwmsAC670*fkqDEZ1YVmKB$M zEV1X@2BX8xhn~cIqp0bOaJQDl*tZn~cHr+RY90CirCsoBN0D0z)S@;RpA2lHD0lL9 z%Xt?;W%f{3hE<$Kcw6E!WkE&Q@kX~d-px>s+kqBX;`yg^sMUUM>QCZTh zF}EU@475BE%4b>F^vxgLZ_{nU z!rEUopvA4mukW4(QetQL&zHZ;lZDe}5rBq~H8*yNe`jU!e$&=`&@Fs@sraKJ25}P`(z2;n5D`;Xv?F;>zv4`g;BkFf1cWZ2Ptdz+0meT{%@o4G!guGUhWFXYyD)&*ZGuVn zzC!ET3zUj|M-HcD`qT|pl;;8xedo&bo8g~&&K!Yd#C$z&#+7f{fI-n``3zxU7#P~{ zU?OQRNBtm-CXnupflbt(ZajeGlV7I80${Hr0uD4hT`oBa`P@u%tgdDAuLeN-PC?@d zYj-6?wF*MUv6w+@h{xy|txaXLJl|;bN&8_?_n=UVbaZ875f9jI@Thlz7am&stqFPi z6(Jsm@bmsYh0?{6GmffApCVgpAexk9dmh`{-BNvLWYB>Dhx3o01nFQu@NKj5F}|@j;9{ULP(_m7Tsu~J4SZ$f%L0l6A~aRsmRNpVIctQD#Rq-dpB%_tC&rl% z^?K+sBHU8#z{nASMs2u%z)Ch6?Ce6KX}GNV3gc~b$0KG>ry(q|LAfS_vQi>^_eept z;8$%FQG3zPWz5eBwb1X_r3ilOO;wMK&s!b1fX&6!Fvi>RHQDmq!=7urxgsYm51%zt z^;RDBgfnOpex*3wGFQ!V9LIhk&jg(hy@NKH!|ZJIJQy9Dyk<*YJ4>H0+zg>MVz~Px zkK(uib;Me#uBbKBZDEG*vG;fiLOyNsvipY=GN zrKTl)3_G7z1t>74_YiBiBXqJU_x=DqHdhH*K|9-z4d}TBn}qqfpTKBlYh&^>Zy0(m zSgXxE3*0)6tfRTgAOG1&Zhv+ehiOo{>W8qu9-wGu5B!Zl*aZ3<9LxnIE#tXDpyI2( zyk#wC374if==U9>vA>*a0qMW9IHuv-=jbH42&H*Q3}F@w?N84x5cYkMi|YLwmGq|c0DuW~ z1nJ7isi$HsKIWlJE5l7{oeu-KvWnVk$d#PHjChKsjRCFJB=wn7yg=IpY?gA(sI#>%xePLeiQBz%T47McDy z;NlE}06QN521PL@FSH)4r(6;L7~wbT{c|qOjIyVUb}6=>o7>xW6^g)#cPfj59%CC< zoHbT`jkDIx-%W}1!n(SSFUn@IW7%V~f5r}X3r_rD!;0u{nJw5HsM;8hBl(#0`k{8V zft;kDJPhg#wXg`+etcT1%fxldWcnZ5t1kU#=D^1pZ7WL0fav7iIa2tb*)sP12DdEG za8t8Mdnnj^aU(Ty^g9OAc(wPrIB0pfe=+7z*FHc&W(I}UePMP?hqaPo$kU+Y6(u90 z2z9Z9em$1C&32O^&|4~dzuqUvqLR2feSb)nHS#TWkUq}niF4LL%$3Otm9PpcKQOCj z>lR*k33(&n15xZGL2JjZ+fVYO$oe{-h=I@@>jLqO8%1vg$Hr>z)FS`=b_)Dadt=P3 zF-BlCZIR}oHFop10&n@giT?Dz{7evyUElv{p8O0>2>NzY^^X#$ff#xo$cGg#TP;_< z(I?N2ookO-!L~&ST|exrpL4xmIqjOee7(Vi8MbSW4`lz-n4M(uQY37kl1IiY08PzBntK@jh=9uS zY=05>bVEZ~VUDI2tRCDpynmw$dj{x|K!A_TcwXG=3SR?qx3KhD6kO53Ia!1eDObhK zha7=Eg5oUAtuUEkh7hiX6G*gNlwfo{saT$=vhW-)l|FTV>0u7k^09+%e!aLjY{?eR?PNRUIa z{H<2$9a5a^L}jM#&jk7KS06#J^WBQFo=%irsMu+D;IHEo+f^r;FCUsr*Yz{+!NCFdvIwPvwB20y@oTH8W0YxAVsrDPN0ajqo(fLJ#v`;5Cp~7U@st)=@`z&T z>x(@=A7TW(ZSu|gX?5tfx4+NJ{@UQ$_7et3;xWu8FYz+URPvM-DXR{F#|iIE%)L(- zjK=E!`}hTCVJ6XyX1Y~|Fz!Tix$Bmn(5zdF#Eqx}_@SOTT!;D2fD~`juAA&)uFdFp zW`oWRs_gg9@B6ZjKl6e@kUmUxl>FotD=o12(8C2OWJ5RRpRm+F4Wu zxcN_;$vKg_YTl%RcVWb1_h7gGYmVunk~>fk$3xt&aOi3krGH zHEX3}wsZskJ86kvyR*hVe@wILQG)IGP;TmgUVx8Le1Jr>OZOWnwtURhKtds*VBGl{ zW(bv|xW{$O8|uUabmoe4?p{SR$JP8^L+AQhT)5!CYV1;L^(`B_ndRKe7LS_;Ek+06 z4%|PCSKd(AC|#me?<|BGO;4J6IaSodz*s$3F3gK!C}ANp21WroL7(nW^qKh`A^&u6 ziGE`vKB|u;qV@OhZ4v>$DZiVI z;$ZGk{P}^nSVeois)HFYVJ_Rob_C(-j4#yekpcEsTMipdB0wode0(^N`uM=+x53aP zMC>0p#vNI)Sj~2){8+3APaVN%Nnz~z5E-YA6#ThWqGM~SKY2V9M2#4$!E8x+-t;jY zvzl=wxI%TOV!IK*rd{Qx-?YL%I5+&gbx_3QfQWa6QJcDY;vTQ^|88}Z27sFWzh0Ol z+p!gdv*EL&gZriTd?)NGmMxcD`9|g)wSd)gax_iaMgJ1mzWg<^c%L_!F>1}SuU#uS z-vs2`$|V+jgk@$+ucGv7>jH6C^Chgx{mURSSpNyu?B}Q zs4mfRYZUA?z2}RpKz?OYN2-cFELfk8u0j1FTHY6F4|QMb)}BDT(4}UHE1TPs)YG+L zi(noMEn=9!W-Hq@sK;kB157hm`f=e4@|yOGn({jJ4ba6{c1!#`mfYwjz#VB8`jB4r zzvi$|J7z_fQ7iP6k4frMLoe{DmV(i1P%t>A<(t7X;0l?KACj+3McZX*YH~6xdS7TB z!bZPebb;(ttrc8infm6>Oy@Qd`co{HWORLRke7Op(nm?W_-! zB1~_P;H~kI$|pU4c&3J_%`cEI11PYKsi}gl`JpZ6S_ag(VjB)gm*dPwoc2vah-BB)9+8+-bc0*vOFHUMwDe?QidKU@j{NU zss*TuqM!0Z{IKr^+nfvN#uXe*BhB*Y9?SBq*t_}>_6<=T<)$aM;!8w1vDOShh(nfq zN$wReqBM+%jr7S1H0@qDHSmu=Be@@IyQz4bb4E2wMZ2#^n{6*xy*Qj)5DN*dpvSH< zirTUveZSb0?#K1JgtMyhEVsZerd%>`-FjfG>d(`v zh}RCV9G9$B>Ag9>@;tpD7_!jX?Tkd_cJ7&2pK3di96Y$@S5_DCZpuugf&X1smK%dJ zU~*m}Jgr5g8r6S0@n^KFe3YiEE#a`&C6FhAO`s|G>8;%1M48{@ua$KQJkNH2+5m_% z9JMJ^cg0N<-5F!AxSIKWKy^(|`4C*2*?!6=sl^o{gV@da-mxm9QY*}VmQKc5fH46p z_s6WuNAcRi3B?W9wvgH6Wc8ZJ*(0pnnx$=PAQCY-q5BjhP}ME)0Sn~o@W$0aF4Qw9keO^HV*Fb_r<8!4;xC7 zE&h(#GKPlF&&8I6b52dZN~c}cua^ta4K}RdlC!9qJl<6+-H=zVHZCG13!v6xLgQ9G zGF;30P0s&1 z-hq4Tej)517*H*=9i4+uBd~5T|6V>PVSFjuv@iqgq(^};^Au`wbVJV~MOrF%_|O{@ zAa+|I1)c?VEaIV8lKz?g4g5LghJNSv=P>dWYw6ApqDB0b(zihoFP1Ka!17{p#!Mp*O>85WXS<7dsG_aw%&dV%@`Rmma$hJvyLdRL=geMM z0R|QA&DC}rHUFY{^dJ>w9QfiC{mpvZe0jXZ_=V)wk4^}^b~!gX#FEl|Gss9AqU(VR>t)@rVO=e zYijFZ5FZL(MICGt?y*(P@AZh!?JgYt>0V_i=V)jz8%3M>*p}pPPh4w0wPVc{+BWtA z8Ju1u8Xf9_GjI9+m=)(ZGF-Wya!+u9qj2lUp;@b=JoHnxSu_aEVwlCf)F#li8Md2~tL~FRYeS;^ZXPis9SK z06%#2Af=&&TCnk$qE{V$qr~r@RZR2Mu->2>tbyz>nD@pv`+b^MwVgQ)wOvRf&d4n_ zT#ClJE67x5_3>z(FSgr=KHoH8K}cH|l0=}+uiO?)*Qa;xiJi1fY&fMAR%8naV>>nW zr4u*0ZDzH>k6uElBWmXdQZ1Sb!v}6Ff}~C$-0R|(2-8A)T4_f99+Sys#Ji?P$3zQn zBC3ztBFCo_P1V`rR$tB6M`MVW$3lMhB$VwzhaGb!1m)=CLrlH$KG6#-=e%7BKab6) zIkuV`T9l7{>>1@mjF&T261E!B=TeMmcQi>uTt z1kah4zrRfT>A;3r65p-2+f9rkX5_h%B36BiyF6T=RmB;Xt-Ak%-n#J7-kN*X79P6R zY{0yYZ^1a_Fz~(~xJfo~72?)=6A1_rzEoRkf8qL^K+zBcB)!wk%-HNPL1 zpJ@69VLmjlj0(tmokL;mz)FM`Fby|_H4e|taoQPowZW}8n_(Uv3!eq>pU$d|hv~*U zA;JE%`dhY^Lu-}lvR+U3w=eThD$XG*3!SaX2jrGDTiDznED(1x(~`lL-GGLNv=}gT zMq*~al8p)Sw+1RGmu9&XV$!JT>*VUW(e2jjjw3YJ=uRkrXDZ$ox?6mf6C#o6Ti zs=k3sCne0QY;J23c2o}7HHNl&F z8l8aRl9FNL^XX7+Bmf>Ra}fV+yG3KSMPElk*e#h>v4~J#b6E@efM-=TTkK1KQ)QjW zN^+vkWXSoVu~LG4qZ0CO=!SaqTy@!#JDzK8=^7fk;-2ITv|`53JqHMZ=+=XLObtx5 z4v_cz?;IPWMRKjQn8&xJqs+abdjJGvyEs8C7heN!8X+r0fI|BS@KZKdl|urenW2AL zj%~-}({~(r`!%epVOK#ay*YRTrfzQQD>D=s6(s*K#qQouB2puOcmPcAc;E*Oz>&)O zuk?Wi{|oE!z@*M`itZ0>9U8t~Hc z&Qkkw?O<(HjFvoMiru= zOcBJ6k+rgLpCG;utl|N~Xcvy-@WII1+c*nwEmfoMpSH+vbIIH1PLyn#j66-acr_?={IUiR9wX(WCb)FA<5gRiDsy7Y*DU48)5%LQ z;lEp({+Xdo3+-q=9#f-OtzP}o$4~lLDpx;T(fjW#IO5|?x6Q8ElbQDCh3(gLXFoXL z98zUn!&Sb2A;70we7a<$2-3fNj=GZPFF8f|MXnxk)D5zHXGRf!LLs#_X$b@BnjQ%S z4I_{CE|@?jA6?}5|Iqt5Jqy0kOM#C19jHnvN%=EhHf0a(F3V*1r=J;A&VFdiwzq&g zT2Mr!@bf6dT1ykx>j!ex)YZx;pT!&AoG59LDO&R4dEUs-({GpDlJ$*tgiQONO=IMc=<|o zN-7q--3QN;&D_thCo6|uWe}?;lFo_NBDA|78rsj_Y9xvymU#(I=PZNn2NTMMc^VGrrbdpMjn(4UN`S(~r35H)8~c7zRx&4HVqid>9H-lJbuV7w~xqVg+< zPVAPK7g-XSNyR5uCc`(ZOr2&!b6HDChQcJ#-06Jo72NMeYXgDj ztAw3633dAlgox~ZfE=G3iw@DAylRU};OEZi1^>Z4_U^1B-h-FgW=;q-i{liJiJLBY zTa-HNSK(>IYj1VcCDuT!Qk4&o@i*Gc`ZQOPuXYQxQF*&EegX0pDI(AdCyY%Kn zE`k}4R_3dew4QvV^tQ4Ss--fC(TilRNBI=I#R}BBPvP~$=mRvJlAl+9 zrkF?4&LVKHYXL@enDF+wIouCWGUr6S2d{n5(g|cCfoYg}@az74#z^2lJo&W5eg`L*?h{=-{MrmSTI+RI0p;4eKil-m_4QQi zj|?3sz9zivTkkbO_UsJ&!leiyD=DJejaF|q8sPpxsREs39Od~qsEk*@JrgHT%zR+g zzOXY|16GJS=F@{-tJU)XHmbFmR|5WC^}?tLy8B>5E4j%o_zZwp9A$960Q= zO(*)T?Q-w>QggY~Mn?LkZCc^2y%W&71O{hKD%M*Z5xc!bA`N5+L)VG4?V8R+ zPAxBVQM8Sk5HQb6cf`8MY#1RG*jMb%vGV_WnbOq7Kbi&~!hYbmMtr%f$x@B7Y{nVE zp3wp@-<&W?CycJw&>OLsZL2uZ^3^y6HO^9%zPpMnV#!Gew`B3Yvmx-*tU}x2{Xa92w1B1$a_t-sET+k>G z?LvPK#De)O*+|4h{rsr6?o_F4rt!P2n1|Zx{Kfb5{h}*FW>9jA0rzxm5fH=0H^{PT?4mWlZ~+-BK=S4d?+Sd=HyZz>P_VhN5%E8LkcXKLQS!WmNR?=$pBiFsx?@sawhdEaI%glz2_~EMHla2EwUht9EC7Opr=)M(!2Zg-L#}J`w8Kj|;&~Pcct8U(Pr>|=)HxW*m z?4O}&RXsXoc90rY;0RHO|IRYDHsSp{E%4WMtZt~w4JbAJ`wrkN^6I*W1~RSR=Z3#xs2DbbZv>8WhypLbZ~^^%9CuO@`GBe|GI&E%d_ z>WggMej4%mLT^(|l=|vA#w3FBh(BUnl+-`Ys4F={i?=O!My!5VV*g~iXP@x5fB_&* zHL8?Qwi%g@;7PKp>$G|A$=IzIjJpV5k!L_G7U+*?m^bv2yvL`A$MD_k%SI+8c?FSq zf{LX6EZX)bvu}C7B}Wa^!Fqj+$kdkuN``10x9izIW)O1B&Ss^!F*fWkea?H9cZ5VM zs26)67jWEj8?p_ftMWDczW0)}0e+Yi{n`t`Z3bMXfv*iQ;(1u!d&`3Tyt+}Ic-+^DOt~L`VNx8Gsuj6 zUDi(|4z2h6830i=sY8|wOsi|E<`bPzE)u>nHoV_w+2R9#WDU|g^af3FnpgJ^lHOA@ zA%R{DGwR+|*Yv%Ab}X3O!L_+!h=%Cc_}$1GLsyaXjf3}S)y??m1t-*AGr+`aB6It= z(w@2yBQGuj;t@|?$-Ur0PHn&JK2(p~z?ig`G|VpOr8~2VxO84ooAp}6gvz0ki{W^; zmzU?dMXS@(<}~U$9R;$!qi;+DEZWl-0pRB4G8$B{X2Oc>OyHg#nf0HU1-%#NcYD*Ok7OXioV9mCnwn}xDqkWA0}0zM#GtpuO9Lrkzag>VzjY^NVR42U zm|pJjo%j1}OpYIR-ERZ!w}U`V8g&;M2}$a!yV9O#;hqdmwb%J#;++YF%0tN#|O zIv~JgtJ>4_h^^K*>?e%?issUu@W{UZs1h#2{XQUG7e@0ssMTc?G|!FclFKH&zjMZI z(^~%-giQUQEzCYs{WQ^y)Ru1;*^0a2ZL93IEXqJg&t^h`q=&PhZx4+gO0ITHZr~)~ zYWnk8E^l;Ws&DNMXu67Mo}R$oWdZ?EkV74N&z#u?T)Gx-U^X;wooM)`l!;iKX;0J6 zwySlC>3^VS++Pv>xa~4z(0^{9#Yd+DwR^2pOBNJ0W&cOzn}k@x+RKOPtEQsRHxDb; zjyfHnMP(d*3q5WQW9+o7f8>>D4+2>Bdx_XSCuOhd;3We%s6(N+?(& zI=b83%b4n6PrCxJ_rsx!4Hc_jQ7n{R?X<`xZ>orz%G)lphBxx_g@;Y^)f!fCJRmK$ z-(B_$T90iWQz19?c@JIT?;C$mjlEo5_xv2$3c1I;7O4sFg%qpFxBQW+l;*%gNr}WZ z3EvVg!!}2(fz6Q89PZU!F;YcM)C?JoHB)^~7Hg2E5h}5xW89j7-dMRVV5NB`UfCpg zMW3bvpC(B0u{l;A4w~-uEP_}|HF*WIU0QAwf^*v>Re z&swyjL8t(3-zNLeB>8<-sAu+O9mb5%cjLv8W%a%XW4X{PbN(qHOJ9_OO2Y+K*u zTLJ-qvfGMpRX}Z2tZ^KmzGARg%0^?zH%rfB{y-0HxgcnDJ%8of*U9>YLoJ}eVEnzq zA_y)YD53^02k#tggYP=0o^CMk9Xkqahd`P0bwGZ{uVT5@nq$4?zRg2wbf=$|k`7TOYyE*xcdNW@jAAhRpkf_DrWsly3|jRAL{s1O#lJUi_s7 zcvCh!jMKI8t5yt`Ck4#%x&KADKSaCe#TenfDD{<8T0+?&`{6k}yQb z%{mW_$~`RkWg_?;0K^|mCV9DZI!aQt!8PolRKuc?$F5hc&7b_~X7RJjNu)QT*dIj?uS0RDZ=m1nnVOr{Qk-_#exn%Z52(<;_9 z+)Trz+q&z3Bmh7tQ7IWvgy(yhd&tvr3$fno%hECTj+~+pYI8P189LaTZ&YF8BE%%c znFgauCjmlOcP;=?4*GP-+Ll1m{yrZAL=vj=QzG%?3jv{o;Sq+*Z9n3 zOiT5dU3vBG{!6-Z0lYhce#@nSLW}90KAx4E%wO!j@jJX1ADKrX1IK zpRmW z)B+6CJpK$u6ZtWPHq^ztqgOV2w=DQLuJqOt0Es9d%L@U}e^={WZ(2wHaj42;(%-)K zS5i_(wMzx|s!^Hm?@(l7Zwm^dV#IsZn(X$syS=h;Q2KfB-s+=?a7nY}UZ8%wHSq2v zWJ+;3i)u@uC#(SVN;SIg{~Ov_hQJ3$@ALo-8>8VL5i2t^W|khQ%-u$LdE6Ei5-lhF zMu}dSU6uk~$`W3YO)@MHh@RIN%bUbGg7AMgr|==lvGq!(a%(D?s!;^yQMFjPkx9)B zffEgI&Z~T5R5oI@!kbD0>E$Bi09j6POZMK0XMi10$@J;|@i9|}p``rAsC&_K8IJZa z*SNdU^BLOqpfUnJ`VT=s?KU-iN0zh-LKv&ot9|Q~wB znzSQ`Q>JTxC*3qe@SWHlDTR_BwFW>FSGFx6+$Gh0XAvPB{;tv-ol=cbkP&eeOlk z%EvJq@mk=xxX)E7cLmFzInhw-Wq9y^LG1t@^#AWFSXfKdgJG7X4;S5Ko2vcA1Q8)K z4Cqb`1~Gww4nqXSh?3@SnC?rpgwGsjX}c7aGaA;AJC?c9czp>w#HN?cHk3UB$PZLg z4P1BiE3fSqrtmJ>y50kgi@F8uZg{0CCeH0Xp+EL4cigSy^pH0#bF!fy|1}r5oW5QO ziGp=ty0A~XNB+3WdzdxVxW7-h560d4s~*%2bORQKaLm$&f0%5IgyjilmS@9YrS)MO z!;?K{v@3LnV@^$Y+3|(Rj@JAGk1}-aQBT_dAX`8KyfU(OyqXiwY(J2m#TQ>V1LB{f zu4*Vo%}-_qZjo49eRiAEy&Yv_@$ZI#=H2rTx8csmd2aMW9~cOdKTyTnAcGkI4R^!9 z2v^{k*KlQy^tF2`_V9ne$H*O{`0i4uGs(o?P3&qmd-4_-KXT(IQr)A|;ifmt+~7x6 z=zI23h#J#XhZ2f}dkvrs9!C??V}o@?ctXZ&yo+aAq;G-U;^^3W$1d}Yp1Wy20US$_ zqd}bHCx6v6=I#;zh+xm2%-6Yr++p{j5lY?G^)QEp6^RJMs;z2DMo8{(*ITwM|^AXt=;8d##wZGdL_jhFnzIBRIVZ+6-H*vw8^`<%MqNP1xQ_dmX1rzBs z_~z){Z*6VKN}rTewlEhS)!U1Ecx;2$VodS(^$gU!rLP+JeIx9gcFkHCx7Q0-j3>5p z=)}u7XwEn-=vsm4u9fX%iq-0RquG4E^w#um# z{T2AJ3e|UPrbqaTD0sEt4j$KI!@CT{G|@jQimr~#29CLPH#rxyMjtGXK|^r;xRMR8 z*gVN$Pbt^2+E&3sZ|()Vdg>6ywT0JJMy0vl zhODaYe+rG_|p^{19)z>ucs&BzHzN(O#o!LvcE4Lp)+$enUQ;(9qiJ9pDjh>j6f1@ zZP?<@b?}&fDNNqoBRW3%VnCUj99cbZ7JD)?n0u6a7xNbd@jJS$|Zuo+|TcZM`+#Ns(1`xT2Bzs~O1(QrmzNvurlmIbiy+09-RW zVfo%r+il&V*U^=0tq7j_lV^F)0HloHZ-B6LBa!6X|EzY{J84K94~!|Sw;Kh^kGRiZ z_Bx~UJVi^wAi20P27qj+C*u&)81XGz^8Czg+KRk$eRu}ff8pZdP&>^c6q`14j#*bi zi}L6643EV}ZASGpR224DJc4Pf%x7O$hr1k}CTUc2YN8fI335@M8|SRvuUXO4lAOiw zq0uJfg_*}x+}57A)rgj(7d3|``-a|7W23s=0Poiuy(OE~-fkCJ6CejacfwrK%Eig2 zZh@6$v%WiI-eohA1#9C9l*Sk;ehCRWfdf84GuPYFea*CpuP{kw=aF24f7&tcPqo3S z$YP(Ym!8)7IKKo(DAru|Def*%b6uZmnY9R;+$VP&Kh<7=Z|j z@vUj^H2<2ZTJc5a{QLknd}LkpByRi6gh(s=r?Mb{6Av2`$2A0^%AvEpZB$Ga6ghM? zqWiyHnRZ~f6admqe<~CU28|rky<-53#B?Q;Y=j*?7cu5lM~V8QHB11+=xyC7%4d#_ z(1stk!zZo2;yt2omcx4XQn!jgn2SMvr(qtbzGllpQ$=fzSMQ1bF;xLQ3Q zJ+cyaHzF*WPc4ItVMJhTIFs?4>7t_puOL}|V9b7KQNI4cpzLZEA{JYxjI0@?w!siq zgq2=e-1=m|17O%t^#c8Tss(R!bf#(pAh532v$pGbajNp&4d9NEvH8Gy>a>h9v9n2s z?)+V)4|MM`@6AIMx@}0s|6uJJek^7EAwkYnyT`I)KkwL7-P)?|bt{$t{cJ0l&fK8! zxVuU&)ct%S>lb*CW#mdzGm^)cxwQB;)cySIm>FQ!kKp5jVEw^op$k^aO?2JFN}Sf26BR5ZlaMk@vTY&kYlz~j-zPjMqH&j2&c>__KP53B!dC40HQGcI2npr$FuN0ogK z%Pr?YgBOM^e>Wg_thG+L#CG+UZk#UnGjI%Ur3|tytngCh6N^B506JIg-RZ&>LuSM} z&_$0_(eajMU$;E$+cn5!hl8NJMrYKcq`dA9vZ^lAIb3HE(P1KD%=9*%HHra~KUa9W zhm@4v?do`l(A2(NGV6jLRF*_l|KW+6iElm>5hkvEt)~?K$i>URl?ex-$SA?DiNRs_ z47Km7(E=wih?Yre635oZ=3@%NI<%Hzv)h>!yIShJ84}`eKP&qF*lUt~d}M2pds@hw z9!ERwUP4u8ElD)}v3R2itrpzRCHOm0OobFbw%}_`F3bf_uke$IsnTc-A1K? z{K!9Y)}U_a?-E&eLm{u|7SrUWp=kPb4njKlNR960K7S!gMivxs@NZw*v6KLnP>(P@ z`Mc+i|C8rg7<+7g-Wf8&U^PbTvG-Mnc<05T=ZT1e&Cx!@=_S3&*i`#UKySue&3lsw zF{u%9vvJY7tGw&XC0&BYAD(zbn=dHK%Tw9n1l5!YUOhuJbZw+K+v{>N##9mUrq_TT z<5ts{!AY0=Tt&}GTf}@zSN2d^t7uS_2I{O-J`r@gfiCHK{gpUH|T6DuoC zQKl@NGPg>^4V!84)0CQNVv3SFWvPg0t{`TXXl^xDni@{ylw05yf~mLwt|66zI|>Ts zE|Ln0`Y_+;`JC_XoZssok>gP~_`crveP7poU3!>_nfyzWc5M^4hdIGZ%>|dFU!`Zr zfvh%0zI!+X~>evQPJ=9ROt*tzC$})QI4S zD39NmDMHH+*=y|yW^Bph*N#Nw(ihh03uDnB{>FTi^VV#^M)g!4eJT$KVS3j^&N0&) z`9c3WkcKVJ<<3)-_&cY?A#M0iotZaA$8>#n-t4e{#g}(S>EVc`1G07MCU4YQc6dw^ zFVK8vzgB3k%~4v?h3{TfZa6O{C9*f`nmEgNUX=2c^u=$aY?j}Zmj9rq$k^2XO_`xC zMYbx@@xP}|h~9h`Z*I#h*pe53KX{u#w&;+`F7c@M=8t!$x<)G=t=3B)yR?mC_D{x4 zCdT#ceHljFF-n)};_@mVsdk?kZ7|ckGP84CXjAs_HItHRLhG9xQ@UCz$={ABrB6^y zZ^ZsS6L~XTHs9*`SXw-D*5dV@koEWbW)soDo4Q&aNv83u+3Ls(RPz>1l+69~DEW?l za{7-xr&oWA$@`_gYy5Z^wIqFKL;Nfp4H3lt8b|+;rz-UcU2XZO{8$>=IkfDafz+vC z>iyWZD|<{CL#J9B*-2wNup4~KZ#rYa?-%#m>eP3mPB%^E0~@DjMbWS^_{*iI@umx9 ziAqPt)`Cni$Pjh*3DTHe5vUNItIi2yolgx~7@+!|vzy>2?aZ_9FZIvcHn{y)L$D5t zdCQP){jzWdvMWbpn7->^pE8bXPfeJyIz;R?((B;- zYWKaT82KJz^4tapng06@uJqONPKZ7ie) z)YbP=w!s~X>lzD|u`i1g zgZyz*0wcI1STEu;I-~dE@bpyx3c@WeUw%iHe?gCUlo9iy?}m+*CN8slf}a*h!UE8r zD7Amqc~(~+I_ug0x$ol-kYLuX`+^sV5hoq$Z>44Ce;iE(w4RORw%4`-oGFt*DCl%y zN5F&H)10;5>$r4M(BL`P^sVk(2lG$m=&Rh(jchg2yp8#y)ix8)duaBPsX>L#B;1>= z<}{P&6eHNrptw8surAHL_EPaHPK3^P37?dxpI7pqm&auaoR=4`tO>q|&;nhoD zLyMnzzI7Z=RBl_Oj%wK`Adz2%IxYiV3?4-bCM89fix!B|m>GOn$W>kL%gwMLBJ(R+ zIiO!Y&4YesUQ!vHxRVcyMfAK(x9;hw1~$%wjStxws4u@TwFj|}Y|P4@T&(l>ei)E1 zn|gl6X}M)4?}NOguJ-JmxG%P=mQIFvx}S0bJ4H=zu#1$NoDO#lm}$msvq1ze5yT0X zFz5h)Cw=(3Hbiggx&Mr3IGfP9mq?mzhCvM2u~o;YKj?j5Fbls|~Aq2Ao}*f%=fp5i4H5R!!`>JE)HS4 zS0TL;4C9J>sOe*Bu9PzV%r$57*0Y91lN&Zw4{IM%CvBfz5qSHVtbAJbjCpqDQ!{1h zXwl*d*}k?l*IS==9IE_Ta^<`cbV563M3lSGkuuvH1l-C2@10DC7fy;ZTqR>DRDT zXn)xkw6XF|^7@`i@)syC|a@Duuh_k+w?)s>^=#=0IFLkWP|R6@1K zBT^00w5DTJEC*JPEj-w10{{&@EzcDG0>M{cn2s5^-b+q{5`UHR2)Tq&{&Bpeu@VSN zWjVxs0$}lT8q|fZH#=9&m~&nR6ioAv29FoMP_P$ne6OFXeYig1`?yE`jSqNaZFdum=a@?e+E$(K`}4t8la(|OA*{r z--{7Wy1Oxd{Uc|+wKAxHg}EU}K^}o8VOJv}9;+7Czc;0XE*wrsDbOM%PV3Wg4e{@P z-I!&ao|jMb*j!BS{YN>k99(2w^dZ^|kBy~JWBTyxE~0nNh?w!N0^tRAM2rWayJK;x z_I8>3+Al<*iB~KT}v6+wl*pi-=Gg|^l z2k)wmn2bpr;Ct2Z>}@z1gNu(Hl{};3Cm{Etoy-!sIO2+%hJz)b|5$AKX%Wzy?@t6F zPStv9Oz%;@6ro8SqzV6W9M}(+Nydv_D;>(H;$$nS-od^f5n6!{EWujyn0udWuho96 z5;*VjvbD7*-jJ`pN-M|EX~pOBz0*qw_P-1ERz;LXMr&s!#+V_e`_3%9TG>;1hEvfH zK0)?IY<{r%S(zO_Tx#0q%H+W>sqq3YWf+oo1<0Lx^7RU5h(L>X?=ToVBxL zdph?Ewmjl5XQF7*u6%S&7y#1a-f<Cp#>mdG*M9o^aGun&yGyjN>qk{!Rz}i3wq-$ z0zcW&LuIc3GuwbyEA7xk)p$g+_TwE4Ux!T`$-llqbMa3wXro~=Wo?Ny8XaU7BCOR! z&Wq59e7+q_BTYhhtP!j<{!7(p6sLQbPCNBxe3P=jl8J2MMceNwZ((y3Q4!va#4_)8 z@8(rny|6T4Ct~1MD?qrVm6m^bW9=WRUM8)1SU-MR2b?c^g3`@B1KYS7jOdO|zRX-C zUw4)_Uk{M-hi7I2c3WT`FLburwu1?QX(|~)I8|)vTaI*UT^vs=kpEd%Ye6Ov1IH(S zT3#Y@4Qnh?{o}O~)PtdZN*i`4ZC|~t)8%S?*K06%RlLg-k!vqKUzY7LEBZ?bdSDgt zi^(fSqrU#k=>^h;XgCT{ATZjpwETY{DH4p%+5e_YzGq?WJxH6O7XQV_)2Ej@g_9_I z<~nD4-{`vCRw~6HT(&BCzOY7Ki2ZDBY_#>`hmR@wh;61fn@4m^V}_^y<8BVNw5@t* z6>VyY6_Cb`Woq!J5wj^#nt@bL%PLJiG5p*LvyNv)Q>|}5@##i=olkWTJ9Bs$v=Bym zCtkg6>J%=E`r{8rV*I*v)lzJ8wl_%u{OubxWw;fW>8&$?a!0#IUt35feGz{R8yWXNYQzZfmh#>vct$rOmLH zWwh*I)gTmc%)#Q`h?^>5f7(=5_Ic2>S{-9=a2-6r$}5}q5-PL#{tmJxc}hU%B&LJY z4rHLw_md|$;Db@-CH0F*F2)!QI0&*B${~;jlkatVVxf$$<(b}{>XviB>L&DS4@*TS z;APJ9?n8gTL5s0FO@9##_jaHJt1u1hsOT&el!%CytLNgf(HAlUjy3ga;eYt^*B zda(I)(s}8QqRDKn?0^{$TRg4lo9?#Jy5SsHZ89oEKBGy^eJ0MVn1cmNxzcwdzlK?j z?E~I>ZJ`M*K5T9P0r`QzCk_3>uA%#tJTex8;Xw$xp7*t`*+~+d$F9Rp1b+qHP?ye4 z^R-xSbCv@1m2|a0dKI?{JHxDT$B6yCyKdUJWK{?U5mh(JWzhvUgMf_G+aUWEYQ*)q zVo%wrGpoRjaeY06{3F!_GkeBskKfgX(6g4&XQSMV7f*p#{HpZXC0GUX?OI7dlU@AG zW8=+P62{-IkF*Oh;W*;u5B(O9cs@wgKKw_dEvUaTs@ynWL%07v$Da64)EgBMF0_m{ z+k1V3$!G&z_vbq6@d_=@NxmAwH<@-n~Sp4{P=uv^Vd}DyFVgaZL^V{TK|5whuAK zgX;mH{S-R)KfyR#{UQ0@w(63QwTUZsja()-Zh5ek)sa6%VAVw2z8V~JO$l*%!r$1s z*l?B10?*h($sWXrd@d7;<@dHCBibo2G0|oV-G#ZrLfV5~aI8{32I1AVL$4 z5`A)y58snbQm`s{f@)Sm`A6-oZ!UuR<>ElrsFvu;*jh|meoT#Q{AsD~o``*Y;eUZ) z_0u~YLTF!?u3L`Jei)FL!uta6k-5YcgF*2pgfa$W^a;wOVg2>xFOxU#j4zhw7VF&5 zG|#iIeD4q6@Y3l_8sph^#RBOz#YyZIlX`(FPt)PfM%+({_tExqz;q-Wy3; z0n+!ax1hAqD)sD1G%S$d|6Q)+ba?|Y7j#cnJ@fIh=dO&(Anil#^B8I~@ilv99`!rb zoPWds1}Pi*52xE8vXMXDW!Pwjp$Zz%l@U;;)*d*5HP&N*#}F!G@l)rUaZ?pW!!CxuoTZF-s! zk>6A@hGj`9mf*_J!~j$dU}H(c!G{+?cql8S4!T57?=H{|H%rQlR!H`7wD+m*1lNS2 zLrP!a%7$w6yl$H!^OLa(_S>4}p=x7@-Wm%Y701)%zFRE4fnbI>N-SS~3jpXCLT0%a zfqokJFvkhgmmt=H7eKz({1X^7qg&(IqOOx9|Kc!ilqvI6U6i|lQ4y7w*j;^KZhceW z$X-Rw!WqdgwsKE(k`?C>TI+k;_V<>R{mF)=Ga7~)yll_tq;!G5+BK^&U6W_jSS>@o zI`kQ@53P6bPSDCgAuQajRx(q)4N$KEoj!gi{8)U1S!OFH*n6=rDIqI=Aa2fu`=0`B z?j6(Yz@)m|&R34oJ{%#~WaheB{N@F(Q=o|lt_2Z(rA1f_73VGQtmHs~(!c#XzF2-|97Pek zzLp$Qq?H+N3O@(VZ|v!sbk7$SLIzs}ogM2A#-yN`v<6(g%0Q`=7v+qBJ#IKPCH{Bj z)53^v0|DcchY*ZgUbsp@hi(Apw6BhdPQINg@4zW2I}d9sQth!7cC-#=U4h6-F0S;( zt@ydDL^o@!W;t*dHkvfSptAE)vf}_v6W!%?N_vdUctM^|cL_HFmTba?a3;|qX(g{? zT#e?IS}23Uj6(B>xtRS7&+F(MzJF-%J~TvFa>SNak(M$=Y;iROi7u4wu!*K=XA)`< z+}-Nq;&)N`=EJO)03VumY9Z7wnR7{LCo5!p<`AA7(F4WAG)B`&jzMnr$l9hfOOlV` z{XIxn_~R115RUYpU0w}Ojw-VjqSOv&sS=gs3SX#O6I+rxIAaMUj2Rt5k-2*`7XspX zq2_%!mY-MmEXf{j&N}2k7XO~!`$p@zu1>#V>^el)G_vWn(}1$mQ{RmBLjUT9@EK2I z9aRsDVg?d*Tm$^4-R1`lsb~h`6^gN&4_!k-hpPj6a#<`bTj{X?sG@&0X2z=J+1MLc zC^uKHlBg(BVqr}uXmjIwtv4ZlG)}fzu%&cFl)YM8y!&q_eSxYh?3Ab%`v>1^y$iId zd-!l|38QiI@>=PHDpJ<6WRUpme@~B4^ZQ0xTvp?IaP3f4)yr-Fs83i=g#Ki2h^Z~Ktd_iF z^Ehf=+v+%0Vhvx(Pqe#j>NBx_*nPq?s3u@;^`-0Z3PK~h9mBDz@9-SCGZ1b({0AG} z*L>op=&Z6vJr@k568i5fLR#w3$V!+_*N|-fuLtQONa4_d#<+=K81)O?ab|w#r-k1U zs1MY84fse@;3TInDROXUEG7+wRFP{@F{3bj`dWW>s@8lDq^UZQkZELl(lozD+q6{A ztj63r|CnLitfVdVb?j7+f$(u#2AH*j2!nSXGRzl@6>>ZX=f_%#Bm_49jXK~e$Tnf- zX@aN3b<_B{UddZu*rhV2Hk0kfZ88F;?` ze)~n>G~9TEah#P{K%(~^*=KRIqH8qvF8)^Y)6td1y+gvP^n!Ii*7+0<5V z)92$xG-_C1%3;sj%ReqXzAPI%K@2!XHXF!O-?(6(0=Lo*2+)n)nVxba`|PHfc96-~ zT@6&+eWUbLIFa^J!?w0F&lpA<+gZj@Z8=1k?$v61-gJ?61mpQS zXHCd~LArO)?B?%8)@!xRwP9CDuMWT;C;Y~zOrAFhGnmJW3J+*R<;OZ9(o?;>Wd{Hm ziWHFVURdZa1%a7~5EPJkO?|=5!XF-j^Yys0bd0vNFUTvKXiQtHc6F^;OI`xvD*G`PeD$az22x<+(45Hoh2F*U{1jB^ML$UEC=`mk4 zqbk#9dfo;4)myt~UGQo&3L(7``wZ3Be-L~7*0sE_Pq>KLm8h2C_wCHPQS1en_$dC- zYx;d7k`brApt*OaUOaq7Wa7@b2ttg^wEcQ&HEf?fKa^V-^*R@xXKcAyad&ll0Wroi zU7k8V5X64+w!6WZN(plM=_;O^r~CL-s;kOnf}IolA|0DS5A$+-m>d^HF95!_RNd22i*BzjwAwmJ|^`u4cnl z55(X2<`0$zjP>2d3qr$}x})gQUuOhWtBrl#%|n~}r(gVWF#O+H-+X#h##LRUk4cJy zldq4B-c1&w`DoOS=!2>6g|8E9k6NZvt=0c{Jj9oNJefTfNeY@U`80KsHw~ISL9M-m zUHUj0r@+sBDsPZqniXzQ91V^ zeO?$D+Homr1=5p>R>9@X+x+pFDy?l{I$wQaiHwAZrISS*yA0al098vCi_(xAOA2*5|xLYp^BemXmc4ixo@*b*0nX&d0#?rE3 zT8qpBiSf&tIzTKSuGem;bvV_~@^AM*V8Lto-%Pt^1|s>*C7>xR!cAJd$<$Ih616n_ zn1lrl2>$?%xcW1>G;hhwsgVluS^1hj$1sU&4C^v4P7JqQomfBU)r!FUk6wzrMZJqC}YGcBO{( zG%?~2-QwE|KkaU$w3K@?z9@GcDhM4sT8;dl0T_Cp8PoLH5V;ZEl2qb!3?_TxiwC`|+9_i|#Cpy&4BTFR)AEt`lOWFakomI27-jZ# zl3;%=>1+^nj$-5Gh8DKpm z40A2K*20umzYdi)v$Vf8X?NqeEp=aLXw}xjm{O;(#M^O#pPe^kU01F~i%K0OR3GLC zgR!${g~;Gi!Ex?|;D}xhoiaE~Ng7K&0$%tfXJ~l@$4D}A7#DV41Urd4ZE2kZ^|Y`s z3j@o|=jr{V4c^0?MaG26d9|_cCWW~EwVG?uvgew52kc{Bn`43@l1F#d`E=6^MU=n}4dV6uX1@gQ5N7KV8xN$tQ5ElNwVy$` z9fy2F*s+plL%-1Df}ABbko9=_Y!`j@#$4TGTLCz}YI5NR2cHI&Z?Pj>Pna0jHnISG`IO5VcRQ|J5@od6Kf>kXX{(EB)F_K3L4}WaG9WMA=Y1AMGD9*=`m6f9HWd(c;JJI>HkY zqYsdU^%q3;3>i{Bgy1K|W%@@#i0d!j!1B6J=uA^ohPY6#J|JXxR3C@@VDo%$678SF z?L!S0bbk(=TNeNB8&E!yVB9}1Z{j7*5o-#xo&9M~nt9PH(-)Qpvfn|%gykk1)@Q+p zTT9BjWnr+aLeyCx=$b6+_O0#3_>*}y^Up>er@G5 z9U^qhts!+N_fLQhihx+!2PR?xw{E>8V7^j3%MRW%>9-UInoLa5ar@)K3~A4%S>4tq z@t9veEq%$%1iklK4Q&ZkFKB?RE_NOaSP?xkQbtY;w_kKzw)rcm9J5L*eg;{u7^^PC zjg%E>S5&_Hlnprr-&|_eK+hLOz87+ZrOJ)83Pfk9&18 zCco4j3c&O}^p@XcjEH}74)z!6qyJW~@w}JqFf*bhXuISsusZh3&gRs!tL!Vd=^hiJ zt*qpXZ=rh9U}-iM(3V#DxFl2E1{L0GlLz*~?839Y0v$qvi*eqhBQvcVkqyktSN+MM zEoSQT+(%0FVtk*lDU|iX)MtJmhqDw`zR6kwhH1I&vAT|*?Rvb(sLNsh2n+VA4jtG_ zXMHgQ*pjUJX9agx5sUY{OYQ|dFvV`RGL*H~q@F3J0npWCW2p+iSx?vZ;HZVTT$K9P#?zc*&w;Qr<+S1*KBBmx-T3!xSRI#1GU&P}l57|fZ z-XxB-mfF+@7k|7_9vW(*iN7$+?)R!Q^}6g8RLqLa9L)bZS7drv0Oyt!38x z{DWo1I>&=4n24u0*vfi1N%Vs%8qvSkHo3N{M$nZ7=8H}`_yi>_%Z=q9t)&;+q{`TiRd_YPd2ouJla=rFIdU9-)KR)0&h%S`fO070EBfM+@DE)NvW6j@~sU;3ApDEmrf55g^$mcBOAs10+dRUmct z?ezQ)W>{!==$)9KcoA6+QKEuqHllZ z`w3je9)3{P!TP$DwERC0+Cq&y{&+EZl3$wNGE^7x2PG2QxtmWZe|E#_aa4+Jhok>9 zt`+onpJ#Q$4CN$N6rD0y-s#B0mwUd}SC)h&o+J0!%3pS{_Hw?%jk5ots|`$XY}HlE z5iE1Iy6OnOq0Dg=Qm3?>AZX0OKTJY(V8O*3H|M_i`6o8u^ZHQdCWqbt^5*30H~jOy zQse`feEJ=DW{uhsc{Wy;d0cO;guN=O)N~jU$E=`)lYrEMxK4RZ3|KQJ>e^rFYrz5u zqeF`>(uBvfXs9G@LN?$I{^VI^767-gXg$%=#s!T5jE(~}@QUL3baZ&rI@{r(h6!$W zW>NsLU%4k`Pti)Z1lUUmacJ_bN zIE;xn9{6*c-;!!sY{xUL!mu~8>Mw=?7;aMzHmIA1o{e14nF`h9coNd}QLj7HHLrVr zLRSvFP!W`c2#YDTjMpUGPeVii-AsZ(k`})nLe`6WrRTnLrluE& zQV|KdmiQ%{<)fP%37wuKqB-yxpGJ(i@Z92G;>(-co#|#1&5e`Za`{p3^({hKT9+ z8#ibaLqdpX^RnYZk6v4SX?%T!KKor`wHN1tSoAL}Kt>#;JAN+!bG6b)U_IUs;P5{S znsDz`lkp+dXC?fY$SXkdOWNp#k6@J!EbybkW?v3 zY#b~bhwL;_D2p43IC<>O9zAnF^pq60mUdk|SJxIQerwg_d}=(b&2f*vtmPCS=HB=e z|DYwO1+9C~MA152_T#BIXc?(L{T?Gi#NajbR}8mt8)~!4UYu%g&#@L5 zS_WysY=;ND^yl-yh=FW%EF@H9nvq+(>O4r)Tt?Z4~V6dc&_)nzV6$O%Y-YBq-{H^6A zBp$W0lz2R9hAh8r3h=NSnms{QisL}BhnRn=#@qhVt>~pkTMv|oH$7xyKo}t z3fohiFNh+HjS8L6zk@pTX&3B*-=yN)!AQIVjt|x;R#P zMknazh~&Y+_{iF(_uPF zPB$Z8pMEh5`oVlDbn4cy4PTOB->*(3#6o7Dt;4sbk-9KRit{k`lK9qed;7gtt33Kr zF(0!sy5=sOBx2{)H)#p0d}YVEoH8jj0B&tQDFAqoZ=qjkng@H+kLTljU&IksPbN~7aC zWMU{gc)XTjgtw5@6b}9sqG7Yg1JXS7nOrcsVRw0TT-pYxiDs6s-fFuzl!V_<*l68q z+=s^r?%LS?d}zJgsx%)ZZhLHZ3JP=?yns$?lt9=~=Pu6z4~?~63I@YBV>bK?&GUf+_(rrA7D{PjHNAZHwg^)-{w zkNMeeJVej8UA=~(OH_llE;kd54QasRCA;c@<;6Ezh|@-+(K&^{6s@cxb5zg{O^X3u zD%eC>6sff}KxgyfJ;KZ_=Y7Xy9RO6E?yY;qxm03@n|%fm3G?%;u2?LRS)Y>5I8gBH zBOp+2Ht%P$ofng9r&IIX*ha!KcM!MLIVpn~r@b%RrOw(hf=lbl&eM4H0U%^QQ8O~E z^+(HmHuYmu^Qab*LeOjjI+aw)6kQxzuJu3DeCucWH;f`_RSCupm7~#a(jys)&=cy2 zWJoh0$ul|ON<<`wPg5*vXc{fclL~B4 z?uiSQB@=Mf9i=`-Of-*ehrz8)vGUM*RvQZj#cXcebZxxYqUcT6@8V^|tV&C)! z`aj_uat4KJW@UbCD$Ou!*-wCIPT!rAkME^5yBZ$V+^pz*n2A*kyht@)g%$-SR%`Wo zW~zfBxVzFI6g(Uy>20ywBxZ&|x-)aZkPC=a-U}SJqd_}`O-u$9e5)3-?y@Z>alQFZ zl%e6|Kp#*|m|CBmPWXFg-L8v`nCO8LYSg>Q9?s7~>9{E!1B!*W`>xkB#{3a;l=PAt zLFZ2`C+=ckpDUL+dSh|vmR2K@CYF2g2In+d{D^P$Eki+TE@AKs{HiU;>U7} z3lqNSPl{^noc*2!HYJ(PUxo^+tLJlrYAd*ayb3~@`=HnvNV~BWAL+In+vCDc_4Gh~ z;2hDSc{<1#a0i8K+P^4dk=ul8QKqW=WbrOiSp9ZQAlaAbNI{4AS}p|s+!uJ~D!VvV zi?PqNpxJCXgBDfsE?b zJWV|BI1Uo+Lf;NX$WpR&n{UD{fNf?DBf~r0XQzj#j_ZeiL->=*qn8p?&I22=(}Jp- zhoo-`rqA#PdkjMRI}*~(^QoK%2o^Kj5hhYeEy!Wtj(Cy1no4oBQ*WaijxL_4I- z1Avz2{WLER`z1 z{~p9E*{i&63z&|q;rnE29I1IAYiKwBcwo&#iedSUQ&n(csFUy9 zi}B-2`bHi`!z;|%R?oS|=gF1smXFWtC0UIAE1NVhwUxANsEHttu)~Vo>^o6;pHs>b zRUuhA)PZ{T)XEC<+UIwXfYbd0viZtCM&Fzucv@?OPGr2|I_K;PK?M5rUduhxw{%)Np=Py0e)L8-kEbvc1>o9$>AFhe(11AUy3e6 zADnmkaz?@ls+b)u`EI!t?9Gx46wb?+fe%I3^!9N6V=FS$BTD>KRNvL)N;FNU9t2WY z$Pq~oAak*g=P=MrhLG1phOl+u)D)wrz@cBKNRb@9L^{;U{HX`m{Q0FRsJ}M{yS&Re zPdOVFeTjXy*#F#1UR#>AIqj5o&=4Db*Ldvr8ps;cprYlak#XPBNt29OY$WE9XslGi zdw~xJ6xGX1Q!tU+9pd~d86-#vFBl<+2?6~K5m)!dejmmbl~`^nsLP>`o3nG!8)n`m zG}h2b``VBDWns;_VARn7|6s;S+TAjL8QAV59h!5#o?UBE8|U8ZYCo??@$mbW1;#jQ z*p4vvmJfd~Qzb4k6AXdfvMLL7m~;=evAN1U^hm|xBNbP*iCuY(+CerG(*dyXqc0== zLFP}tbM({*{%}twOS!z<*2eXaSEA|TU36NoR~C-@bI9y1tyQTlXjPI};%t`^6#lb` zjqE~v%ylc8XVLWV~9IZLrpRFeNP25qMu*N>g%1}Za#S2)e1&z{X_a=ziD3;#>9_#13~ zfhp0W>ZJO*c=Sblp|5pqukw`Be$IRAJt2T^`BSR?C~TAce8TcOoTKaQHn3C z{qx`_JBf+8`o#WlOi2vw!kkhjZ8A}7NDUHQbP{+QnO4E)Ivk}(EpM*hI2D#fX}k4Q)>yQ&VQzhu8@cw2o#xJu{MWvUhx2o3w`FltfBBeWu z)LOfU*8t&6lKC{RxDg=x}v^I7IA#G+~#EP zrjITV0Nvi+t<)Jv9(@sGYil&C?XI+fS}|IEM_>v`bE+EFD)^wLIkTpNXe=rF5_s#X zZkfu}+Oe@~-AZeQ?=GWZZWMn*u#9O(+Ub_pk~DQKTMHOIb^-!}D9ZFtRsILCFWGaP z12Dn*Ieo9oMdOAox6DM-KWsbbY@Zf(&=myJy<@(U5GZvGqL`t@opxUM#j)03i51Iv zrpr|s@)_m(9nJhej0;DGZ--|@x9^%9wLYn*yK?PHWZUq$Yr2 zsXhLWlXw>$;VT%i{_ITg+FG1AxzkBjH<_3f;HXDie7ry`(4yaSfBk;P2J2zLG=pJ% z!wb0+jwl_8J!A5&eldY^!>X#1zIY~T8Zf$6wB1s2tMUvQXx4ldOuXkWX4>fm-i6tA zg`r5bPtprzfqxj6rO5Xb+V+3%o^H|*5LC$@6f)Y!g;&gM@KwL7USyh*5^+(jq2)O2 zRtJU*vMu9dio`9=r{oZ4TABL%^sb_qbQNq)*x7d>YiTc1qZ%sbu-hUz3G$?($R%+X z5$y?!E_rRsHPWNqR9}n5zduh8O?O#2K*uiJjbN70+`x^zqlgW$mye>?F~qJ+D7bA$ zTT<%8g^syidBkeWZ%-m2ff&=BzE@eh#yi94V@YA6c+JYC(fk_ZoNKC z=vm*>N0bEabRcushHJZ zZlyJ;x}fi1F<-r1NmBK971+7xhnKGmij}LBVNHo7ypX=Uv+Kv-W+q7v%doRfi@!L~ zxh6o_m`j1jWhG=d#SN7$&yO56wAh>aI-p}R$)b>7A^HK%#w3i>SGOZ8MI$!97RW2R zZ^`!XYHIj+w*`#trxdH(957%bn6)H7VUnO9B^kI^24 zuM@xUlJFLSa#UJio^vg#?;*B6bd_K)D;cVGcu~grPqeV8`)UTlmO?|x}@A34vGPdY4Hwr#e!6j+_?o{e+Lb6vP?wa<2}>;zd1KQ%tB=DKlf z><_*+gG0l|@hnblTxj(0(~dIL!v*Qb_dCQb6^ZlW(Pgu7O|1(<%7_0uKAe9y+-teD zHYxdEC{#YueX0z`%WBx_`39;Fk0!VCV~`&+%5scs%XJq(F$bcteo7j-b~}GM_K1@C zXtxTAz39Fe!l?HNG;!)U9!Nc1G}*2!+m?veXAOZ$xl7o2Vdz6UCgj=B`+3O+-{DJA;I?no&xY^d^4d$( zrXa7WwfuKsa2Rt-Hd8o=L*=U%l=nSO+*W~VC$LO{R~}_t2>_sKpn_7n5kS%_Jv?e*%Frj3>gF1#Ne_(w3qR3+Iv&;e*kKkTy8V5s`1boZC$h zaK#Y>P&umz$p-&s3z^|i?c{eRhE9?o!vK3pH6@ts(Q)TXwHYo{GO$2 zjl7GLR<8ovmL|0jX}WS*bjK-rXsI1Yi)SHXB-z{Gr~Q3odzby;Y6Dp)+)RfhbQQ7J zsMh+OZdH z{kcQ7uG^{+fvX;wd`=i_eoIpG7q>-APrNI$vM{mF zFHJE&X`&bCxm8o+>$2g6AF8oXhIv3Q%qzTS&SdD@9=#NFAR|9lO8Z#zG8g`Lc~lv! z)JM#*-ls!m+@G%S_Tm}G$4Tzd%h2Htq0->{-yE-QRtO&jN#B!vMBMsslWhiG;XP}A zlXO>FcWtd9M#fDpMNaS3K|@%-kH2lbK%VzS^_{?=5ssxcdr%u@y+=)kY*{YI1;3f0 z>SN{6f-6tj^bEk1e%_zzS21N-w$$2>AKPAJMiiU0jwJ(B8i8|yGUWSKOjb6fg%=0v z_bQqd8Yis>dU)?jkuX1t1hmSh` zlX#;cYubJfzq6BWuO*mkef9g71uA{Hdl_-kUtUH=JSZ~O+yEY5FFdWw|L)inHqqrn z-k3e^UR$h&)gmQ4e{Km299iEbj(oijx>j#FVFa z;}8h~`m>3T)go0-S-&6b`M3~}6$s!6;ye_-s{`XigFqIc>HaA0DQ*8|1?vyj*QLja z^XUTDe4;a@2jBpzxnOg=;=PLnP`xd^n-zK@Sa$5=fb z-@7BkO##`ePACK?DT6B7GCRpC8oEx&0fe5h1F7~ls9MJUY`ar`&qFpCb4}Tx`0?UL znINSTg4S~x#l#CfE*vX$VrR<2B3WH8jFrmB-38H0zNVIuTvxmI*-K|l09p!!WG~g) z18ufs4kw$kGz%U*Rw=n;Mp>?d#N(9W_Qj}0U`XxRJ~B=KR~eVJ%>3mf8x)*{&w8`m zkdz9s?}zU34mgp%?@M_-OD*erJ+!yi`bIEpi zcjy)bFE@YK?A6?At=At}e|LTpT59LpRi4{y48UnrvH|z>a#doWIi3Xpm5E-$E<3Vp zAB=kmfm70KFv*N7dWB?+>4l$)jCM1`u-|ZiH5DvPok#^YzknhbuyEHUy&?wCc;`4j zg0H$EfzasP`Gim}H15{NnvhTU-4xa9YSt|iS>~lvJ?i=i~XGnTV@;`ywJei9;s|8b%UdcD(1x?ZlZI| zbhDmi#C~Vc=)04!dEu971`r?d0=*!lwUvuGpKd25wcHH)4r=}W3BK>8`7>c1sY0~kB1O%<*J9H-+MNj1GZ+1 zg|*b3wv+rV#Xw3IRgNOFmA7PD&j3-HK8zm~-4yC9PrgCwN@2T)!X0RfY_L~Md&l6J z0vY32*qD3y~|<~EL7qD;3IrsQ5@rKw5f?G;sP!Rnj6z3h-fOHSgt6kU}(?Py*$tT`|r8_ zfpd90dX8RSKFj<4dP}=s1YM=C>_ZX#rKXudyitwwkTvvgQ@e9AHkP|bq}xy-sF!n| zu3oXK(so-f>dJtMQ_nd0ipQ}zO_IZ2n^K@2c! z!%Xm96oU)(^Z${qtS^24|5aEQe4fcXKWuGAu6@xXauYtx`NS;6^(K6?$Nu;F_ux!0 z@zCXO!36EAcQkl>_CoCIyDb*uRuKzP_Zm}U7o-t}iuzHPHR0p=r!$33Rg)BPlb+G*yN&CR${wHA$$8H@wboJF9K7I1 z{PgabgWSUUi3<0bp3q9p}0>X9X|i*KlEgl)hIU zu{$zfAKLcQUQ_zUhq@QjzK|H$PzysxoSa1jshk~a_Zi&qyI;gc)(nds1b|tSYInp~ zVo^-ig7%WNkn1~U#JCo{XFxY~UQ?x$&A;8@19z*iWXwzUCm>{0s~@Fa_EAS!9X}}2 z1-B#3PDskj^hf8rDp@isq#qIq=*gzgA8Kaw4sjNJApi93F+zG6bKYFbQ7K$;L3o5r zQo9WQ5dq)T#ids4Wg}gsq4XMN(oA9esL zMp8e~|qx4R0T!9(1`pMAKMFZQG)@Uu`V3Hle{KucnVDd+<5vmgKQC3R{ut7N3HJjkUZM zkn92!OaNq$Oxemg4fOT$9MCrLas$RIKQ?ZYAI=$S@f^)1V*L)lKBooy zq^p`h4w{N>pkHUt4gCVw45(ke879oo16vO3=^6@4ZxvjWR`^iVH8^{dZ8b)%?l{vv zyKJ$rA{Xy6!t@XMKkI^QZ6X1=imLS-s8EA-vOw&WpUS?VB`80K_#5AU%$jWwLayeX zT=qN)-K;Xk>i9oNqYHDT66~$2^okdG6xxWjZIE&`ME*!GWUJ~t*(7?U zQcOs9GmZNFh7vggAI%w2Fd~-F5F_5eb|1d+XHxAmd&Ln_8Yl_VO2|+RFdkFa+3=p|TckU8+A}@WkADtlvf6-1*6-m-R*6$dY?2_-$_VrYsK-*1g`Jm7(Bi7I9rj zO&>&<0vqu1o$cAwYTb=ns|OMjG#(j1Lx-u{oD2fCW_+LsYeI1_Nl(!+B#U2c`5zz- zS+;N=Q_<~%xf(Vtt52&}K5%c7n|#$(TD_|0Gd@Y@<4`rnjPo4`ytfZAQNDB9>Oa&) z^(GtDI^K+AUbrNhcA$JOPrf`oO}Cn}DpqWU49y(M2mHSi^Mb=VRepl!?#!a)lI~hP z+f#P{h26hYX4n6FxhX1%s-%QXY2<(Je0!aG(rfj#Hgb@)rq)BjT+Y?f7tJ^M*P44(zzrPm&I`xzQJ zf1%+hJ%#mrueI{c%fbf^L+D?D8GXt1>O96s+WVfK03&nK=*7if>_~%SF8P0Zk5a?3 zTdvLY-;(ZeFv-^TVB!JU*Cr@6-4TrHMxutu2$% zHkk@}hvI7{x(E(7Z&KX~X(S!st=wRk<2P}yXSxp&^CcM~WcT6Mv6swr%( zYIYyO9D90aRS3j2-1iRDkxm!tp|Ufpu3qeM3XK z_c<6!ZEPz5gvDZ#7nLvJxj}Iefpb^V>|EnTPhoN@U~K70fflePn-EIteE$yV%Ww|260zgdBWqxc1IQWSO0t>ew@l4=Nxm9-gUBS>FoG5edWv(lg&4qGXLTlK~L5- z#>x=l@|Qyv6IH{p>EOZ+P7o1q-pipy6`NDuv%+uf-G{AIug`&p3EhKYA8>b$vv?&N z(-X}{WmlTo*9X$TE_tTGTWkA81Ye*_JgJD8w>K2@xgZ22j> z#8ya4#ZNdcG$p(T#)3l@P`6g@!={7TMrxYGxpSH-1vDr!!wgD5Vsn%etolf)0(q+S zEgOqQV|h6(N*9G?GNAAV+6?Et8x#`T`>yr#mf2u+4A_T%9#-p{R*~%A`~)Kx_)tb% zRXrplPc>(2PTUDDV5ybh%I(ZfERNJZw6iMH+S4j&)E&?ooNo;N-W@y1%s2A-+mw-I zP=GTBDHz^+B=0L zDNQ|-x{tUq(!P4egB0=C%!j`t^>p!@)D*QWfMA%(yti4~^p~&1Kf$ z^O@A97&UUW`6ZJfQK^;(p!*XT&Nhsd&JR?o z@l;n8`;sr@M9hzR(+q)>J$CO?UsWXnOK zr|Ke#eWQI5q(=SL2wrI~T#qS-kU=5QG2 zH|-5nU^5Oq;jr{h1=2caf6#bWHfC-l4 z+m`XwDiGy;#9-h8&En6c%_UZ(#2vSok%N4h8FQ=f?Raj?_n0W3#edMH{WeHL6ZM&% z6B2hcpvL&a2GGPkW2AkPW<-Vq>WmXqO3x7StJRn)rkX$TBiNI|DF?o&865TAjEn&s zW1iXS^kqP!%Lw&Z&*kr#VEx|q_xs`E==^tDS3XYOMKwBWz72I>54O|n+nugR$G@q2 zp>@VTpg;?%`RNwv2ehi_;1eo!a+eYYWF43EW_#OZ`Slh73Wl5xdQY>J-%=&*ocd%8 z+VjIiAc1t=*ZSVQBBgDd&Unp*(80#v+B{NnLnQt)bUvfIV`qX%sEl;1n6YPm>KGj| zGCH>OSE}vmq)LmOA-X5%i~hAuH0RF2&@G(cLx-8!9i5%l@}{eF+*OmQzvCV#6D8@^ z7mush`iu6GR|IpuiFlr$71fV2G_2_4lo&2^+~>rNRvq?R=izD-=k|$a_aHK>es3|K zy5#Gj0*0hjiwToaJ$9O?-f?OH5GoBj86y2rTiZB=aq1w*T=*!)tY@66Zx9q(zpN%YmD zux3ESAMczEZu(MOvhbJk&^cxMv97>?enqZn>>FE2h(XPgy!%CMjcD>T$gF-at(#O0 zt1x@3yGOzFPlLj@Y+?$JK{DQ>yGf5s;dg@o*oi~F3v8r0h?F*S0lYD=-vB(Z(xsNX zJYLPXDVsVaS;Dm|-PaPdBp$rt06@5uaCMQ5`jG4J}|D2ZWGw~02 z81tj&Gy=?DIS9ATUF0yR1xRdAQE0qs)g^JwEGUAZz{uNXeNZ;a8a@G24mr%Ht^*!X>b)Fn~VHlJ@d9G z_1AE>oY{GPoGLUd_yG8px5scq4BK!|FT&BX)p`Ewyt>cn*Ox1JvqqJpnk+zCf;r& z?IwLB{g%ZGAy<{du$TX81X5UM9h_(Ld+G*2TDd`MR9Ncxt8a!|x03!x#Vc ze)7ZbA9_w#=Zn`)&bAFhU_XSFOdV)-zj4EGy%&pVddp`teS#o{y9%ue|8R4(Fu-$u z7`)6yI~6=014e39t|5^1`~2sQe8PAjR-`p9DILZr;Xw(IgOaab&GV)4HMNRy@!Hl* z8{J1eyeqV>)zY8m>J&m$A7OuGt$;Pt-d@p`QU#1YQ$a6c0Cy50kqAsxpR`u4kYGpXc^v_9;cpt(o!&#IC(X+ z%a}_~_s^XH@BV$)KMLhd>jr7ywV&06f_x*#l_K!3R)4lXX@g*09BP>z6MiS6m-o3C z8n2cmGE}wv!;?;_EN3)jrSav_a8TU_%Z(V$$=w^Jg7QiTwp-}mUFE+IQeI0Gfd{W= zx6x;>4G#P&6*jfI5heGJ)P-(Mt2&wEl0m3euJW0tzX;1G6tWVI``&us5z7I;zts{Q zo?=53rW*MWZo&j7rtLnbZ3h`%iX^MRc(X4rEDre9jtIr4jpU!yM+-c%mWx^P1JV(Q zIV;D!gaEWKrw9t3Bq_U-`M4de)ihfYQL_fD%6c8+yKZ0BED9;hMZW+TNU~MM#}Z1J z@v^C7J~<*MQhMxynIlgS&|e=!jY~tszwry5BVk17{kDeSt5&ihe9GRc@G-{eFzLJx zz_Xe){WZTj?uz+&Xy&O)SnrD6n#T{(WgVr4^Rg!-BI)7C&)qxp*l`Kh{WWtip`A|i z+?SSrYOFk}Gogl5q~K-|v}>yK$P5=Y3G?CiB)h)HZLqwcO$A<$`Ba!nCPJD?h*oam z*vLa*(;36gY|C9fo#4Towqr8f3XO+e#!>%RXl48q7}_o!{GnS75lpo*3=F>8)&QGq znbA?v2*Fy!4AeEvN|Va1#v%@>e?^1rv|jITlt-DV*t}<`&S@79`7VZ!#> zClb=Rsn+Rp^%Vx-#gRIOeLA4OAfx;yd%^Hw{tiUFiz=eN(@f#2Ae2{Yc$t;BY7XN% z7)lnKf`dh{iY7YOb&KM%>AE*Qv(yw?+ZF2g!Al3XCcR@0miToh7Hv_Et&4XQV>jnT zhRHEU&csfVRQ9*5KF<{7f{q7DTh!LWkrltn?-yNWuJ3WdcJ3yV&G%+|$BO3ATYhCu zOG>Q`^pJeJc-FLwqyK)B-p9}IQ9Az4kk55xz_e+;k;}l^X#vyHDHr^ByuS=N6FZ-0@{yL zC8YN4)I-G2o&qG!<^)TvE6*dM)W`<#f_R-{5M>JI#E^by$4 z9WQZDbs{Ut&j^CUG5dC+#-@HVq z68oN!IbXP&Cs_}D!SAl01b=o!v!dCO)_1evfeQ=-S}s&pT4Xq~zd|NE}=a#c+% z^Y({=dld^;9%8mN9)cy*$X>^|iNr2{;)l^HC?o=?=iJ=J?wVJ z#F4s&q0CaViE*bzzbodmcadLxWi@#}*493hHvI7SvC!A6lOR`2xau?ST&7Q^jQaIeQwjrdKy?#_^MA6BnD z8OfR7!!mtqxr&VG`FBxeZu;#O2`@ua0pcWXf2xNH($x&lCZNjcdecE$KH$U z&Ch6v+pf7oMT@5vUGufls|sBroxJ8krc2k*pG=F{Dkg%t5*q%R*A93zRbs*#tP}e9 zEDl&f=en*PXEk6e^-_&)IE}c*tk3&hCHe!=ZpL`ll$fSRR+OvQ(&NV)edcC@97j^7 z|2gzSP^3fMjex~68Ea@KB9xuY+tJNcMgrih@UEh$3zXfFFAT`DiKY-MFVJ`l*U(Ri zEbMKF`fcE0+!2hSyu!>&=6^WHNKo>%)CuNwT&MB!|Bq7Wn!Ck=Y=^MpaMDcb{IhlM za#85h9JN@;@>*A`J}!rF;ooEHg^b4I6*voT>sCYOmdKSc<)s1`;`-xZtEQ@+Q?S;x z!bG)^U~tvb;Zv0yz<+dlyq-(^)jf8>?Tf<{^*DY|@K1{SRMjxS$de);LSMfgRBJz+ zjMdx-{iX3?>*bH&;*S_Jk$)M5g+w=Q=3)SRk{Zf;%nXR%IdVK~kBY6mC z8)I%ewd@!h{W;6tc5$R(q!6&#YFWNJ)P_go*(Au@Z*&*(?F^v#Tf4osB&#h;4>D;h z#BX&$2o?c3BF$%V5}jZH&W&GD`*U#LurT`z;yY#;i0luxdSIho*pnNu4CzGOw?0d(t zNl_ML0ah@61HGhf~CUi%XxBBjV|)H)yQd5BCn8B9s8V1u@!Lxmu2^lsRUR8INw3( z^ivD<4f)NAjNqnekOOhq_q}xn#!PR%%+_{8u!y zg=8XtHMW~dIe7Ng;K-5TiE9z$TsufcS^dntj3_toNOy(@iX}T7`c z$OkgUfsKaG$1f+;WTZDK6r{Gg zTrvKrJ#hHi13hcYD)F_wt;4`X^^wuyNb@y28+e!Ho^k^e>7$^=Sv0<1@;Xh&En5Q9 z+dA|nIQV{6Ys+i^rXczI?p!=<>R@u7Ak<)md1n@32dmF_@u&NKj#Xv*|BJ22Q(!o` zmV`-xf97mriuJo;!oyjk~J#C@* zobM1I@vS9sa>+BLvv0Ex0aw=n{8j$ z*Ejx0S+KTWGkCNA^4Ep71@?O3li{|lRnmIvj16f*u9c97hs$THw`Lh^ML!!x^~&Dj zf9xsBS*I<iJz-!mM1Kpib5%z&?+md zq^PpIue_{H9&HW~S?TvlC)O0|7WFZ^SjCa~pW+=VFv?%VMf~)|n#9YZ&@GHzlR0#2 zBgC2~DBkk1%`})HZJEKTDC-?C-0MAHPO`=kjd5K8$Zhh`o*lPU?GWqiQ z2GaW8FJIh_CScEgem?P{99;OP0{cWK-~6rUUE?}UR7!VNX4bk?}t{X;1X$&9j0)3Q~JRKW9R0e}^Y7AKlS7kstPnl(=1 zPu@zWn3$Qi`MZ`-Ct2f-2g+kWdXk?8-M4CZounI%Q@>S$Ci><5--gBLN<&U6)f~EV z5V3c1QiK@hgMaRj81!JQ%bQ$OfK-5=)~1}vXUmU-xP@9jIHm{bWxg=3-`|pWJmQAF zZssBsd+v;0U4bK*;kWrN0f(ayj|& zV_Jm|^^qp9xlft zIs)6(Zc>Q$yR^XRr4fU(GH_@FSn(dMU)9=mi2@pv-TIQaQ$lDd#%*-n%uua$(V!La zdTD0GfrY!Jca${Ee6v8dloRWc%H8br`s21JMOTnY{xgPs&4#W|G#Fd9WTDMDqDso3 z+6=FB{n|EjP2)KeU1jyR~Im=3bh z2oU9Ke@IwJ431QpkQM`39{Vj>{f!E2V0H5BpKWsIps_J^L=~U=1UZrmA{uuwD(e4& zEOUY`c~7lW6>XS%X;3HMR>4k^xexRo=MaO7>V=+9^4S{fLira;5f>nf6!AGf z=oYR%)%3rd;y2hzzPWT}weg93eQ|$@{GgKhnrlmaU}bFaG-@#7v4ixN#y_;+!T*dG zOm-o(7roSl!fS=%KRzP7CVsBW(76<;uU^1glkbZ$7iv@lDwuOk2ka9fkAv#3&(N+{ zLVz<88)Z~A*We!R8#bYZqjs7UkF%JM(^fTf!@@Hx?5VSjfs*d`d9NEJ`r4Z1?7E`1 zxSWb6*>n;8Q>krMpO&HZ@vKl9Lw!Bbel>C`0q43r?YcO>3$kX+9M~z2Ug!;OA*LGX zPBX}**|x{Q(56&D&@QvOtr1tizbcChIufj1D)VnRB>RL|pWR6&#{mRVFhLUFRKC5V zLaEt!$a$QT*A~1fiLZDqU#WPs_?)@XH>1tQb2onWJ^Lp^n^AD^URL zei3CS;178K`ojH#eTn^mP0*OV{90WFqkwsZCrp=ezmRRvbD$?k$tRyP$LU){Zln?2 zisgC9Dj1LYobD1@EBBDf?x&FTA-!wo;tLg?-)lEz2=iOk4z|JnC6EPT;FH#m5B|0r+zNWb4kEv= z{#8-ArG{Eo1Hff^#47c#74+GPMXcpC{C^$kRTmY%e7oU(Ba6{i@wM2CsnfU}I7NY# zB4}xP8q3fr`6izqOS1Cp_^nE9>8i9QZ>|uR(-URp1VD5F)hN)BK2~qq@h<_r--0Lp zwca@3JU4?!WViMVq0w8V5&7SUzriaCR4hOj$LG&#xjr|a8SPp^#F_s+Iv>VN+>BN8na2IkcpjLd7KT!Y-~KjMfF1LoW>v-#E|o!g zW}^8~z*i16O~_yJJG*CF(PjC())S!<-`MC7WSy_zF#S&RY9Lo0Sf7bz{?5#T!?!{g zOVB4Sc!UpmY~dugs!u?Eu3SZY=u+9}<`DZ;4W)=R1D|ziR)t%)nMF?9XSVQ(1By;$SCXbIIGD+7?D3?X=Hxi1AYfWKYpGlDy`A#A6j}|Yyy5bPJWam629FgCs?KaNx6Z-G%jB=87eRw>1Zs<3{x~zk1FQTyB zPz9e?5Bq-j3^~Gpl512&5qFa8qY3xv6q-q}vIRSxkDe=8i7|o`75J>TOlq@vo285J z*~0U@n1)33?7+mv`^5)Oa%G)0$#PKzcs|`?{rn*MxM7I=bs`6u)6#$)oAS+YX*j5> zd5y9xQY~y!Pb=RKQKVZeS$ZzaDbC8jB6Rx=+2q79^e&RiV5t zEyY=RsBENc&Hnl9{_pw4*OUa^~UX90=VZ}!>=P2u#*9lweej#nJEo1yDEvS&mkRuH&k(U911(HkS$j?a9U&3 zoiXN%r=aXTgnEmrVp>kuJvSRK_zsmVnsh3W95igYx9`ijHf$#p3^lLedF12Po~AY) z5qMmZx4+|pa-7I5ESma#+kkM%jLT?21$m>>qBsy;S}gu4X}4FE?U#|sc5-zC3}D*B zXIkZ@6wjqocS-nO&=NP*w*D12qk!`$!(^Gqaze7x?3QeUw8Ec&P%j(Aua7u;usCCE zCv>8YpZ#>%qB!$4horpU+Ht@F5?ox6Zqtrm(VLMhg+Bhq^vP_M3DefatUFm^dA;`0 z!%dq)tA+4aGd`m&H@K`Gv!2#Qppw*%`DD6bN|yHvBFSIIXzP=6J~Pml)sAAxQjc4o zf|>OnT4cAILNRBwC3`hiB=h8{d?t#E0j0&%qcJg~4KF&OPsNKKwp{JDH!*Ty*22@* zPSkyp+{=)J*N5JXx!+#Br;MQ-&XiJTib4bEs5yDy`7QJJqy3B9?q@87Myu2@oWK?} z9h`IDDSDH%7B{8`eY8VATIeRD63@@=LL&CHvlQW? z@`a;!$Fu_XE33rz9tK|mycfS;*`|HQgyUeSkUY>swemVU6WiMvG`n4d>F0$toJ=?* z7x((rh(=8HR@I^^@;w+fS#?N}Qa@SvBn!qoXygVhM}Y@4Ln14xz`0geItDeYu|s+Z z0I2Mc!=-{DCxg^iq5XMoa2WHaz%8bWDamSRPnEe>^D_`~sw)%iB7Iz*!KfF1=+N>I zrzc38ZEB8#2wk0r5lzDBD=#tUKMzFEZCpe9jNsHl{q=AgRpUyVrt%cb_0M}40;P#O zW0pDq+xANS`p!8%agGc>-OAO=Us3OC9Dk}J-QkkK>v>TR1b&Q2J#6sR?f#fB0plkq zEgJ|r^te}zlY|I~4*$GAUf9=W$<-aV{AAH68S~Pl zSbygPi7W!MQZ<9j#hhHChJ;zf5-sJMz{6c9?kyaaf)5NIdABB?ta%!<1A?`7r3@s8 z1-~+|KMsbi{>5_61kyFV2E!>16A_mv25T$71JPv8Mu50AKpiC#KczMBWt9h^VEFRT zE2}@0D^X|5sE*L=2CleGROv%Ixok$XAD=S^>?BWw%Cy>&AB!+ zfPfxp1VU`G1}M4VHi$y?sH4V|-&S=MlWBIceIEB{d8yxPS|BCG^TOOL8MYBDdsE!1 zGgXyUvue9GGMVp2BTZkx-qFv*x7Pcu84d-4Mm3O;1Bak+yQY|r@9?ykgGhO)6Ty3c z()9PD{3o(;f0mVLmN2eOL)!g4&l5CTH7?=RsDV_L2NI)-%s2Y_U^R%~O@3Lc46SOi zycl`tiLE?Ndt^=z=V`%Vz&sFa@)|w!If~PvQuhIaIyS~X*Aa)WKHP2d9NDJBMhXp zcIgnxx2x%`BE1~AiS=jbR54U{Pi%Qg5R>2vJz$O}uKa9N(>Hf+fUE%hhq0^WsrKdL zLROqKe}aIU^O}gm2~+eI2Znnt`p17G71n;eInFnaXI2|C1ph}5XNx&P?LKrr}<$pvt3w{%K3I3d(bFUHDZ1&#ZEPuSRS+3+zf zQ2N<@zIIJ06Mlc#d+X)g5sxq_HStGtZp->yCpe@Ad$Bz*GWy3CBAk2yg|Dp$N7XE-SK1wB3)UG3ku9ZX&xvVjKpcf3$&9m%dvd>qoHHjxmi*?pK!MbEe2vD?1#obylG zNKEeeOYxgAP1K*~Edkwzw$@J)N*GJ)r?*a9vZ&lXfzs+vtjxA+R7Aqo4($#m)dM?a zS81^cKKnqtJGtOK-zr?F`3{2nH2PteEtqJz>&Ij}up&craXPOiCpgNO&E4zlCE_0O z5%1PL6MTnk_3?RfGxasbkkC?qK}PZ3Ff<2p`1fB0QvA%`lT;Fd4}n06@Sqan?6l8i zSmgAd=9ZSo0|@Fy|0}zr#^LqjQ+?1v;@m$A(H$y4r@xve@)>;*CC3bxDW-fKcQ0xg zXDbu=G^MG*3&^`l`=N)vY;*;ZjMC8|n@{zZQ#L%6WVNP?-nw*KMeD@Wu?Bozf@0Y{ z7&4ueO`P*sSWQ70`U_5HuHMztyqj$j{M>Bfj!viuotju6NK0fmsF*xe7XtQV$(!QI zx7$(jGS!YUxpJaPlHZ3la08L|Gl(BjX@Tsc&1hhdk_uZ2h!`^Tnjc5}9Fwa(EL(Zc zBH5fVHy%;~MbF;aT&>dA7EC-A>Vle@-K|$2s0_;5%?#ZmL8=YBaVu)6#k-~PF{}Af zcUsJ6M7MDAlWi3z-2fhHCHhAOV;_60IVWcBs@AFTE>3Q9jm3<=pq`SIm-4+jv|e|m ziYpTusIPW<6GG)^1jqv?TzgUUdNa9b#mxeXO*{oRS?+$X=uxwe<_p)Yl+(0A0!I2s z?xw9WY3RTcC2QOHA~Hk1X^0FCSoa->6zH&fkB9%&%@85Q+eXlo8elkd zX$zf7UY@l>-hxfd(>Jd0roV4FyIkfoFV9OW)Ct&Nnil))9>2Hup#`#F6ZQ*1x6f;# zPfg`J7v!3_y?~{jAPu$Atjny(&=_gIN$kte2+&jc=TMfxEpNPGIzyzdPF5KokeAz~ z!uKGSc`nh^4nc56@{)J^PQ3(UR;0hP0HXdUJxTxfe)ck}eWwv{d`l__ z0m)sScJc1&s%`phbI%x?itF(VGlacYLgT?88>;pPAJ5pa88JY27};a;K!f>=|8Hzc`d*m1(`PQp(lg8gK@Qpxow}e2j=kB>X8-Pgqu8T8Le5+C*Om1o+5T{V0XTzjbi21+P#z zSumkZ(buK`!6i#_(^`#hie5LLIpDvrzQqDyYQSbj_?l8|rRbIT20$o6pe=DKIS!K) z|A#iq#nGtKc(#&fN2|E`!_VKEYWG@T7mOFjzBg+*PwN@l5(ocUwI9F{U4F(5@2MET z{M!N`ooJGaQ6OH(55o;*W8#Rf(`co$%a;81FqhWE-$RXug92xhIHVK*3shw+wq~eE`d79qS7dmf+BcOCk?@!Hsh+y`jY4` z{TqpC5av%|h80HRg&+v8Zs9PgjLzz(m>?lXYLiE(Bz^U<)uzTL{gHF61w282_1Vtu zr1ogcAn(9fifm(Q?zX4AJSi%p5fAg#0H|loP71>tSNjl7C-5eFt1P+O5(wCc%gc9* z$PG`X?veP`ADIc7cAALLHEM-Ae7WCl`Mzx=s|vG?Gfe1V&^);4C6!forK|B3jXcga zGWxy_fwmo(r)BKGaI*aaI50w3s90t)ZDTX7|&f_cl9QHC_DEeQ?LRLK^L#A$5m^5`@;t?*q zM>HPIPB@jSgCr}xp%NqyjRt4m`n1)*fIeYgC2!|86IZ8Hatv-|-p@O{UVvNj$O@l6 z4vI1d*p{Q$=2Q^|TTJJXjp*yxW#8bz-nW)}YLOL2Ni*ZHo-B%;5J*NKNM1_D`hH#D zJWW_muWz^*%%$Nt44Xp1%|1|}BF}oA#kdJ!t@T1x7Q`8nM28A^=E_|rupXY?mZg#C zz)8l`8D)B7o5a;SDCkA z_XOwhhFYRg{?OUawgEwxYzsZf z7PCj8$ll$_V;l!9NT;iv)nqvxyLP!p+%28bcEn;;>c(U#6Zl=w&6?G;(R0BQNM)yS zYM)zS$SUKEX&g{&xe;(&Xk&6+8Ssa!)HqT z9V!vEo9B+OO;Jl5MFZJiXa19PXzxD#uhw}lKsFZ`eVqpKxnCQ7d5y=nLOw$d|JLeM zxgWpxs{pPbee0El0Pyl#H~mGZB`et)A~&yt6;0$XO^l_j#U=VI`_s3s{qau;%K`t^ z$xLC@;OiUPN7?@XHxfVELC+Fo3@Clwt|ejD{0U;!%70OquSf}Oc@r@1kN8g`ej=ym ziy;Rq@$qq&0CE|C;bCZ^r;h{Z9YC?T7JIbBm|dpXG*EA?H=N%uJ^#&5~qk?eaNrTbDR~IzaHAt==ULz z@Fg!aRnLE8rwbMQeE*lWv%7pFTcR~*nDR;I&|GzBAZZ`(?B`!X?=~K$+x_rnuYA-_ zDWXd?1_YW+oesaM`6Xp5OWTb$zB#FlA)V%EJYj4LpX!*{z8`z`c9E{DkNHPwM;aL$ z_2=ld63eTEA-fhr9^C9U#%FiP*bvWerZpS^xz z+4TBGk*&W~PpY2RoRW$Nomd2sQ`agmbNw%NP5gnbs?f2<0+rLoKniA`c;bah!0geg zrhuT#^rxtGbczy;zG4v*VV*Giotc4 zJ7jL5mh^q3NUp|i0!s~Y&?TtL^7W8LmKJ!wQ(cFCr{H*L2u* zkL5tgHRshs!yEVQYX`D`5Bap3hZSZ!l#9nc3vO3eC{ptBBC(}G^eA1Y)rXpzR-cCT zGHvAtU0^a95OJ0Db22_aR+h~#rPTY`SW&$Oc=c7`Fa7tgW+txwHqv*_8`%hvrB*i( z1nsZ*w~nJA;X`Zk8^j?e)EEGZY=B_Z?QNLOrc`0r@N>Fsu1@z9axA06l)Si_=H$Cx zSQ}_lG++QDq#}so=QHVmzNWH?R6z_Gpj>NGhgz_eY$M*dhyR4&fCR9yqk6FVD&b39Cp>}cdj3b{r#O8Iy=}(qhgT@qHOJ0 zFI4MwY^}m2m1G^LXL;}0qU=}rkxqf+E2=p*?}`N}OnBAuapsNpW1a7Yk%B)GAQ$Pg zx6&mhWW@$DZ1Ye5K5w5PLX*Wh=a5hEFV84i0on_!%uw%&Q)dv0&vu_xE@WT<;fbgw)GlI+@~B(Nc8-2 z)f(rWR84Pl2Q>DGi@jBEB7B_ShZoy80ZcSPl<4ZcblOPm7E8%T=vU)OkYK1zBFcsyxh1f)T*D#!ro6$!cL;v2S|- z23%P?n*t(QKFVs?%F=SHvc-q1gMx?~JgHlP^e8BJTZ38g8jf)={~DA@Vbyq{&MRhl zdF`WA4dV|LYDBKK&q}fZrdHDVJ(!ICK`Dlgn><*IW96v>HaCt_RRf|_#Vg_94tvN+ zXlyOxVMcR1i>b(WFp)dg+pO8hBmci1oAhz(GkNPhX`Q}KTjz^`@S0ab--khKKz~)@ zv-;xY!-q5=%;wvc9CeTs7H|Hw(@4Z zYobyIJ_Y(w@NdZZSw&0pZ`PihF=N3aicMoH(##Lyq8>aZ^ZL?R0Bi(k3tvUh^>G0G zQlf2$XLozI=OdwpK=^Cd@kUx0UV6MS77V<{4|JS}_z}1*tk)~~)>Xfc(zEdX0(?;A z4`v~~C}GDN|BVP<2Y==NOt(=4C$?qGkM7+tr;HkhDaGc0T&?dY%-B0){?fX1a^J(m zBjDf9=6*svCivn*1Y-7hVU;OVebU-ikT8PJbaOps6Z&+<7nL%P>V{XngptJ%pic2&N zxFqKAIZx@2#r2MY)xo=S(mh$?yf)s*vO41!47(gy;lA>MFT2V3DM*}l4YOjQQZv>E zMIeqJ${cuw&!v6_negH)L<_W`7Ja&;l<48#(b0F7;jR6r*)MtP;zu-(kRg&gVl4fZ z%L2M9nX8Pb2|PcuKCDBW(QlvF?gS?Nt-b3ZQvZ#K6I=Uk6<@p=T17q zslpQx`|Px#nqqLTcW#lZD^epFuRB z|G~83!{iD7i8cg!+xe2OrU_y;Wb1F#hQqozIWxW0mGCZ)mIPl`g=3ezjem;x4Il%5 zqZX+%*}Tutuc8EkY++(+0kLTn?*b%lE522rDzKa6gI|3mDw-tQ(2wMV^DyFyoSm>G z>HaE%!eP>W({sL{3XZa}WmY^DIq^`$HlE4^o+vc}=<1vEL7Qw&o=or+h~D9E z=KQqfw_KYgcWIpaDFGrDPkArJ4JUG&=QlA_`&CS*g9Q0O;%MmEtyTyWpE;!f?eg_E z^-!zZh_YUjGA`zZ!9-G%$3q8698&Ddh8ZnGkavg6|0~S)ssCpcWo_6a@->1uRmDZK z^V$w@LGX{kaDXY`_*NN{AlM2FCw0!Bh%W8ajpy(DJ)av|8MCdwdgo?d2F1R{PTl;; zXvpGtW73gzgWKQ~-lH&4tIc;?nvHwcz*-+8kG5BL5he@D+0)c*ZZM3A$}!%-wwFd9OF(FU3{!^ar%2DmD9RVtY4|>9lrt^ z0m<|W_L)9P*N01W0F4WWos|`JH<90!ZNn_HZS8W_B!nlQ%Xk-Y7iDPK5muFwsPlEn zneqZ1ln%F+icZ_(giiyS1W5o)vCD}r=|bBy3p)yrxU}HfA9#^|xsP2}2v{jc0w#_f zgG~!#rI!o9Qz;A*WlYqmg0K}o_QvKZBSlNSPn@)KyP?*m2Gg}Xfvc@7JKCHzK52pb(G*DPUJjs~S#u(O?bnbDM4EH!%OGrs7RY)wY zY-j2ew|MT?vglJG@IlK-Wu;)JoIom^Yf>7?kNrvB)*qlF<UE3EG9&QL;xIO~%X^&6- z*^W(k4F}Wfq>Yut^p@YyEysBm(b*H9qi@a>Bp{IxUMpY^o%Y*odpREZaw{dTpls!h zb1DN@k8D0HdLbp*x2#AkM9sK;Kmf=O{Dm+P4KKbqW@9VcShL#@NYbq>Z`;<@x%1yk z4;>mxHe3>_Z4DQ14%Y?5+?ag&KWB2of7X{@$_ef7dd~??+pdf0LkmR7Ax>m5YP4=f zck9Q={JG@ImLhh-xf>_@F^Pf4Ute}P-S+w3j7VdhW|}hL zKhOM-mbo?kPhhCY%DbvwH@9NfXddrL1tNF&5qS(Mf_$wRj`&33Ex3h2fP>g|c4ld$ zDStdwP6MPW#O=u#rn7FL{OoEZoBv#9VS023Dv)l~=<)3uctCr!Wtq@{OWvL}5%Neg zZDWFcM8TiaY;ws8qJGUs@OYQ!x8mC?Zf#YG`+)RdpP_fH+Oxg%Mx|Ad!s&qFphwY< zw_!UWR<->}sSu3^FCj81?c2gXR)v2g8OKUKq9Tr^Wee4k!y2}c*TWTnqDpGgZZNr4 z_hb`W1?8^43wCW$XE@ZdKyLph-|-gvN0J$VUK-2;Ps-BpJVFKzi0dm5pnlXG%&LrP zDq#zcp__FUeKd6qn z!8}DDcCLHFB=91KPu`v^~9-P!N7q_U^ z0(Ej#RdkC7S~O|TB0a1va=|IJ7>6im!_dMeRSi|3n-XlZ=t}}I&hH63p}C-}0=l># z!jLzxg*(=lEB*AGzK;@~_$@9l@^L*zWmr9=r{1zXsY)5AKffrTKYs}fgB1TrqgG~> z`;?oU+X>hJ|5xw8IIwl?V7a$%!FJVEfVMQig6Qa4t>lNFP+sNvXjDYH>~=#GR3qlr zXeQ@UFG+aEs1+i~{wQ;jPF#z@{7=R3rmc5=x%%E4gGw-4hovKKvNh`(W=hOsdAuKz zz0#$gAe;RqNIK!jqTK=K)06z3noq`?1c?Dz7`iTH@|%^tLT&QRZconlrGlKsBNHo_ z4{XMhRC=KCv@QBuoYJ}`x$}+N$W1P z$mz1Iap%E{xsMtjKEwrVaP3zXe%pcD0}Vf*3JUSqkW7hLt!1@St7R|LbF=vL^p`~a z^ax?Iyp7tAvMz80E*NwUb!On%%kPJrZ5K-cv=TV>yPcT8d-CK$=YN7e=cvEW^=(b+ z*v9CAw$=iEgtGtl(a*FodjpG~?O7TY^*&)#duIZ8fkg~Uc+UH}M%H0MhM@B>t0p81 z3F&S0cyhItF$|Ph@@FaM<~wuekE7^3fLeZZpfk{UNUyCf5ExOYyoyI_j#c5cg5LRlW37t7ZW1mE_YtmwHI1oNS^RH#72Uo!^Mnb~ggL<&+NG zPW05D@<0Ggo!N1VlV4sxFsHR8rGEZs!d_$SNHdOn+v?K}25p2lq)#-!Fy0MzLbEl*+c4?KClrWdP(^<*M&SJZtxth<|Xp5l8+)YY+_U#USE zJ{0}%2v#k{P#N0luIM+Br0OZ_v8^#miqTzV?hKd%ZTC=a?Rkx4T3LxIR}^4Dkrz#Z z5n+&>s!AyFMoJ4&-P!`_Ty=QnqKY-0ayUnMsV#wM9u!7)I-6GHCa-G~&^o?$&SvIZ z@DL}Sh=9TiH0`rne@TO(uFwx?%yw_bR5iwZj3WF@f557fpJZ(RjaBqI{MAIqKJS%~ zxjz2+5lZz%XEh3^@iFQ1c#%QaBQr3%Td#MP(Ozfj3qmu$@ zLwoz>$>!u)#8V>TJ_$hy(f{z!x+h>|ul-#4d3p&T)VelZqyY!TfBLEf!#%J~+7MeV?*fyLt5Gai7Q{7}&5$gz#jm#J&0-t+#ScspsI2UZ=%Kkf{& zHfx>KR$kc7oyO%`ub{}*G~jxXNLuU82&7LwPhX!?>%nt=1*zi%b_?JfMtpnesCtOuSSFSL$$-IXthnsi;cx*u) zipqB>&}ZuiqzdlGRraJO_j`YX;Y^YbUIGecVzvVjK9w?A`KPU zr0018;!y?vjS3+7b4*03F09GCJ@V-U*tMYbc!lXbbkws|2bURUz^u|p6>{Sjn3`}_ z>K9r|X`^SRnr3-f+bBa_`swJswVU0qywS~5qp%R8A(xmA#RR=fL)8`8N*-qUOpRpm z9xRnnMmakkLOc%ri5$85JnwEu&+)XP0d$U@w8XTHAN>Cnhi~Y-05ByM7x;#l{|l8< zn~z;84jp_MDvPb^Hrrz7)-JtVK!35+Pxn)l>H?d+r&n3Pdhhu3->ZBipa$#e>a1<> zXkn>_{=Y76y8&W)%$S z4&euq8-RLC((pp2=1|H2fpQeZt4c1*;p$dT{SY*`21fp~+H^$k8j69YN~3SwaP*e6 z0wrURc&0|fw%&C)@m=%IsCw^+f18Tst<3)%jQ+AM&2bGvSQ-j(70`R%M-BTv0KK~N zVH>r=7;64+S-#QbZ>>?;kfxD4#l^_rw>pvoZRb$6gF-X@7wie<(}uWUw16?21)MSXeQp)oNp(Heqd$?+;r3SO(3ma=FHPA0@$O84U^d zrC={>s!l+U`?Zf|3R})7pe0u?RCn12$wUBtBr-G?tk#rOq_$GVE91R}r3Iyyn983e zA|&RGex%SEjWBH^#$VneG2aRLXux42HcG|&Z_pl24Il0go$vbVWUM;>H-Nm^7%psy zQ(4hd7Wu%-jOPo|HG!JuGDWF55(eCq3magbQEQ#&VL+rgwpG}hW%A{U4|0&wfp%VqqTR&XmgPm}VKHuALwn4Qy7jlJ0?BS!LPw132;%?TZ8VG4@%W>Yq5s>UM z`L*t5w?dkq;47*Qz#!;B(HOs}ttzH_rw&&#am+UMfiq^r^i|I}2RHGj!HJLW*VKe} zkm?4FnJ?WNK58H<$ju97APrb_tJg!$fAAMs#dK#iFJp^w~uW!-2}^5@{=0&U+`LIg%S#EI5jtr#;pB)ttb5&vbyP%^3dnf9($XW>VQxi zR}|XQcS1|Mrq#D?qzbL8YHXvphR4C#>#-i|pZePlrWu!X?@mM8LQ{ zhM{g=z70(msAvwSzs%jsb4byAn*M%ey5L;TJL40|gH?&T(Zowg&TR*cjh?)XCG8kxK7JCt1|(rZ+iuu_LOL*<7QnN~y)? zxf#q~tM2&&RpgMe?lrYT)t9HfE=z{ahD20D*LMYlL!QYVLmOr`iQp{qWkHiIj4)`kRjkJkBm*C18gcBgt3b?+msme1~K zS_N-NEPomI4np&IVRrHwgM$kV&ens&i$Qi6&z2*8(f{7l$Ip75k9?M~RvnNZ<+o(k zJN8}%SuT4Qlmdexe3KQ)sB5Cz4p7kzyP*ue%Om~^n~^!rZ%yaatv-BF? zn%@tf)tDt6+?qbm=!#RpnR^zU=khvtwlR-l)rJ}ldH{Y-|Dw{H0&0wQn55~>wuePh z9LFdLlgl#uFIFV51OH14$qx=W85o*q#BmH|_P5T-!lXT<7wut#tzZPKpG#Tq#r zev39xgM-x$sTd|;EXJP@F>mIE0-btZe@13mgxMiz8GEo1Dqss*(~Hvcsb&6hZbXQI zh?csiMcv+wvJlgkKUa4~NV{6sY~-yL%Q%CT-ZtB6`Fn`#hp~D#>iOIsdG-;Z9(iYs za}4*&i?eu86}vDx%*+p*sg4=t|20Zd-(X&#^sn}$#jalT?=i$;>QLp}2PrHL?dYP< zZaNySO1lt(W*qOGO?{ym)nn!9S+Ssf*^#lcB6>$*SYlx0ZzJw|5#t`J6N@bam1Uo& zX?xrdg}&lh&LY^b*Sr&~G!p)RcX&bHP&Qnr+g&3z1y|7 z5?u$n)2t05}mN&+`#~NUO%+ zQn+GZ!nmR`m0UD1%X^MB{(I#+6+KBxj}6@BwB%a%h!*Ur{)<{5l`_e`W;!oDVCPsX zUlFmrEV_Q3|IYM59;Se(u8OHsr0_VOZZ6zzB08?Lo!PUVr?#fcTGaBJa3jonCDkTx zH%tS)b+=}1 zjgeRClwxs!yf-#?hy11#FGq)>jZ=y=Z_WH#0g$SNSp_$NtrxwL!n7Pn;2d@~=Tkp7 zHF^Y#Tr$id1G~^~T!FQGdaA@`=KFVD^+VO#F@uNIfhX2;rK0@@73KQ>219O4hArT+ zwo5PQz`G9h(SKfe3eii&*s_;v(IeGciKFXdaU`Tb=-J4!TBvbti;xugcv}H6V$=_i zE;B51?48h+w;!24;(qyk~X)%dy6?P=XWYeC`oD8Kp` ziu2fJ$+K~owEEwzQyu>?Yt?=3cr=9{<{E+jA8MRzc6Xb+VDP(JfJVt2XWQ1?w9lWa zQ^5w6+u2pAQy_!E2W;)wU+FvV$eRp`O3tu-iICorV`Jx5&_VwWB&4Uqbdz5b_gmlf z`qbiv&Iaur4BFbbSq+y!0Rg+-5+ z;nbhoX|0W_W@z?~a^d=kS9^qOl1oy{FuhRum*XYMdW0~^WBzVU?%5IFL#!Sf)At}W z3c?^+?5#hXHLw~p3A6D{+~*zKIdITh{@iHUCEHCvg&wtSpRz<~9l>@x#y;&d%OE3# zzc^G^JYO;n0o{(Vyf=;Ck~{!<1moY%)?41NC+#+du~F0SsPjBvK@&r9>M5yRN#YQQ zRJIw=M3x3D+&f4G5t)W)>C*8k ziua?g>Xo3_E-weJ`9oS-D#D*}E~ryUnc@SR^P-)~3V-Sm!Qy;EHyg+s`;u^o!6B9S zbQ81%P{QW~u-sR#16;pvXx^pGSf>s^fI2N`8_P}h0Aw~#hHG92oP-8-*Fi0=nu#SA z**c&n#nt!0_5ilwB??1U|4wUL;(^sZs4LL#BTD)Si^gU@>1?oQl{*KU-E6!Vb{;l> z`zjG38cI}j%NDidd)^)6)4WI54%v-Vp@IWZ44ask0n zSIDDFI7I^nv+cd{{k951Dqc=hzLFwH_Frm&cuxb~@M74eq~HarlUTgr-W4|3u51RK z_6Gnom{vq2g)J?Kzv<1RwP`*US={Uq-lU}?p}(w8YY|AH3$6Qf=qaX&SuC~o2UBGo zxuiHtPSZfn=H#WKgYtrwmWNFNDe=+{V#9Mhv|`l5kW8TQPxpo!-3qSm!~-LYanZ5c zE|6GYhP6%(u&GwIS1{F@Q*!S{owBEtWJk|IuyUeVqJ187qPFwY zR$_2o>&JSDUUm``bi|Ey1qEx$MG!K^*W%KY&H6!`SNxnT6A;e4zI za83e(xVgudUfTk0=pRpbHmzO<$Op4?3pHe;F0LY1NJa*5<=A@h^KNtmP=Kzcp99u;BeBQf~ z!Z-6;_z^$;!T-k2KU~LigMkB@NjhcE#7K{{S#}>Y`u);d$jkMsuWi~}y_U)jv9(dL zMnO-be`|<~163x<+1nn(uN8aOJH7oh*f94h4hn%Z(8ht4wc}EG{54_oLVbE!wB~zg z6EI&StIf1>5&M12N{O#G(Q#v~CH_*c5#zywB$VkD@J0&3Y?T?0_cb%Mh z>b%hbaoZnYk%Y_A+c&rSwG!o?5MBi3k@nTw5n=t?!)DwN)jQhj1^}52HZDuWLwIfqkgD$hZhW)caw;ponvpQ|QVXaE z4_@Xj@&&O>6OTf8)0pp(1;BT3X>jPW*r?7HF`1&pS{x;7L4$sQ1^zL&A}(xEc3R=k z(?bG@pGA1A2%Ul4q#oF~F2h6E=5;c2z=42ES%Ob>>z3(o;yo92bMI ze?>lvMUDWRF8pmO>7n$FbGZi|FzZyZRyy#-QO03?hDx$4>MT-H3G)r=jp5K*({OmP zvvthpCgXGEA1yzPG7C);Q&iwOZc>h1SFPw~Aa#iQp}tn=O9Yz=CPXIlY}1zbI>`DQ z>Ss)CSGuc7urWP5Mz2!OmxU8H~Ff9VG!zJfI-xN6&DQ*f2aKl7Es$un6UKfZU z3QFbZ8^%uTjff+XFrvS6yR%HM^!O}h;&K`eILnyelaRGk7ZIa*{cEP9Cnd>J*f-|B z9}EJT%Hup8h?Pim2fG|QK}-CvfWG)o_U0yzh~3({BMD`1ex!l7LeuFByDHbG010Tz zj|167gk%8414PQy1$&~+h9*{E1%|C)x6*Og$@Ifsm)YVxT)XgXd z8d+kd33hWq9kVPiOQD(2x9B+Ra+3VZA3+MkeZ(#hoBfwo1x%+0Yxo_-`K_`Ky`h#4XUSlD^P9 zOOIG1wbqg$b#O&}5;zTnt+x6__&je-+X}xZ zeU^R=bYCStz6=O?Uw#8)!_Vy60)g7)4Wp2^Q(o@BWXyg^j6}Q#!_i#V`cDs{wZEVC( z#c~csSmU9Xbk5W(FC44TIq>f}4kyTI(s7x>9^wvaKDnnCO$kfavz2x-EE{#9i@(k4 z`Yy%g75oe{=h=VBTuPD&a(%gM^8}Lc*lNK)&tGS`xlF9^k55J|Aq9?7B z-EM5Yo%&=m|J}@)ldR^sp%c#HpQz%N0o$qykjK*E3SqBdnvpNiZ}UxheG8&T(x2bJ zo2^I~c@rJ7Jp0Wrmv|5R@ae;X92H@r8bA`hh%#opp)?SQ5}URMhB9RK?uA$UMn=un zfMz+Tf%Y=^8b9p-V0mA!Znk9PhrlP{HYD}Nu8lf>BtI3{|4!}wXr86y*~0@iXJ+!J)ny2ZOp!N zWpHBR-Ogz?{VL z1NiOXiUw2n4f4}MOD6O5pIdhH(H*DT(x(N27}@8%&K|XF9r1Kw1vFr#@m^9o)}!6S z$+sk+&xA}pYw(jHdUDpnxOju129+HEtDN@~tSJm24t3$Z&=^AWt!;Q&(oxs2p ziZP2iU|Z?lAAb55^5?X)z&8j3Pj)B9mw{3Cn@2XOVyN&x#jomJQ$)6wRzOORb`5Z7 z8Rjg=nw`}!6QS6V_gf<%tjE~cW+2WiG}!)s9>WitCr_UW?3xw`=RnYR57y zpGFvoE$=<{+yRLDSVGsy;ellex2V%1qNYi-KX8HE-J)B4g&xS#i|1T)QUWqeMZf_yBC7>nC(J^b%j<1UL+fKfJ^gXtE+~EIT!V4jMcwMs09PaY@+7n|-xYZHJttmOs7I-tft`s;X*gJR|GT*IZ>@l{Z?RDZ7=r zguANm+tKKmWz}osONz%>tSUsR{HnUqU zx2b{mtMLpC>4H$Tt1o?oZT1dOWK~wZo@%Y|`{NJhizb@zLVBEP6uMK#;OzW7z@~O% zb?c+m3Xep-WgZaq#_2hu|D`7ZfJYY~n$PZjCs%0GCOPc}Bye{_;6)%K#6?vc#!?a= zAdiuh=6`B#bqN+dNgw{SVB=2A4j)g+KuVHd>nksuqVmR2*K^TfjvP0aCRYVaYyUyR zB_COsP+?-XbI|HMfcNLSoJ5lS00hF75MBd7TNw1~uv2fDjrhlbC4xJRC-%4zEzJa{ zzse7o+YYmMBXPD-en}snlRr%!9%JK+qRp$sY%O?Th>s%WrgQUn#E)@c_AQn$1GGevrm#xm9z=wvRFI%cX&wOP6ZL{pT|C zC9RXbkDlp~kzd0ThERVfn6K%2T9iF(G$imJPAk(@6r>H4yVIL4YRON1Kvavank>cP z*V!c-wJ9xO4HtVDj7&p?;J|o8eH|oO)C$;4fn@Yf0YAfQ=^ViCoaY_Hii2php*6Vo zN@HfFf6V`CI9!={)|Gk;Qc_xem2Q2L`Y={;;) z(IQK6kV%xlj}Ab~dgACuF>j?$NE?il@gvVE$ElV{Q!)FUH&Fu@tDb!TlP6FiVeM@y z81g)Y;j!7H2!g{s(MuPGN*fshRj1)Fj@eiX)4u;HEaJ9$qJQe`C1WhIA%5m1ztb&t zk_?(=m)S`>z&o*Sk@=FGn(0?yvnodN9<(Qg@wHYm>l!Fy{$cUlB~S%|jcGJ39&!m} z+VuXgybW2p$y1)^qr0m~=3df{jQyJoH&w73mvP2n#vNK0VSkQ%9&J@@7jJvV9KF!z ztVxjV-(=J1P$SitD~oKJ*y5yk3_HWglFiiLm5{J2$Jb~Jw&=M^s=%lXYp&y~gWW@%p@axr}NN zU;`YN4{RN)tEydJ$I6aiGmpKjcM4aYtQ(8oDn@t1w0~d;{`2joFi^me{bWSIi?&;y!enr)Sec2+aztUY9{VS zSc}L}eA-%bMr!0GM=3|}$^gT2E;C6}MQGY0dNNJx35_rR=_wBQnxb}K*@BN98UO9WtNLM!5N9&1z;H{f21+2u{pqJOZ&1M`#D z-g^xw9XN2W1mDG8R1?yA?p{`14Jv26yGv_!RC5y-IkI}*#hZTW|5IHU2wlGELYRK_ z9HcZ=S<#)T!d6A@U0p+Pa9O|-E}wtK6SF3ok?v0s0!5C<5c{n$j( z_WG4Qq!qbhT2L$RkGng^DIhN!{Pax&^!o8j;^}~1-p^UnCfb@&{Cn^j6Kyb2KKzRg z8AIVZGcIa^roWxh1JF9V2d+^9sr_#vyV;%=|K6Ho7z5XR+k7`EzVPU8PAcnUCMnr(O2$6V6zwj^g(B5iJLway6!cPb0c8$z$A8s!5 zEGIpzdZu|YmvkHzWzMx570|SM!@n}qont(l`HTAYo5Oh!9b`v(afRv5vFylzRMltz zA4Y4sl!{&l&^B!cH8&Sq;?*5y5^^+Ekktj?nf4~J#o23nh`sl>jh0H*5`Slp{Aq4^?)QR0l?y-r z!@_F@HU?ZbX1o^f!IRHY!x*oLhJz_wxOcyof*LQ7u!;D|^xw_S8u6(onwr%r#nAq> z{VqY)9}pI$ZH<}_8eGQO+f9?=>uon}aI|!E8)quNeqHDf{#^gNZIomlx0+sI{<7B*C|Tf&m8)pYiuQD+QK7(X`OATz-& z0L;v$Ta%0q8uPo}6iHNF$j;k%EE{DJ< z+GGC5MQC$oCnf%Qd8lamk9&?=!#q7R)&~UoQHH*htOlpL``WCsE%O7x8zoy~?zXHkSJgybq^Y3rhRsIBsb@aZ&E7d=x{o;H zJCj_g$w24W0r|*Zo+xWxTT^d(Z02ALfEEIxc<(izhyHQrI8)uh-<;$)lR~`4EED{N zS4TYTv1bDcDLHj?oWI5(?W%1BGa=WV#4L?h4|2ENC<1u(zk`EKPv?X1oiO$Bw%rLi zrOIPRG$RIv@6jvnR^yHkB3aut=_Q-@7LZ?nbW&h^lb&tFm)9j#?((3F7;kl|ucF}z z^0j9~Ythx+W7~FvK^_znSN*V4SZ1~*=Qe2Y1DF~n>dV*W|1-+#aGqz%G3fPTFf5jg zbeCraE#!trht>_3`>~}3PY`Z?n++5~;S6XmlP>RymcCFOY@Oh;H$LDz2i@)1^w$3N z>=iBL0m5Iw5OUCR#|1!d@w<1=yPPCax+W0zDnnGkAL%*xS2fO6Hynbd`IfIPUs4n9 zbBpcHqa{yIrJFwU2&4?}4}2B+;i@y_r{b!lcdd&hAm5e30vK9~GECaz0tNb_0eaDB zi95cPA|>G{ADz8}NESB*1jv}ANhu|0=Hom(z{zF?nrCBY)76t|J`qZ^k7d3Yo_Oo5 z7Bgd(rUQ_%1Nuz_*G=T7ljKz4-iVf;b!-)dE9}*X_~GYTs5d>%tZ8{z@T8uz_$)M> z22-mphj0$z77hjNnKqjb%M-XMWV4g_Z=^;7;KwS~V3;)~f6a-wYCl-C&Q$O`;T%)u zn^FcOefH7?dNqq}MCX{d8;NQRNcL_bzWCv<;+xlz*9cbuZb>KsXM%5zN^J6 zuHFu8uiEHNx7_Cz`J}8?7{A_|H+*GE;3HwG#l>lX;BWR|J^YOku8N{spiq5tDWF?z z*<>GTl=$e^0~I*l46X|O=CHg^%i(nl`%OU;QBifOXmxtc@@3Z{tog0(GHzFf8s%N4P zMqf2}7(FFeCHEt)8YESo8c0`V2S2_*esORJ$P$uc=Zb*3hD9Lqhn-v8T64R4)uQfv zb-(BQ)v^nPB`>Nq5KY#%|2Kea_7Ey^i3{^LPTif~Y6Huox&d4J}4TUJszN*fB8crLM zAd8fIdIENEp4Z^qjUHVB4rfhiVAM6Zt3yGtvC34`qXS@ZP_i2#3!-W){4GXeHe?1y#yV)qN#R!wcPSX zd?f-X9NG`^xjaTW+lbov~wnz1PAg-4ir#Kof7nw<$I4-f1 z{D1o2#M>hR0to7-!xKM74^*5JeMwk&b+B-{*!C;GvM8mNVN}Sz+8Wk4`dZs9cB2J+ zp?4GyMy`3La_?n*ZqS--xlUx*58~C)zdEC>`|UNYR)dNhJM9%#r#YMBpK1kr3|^i^cWTPWLiO-Fe|+;jmrDz% z;T=m*3loRDUK?~ZTb+1t2e;=i))D@Q$fdtCKn|EXC24)cqj_Yc%o85$M2{;Kf&#HHzn$B|D^jGISa?ft#9 z3j&cTpMm`@xW9E(-7PcasN?WnAvWmSOxdc8`9hEz+e;w933UTd!J&fJ7J{WmBGr0h zLO?I2mn^v6$9gcPlAKa3J*qQzn*CNvzQsF5tXXRgGpruqFFkFFm~?aUa~Vhr54#b+ z+j~tIl93;fnA^NNzAhpetR_yDg#QkUn)Tw+oK!O_V~mJd6P+G}o{z1CtfAx6?RN*deAc{c4!Uw@=QtR9PCI)MX=8LcV z##4xxD8CmMkV;@3iR7am%pA_k-lOpou*7(Ecqz13X~6iBrV4N${ZYoVy3wxD-Zi$7 zFm=_tccrz`!76lfrPar9A!lYJI=1gSC#Bq=DQ5l7fll?%`Y+MK(&C;K4mB>GYXANv zjaZS3np>t7XTMLcUD>kOWcv1GSbT>*izu!W zyt1(=xBch&pvEA}q%Nut@nZ9MNwKoD(~Wv6c0PiLm$ApzQ7LB1z{HR3TNYqtg1!MN zUrrFzo*}++x0#SDy|8LfR^Qn(uo;s(_o6Nxr>2h=jM(4=@e*PX z`xW@3VZ!}mlF)Q#Z=sbn_HyyEgVm1pdu^G#af`cWH&Q)|7TT>1P#PN0vH(cdl11Uy zGjE)kGk<^yHO`&B-Idg}@t*o(iD1Ypj{Eq?z(+ta3VTTvJ~VVN#E4RqvFFCnOFbj> zRm0d0TN&B##a6@}EU&TBp>lMkfqw>IPY3HP4YQAsI{37pb;B&MQ+=eYG9PS9m2O)j zi%Lvxv_C##m!MD`<@a@FOg4f=31I8ev;|4plxqFgEnUr1lWWhtlmcsrO!vo5QH>s` zOVZ&O+Q`8O$Dwy=7m)K-Y=cum0^ww%3AN^t2vG{rxSm{j#p$p!A>Xv(!p-2? zoSr_{s#g75eoNr_9_9gLV1JNquR-Zf!_or*yO@`+->)q9z7WPFZ|=W-xi~}3uC$oq zepKhWp7{khR!F)(5cA>%}2umcLe8@#Jq3eD`lc0ip`>~<%Z;)3l)|o=Dzf( zekktuC^;z$2t4!o;i8OQ?-(CkI3DV2C93*j2tA3HKk0xQZQ5k&l`2;Lyl5`+4UIpi zsy5^@rzB3%!|a~aM~dGYgqlaB*>ckBTb6=&(~b#2!SHVhh7Ldg?8+DIqv&O><=F4O zzP1{Rtz)f!RzMC2`^80@7VAl?Qx#13(Zeni#@SOqgh|}kf23IY3%lyo_`r9*g=!Kb|=(6${K9>c1xjo zzgx}W7}P7#*IVqE3luZVUG94oi0&b~P5v9C=s209BL4;if19vmS&7(T%l-D=^qNug zWo&XW6!RcmWrK0ftJx>T#1xCTw}&LcwU9;Ju~xkmxc8L5|L9Az1yLg@r>SE5UvoqI zm)P3-=lu57jxV|FmL9T_)+Fp28EoJ8++Y0163z4@*g3S-WWc8&)mo~e9Lgx0QquJF z6~2SN>PZNM^fK~41DEyt2-BQ)P!zr?vDOE!)pN%8t(A1XgFi0<_D{0XU z$G237Y&u^gyrB~^FgsWl(Vvz9y#~J}Z)EbRE?U-!7DG$lcAl3BQ{$>zvp#r5IMkRuxSLWUYB@t~iF@s8 zz7*kA)O}E}*4@*(jqE<|59Ll<#AV3%AFq8H%EoJsevn=dUKYQ%7obH^Yn?Rp(NwNT<3E%7tJxHPtbPM4YHqn+ z=aYEVpx+d0-g;AJk&qFE7@ByOZtD>nj(}CeIAVwgpgAPNUVaFOyW8RTh;L*ifFCD| zo@Q8D=**^Gr_j!0Vm1DCF^p*Q{DLDzQTwYQv0)4sYoH7-igh#~1F^fvMH}+^gVcx} z&g?W~W)12=!FFSV`RzOKb(Rx<^#2_^`8O|OD#G1UWzD9G(Qo}w#~#sQ?9IPpd-Ts{ zmjH3;g7QSCb6w7iUn9clr5S%Zk8R$Sk?!7rIN+hbV(PR3t-C~_?RMTFXGWP=cp=CE zkMUED+vK#df;DOTJ`EYXg<1Qx^|_;eD*kgvae%2Ba&3_F${O~TQk*v z^aS|prb}xd+yt37HU3Ple5-0^zvKnP*HSM;q?zM%q6O0(bTOP5YLlVXV=oBHEpyRi zQKm8m9v?v+l{f6U3s^WuX@^U0F}rrZ&QYntZ)rcAY~79=AaB-V%D5 zJo0#7qk&}H5i=m$*rL@b*#In7ae(?awyo|dRgzg5qLG^JOpKZQE+gFzUkY4!A(QHX z3?Y~6d*Gq5E2lRX@tA+tUTeMyeF+WkBjr~Pt3hZ(9jivIMbVp`cLtUXLBVEg%pP2) zo=h6mH>@VU`!yZA!R@OV+nk^3g2@(F*Wim^?7)r-u?wjDdPz!sm4X2NxuAAqb!r__ z*)EM{*Z=EM?^uZ+iTNyE9jcpshjs(*wHAWY9YXPJB!^1DwbfyV98qn{t37pSF-aqP zzB4K>+EI0+1{V3R$&Ine57L;cHp@#tPft*3{2pcI0Gqo<&~auRbSS17s}7 zB|Zzfwf5HY8+G&m`u0gWz36rnWRV(qcV)S&4_`WDZ4!ES#ltY>md1m@W0s>!dSj-s z!t>UV)wh9fFTC(xpRgOjVQ;Sm;HhStrH190KaP$NU$EDYvA?`FOMtEp`uE90g>h51 zvbL?NI%FsOaFW2clQ106hW}U_<*+JroMZ0 z@TEFP%Ba3WrOIg2n(MB_wyC-T?%diOx4_xOMObW~19 z?8ph(Sml({$)!#iGIlzhaymk#klR9svN0?Svn9DMB!!Hr6qU>^W-goS+!_^fzieZ2 z-weB$`MuBQ`}uxbxNy|)u-ft>M)t~x8`SJ*2C}WYVqvyva;k}Q%HopJc#N5jzeAg(2@{i;%327CMOq9^7X(EhVJ;Z zGtS)3051lukKzg_BYn%W_;)JkF`=*H@<3J|R^s(xxcw*sdwCPDIOBwgNWAz?1E|x6 z*^B&Y%m-3cQ#7J-b5VY$Xd23Tkb1$X@V_k0{Hf@Q{Ug|1pM8;eB;b zKKzp+XrDFvVpzzhG>rI`$bQ-XN2GffkQKcEr)2#GeaS3v8Rosy9Jp-F27`aF>g(E( z#X3<>O@aqiB=w9vA=PVGd;b(%yq&87k4_yCs3Utu`pIip;Ij;CE>ibgzX&OX2E3c#gRdsi9;-UV4eOIV4)sY}Lwir|wF6}+9J99kK zIy9VUs<)b6Pyh*1x6#9W3Gr7mA5<%iio?LD!r*QHSk+4D0x$|R!fd>*tGJWSPTeWs zHpT7etpeT?EoaM**4Vr(rBFSGYJrYPfO+E~bHU(>=u2hsnU*W(U;=;Js<$eef~u!r zB-Yu@wA88X2WRl-ROrNNd}-;BU@D6VE3d?QSlwL6!1A(7%%7Bnru}EJy@KsFMdI)e zm++pi%(?OUA6YG-$!@d5QyyC%5x=FkYbM8r{e#{+D5nd z=rcq1rmbdZfPI=+^-Z=OLtQP|6-LS@H3mhpGG0p|oei1|`XGfrsXJ5FLKo^wI`bQ} z&&4msjh&2U#x2f5M-=w`-WFr{D8!dddbBILFAt__Uz)RQy)lp%ehI_eUhkue z#P&4g-rvW=i&Pso_UTm#1xNb86N1MoZy!K^=)=(%LZ==ZXA1iWD~%>#af z9xCHF6MZ95T|Ve~7ubTeyIwFw%rfFKj*E-s4g5k8e;Ysqs7$Z#*wnsK#qNZ;K) zzlk$jl`1T-DF4~wc9mkgIyQ5`9tfIuxU>?{8%3?%A#ntVh%jEM*{*yFWWo(JW;(u+ z$)hPoIxyQGmBPni9w#Qpx@nV^dB6`$4Z7eXeQvBJ>*i$m>;@&zbA3x|yfVCg-17k7 zI~qIa`Dk;wKDusp_NoP0EPUiM^A$?*Y>4oMpzXI_%n43bMWEfy5syTDCR$e8o`V0*)Q&co~wjq$ciE#61E{Dw-vn|3{-8TOT90ekzPB)vx z!-LA2#GOPpG~+JhL!CKs81Z9u#ZH6az@yglgNbUjAV#i4x#xnsWu}a%IREXf;GOcBw)9jh+Zp_dFJ;7&gbubdokD!eECw5_$qP(=~kH806 zqu0MudB!+YD*F!4s55HHd=Hbk8#aEY!AF7bWPDMxBg|67WHtfNtRsN>HQtH!sIG9F`o6(Hd0RL*} zw36av^uUj|J?-}Q#8Ug$^|5Nbu+a7^aMN8Ho>T zkbVZMAG6l%YiU1gNEgwhsR;0oN}BjV+_$VO)xZ4D3j7`SxUGlWI&M}SQciFr>l|VI zIX-=af(lN|xo!YYv@S>g@rlZ0>%Gcp`t9fyzW~@t1?NJ8*X4;C(lB^fqN5zzVrxHq z!*(hK74ZBM5~4j4kZWopI4m2Qx{a=?bAoONKAQ44we0=4Ybv2mBpAkgP+3DyZ}A~5 zQuJ@J@6TdB1Bhq!KX*Wn%NfIO?7o8CL3MpoBGYyTytJ$qQ*d=kv32>!4A@_xX(kbfuec~1rauXLp@V}f_;`A;d`1OOUj*!;&<4Td_?`2&|s0MF~Mq}$LF7n z?RKz}G+i;s9jHSeu66GA|9|tDh2gQ}zp(UR2T;=aSJTn<-%d`oWHeJ&Qp6M~( zL^lMa>l(hZE^B+fd}V0$vXadgu-X7rO+K&cqj%2gn6Z4_g6~6kXVVLOW*j~20crvu zBfe)Yt17%ZOU+gkiVK?`e{$4qGv$A9XP3_JaZVwXW{wQMi31c+Hq4Q(9cI<@tIa7n z%|y=B1_9D@*MC z+9Gp_WN&fUs`%^GU>%K@l%NmA8fsil3aLOir)QL@EgJ$W&#t~)?C*7po5{{M#}A}- zkdqA1v6cY*|ezr z7#>K)px30=2%C`ygPmw|TH!XD=xtIC;b5u!j2d(zor?vU1JkojSek{k2}v}(*FMNv zk_xl;grerme7G92gwx@yTRYo&r#>g^=hFEC1x25d&ngUVIZ%z4CMIy?<~Z`pSFUq;#(GG=O51!LtXRwqNBp!d7&9#difcyQZ<_T^ zLYxsUBMEzl4I(5OqzSd0vGVbg^2WQp>0p%=GC`DRcQ30`caj+$^HoXw@#|dgG(+jp z+@^`r@L1yto`=rhDr|{j>#HEc)NfJ3wGpDUxDRMBb>E(eqgh|2nvI{1=Fd+2-Y~mw z(mBwK+2A8HTX{yqtZ~G4&=EO#s?ZG?Au0Umfu740tfhnMXT3*L2(pvgLpaXbON;h} zOHoQ?p$XL4)lLUONSy_4&`eS2VB;Ao=BV(iBWpNepVdaP9B3G%I(}tMl$C?PZLjr+ z$d=2AS*un8a+VKKerQw&`Fq3SMP3V33pZtEG9rmO2(lL|K`Uq>{g8$V>$$rZH_x`) znMM1`=rni5NAs(Qt)Y!xO)}}mrlxPC{M-szkMU-Uc?pEzLcce&GpUdG%BZ4vK;OC* zka+On)2KS{N&bPYmQr)su*IMjwEh>NFdWFvrSyT9S2jN#9V7$)rj%YsC=^@a z%a@1|lJq4+L|`O`twn=AZ3mXJH<3yL%=QKbGr7DFX&fN`7yEW{pVe`0{%gMr#+%%! z5}Go?Cgd|~gm7~DI>to;8_Nq?&YmFLj_UrGdEu_mB+LB@Wxn)q!?bE2*fXTlm#aMa z`#3+6ksg7W77UtNuQsVL?SmCteH$}OguHfX(j|WHf#3h<>PdYnIbsdI7yXv`MQaVF zU;rz#B^o$1$}+3A%WGF~ga2f%y4Rk)8Bo7q_jMvM$ZBfoa8V4+mSH>nK@j+%d?*|^ zZo(#9D|)fZr~RwLD7MqDL=c~dvlo^?w*owEZkvPIoRj{?GDQn zLa$JFR15K18S{=1<~f5S8gJ?cf=wh%zk&wwZ*rS$XV(wITsmT+$*_^!mv#@i0-u7k z4)Ws~QOs;k18ON?=RJL*#Z>!+OEpw!S6oAIY^)iQtT`kq3R-sz&BtWl?bi#>)m=Ar zXl(-gCHqYG_hvUAN|oOfp?nfx!tS$>>VZ+yP3|4qffhcPG0gd)xBfOZ4t141U9cPG z^Txqvhcmm98A5AO;zCD9YWvJ;b{u%F7K)Dt2+fo{RmzA|zVK1m_9 z+7};uDlH|!1;*bTE1|Al2h|59D7DaYn&&4H_BiNHf5E*;tDYaXI%FJ0f3}@Ul_p;t zE~de(xh?xiOc*!UX?BtGEP-u>Ut7OnBj6V89?{ucKEUA4wcB{yY!0J3Et0!`4zF39 zH!-m0e}JA&$BG{a?*fT%%}%58b#?j84s~es;&dEM9|&(ne+)_oc>y`-E%D}yb){aYaLj4(aAOda0!ss9d-qrrC%BSkE1R}<gNv!dXu2ua!S=OBM)6xn@v0gGM++tj=Y4#c;aC~H$w-kxkan>RfV@r&T_bTsix z=>D$s#(gc>ypH&^$TuYxGwyJ#mKrn+$170tHcfjI^*m*Dz5p=9PzsjMhv6=7aNi!_ z^yP-54O?5XbUnEn(cPx@^H;Z~-n?~f*$36i*CO=4$bp4DNsFACsE<9{_7l?h#FsK7$R+LYC`_R@SWKrvP&yt~@tiZ+!V+)aMPegJBiXSM~DsL>@DL1Lv~O zZLGn(wcS(F-BU0HK_Rj}ZSSd9^X)tq+Bel(dG{YW}K zLF=Q>y*|K{|J!E^OAtAqAOxvcPscw>!{6M{9POSNTTl$Fp_m&eWtmFpu;KuxP+#wt z{D`&HqHC+oT{ z{^DL5FKs5bE8ZPCM?IW|P)IgZ8%3&(BUG`O;^-EA`chXnW}CrQT9)5knE?<&Ksq$b zz|=-JEl+v`c`Ep3XhmnplckV|E@twFh#aOUWwX3@s{C(*j6N0|s0 zIG*i0o+eq(Key&H8n34Nf3~8m+W;uej2eJlsAX}~BQY%b& zlUp-3px)z_R(;=l-o6+b%Bb`xinsKe(#l-tovEcW$kZ0N9e@yPdN;0G0yyy>&t<#x z-+pAfQ2pORWW>fEGr`$nt?cbld+l8_SI^uDG;4NA9f5XSoVEG-ZfhT;Vy7Q)vCCZa zDY4(@2iyu(+P0t3rt0m!*W}S~o^`ucjee&+%Co9U9OJG#1oj^eA&rcFH>DY>)_vgz z|0}h-zQHt`Xh9S4X=F419StttpEP+IG=TEa?T!@MJ@IJ`%~o>(@VlCuwkthD&kFdBEj&&OqanACE? zBjC>rCB1+X`)us0IETW?=|M@cg!x=2+xmycKJQLL=urcz-@st?A2`jXq4r)Cr+je> zm4_0zNn{x0;SKPGE>JvfHD2F_tTywW8T`g+Ko{W;*D3P9;{F?SJtY>lUL+5Ae$qMM zpYo<%nXxzrH;E=4<55;>N^@pb<8JPBy-&U^+n;QbN#Gq*i-O$DCi_!; zQ)O*e9ZmbFdI3lK;+5Rj9~^UD@+J-B{+=&wYy{@;;TAOH&5*n*+PeTout-qp9h#b< zzc+5BTU;^3a}D&2=baTjZI9f1ob7}k_I$9rJMZ0-go6JXB|cB^Kd6;GQWe@;cFc2q zpOSNc|Bigsbi`*ZjLYHxBjOdoytA*q4}?S$PLO+hgjcm-Ju~(~r&D4}Pij&@RG-yx zJyWLvO@pzUW(c+q{GDnlTIggW+B?gll(neu*7NQY@1x;{L5+_F_#|}}#2OJ>v;C9M!*$^zDH7o$lHklonQB^6#4_96vjVMWJ zh$uu_4Lr?5&9a`K^tBDTSsdRA_e-m+d7}E+-fL=>Fn^%&LdyD;FmsN>hw{(6E2Y=% z)=5dW;yU-S{8V}Wqte_Q<&nCThxyv*94tBsR;A&v+}33er}LUMm4{aEbvqfSfZTHT z7zVGKY07e1gg<5J?mhV{en^pbkA&J_-}y0>D^>K`f>%b)&-j0%6$4jonacBHQ+Lz!hS0t*p}C^HC|%C(2tkAv$}FlvnF6LI1vGj7P*@mCVTI<`eEB6(Gk z&eRMFj2vg4!S%?SQP!un#yB8d`BYki&t~YtT!$1XTPcK&DZHEQ`cMSUK^D^$h<>4y zX~bpoGqmoR{Ro1va{Uh?UMFqn(7CT!uT`VLwzs>KOr}a2Nfc?dW@EIX# zZ(G#5K>f9jgI};MMb}-Tuu2c-5ohma5N$=X%bhWCW<`TtIVAD))yJnO&LA2EJ_M5 z=(sc4g6h2UamK@+2AkM-Lj`lJM?1EA=2iR6x9?NJzW_%qs+C_>$HhSca(3(Ds%tV< zN92{{-dp8*oEEKH2S6IC{3fGqGtiOLJD#kW?nvq|N~Cp+zl*Gj%@Ap-kZ zswmY_^dFpvN?)**;Tf7s!!DuI&fAscFYI>KG>ZMdKj~4~(j9LAK6LXZh`{)$o61z% zeJ_q>dfNFF%~oC#kjn2RJQVV@V94o;FkzT=OgrHB+ow`Trgpe72G|x++JKGZ2>`ky zg4Rsv1YTFFUL|DC1++Ff?*%`3xQj8b4~?kyI#>d72JicjWD~5VVp3I=W+V z+atI2{=acGb|y8Q9{I9&%7H99$olnjUG{po?xwNZ`l`zVzr7E6sshA@R$uAgty|w3 ztE^-~F5P;5j}XmPJ*-?6=TS1B@NZ>S`Jwp-`DYSU9XvjCzGa(9-WI#o!uag+dCoK5 zuV&&WQFiV=r49u1#^!Ts+$6g_m)2Wjnw@;MQn{5)?6H=#cl7!hjd*izZGKz4fpYGU zsZ)|rpm|vwT6K#QE6hifN;!YB?G_Rx?3Du+I&SX$A0f9P4-0A~c#i(=xReC_NftO9 zl2e*0=+}V(ys%yi&b*@Kzy)(G(c4hqWG1pB6CFeHUu&dN<+=q+F)FGk+RTJ(aTgYz81@3TSa ze$lv2cJxiSu!y3>|G@r6N%9}W{**ZjvLsMqSxQd$wkAjLen&;>fD&>{XOOj{l%HrV zpBBGO>7s8^5@+jSgX1RNSK$4Unw_G1pHa6AW8Lnk6^AbHHD&8eYS)lNfaKC8rV%
aA34wt-MUCj!Z6h}Oz#Tq~Inb3*M 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®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
+9EzF2S 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 )`<#WMc^lS(B~ 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�t`6$1|+X5&^!FBEnmUivA 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

WNh8Y?vPR#|^JSF2KgfO(rq51C#D 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�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 z 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%0DAojBDfUS06#jb2d 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≶aI2$U}`IxaDQ_xF^|SpwikVT>&CrA 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

awizWseZt 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

klW^su(T>YEb{kI}@+ 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 yR2`$uR^k-# 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≷v$P2=(1jblKf>7exKAU$MDM+*>UZ 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∾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%0DVkA8w6%b2D>r7 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 literal 0 HcmV?d00001 diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ca728de..6602a77 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1398,22 +1398,54 @@ namespace scopeone::ui return; } - PreviewWidget::PreviewInteractionTarget target; PreviewWidget* preview = m_imageWorkspace->activePreviewWidget(); - if (!preview || !preview->interactionTargetAt(m_lastPreviewMousePos, target)) + if (!preview) { clearCursorStatus(); return; } - int value = 0; - const bool valueOk = m_imageWorkspace->pixelValue(target.layerKey, target.imagePos, value); - const QString msg = QStringLiteral("x=%1 y=%2 value=%3") - .arg(target.imagePos.x(), 5, 10, QLatin1Char(' ')) - .arg(target.imagePos.y(), 5, 10, QLatin1Char(' ')) - .arg(valueOk ? QString::number(value) : QStringLiteral("-"), - 6, - QLatin1Char(' ')); + const QVector targets = + preview->interactionTargetsAt(m_lastPreviewMousePos); + if (targets.isEmpty()) + { + clearCursorStatus(); + return; + } + + const PreviewWidget::PreviewInteractionTarget& activeTarget = targets.constLast(); + const auto valueText = [this](const PreviewWidget::PreviewInteractionTarget& target) + { + int value = 0; + return m_imageWorkspace->pixelValue(target.layerKey, target.imagePos, value) + ? QString::number(value) + : QStringLiteral("-"); + }; + + QString msg; + if (preview->layerLayoutMode() == PreviewWidget::LayerLayoutMode::SideBySide) + { + msg = QStringLiteral("[%1] X: %2 Y: %3 | Val: %4") + .arg(preview->layerName(activeTarget.layerKey)) + .arg(activeTarget.imagePos.x()) + .arg(activeTarget.imagePos.y()) + .arg(valueText(activeTarget)); + } + else + { + QStringList values; + values.reserve(targets.size()); + for (const auto& target : targets) + { + values.append(QStringLiteral("[%1]: %2") + .arg(preview->layerName(target.layerKey)) + .arg(valueText(target))); + } + msg = QStringLiteral("X: %1 Y: %2 | %3") + .arg(activeTarget.imagePos.x()) + .arg(activeTarget.imagePos.y()) + .arg(values.join(QStringLiteral(" "))); + } setCursorStatus(msg); } diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index fe39b9a..bb982f5 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -869,43 +869,73 @@ namespace scopeone::ui return keys; } - // Sets the global preview zoom percentage + // Returns the viewport state used by one layer or by the overlay + PreviewWidget::ViewportState& PreviewWidget::viewportStateForLayer(const QString& layerKey) + { + if (m_layerLayoutMode == LayerLayoutMode::Overlay) + { + return m_overlayViewportState; + } + return m_viewportStates[layerKey]; + } + + PreviewWidget::ViewportState PreviewWidget::viewportStateForLayer(const QString& layerKey) const + { + if (m_layerLayoutMode == LayerLayoutMode::Overlay) + { + return m_overlayViewportState; + } + return m_viewportStates.value(layerKey); + } + + QString PreviewWidget::viewportControlLayerKey() const + { + if (!m_activeLayerKey.isEmpty()) + { + return m_activeLayerKey; + } + return visibleLayerKeys().value(0); + } + + // Sets the preview zoom for the active layer or the overlay void PreviewWidget::setZoomPercent(int percent) { const int nextPercent = qBound(10, percent, 500); - if (m_zoomPercent == nextPercent) + ViewportState& state = viewportStateForLayer(viewportControlLayerKey()); + if (state.zoomPercent == nextPercent) { return; } - m_zoomPercent = nextPercent; - emit zoomLevelChanged(m_zoomPercent); + state.zoomPercent = nextPercent; + emit zoomLevelChanged(state.zoomPercent); update(); } int PreviewWidget::zoomPercent() const { - return m_zoomPercent; + return viewportStateForLayer(viewportControlLayerKey()).zoomPercent; } - // Enables or disables fit to window display mode + // Enables or disables fit to window for the active layer or the overlay void PreviewWidget::setFitToWindow(bool enabled) { - if (m_fitToWindow == enabled) + ViewportState& state = viewportStateForLayer(viewportControlLayerKey()); + if (state.fitToWindow == enabled) { return; } - m_fitToWindow = enabled; - if (m_fitToWindow) + state.fitToWindow = enabled; + if (state.fitToWindow) { - m_viewOffset = QPoint(); + state.offset = QPoint(); } - emit fitToWindowChanged(m_fitToWindow); + emit fitToWindowChanged(state.fitToWindow); update(); } bool PreviewWidget::isFitToWindow() const { - return m_fitToWindow; + return viewportStateForLayer(viewportControlLayerKey()).fitToWindow; } // Sets whether the calibrated scale bar is drawn on preview @@ -1075,6 +1105,7 @@ namespace scopeone::ui // Resolves the displayed image rectangle for one layer bool PreviewWidget::resolveDisplayGeometry(const FrameSourceState& frameState, bool processed, + const QString& layerKey, const QRect& area, QRect& displayRect, QSize& imageSize) const @@ -1086,7 +1117,7 @@ namespace scopeone::ui return false; } imageSize = frameState.processedFrame.size(); - displayRect = targetRectForImageSize(imageSize, frameState, area); + displayRect = targetRectForImageSize(imageSize, frameState, layerKey, area); } else { @@ -1095,7 +1126,7 @@ namespace scopeone::ui return false; } imageSize = frameState.rawFrame.size(); - displayRect = targetRectForImageSize(imageSize, frameState, area); + displayRect = targetRectForImageSize(imageSize, frameState, layerKey, area); } return imageSize.width() > 0 @@ -1127,7 +1158,7 @@ namespace scopeone::ui frameState = *item.info->frameState; processed = item.processed; itemArea = item.area; - return resolveDisplayGeometry(frameState, processed, itemArea, displayRect, imageSize); + return resolveDisplayGeometry(frameState, processed, layerKey, itemArea, displayRect, imageSize); } return false; @@ -1136,13 +1167,14 @@ namespace scopeone::ui // Maps one widget position into image coordinates bool PreviewWidget::mapWidgetPositionToImage(const FrameSourceState& frameState, bool processed, + const QString& layerKey, const QRect& area, const QPoint& widgetPos, QPoint& imagePos) const { QRect displayRect; QSize imageSize; - if (!resolveDisplayGeometry(frameState, processed, area, displayRect, imageSize) + if (!resolveDisplayGeometry(frameState, processed, layerKey, area, displayRect, imageSize) || !displayRect.contains(widgetPos)) { return false; @@ -1171,13 +1203,14 @@ namespace scopeone::ui // Maps one widget rectangle into image rectangle bounds bool PreviewWidget::mapWidgetRectToImage(const FrameSourceState& frameState, bool processed, + const QString& layerKey, const QRect& area, const QRect& widgetRect, QRect& imageRect) const { QRect displayRect; QSize imageSize; - if (!resolveDisplayGeometry(frameState, processed, area, displayRect, imageSize)) + if (!resolveDisplayGeometry(frameState, processed, layerKey, area, displayRect, imageSize)) { return false; } @@ -1231,13 +1264,14 @@ namespace scopeone::ui // Maps one image coordinate into widget coordinates bool PreviewWidget::mapImagePositionToWidget(const FrameSourceState& frameState, bool processed, + const QString& layerKey, const QRect& area, const QPoint& imagePos, QPoint& widgetPos) const { QRect displayRect; QSize imageSize; - if (!resolveDisplayGeometry(frameState, processed, area, displayRect, imageSize)) + if (!resolveDisplayGeometry(frameState, processed, layerKey, area, displayRect, imageSize)) { return false; } @@ -1293,6 +1327,7 @@ namespace scopeone::ui QSize imageSize; if (!resolveDisplayGeometry(*item.info->frameState, item.processed, + item.layerKey, item.area, displayRect, imageSize)) @@ -1319,11 +1354,13 @@ namespace scopeone::ui QPoint endWidget; if (!mapImagePositionToWidget(*item.info->frameState, item.processed, + item.layerKey, item.area, startImage, startWidget) || !mapImagePositionToWidget(*item.info->frameState, item.processed, + item.layerKey, item.area, endImage, endWidget)) @@ -1356,11 +1393,13 @@ namespace scopeone::ui } if (!mapImagePositionToWidget(*item.info->frameState, item.processed, + item.layerKey, item.area, imageRect.topLeft(), topLeft) || !mapImagePositionToWidget(*item.info->frameState, item.processed, + item.layerKey, item.area, imageRect.bottomRight(), bottomRight)) @@ -1432,6 +1471,7 @@ namespace scopeone::ui QPoint clippedEnd; if (!resolveDisplayGeometry(*item.info->frameState, item.processed, + item.layerKey, item.area, displayRect, imageSize) @@ -1442,11 +1482,13 @@ namespace scopeone::ui clippedEnd) || !mapWidgetPositionToImage(*item.info->frameState, item.processed, + item.layerKey, item.area, clippedStart, markup.start) || !mapWidgetPositionToImage(*item.info->frameState, item.processed, + item.layerKey, item.area, clippedEnd, markup.end)) @@ -1475,17 +1517,20 @@ namespace scopeone::ui QPoint imageEnd; if (!resolveDisplayGeometry(*item.info->frameState, item.processed, + item.layerKey, item.area, displayRect, imageSize) || !clipLineToRect(m_crossSectionStart, m_crossSectionEnd, displayRect, clippedStart, clippedEnd) || !mapWidgetPositionToImage(*item.info->frameState, item.processed, + item.layerKey, item.area, clippedStart, imageStart) || !mapWidgetPositionToImage(*item.info->frameState, item.processed, + item.layerKey, item.area, clippedEnd, imageEnd)) @@ -1516,6 +1561,7 @@ namespace scopeone::ui QRect imageRect; if (!mapWidgetRectToImage(*item.info->frameState, item.processed, + item.layerKey, item.area, QRect(m_roiStart, m_roiEnd), imageRect)) @@ -1552,7 +1598,12 @@ namespace scopeone::ui QRect displayRect; QSize imageSize; - if (!resolveDisplayGeometry(*item.info->frameState, item.processed, item.area, displayRect, imageSize)) + if (!resolveDisplayGeometry(*item.info->frameState, + item.processed, + item.layerKey, + item.area, + displayRect, + imageSize)) { continue; } @@ -1716,69 +1767,6 @@ namespace scopeone::ui painter.restore(); } - // Draws live cursor coordinates and pixel value HUD badge in bottom-left - void PreviewWidget::drawCursorHud(QPainter& painter, const std::vector& renderItems) const - { - if (m_hoverWidgetPos.x() < 0 || m_hoverWidgetPos.y() < 0) - { - return; - } - - for (const auto& item : renderItems) - { - if (!item.info || !item.info->frameState || !item.area.contains(m_hoverWidgetPos)) - { - continue; - } - - QPoint imagePos; - if (mapWidgetPositionToImage(*item.info->frameState, item.processed, item.area, m_hoverWidgetPos, imagePos)) - { - const auto& frame = item.processed ? item.info->frameState->processedFrame : item.info->frameState->rawFrame; - int pixelVal = 0; - bool hasVal = false; - if (frame.isValid() && imagePos.x() >= 0 && imagePos.x() < frame.width - && imagePos.y() >= 0 && imagePos.y() < frame.height) - { - if (frame.isMono8()) - { - const uchar* ptr = reinterpret_cast(frame.bytes.constData()); - pixelVal = ptr[imagePos.y() * frame.stride + imagePos.x()]; - hasVal = true; - } - else if (frame.isMono16()) - { - const quint16* ptr = reinterpret_cast( - frame.bytes.constData() + imagePos.y() * frame.stride); - pixelVal = ptr[imagePos.x()]; - hasVal = true; - } - } - - painter.save(); - painter.setRenderHint(QPainter::Antialiasing); - const QFont font(QStringLiteral("Segoe UI"), 9); - painter.setFont(font); - const QFontMetrics fm(font); - - const QString hudText = hasVal - ? QString("X: %1 Y: %2 | Val: %3").arg(imagePos.x()).arg(imagePos.y()).arg(pixelVal) - : QString("X: %1 Y: %2").arg(imagePos.x()).arg(imagePos.y()); - const int textWidth = fm.horizontalAdvance(hudText); - const QRect hudRect(item.area.left() + 8, item.area.bottom() - fm.height() - 14, textWidth + 14, fm.height() + 6); - - painter.setPen(Qt::NoPen); - painter.setBrush(QColor(15, 18, 22, 200)); - painter.drawRoundedRect(hudRect, 4, 4); - - painter.setPen(QColor(0, 200, 255)); - painter.drawText(hudRect, Qt::AlignCenter, hudText); - painter.restore(); - break; - } - } - } - bool PreviewWidget::markupAtWidgetPosition(const QPoint& widgetPos, ImageSceneModel::Markup& outMarkup, PreviewInteractionTarget& outTarget, @@ -1814,6 +1802,7 @@ namespace scopeone::ui QSize imageSize; if (!resolveDisplayGeometry(*item.info->frameState, item.processed, + item.layerKey, item.area, displayRect, imageSize) @@ -1825,6 +1814,7 @@ namespace scopeone::ui QPoint imagePos; if (!mapWidgetPositionToImage(*item.info->frameState, item.processed, + item.layerKey, item.area, widgetPos, imagePos)) @@ -1838,11 +1828,13 @@ namespace scopeone::ui QPoint endWidget; if (!mapImagePositionToWidget(*item.info->frameState, item.processed, + item.layerKey, item.area, markup.start, startWidget) || !mapImagePositionToWidget(*item.info->frameState, item.processed, + item.layerKey, item.area, markup.end, endWidget)) @@ -1872,11 +1864,13 @@ namespace scopeone::ui QPoint bottomRight; if (!mapImagePositionToWidget(*item.info->frameState, item.processed, + item.layerKey, item.area, markup.rect.normalized().topLeft(), topLeft) || !mapImagePositionToWidget(*item.info->frameState, item.processed, + item.layerKey, item.area, markup.rect.normalized().bottomRight(), bottomRight)) @@ -1960,7 +1954,12 @@ namespace scopeone::ui const FrameSourceState& frameState = *item.info->frameState; QRect displayRect; QSize imageSize; - if (!resolveDisplayGeometry(frameState, item.processed, item.area, displayRect, imageSize)) + if (!resolveDisplayGeometry(frameState, + item.processed, + item.layerKey, + item.area, + displayRect, + imageSize)) { return; } @@ -1971,6 +1970,7 @@ namespace scopeone::ui frameState.processedFrame, frameState.processedRevision, displayRect, + item.area, frameState.flipX, frameState.flipY, item.display, @@ -1984,6 +1984,7 @@ namespace scopeone::ui frameState.rawFrame, frameState.rawRevision, displayRect, + item.area, frameState.flipX, frameState.flipY, item.display, @@ -2100,14 +2101,24 @@ namespace scopeone::ui const FrameSourceState& frameState = *item.info->frameState; QRect displayRect; QSize imageSize; - if (!resolveDisplayGeometry(frameState, item.processed, item.area, displayRect, imageSize) + if (!resolveDisplayGeometry(frameState, + item.processed, + item.layerKey, + item.area, + displayRect, + imageSize) || !displayRect.contains(widgetPos)) { continue; } QPoint imagePos; - if (!mapWidgetPositionToImage(frameState, item.processed, item.area, widgetPos, imagePos)) + if (!mapWidgetPositionToImage(frameState, + item.processed, + item.layerKey, + item.area, + widgetPos, + imagePos)) { continue; } @@ -2133,6 +2144,62 @@ namespace scopeone::ui return resolveInteractionTarget(widgetPos, outTarget, sourceId, rawOnly, QString()); } + // Resolves all visible layers under one widget point + QVector PreviewWidget::interactionTargetsAt( + const QPoint& widgetPos) const + { + QVector targets; + QMap frameSources; + std::vector frameSourceRenderInfos; + std::vector renderItems; + buildRenderSnapshot(frameSources, frameSourceRenderInfos, renderItems); + + for (const RenderItem& item : renderItems) + { + if (!item.info || !item.info->frameState || !item.area.contains(widgetPos)) + { + continue; + } + if (item.display.blending != Blending::Opaque && item.display.opacityPercent <= 0) + { + continue; + } + + const FrameSourceState& frameState = *item.info->frameState; + QRect displayRect; + QSize imageSize; + if (!resolveDisplayGeometry(frameState, + item.processed, + item.layerKey, + item.area, + displayRect, + imageSize) + || !displayRect.contains(widgetPos)) + { + continue; + } + + QPoint imagePos; + if (!mapWidgetPositionToImage(frameState, + item.processed, + item.layerKey, + item.area, + widgetPos, + imagePos)) + { + continue; + } + + targets.append({item.layerKey, + item.info->sourceId, + imagePos, + item.area, + displayRect, + item.processed}); + } + return targets; + } + // Initializes OpenGL state for preview rendering void PreviewWidget::initializeGL() { @@ -2355,6 +2422,10 @@ namespace scopeone::ui drawRenderItem(item); } + glDisable(GL_SCISSOR_TEST); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + applyViewportForRect(rect()); QPainter p(this); if (m_sceneModel->hasMarkups() || (m_roiDrawingMode && m_roiDragging) @@ -2368,7 +2439,6 @@ namespace scopeone::ui drawScaleBar(p, renderItems); } drawTileLabelsAndBadges(p, renderItems); - drawCursorHud(p, renderItems); return; } @@ -2521,13 +2591,14 @@ namespace scopeone::ui void PreviewWidget::drawFrameInRect(const QString& textureKey, const ImageFrame& frame, quint64 frameRevision, - const QRect& r, + const QRect& displayRect, + const QRect& clipRect, bool flipX, bool flipY, const LayerDisplaySettings& display, bool firstVisibleInArea) { - if (!frame.isValid() || r.width() <= 0 || r.height() <= 0) return; + if (!frame.isValid() || displayRect.width() <= 0 || displayRect.height() <= 0) return; ensureGlPipeline(); @@ -2631,13 +2702,16 @@ namespace scopeone::ui } } - applyViewportForRect(r); + glEnable(GL_SCISSOR_TEST); + applyScissorForRect(clipRect); + applyViewportForRect(displayRect); m_vao.bind(); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); m_vao.release(); glBlendEquation(GL_FUNC_ADD); glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); m_prog.release(); } @@ -2645,29 +2719,31 @@ namespace scopeone::ui // Computes the target rectangle for an image inside an area QRect PreviewWidget::targetRectForImageSize(const QSize& imageSize, const FrameSourceState& frameState, + const QString& layerKey, const QRect& avail) const { if (imageSize.width() <= 0 || imageSize.height() <= 0 || avail.width() <= 0 || avail.height() <= 0) return avail; + const ViewportState viewport = viewportStateForLayer(layerKey); QSize s = imageSize; - if (m_fitToWindow) + if (viewport.fitToWindow) { s.scale(avail.size(), Qt::KeepAspectRatio); s = s * (frameState.zoomPercent / 100.0); } else { - const double z = (m_zoomPercent / 100.0) * (frameState.zoomPercent / 100.0); + const double z = (viewport.zoomPercent / 100.0) * (frameState.zoomPercent / 100.0); s = s * z; } int x = avail.x() + (avail.width() - s.width()) / 2 + frameState.offsetX; int y = avail.y() + (avail.height() - s.height()) / 2 + frameState.offsetY; - if (!m_fitToWindow) + if (!viewport.fitToWindow) { - x += m_viewOffset.x(); - y += m_viewOffset.y(); + x += viewport.offset.x(); + y += viewport.offset.y(); } return QRect(QPoint(x, y), s); } @@ -2690,6 +2766,26 @@ namespace scopeone::ui glViewport(xPx, glY, wPx, hPx); } + // Applies an OpenGL scissor rectangle in logical widget coordinates + void PreviewWidget::applyScissorForRect(const QRect& logicalRect) + { + const QRect clippedRect = logicalRect.intersected(rect()); + if (clippedRect.width() <= 0 || clippedRect.height() <= 0) + { + glScissor(0, 0, 0, 0); + return; + } + + const qreal dpr = devicePixelRatioF(); + const int totalHeightPx = qMax(1, qRound(height() * dpr)); + const int xPx = qRound(clippedRect.x() * dpr); + const int yPx = qRound(clippedRect.y() * dpr); + const int wPx = qMax(1, qRound(clippedRect.width() * dpr)); + const int hPx = qMax(1, qRound(clippedRect.height() * dpr)); + const int glY = totalHeightPx - hPx - yPx; + glScissor(xPx, glY, wPx, hPx); + } + // Returns an existing texture or creates one with matching shape GLuint PreviewWidget::getOrCreateTexture(const QString& key, int width, int height, GLenum internalFormat) { @@ -2850,6 +2946,50 @@ namespace scopeone::ui { emit activated(); emit mousePositionChanged(event->pos()); + if (event->button() == Qt::MiddleButton) + { + PreviewInteractionTarget target; + if (!interactionTargetAt(event->pos(), target)) + { + return; + } + + setActiveLayerKey(target.layerKey); + emit layerClicked(target.layerKey); + const FrameSourceState frameState = snapshotFrameSources().value(target.sourceId); + ViewportState& viewport = viewportStateForLayer(target.layerKey); + if (viewport.fitToWindow) + { + const QPointF relativePos( + static_cast(event->pos().x() - target.displayRect.x()) + / static_cast(target.displayRect.width()), + static_cast(event->pos().y() - target.displayRect.y()) + / static_cast(target.displayRect.height())); + viewport.fitToWindow = false; + QRect newRect; + QSize imageSize; + if (resolveDisplayGeometry(frameState, + target.processed, + target.layerKey, + target.itemArea, + newRect, + imageSize)) + { + const int desiredX = qRound(event->pos().x() - relativePos.x() * newRect.width()); + const int desiredY = qRound(event->pos().y() - relativePos.y() * newRect.height()); + viewport.offset += QPoint(desiredX - newRect.x(), desiredY - newRect.y()); + } + emit fitToWindowChanged(false); + } + m_viewPanning = true; + m_panLayerKey = target.layerKey; + m_panStartWidgetPos = event->pos(); + m_panStartOffset = viewport.offset; + setCursor(Qt::ClosedHandCursor); + update(); + event->accept(); + return; + } if (m_measurementLineDrawingMode && event->button() == Qt::LeftButton) { PreviewInteractionTarget target; @@ -2946,7 +3086,7 @@ namespace scopeone::ui QOpenGLWidget::mousePressEvent(event); } - // Handles double click to maximize or restore grid tile + // Toggles visibility of the layer under the double click void PreviewWidget::mouseDoubleClickEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) @@ -2981,7 +3121,6 @@ namespace scopeone::ui // Updates active drawing interactions during mouse move void PreviewWidget::mouseMoveEvent(QMouseEvent* event) { - m_hoverWidgetPos = event->pos(); emit mousePositionChanged(event->pos()); update(); if (m_measurementLineDrawingMode && m_measurementLineDragging) @@ -3005,6 +3144,15 @@ namespace scopeone::ui return; } + if (m_viewPanning) + { + ViewportState& viewport = viewportStateForLayer(m_panLayerKey); + viewport.offset = m_panStartOffset + event->pos() - m_panStartWidgetPos; + emit mousePositionChanged(event->pos()); + update(); + return; + } + if (m_markupDragging) { FrameSourceState frameState; @@ -3019,7 +3167,12 @@ namespace scopeone::ui itemArea, displayRect, imageSize) - && mapWidgetPositionToImage(frameState, processed, itemArea, event->pos(), imagePos)) + && mapWidgetPositionToImage(frameState, + processed, + m_dragMarkupOriginal.layerKey, + itemArea, + event->pos(), + imagePos)) { if (m_dragMarkupOriginal.type == ImageSceneModel::MarkupType::Line) { @@ -3089,6 +3242,15 @@ namespace scopeone::ui void PreviewWidget::mouseReleaseEvent(QMouseEvent* event) { emit mousePositionChanged(event->pos()); + if (m_viewPanning && event->button() == Qt::MiddleButton) + { + m_viewPanning = false; + m_panLayerKey.clear(); + unsetCursor(); + update(); + event->accept(); + return; + } if (m_measurementLineDrawingMode && event->button() == Qt::LeftButton && m_measurementLineDragging) @@ -3130,11 +3292,13 @@ namespace scopeone::ui clippedEnd) || !mapWidgetPositionToImage(frameState, processed, + m_measurementLineTargetLayerKey, itemArea, clippedStart, imageStart) || !mapWidgetPositionToImage(frameState, processed, + m_measurementLineTargetLayerKey, itemArea, clippedEnd, imageEnd) @@ -3178,8 +3342,18 @@ namespace scopeone::ui displayRect, imageSize) || !clipLineToRect(m_crossSectionStart, m_crossSectionEnd, displayRect, clippedStart, clippedEnd) - || !mapWidgetPositionToImage(frameState, processed, itemArea, clippedStart, imgStart) - || !mapWidgetPositionToImage(frameState, processed, itemArea, clippedEnd, imgEnd)) + || !mapWidgetPositionToImage(frameState, + processed, + startTarget.layerKey, + itemArea, + clippedStart, + imgStart) + || !mapWidgetPositionToImage(frameState, + processed, + startTarget.layerKey, + itemArea, + clippedEnd, + imgEnd)) { cancelCrossSectionDrawing(); return; @@ -3243,7 +3417,12 @@ namespace scopeone::ui } QRect imageRect; - if (!mapWidgetRectToImage(frameState, false, itemArea, clippedRect, imageRect)) + if (!mapWidgetRectToImage(frameState, + false, + startTarget.layerKey, + itemArea, + clippedRect, + imageRect)) { cancelROIDrawing(); return; @@ -3286,7 +3465,6 @@ namespace scopeone::ui // Clears the cursor readout after the pointer leaves the preview void PreviewWidget::leaveEvent(QEvent* event) { - m_hoverWidgetPos = QPoint(-1, -1); emit mousePositionChanged(QPoint(-1, -1)); update(); QOpenGLWidget::leaveEvent(event); @@ -3308,53 +3486,52 @@ namespace scopeone::ui return; } - const int steps = (deltaY / 120 != 0) ? (deltaY / 120) : ((deltaY > 0) ? 1 : -1); PreviewInteractionTarget target; - QPointF relativePos; - const bool hasAnchor = interactionTargetAt(event->position().toPoint(), target) - && !target.sourceId.isEmpty() - && target.displayRect.width() > 0 - && target.displayRect.height() > 0; - if (hasAnchor) + const QPoint widgetPos = event->position().toPoint(); + if (!interactionTargetAt(widgetPos, target)) { - relativePos = QPointF( - static_cast(event->position().x() - target.displayRect.x()) - / static_cast(target.displayRect.width()), - static_cast(event->position().y() - target.displayRect.y()) - / static_cast(target.displayRect.height())); + event->accept(); + return; } - if (m_fitToWindow) + + if (m_activeLayerKey != target.layerKey) { - setFitToWindow(false); + setActiveLayerKey(target.layerKey); + emit layerClicked(target.layerKey); } - setZoomPercent(m_zoomPercent + steps * 10); - if (hasAnchor) + const int steps = (deltaY / 120 != 0) ? (deltaY / 120) : ((deltaY > 0) ? 1 : -1); + const QPointF relativePos( + static_cast(widgetPos.x() - target.displayRect.x()) + / static_cast(target.displayRect.width()), + static_cast(widgetPos.y() - target.displayRect.y()) + / static_cast(target.displayRect.height())); + ViewportState& viewport = viewportStateForLayer(target.layerKey); + const bool wasFitToWindow = viewport.fitToWindow; + viewport.fitToWindow = false; + viewport.zoomPercent = qBound(10, viewport.zoomPercent + steps * 10, 500); + + if (wasFitToWindow) { - FrameSourceState frameState; - const QMap frameSources = snapshotFrameSources(); - const auto frameStateIt = frameSources.constFind(target.sourceId); - const bool hasFrameState = frameStateIt != frameSources.constEnd(); - if (hasFrameState) - { - frameState = frameStateIt.value(); - } + emit fitToWindowChanged(false); + } + emit zoomLevelChanged(viewport.zoomPercent); - if (hasFrameState) - { - QRect newRect; - QSize imageSize; - if (!resolveDisplayGeometry(frameState, target.processed, target.itemArea, newRect, imageSize)) - { - event->accept(); - return; - } - const int desiredX = qRound(event->position().x() - relativePos.x() * newRect.width()); - const int desiredY = qRound(event->position().y() - relativePos.y() * newRect.height()); - m_viewOffset += QPoint(desiredX - newRect.x(), desiredY - newRect.y()); - update(); - } + const FrameSourceState frameState = snapshotFrameSources().value(target.sourceId); + QRect newRect; + QSize imageSize; + if (resolveDisplayGeometry(frameState, + target.processed, + target.layerKey, + target.itemArea, + newRect, + imageSize)) + { + const int desiredX = qRound(widgetPos.x() - relativePos.x() * newRect.width()); + const int desiredY = qRound(widgetPos.y() - relativePos.y() * newRect.height()); + viewport.offset += QPoint(desiredX - newRect.x(), desiredY - newRect.y()); } + update(); event->accept(); } diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index e21183d..3316d44 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -97,6 +97,7 @@ namespace scopeone::ui PreviewInteractionTarget& outTarget, const QString& sourceId = QString(), bool rawOnly = false) const; + QVector interactionTargetsAt(const QPoint& widgetPos) const; signals: void availableCameraIdsChanged(const QStringList& cameraIds); void availableLayerKeysChanged(const QStringList& layerKeys); @@ -191,6 +192,13 @@ namespace scopeone::ui bool hasRawFrame{false}; }; + struct ViewportState + { + int zoomPercent{100}; + QPoint offset; + bool fitToWindow{true}; + }; + struct LayerRenderItem { const FrameSourceRenderInfo* info{nullptr}; @@ -223,9 +231,8 @@ namespace scopeone::ui mutable QMutex m_mutex; QMap m_frameSources; quint64 m_nextFrameRevision{0}; - int m_zoomPercent{100}; - bool m_fitToWindow{true}; - QPoint m_viewOffset; + QMap m_viewportStates; + ViewportState m_overlayViewportState; QString m_placeholderText{QStringLiteral("No image loaded")}; bool m_glInited{false}; @@ -240,7 +247,6 @@ namespace scopeone::ui bool m_clippingWarning{false}; QString m_activeLayerKey; QStringList m_savedVisibleLayerKeys; - QPoint m_hoverWidgetPos{-1, -1}; std::function m_pixelSizeCallback; struct CachedTexture @@ -277,6 +283,10 @@ namespace scopeone::ui QPoint m_dragMarkupStartImagePos; MarkupEditMode m_dragMarkupEditMode{MarkupEditMode::None}; bool m_markupDragging{false}; + bool m_viewPanning{false}; + QPoint m_panStartWidgetPos; + QPoint m_panStartOffset; + QString m_panLayerKey; void updateImageDisplay(); void updateLayerFps(const QString& layerKey, quint64 frameCount = 1); void updateFrameRates(); @@ -300,6 +310,7 @@ namespace scopeone::ui std::vector& renderItems) const; bool resolveDisplayGeometry(const FrameSourceState& frameState, bool processed, + const QString& layerKey, const QRect& area, QRect& displayRect, QSize& imageSize) const; @@ -316,16 +327,19 @@ namespace scopeone::ui const QString& layerKey) const; bool mapWidgetPositionToImage(const FrameSourceState& frameState, bool processed, + const QString& layerKey, const QRect& area, const QPoint& widgetPos, QPoint& imagePos) const; bool mapWidgetRectToImage(const FrameSourceState& frameState, bool processed, + const QString& layerKey, const QRect& area, const QRect& widgetRect, QRect& imageRect) const; bool mapImagePositionToWidget(const FrameSourceState& frameState, bool processed, + const QString& layerKey, const QRect& area, const QPoint& imagePos, QPoint& widgetPos) const; @@ -337,7 +351,6 @@ namespace scopeone::ui void drawActiveInteractionMarkup(QPainter& painter, const std::vector& renderItems) const; void drawScaleBar(QPainter& painter, const std::vector& renderItems) const; void drawTileLabelsAndBadges(QPainter& painter, const std::vector& renderItems) const; - void drawCursorHud(QPainter& painter, const std::vector& renderItems) const; bool markupAtWidgetPosition(const QPoint& widgetPos, ImageSceneModel::Markup& outMarkup, PreviewInteractionTarget& outTarget, @@ -348,16 +361,22 @@ namespace scopeone::ui void drawFrameInRect(const QString& textureKey, const scopeone::core::ImageFrame& frame, quint64 frameRevision, - const QRect& r, + const QRect& displayRect, + const QRect& clipRect, bool flipX, bool flipY, const LayerDisplaySettings& display, bool firstVisibleInArea); QRect targetRectForImageSize(const QSize& imageSize, const FrameSourceState& frameState, + const QString& layerKey, const QRect& avail) const; void setUvTransform(bool flipX, bool flipY); void applyViewportForRect(const QRect& logicalRect); + void applyScissorForRect(const QRect& logicalRect); + ViewportState& viewportStateForLayer(const QString& layerKey); + ViewportState viewportStateForLayer(const QString& layerKey) const; + QString viewportControlLayerKey() const; std::vector computeLayout(int count) const; std::vector buildRenderItems(const std::vector& frameSourceRenderInfos) const; From 75cca461faeee3cca29e108d2c0324b8c3735db8 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:41:59 +0200 Subject: [PATCH 15/37] Refine image gallery interactions --- src/ImageGalleryWidget.cpp | 303 ++++++++++++++++++++++++++++++------- src/ImageGalleryWidget.h | 10 ++ src/MainWindow.cpp | 56 +++++++ 3 files changed, 318 insertions(+), 51 deletions(-) diff --git a/src/ImageGalleryWidget.cpp b/src/ImageGalleryWidget.cpp index e770b3a..54158f7 100644 --- a/src/ImageGalleryWidget.cpp +++ b/src/ImageGalleryWidget.cpp @@ -1,13 +1,25 @@ #include "ImageGalleryWidget.h" #include -#include +#include +#include +#include +#include +#include #include #include #include +#include +#include +#include #include +#include +#include #include #include +#include +#include +#include namespace scopeone::ui { @@ -83,6 +95,64 @@ namespace scopeone::ui { return session.recordedFrameCount(); } + + // Build a square grayscale thumbnail from a stored camera frame + QIcon frameThumbnail(const scopeone::core::ImageFrame& frame) + { + if (!frame.isValid()) + { + return {}; + } + + QImage image(frame.width, frame.height, QImage::Format_Grayscale8); + if (frame.isMono8()) + { + for (int y = 0; y < frame.height; ++y) + { + const char* source = frame.bytes.constData() + + static_cast(y) * frame.stride; + std::memcpy(image.scanLine(y), source, static_cast(frame.width)); + } + } + else + { + quint16 minimum = (std::numeric_limits::max)(); + quint16 maximum = 0; + for (int y = 0; y < frame.height; ++y) + { + const auto* source = reinterpret_cast( + frame.bytes.constData() + static_cast(y) * frame.stride); + for (int x = 0; x < frame.width; ++x) + { + minimum = (std::min)(minimum, source[x]); + maximum = (std::max)(maximum, source[x]); + } + } + + const int range = static_cast(maximum) - static_cast(minimum); + for (int y = 0; y < frame.height; ++y) + { + const auto* source = reinterpret_cast( + frame.bytes.constData() + static_cast(y) * frame.stride); + uchar* target = image.scanLine(y); + for (int x = 0; x < frame.width; ++x) + { + target[x] = range > 0 + ? static_cast( + (static_cast(source[x]) - minimum) * 255 / range) + : static_cast(source[x] > 0 ? 255 : 0); + } + } + } + + const QImage scaled = image.scaled(QSize(48, 48), Qt::KeepAspectRatio, + Qt::SmoothTransformation); + QPixmap pixmap(48, 48); + pixmap.fill(QColor(QStringLiteral("#1c2229"))); + QPainter painter(&pixmap); + painter.drawImage((48 - scaled.width()) / 2, (48 - scaled.height()) / 2, scaled); + return QIcon(pixmap); + } } // Create the image gallery panel @@ -111,6 +181,19 @@ namespace scopeone::ui updateButtons(); updateEmptyState(); }); + connect(m_core, &scopeone::core::ScopeOneCore::recordingSessionFrameReady, + this, + [this](quint64, + const std::shared_ptr& session, + const QString&, + int, + const scopeone::core::ImageFrame& frame) + { + if (session && frame.isValid()) + { + updateSessionThumbnail(session->capturePlan().experimentId, frame); + } + }); } // Add one acquired image session to the gallery @@ -139,12 +222,22 @@ namespace scopeone::ui auto* item = new QListWidgetItem(m_sessionList); item->setText(displayTitle(*session, title) + QLatin1Char('\n') + itemSubtitle(*session)); + item->setSizeHint(QSize(0, 64)); item->setData(kSessionIdRole, id); if (!session->isSaved()) { item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); } + const QStringList cameraIds = session->recordedCameraIds(); + for (const QString& cameraId : cameraIds) + { + if (session->recordedFrameCount(cameraId) > 0) + { + m_core->requestRecordingSessionFrame(session, cameraId, 0); + break; + } + } m_sessionList->setCurrentItem(item); updateButtons(); @@ -206,17 +299,37 @@ namespace scopeone::ui m_sessionList = new QListWidget(this); m_sessionList->setSelectionMode(QAbstractItemView::SingleSelection); + m_sessionList->setIconSize(QSize(48, 48)); + m_sessionList->setSpacing(4); + m_sessionList->setUniformItemSizes(true); + m_sessionList->setTextElideMode(Qt::ElideRight); + m_sessionList->setContextMenuPolicy(Qt::CustomContextMenu); + m_sessionList->setStyleSheet(QStringLiteral( + "QListWidget { border: 1px solid #3a424b; border-radius: 4px; padding: 2px; }" + "QListWidget::item { padding: 6px; border-radius: 4px; }" + "QListWidget::item:selected { background: #31485d; }")); layout->addWidget(m_sessionList, 1); - auto* buttonLayout = new QHBoxLayout(); - m_liveButton = new QPushButton(QStringLiteral("Live"), this); + auto* buttonLayout = new QGridLayout(); + buttonLayout->setHorizontalSpacing(6); + buttonLayout->setVerticalSpacing(6); m_openButton = new QPushButton(QStringLiteral("Preview"), this); - m_saveCheckedButton = new QPushButton(QStringLiteral("Save Checked"), this); m_deleteButton = new QPushButton(QStringLiteral("Delete"), this); - buttonLayout->addWidget(m_liveButton); - buttonLayout->addWidget(m_openButton); - buttonLayout->addWidget(m_saveCheckedButton); - buttonLayout->addWidget(m_deleteButton); + m_saveCheckedButton = new QPushButton(QStringLiteral("Save Checked"), this); + m_liveButton = new QPushButton(QStringLiteral("Live"), this); + m_openButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + m_saveCheckedButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + m_deleteButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_liveButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_openButton->setToolTip(QStringLiteral("Open the selected image preview")); + m_deleteButton->setToolTip(QStringLiteral("Delete the selected gallery session")); + m_saveCheckedButton->setToolTip(QStringLiteral("Save all checked unsaved sessions")); + m_liveButton->setToolTip(QStringLiteral("Return to the live preview")); + buttonLayout->addWidget(m_openButton, 0, 0); + buttonLayout->addWidget(m_deleteButton, 0, 1); + buttonLayout->addWidget(m_saveCheckedButton, 1, 0); + buttonLayout->addWidget(m_liveButton, 1, 1); + buttonLayout->setColumnStretch(0, 1); layout->addLayout(buttonLayout); connect(m_sessionList, &QListWidget::currentItemChanged, this, @@ -225,51 +338,24 @@ namespace scopeone::ui updateButtons(); }); connect(m_sessionList, &QListWidget::itemDoubleClicked, this, - [this](QListWidgetItem*) - { - auto session = currentSession(); - if (canPreviewSession(session)) - { - emit sessionOpenRequested(session); - } - }); + [this](QListWidgetItem*) { openCurrentSession(); }); connect(m_sessionList, &QListWidget::itemChanged, this, [this](QListWidgetItem*) { updateButtons(); }); + connect(m_sessionList, &QListWidget::customContextMenuRequested, + this, &ImageGalleryWidget::showContextMenu); connect(m_liveButton, &QPushButton::clicked, this, &ImageGalleryWidget::livePreviewRequested); - connect(m_openButton, &QPushButton::clicked, this, - [this]() - { - auto session = currentSession(); - if (canPreviewSession(session)) - { - emit sessionOpenRequested(session); - } - }); - connect(m_saveCheckedButton, &QPushButton::clicked, this, - [this]() - { - const auto sessions = checkedSessions(); - if (!sessions.isEmpty()) - { - emit saveSessionsRequested(sessions); - } - }); - connect(m_deleteButton, &QPushButton::clicked, this, - [this]() - { - QListWidgetItem* item = m_sessionList->currentItem(); - if (!item) - { - return; - } - auto session = m_core->recordingSession(item->data(kSessionIdRole).toString()); - delete m_sessionList->takeItem(m_sessionList->row(item)); - if (session) - { - emit sessionRemoved(session); - } - updateButtons(); - updateEmptyState(); - }); + connect(m_openButton, &QPushButton::clicked, this, &ImageGalleryWidget::openCurrentSession); + connect(m_saveCheckedButton, &QPushButton::clicked, + this, &ImageGalleryWidget::saveCheckedSessions); + connect(m_deleteButton, &QPushButton::clicked, this, &ImageGalleryWidget::deleteCurrentSession); + + auto* returnShortcut = new QShortcut(QKeySequence(Qt::Key_Return), m_sessionList); + connect(returnShortcut, &QShortcut::activated, this, &ImageGalleryWidget::openCurrentSession); + auto* enterShortcut = new QShortcut(QKeySequence(Qt::Key_Enter), m_sessionList); + connect(enterShortcut, &QShortcut::activated, this, &ImageGalleryWidget::openCurrentSession); + auto* deleteShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), m_sessionList); + connect(deleteShortcut, &QShortcut::activated, this, &ImageGalleryWidget::deleteCurrentSession); + auto* backspaceShortcut = new QShortcut(QKeySequence(Qt::Key_Backspace), m_sessionList); + connect(backspaceShortcut, &QShortcut::activated, this, &ImageGalleryWidget::deleteCurrentSession); } // Enable actions only when they have valid targets @@ -282,6 +368,121 @@ namespace scopeone::ui m_saveCheckedButton->setEnabled(!checkedSessions().isEmpty()); } + // Open the currently selected session + void ImageGalleryWidget::openCurrentSession() + { + const auto session = currentSession(); + if (canPreviewSession(session)) + { + emit sessionOpenRequested(session); + } + } + + // Remove the currently selected session from the gallery + void ImageGalleryWidget::deleteCurrentSession() + { + QListWidgetItem* item = m_sessionList->currentItem(); + if (!item) + { + return; + } + const auto session = m_core->recordingSession(item->data(kSessionIdRole).toString()); + delete m_sessionList->takeItem(m_sessionList->row(item)); + if (session) + { + emit sessionRemoved(session); + } + updateButtons(); + updateEmptyState(); + } + + // Save all checked unsaved sessions + void ImageGalleryWidget::saveCheckedSessions() + { + const auto sessions = checkedSessions(); + if (!sessions.isEmpty()) + { + emit saveSessionsRequested(sessions); + } + } + + // Check or uncheck every session that supports gallery selection + void ImageGalleryWidget::setAllSessionsChecked(bool checked) + { + for (int row = 0; row < m_sessionList->count(); ++row) + { + QListWidgetItem* item = m_sessionList->item(row); + if (item->flags() & Qt::ItemIsUserCheckable) + { + item->setCheckState(checked ? Qt::Checked : Qt::Unchecked); + } + } + } + + // Show gallery actions for the item under the pointer + void ImageGalleryWidget::showContextMenu(const QPoint& position) + { + if (QListWidgetItem* item = m_sessionList->itemAt(position)) + { + m_sessionList->setCurrentItem(item); + } + else + { + m_sessionList->clearSelection(); + m_sessionList->setCurrentItem(nullptr); + } + + const auto session = currentSession(); + QMenu menu(this); + QAction* openAction = menu.addAction(QStringLiteral("Open Preview (Enter)")); + QAction* saveAsAction = menu.addAction(QStringLiteral("Save Selected As...")); + QAction* deleteAction = menu.addAction(QStringLiteral("Delete (Del)")); + menu.addSeparator(); + QAction* selectAllAction = menu.addAction(QStringLiteral("Select All")); + QAction* unselectAllAction = menu.addAction(QStringLiteral("Unselect All")); + QAction* saveCheckedAction = menu.addAction(QStringLiteral("Save Checked")); + + openAction->setEnabled(canPreviewSession(session)); + saveAsAction->setEnabled(session != nullptr); + deleteAction->setEnabled(session != nullptr); + saveCheckedAction->setEnabled(!checkedSessions().isEmpty()); + + connect(openAction, &QAction::triggered, this, &ImageGalleryWidget::openCurrentSession); + connect(saveAsAction, &QAction::triggered, this, + [this]() + { + const auto selected = currentSession(); + if (selected) + { + emit saveSessionAsRequested(selected); + } + }); + connect(deleteAction, &QAction::triggered, this, &ImageGalleryWidget::deleteCurrentSession); + connect(selectAllAction, &QAction::triggered, this, + [this]() { setAllSessionsChecked(true); }); + connect(unselectAllAction, &QAction::triggered, this, + [this]() { setAllSessionsChecked(false); }); + connect(saveCheckedAction, &QAction::triggered, + this, &ImageGalleryWidget::saveCheckedSessions); + menu.exec(m_sessionList->viewport()->mapToGlobal(position)); + } + + // Apply an asynchronously loaded frame to its gallery item + void ImageGalleryWidget::updateSessionThumbnail( + const QString& sessionId, + const scopeone::core::ImageFrame& frame) + { + for (int row = 0; row < m_sessionList->count(); ++row) + { + QListWidgetItem* item = m_sessionList->item(row); + if (item->data(kSessionIdRole).toString() == sessionId.trimmed()) + { + item->setIcon(frameThumbnail(frame)); + return; + } + } + } + // Show a simple empty state when no sessions exist void ImageGalleryWidget::updateEmptyState() { diff --git a/src/ImageGalleryWidget.h b/src/ImageGalleryWidget.h index 5b7aeda..83ca3e9 100644 --- a/src/ImageGalleryWidget.h +++ b/src/ImageGalleryWidget.h @@ -9,6 +9,7 @@ class QLabel; class QListWidget; class QPushButton; +class QPoint; namespace scopeone::ui { @@ -29,6 +30,8 @@ namespace scopeone::ui void livePreviewRequested(); void sessionOpenRequested(const std::shared_ptr& session); void sessionRemoved(const std::shared_ptr& session); + void saveSessionAsRequested( + const std::shared_ptr& session); void saveSessionsRequested( const QList>& sessions); @@ -40,6 +43,13 @@ namespace scopeone::ui QString itemSubtitle(const scopeone::core::ScopeOneCore::RecordingSessionData& session) const; std::shared_ptr currentSession() const; QList> checkedSessions() const; + void openCurrentSession(); + void deleteCurrentSession(); + void saveCheckedSessions(); + void setAllSessionsChecked(bool checked); + void showContextMenu(const QPoint& position); + void updateSessionThumbnail(const QString& sessionId, + const scopeone::core::ImageFrame& frame); scopeone::core::ScopeOneCore* m_core{nullptr}; QListWidget* m_sessionList{nullptr}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 6602a77..c091bbd 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -690,6 +691,61 @@ namespace scopeone::ui } } }); + connect(m_imageGalleryWidget, &ImageGalleryWidget::saveSessionAsRequested, + this, + [this](const std::shared_ptr& session) + { + const QString saveDir = QFileDialog::getExistingDirectory( + this, tr("Select Dataset Folder"), QDir::homePath()); + if (saveDir.isEmpty()) + { + return; + } + + bool accepted = false; + QString baseName = QInputDialog::getText( + this, + tr("Save Image Dataset As"), + tr("Dataset name and optional format suffix"), + QLineEdit::Normal, + session->capturePlan().baseName + QStringLiteral(".ome.tiff"), + &accepted) + .trimmed(); + if (!accepted || baseName.isEmpty()) + { + return; + } + + scopeone::core::ScopeOneCore::RecordingSaveOptions options; + if (baseName.endsWith(QStringLiteral(".ome.tiff"), Qt::CaseInsensitive)) + { + baseName.chop(9); + options.format = scopeone::core::RecordingFormat::OmeTiff; + } + else if (baseName.endsWith(QStringLiteral(".ome.zarr"), Qt::CaseInsensitive)) + { + baseName.chop(9); + options.format = scopeone::core::RecordingFormat::OmeZarr; + } + else if (baseName.endsWith(QStringLiteral(".tiff"), Qt::CaseInsensitive)) + { + baseName.chop(5); + options.format = scopeone::core::RecordingFormat::Tiff; + } + else if (baseName.endsWith(QStringLiteral(".bin"), Qt::CaseInsensitive)) + { + baseName.chop(4); + options.format = scopeone::core::RecordingFormat::Binary; + } + options.saveDir = saveDir; + options.baseName = baseName; + options.enableCompression = options.format != scopeone::core::RecordingFormat::Binary; + updateSessionPresentation(*m_scopeonecore, *m_imageSceneModel, session); + if (!m_scopeonecore->saveRecordingSession(session, options)) + { + showStatusMessage(tr("Could not start saving the selected session"), 5000); + } + }); connectPropertyPanels(); } From 2d8ac5643ebafc751a1ba564c322b97cb59b95f4 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:01:14 +0200 Subject: [PATCH 16/37] Modernize viewport toolbar --- src/ImageWorkspace.cpp | 117 +++++++++++++++++++++++++++++------------ src/ImageWorkspace.h | 3 +- src/PreviewWidget.cpp | 4 +- 3 files changed, 86 insertions(+), 38 deletions(-) diff --git a/src/ImageWorkspace.cpp b/src/ImageWorkspace.cpp index d7c87ac..797eef7 100644 --- a/src/ImageWorkspace.cpp +++ b/src/ImageWorkspace.cpp @@ -8,8 +8,6 @@ #include #include #include -#include -#include #include #include #include @@ -445,15 +443,6 @@ namespace scopeone::ui // Builds the compact controls used by the active image viewer void ImageWorkspace::setupViewerToolbar() { - m_viewerContextLabel = new QLabel(tr("Live Preview"), m_viewerToolbar); - QFont contextFont = m_viewerContextLabel->font(); - contextFont.setBold(true); - m_viewerContextLabel->setFont(contextFont); - m_viewerContextLabel->setFixedWidth(180); - m_viewerContextLabel->setToolTip(tr("Current image viewer")); - m_viewerToolbar->addWidget(m_viewerContextLabel); - m_viewerToolbar->addSeparator(); - m_fitToWindowAction = m_viewerToolbar->addAction(tr("Fit")); m_fitToWindowAction->setCheckable(true); m_fitToWindowAction->setToolTip(tr("Fit the image to the viewer")); @@ -479,11 +468,55 @@ namespace scopeone::ui } }); + m_zoomCombo = new QComboBox(m_viewerToolbar); + m_zoomCombo->setEditable(true); + m_zoomCombo->setMinimumWidth(76); + m_zoomCombo->setInsertPolicy(QComboBox::NoInsert); + m_zoomCombo->addItem(tr("Fit")); + m_zoomCombo->addItems({QStringLiteral("25%"), + QStringLiteral("50%"), + QStringLiteral("75%"), + QStringLiteral("100%"), + QStringLiteral("150%"), + QStringLiteral("200%"), + QStringLiteral("300%"), + QStringLiteral("400%"), + QStringLiteral("800%")}); + m_zoomCombo->setToolTip(tr("Choose the viewport zoom percentage")); + m_viewerToolbar->addWidget(m_zoomCombo); + auto applyZoomText = [this]() + { + PreviewWidget* preview = activePreviewWidget(); + const QString text = m_zoomCombo->currentText().trimmed(); + if (text.compare(QStringLiteral("Fit"), Qt::CaseInsensitive) == 0) + { + preview->setFitToWindow(true); + return; + } + + QString numericText = text; + if (numericText.endsWith(QLatin1Char('%'))) + { + numericText.chop(1); + } + bool ok = false; + const int percent = numericText.toInt(&ok); + if (ok) + { + preview->setFitToWindow(false); + preview->setZoomPercent(percent); + } + updateViewerToolbar(); + }; + connect(m_zoomCombo, qOverload(&QComboBox::activated), this, + [applyZoomText](int) { applyZoomText(); }); + connect(m_zoomCombo->lineEdit(), &QLineEdit::editingFinished, + this, applyZoomText); + m_viewerToolbar->addSeparator(); - m_viewerToolbar->addWidget(new QLabel(tr("Layer Layout"), m_viewerToolbar)); m_layoutCombo = new QComboBox(m_viewerToolbar); - m_layoutCombo->addItem(tr("Side by side")); - m_layoutCombo->addItem(tr("Overlay")); + m_layoutCombo->addItem(tr("Grid View (G)")); + m_layoutCombo->addItem(tr("Overlay (G)")); m_layoutCombo->setToolTip(tr("Choose how multiple image layers are arranged")); m_viewerToolbar->addWidget(m_layoutCombo); connect(m_layoutCombo, qOverload(&QComboBox::currentIndexChanged), this, @@ -497,7 +530,7 @@ namespace scopeone::ui } }); - m_viewerToolbar->addSeparator(); + m_compareSeparator = m_viewerToolbar->addSeparator(); m_compareAction = m_viewerToolbar->addAction(tr("Compare")); m_compareAction->setCheckable(true); m_compareAction->setToolTip(tr("Show two image documents side by side")); @@ -526,21 +559,28 @@ namespace scopeone::ui connect(m_compareDocumentCombo, qOverload(&QComboBox::currentIndexChanged), this, [this](int index) { - if (!comparisonActive() || index < 0) + if (index < 0) { return; } QString rightId = m_compareDocumentCombo->itemData(index).toString(); - const QString leftId = m_compareLeftDocumentId; + const QString leftId = comparisonActive() + ? m_compareLeftDocumentId + : m_activeDocumentId; if (rightId == QStringLiteral("duplicate")) { rightId = duplicateDocument(leftId); } - if (rightId.isEmpty() || rightId == m_compareRightDocumentId) + if (rightId.isEmpty() + || rightId == leftId + || (comparisonActive() && rightId == m_compareRightDocumentId)) { return; } - endComparison(); + if (comparisonActive()) + { + endComparison(); + } setActiveDocument(leftId); { const QSignalBlocker blocker(m_compareAction); @@ -564,26 +604,29 @@ namespace scopeone::ui { PreviewWidget* preview = activePreviewWidget(); const ImageDocumentInfo activeDocument = document(); - const QString contextTitle = activeDocument.isValid() - ? activeDocument.title - : tr("Live Preview"); - m_viewerContextLabel->setText( - QFontMetrics(m_viewerContextLabel->font()).elidedText( - contextTitle, Qt::ElideRight, m_viewerContextLabel->width())); - m_viewerContextLabel->setToolTip(activeDocument.isValid() - ? tr("Static image: %1").arg(contextTitle) - : tr("Live camera viewer")); m_fitToWindowAction->setEnabled(preview != nullptr); m_oneToOneAction->setEnabled(preview != nullptr); - m_compareAction->setEnabled(activeDocument.isValid()); - m_compareDocumentCombo->setVisible(comparisonActive()); - m_linkFramesAction->setVisible(comparisonActive()); + m_zoomCombo->setEnabled(preview != nullptr); + const bool staticDocument = activeDocument.isValid(); + m_compareSeparator->setVisible(staticDocument); + m_compareAction->setVisible(staticDocument); + m_compareAction->setEnabled(staticDocument); + m_compareDocumentCombo->setVisible(staticDocument); + m_compareDocumentCombo->setEnabled(staticDocument); + m_linkFramesAction->setVisible(staticDocument && comparisonActive()); if (preview) { { const QSignalBlocker blocker(m_fitToWindowAction); m_fitToWindowAction->setChecked(preview->isFitToWindow()); } + { + const QSignalBlocker blocker(m_zoomCombo); + m_zoomCombo->setEditText( + preview->isFitToWindow() + ? tr("Fit") + : QStringLiteral("%1%").arg(preview->zoomPercent())); + } { const QSignalBlocker blocker(m_layoutCombo); m_layoutCombo->setCurrentIndex( @@ -594,20 +637,24 @@ namespace scopeone::ui { const QSignalBlocker blocker(m_compareDocumentCombo); m_compareDocumentCombo->clear(); - if (!m_compareLeftDocumentId.isEmpty()) + const QString leftDocumentId = comparisonActive() + ? m_compareLeftDocumentId + : m_activeDocumentId; + if (!leftDocumentId.isEmpty()) { - const Document* left = findDocument(m_compareLeftDocumentId); + const Document* left = findDocument(leftDocumentId); m_compareDocumentCombo->addItem( left ? tr("Same sequence, independent frame") : tr("Duplicate current document"), QStringLiteral("duplicate")); for (const auto& candidate : m_documents) { - if (candidate->id != m_compareLeftDocumentId) + if (candidate->id != leftDocumentId) { m_compareDocumentCombo->addItem(candidate->title, candidate->id); } } - const int rightIndex = m_compareDocumentCombo->findData(m_compareRightDocumentId); + const int rightIndex = m_compareDocumentCombo->findData( + comparisonActive() ? m_compareRightDocumentId : QStringLiteral("duplicate")); m_compareDocumentCombo->setCurrentIndex(rightIndex >= 0 ? rightIndex : 0); } } diff --git a/src/ImageWorkspace.h b/src/ImageWorkspace.h index b7d9a7c..a9822af 100644 --- a/src/ImageWorkspace.h +++ b/src/ImageWorkspace.h @@ -153,10 +153,11 @@ namespace scopeone::ui QGroupBox* m_compareRightHost{nullptr}; QAction* m_fitToWindowAction{nullptr}; QAction* m_oneToOneAction{nullptr}; + QAction* m_compareSeparator{nullptr}; QAction* m_compareAction{nullptr}; QAction* m_linkFramesAction{nullptr}; - QLabel* m_viewerContextLabel{nullptr}; QComboBox* m_layoutCombo{nullptr}; + QComboBox* m_zoomCombo{nullptr}; QComboBox* m_compareDocumentCombo{nullptr}; int m_liveTabIndex{-1}; PreviewWidget* m_livePreviewWidget{nullptr}; diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index bb982f5..8460a6c 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -900,7 +900,7 @@ namespace scopeone::ui // Sets the preview zoom for the active layer or the overlay void PreviewWidget::setZoomPercent(int percent) { - const int nextPercent = qBound(10, percent, 500); + const int nextPercent = qBound(10, percent, 800); ViewportState& state = viewportStateForLayer(viewportControlLayerKey()); if (state.zoomPercent == nextPercent) { @@ -3509,7 +3509,7 @@ namespace scopeone::ui ViewportState& viewport = viewportStateForLayer(target.layerKey); const bool wasFitToWindow = viewport.fitToWindow; viewport.fitToWindow = false; - viewport.zoomPercent = qBound(10, viewport.zoomPercent + steps * 10, 500); + viewport.zoomPercent = qBound(10, viewport.zoomPercent + steps * 10, 800); if (wasFitToWindow) { From 7a4b5481cb5ea1bc7a92b8949ee6b315b00e9596 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:10:56 +0200 Subject: [PATCH 17/37] Prepare console API dispatch --- src/ConsoleWidget.h | 44 +++++++++++++++++++++++++--------- src/ScopeOneLocalApiServer.cpp | 30 ++++++++++++++++------- src/ScopeOneLocalApiServer.h | 10 ++++++-- 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/ConsoleWidget.h b/src/ConsoleWidget.h index 9a1bac1..6f2996f 100644 --- a/src/ConsoleWidget.h +++ b/src/ConsoleWidget.h @@ -1,12 +1,16 @@ #pragma once #include +#include #include #include #include #include +#include class QTextEdit; +class QEvent; +class QLineEdit; class QPushButton; class QCheckBox; class QComboBox; @@ -19,6 +23,9 @@ namespace scopeone::ui Q_OBJECT public: + using ApiDispatcher = std::function)>; + explicit ConsoleWidget(QWidget* parent = nullptr); ~ConsoleWidget() override; @@ -43,6 +50,8 @@ namespace scopeone::ui QStringList getMessageFilter() const; + void setApiDispatcher(ApiDispatcher dispatcher); + private: void onClearClicked(); void onShowTimestampsToggled(bool show); @@ -61,18 +70,31 @@ namespace scopeone::ui QString formatMessage(const ConsoleMessage& msg) const; QString getTypeColor(const QString& type) const; void scrollToBottom(); - - QTextEdit* m_consoleTextEdit; - - QPushButton* m_clearButton; - QCheckBox* m_showTimestampsCheckBox; - QCheckBox* m_autoScrollCheckBox; - QComboBox* m_filterComboBox; - QLabel* m_messageCountLabel; - - bool m_showTimestamps; - bool m_autoScroll; + void executeCommand(const QString& commandText); + void showHelp(); + void exportLogsToFile(); + void showContextMenu(const QPoint& position); + void showCommandResponse(const QJsonObject& response); + bool eventFilter(QObject* object, QEvent* event) override; + + QTextEdit* m_consoleTextEdit{nullptr}; + + QPushButton* m_clearButton{nullptr}; + QPushButton* m_runButton{nullptr}; + QCheckBox* m_showTimestampsCheckBox{nullptr}; + QCheckBox* m_autoScrollCheckBox{nullptr}; + QComboBox* m_filterComboBox{nullptr}; + QLabel* m_messageCountLabel{nullptr}; + QLineEdit* m_searchInput{nullptr}; + QLineEdit* m_commandInput{nullptr}; + + bool m_showTimestamps{true}; + bool m_autoScroll{true}; QStringList m_messageFilter; QList m_messages; + QString m_searchKeyword; + QStringList m_commandHistory; + int m_historyIndex{-1}; + ApiDispatcher m_apiDispatcher; }; } diff --git a/src/ScopeOneLocalApiServer.cpp b/src/ScopeOneLocalApiServer.cpp index 0f0ac78..7653bd8 100644 --- a/src/ScopeOneLocalApiServer.cpp +++ b/src/ScopeOneLocalApiServer.cpp @@ -1348,17 +1348,20 @@ namespace scopeone::ui // Starts Local API operations that must not run in the socket callback bool ScopeOneLocalApiServer::processAsyncRequest(QLocalSocket* socket, const QJsonObject& request, - const QJsonValue& requestId) + const QJsonValue& requestId, + const ResponseCallback& callback) { const QString type = request.value(QStringLiteral("type")).toString().trimmed(); const QPointer guardedSocket(socket); - const auto finish = [this, guardedSocket, requestId](QJsonObject response) - { - if (guardedSocket) - { - sendRequestResponse(guardedSocket, std::move(response), requestId); - } - }; + const auto finish = callback + ? callback + : ResponseCallback([this, guardedSocket, requestId](QJsonObject response) + { + if (guardedSocket) + { + sendRequestResponse(guardedSocket, std::move(response), requestId); + } + }); if (type == QStringLiteral("open_image_window")) { @@ -2279,6 +2282,17 @@ namespace scopeone::ui return false; } + // Dispatch one request through the same synchronous or asynchronous path as the local socket + void ScopeOneLocalApiServer::dispatchRequest(const QJsonObject& request, + ResponseCallback callback) + { + if (processAsyncRequest(nullptr, request, QJsonValue(), callback)) + { + return; + } + callback(processRequest(request)); + } + // Dispatches one local API request object QJsonObject ScopeOneLocalApiServer::processRequest(const QJsonObject& request) { diff --git a/src/ScopeOneLocalApiServer.h b/src/ScopeOneLocalApiServer.h index 2b84c4b..48a600d 100644 --- a/src/ScopeOneLocalApiServer.h +++ b/src/ScopeOneLocalApiServer.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "scopeone/ScopeOneCore.h" @@ -24,12 +25,17 @@ namespace scopeone::ui Q_OBJECT public: + using ResponseCallback = std::function; + explicit ScopeOneLocalApiServer(scopeone::core::ScopeOneCore* core, PreviewWidget* previewWidget, ImageWorkspace* imageWorkspace, QObject* parent = nullptr); ~ScopeOneLocalApiServer() override; + QJsonObject processRequest(const QJsonObject& request); + void dispatchRequest(const QJsonObject& request, ResponseCallback callback); + private: void handleNewConnection(); void handleSocketReadyRead(QLocalSocket* socket); @@ -40,8 +46,8 @@ namespace scopeone::ui const QJsonValue& requestId); bool processAsyncRequest(QLocalSocket* socket, const QJsonObject& request, - const QJsonValue& requestId); - QJsonObject processRequest(const QJsonObject& request); + const QJsonValue& requestId, + const ResponseCallback& callback = {}); scopeone::core::ExperimentDocument createExperimentDocument(); QJsonObject experimentStatusResponse(const QString& type, const QString& experimentId) const; From eda9c1624ca174658338edf994d0cf4957d728c6 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:06:15 +0200 Subject: [PATCH 18/37] Add hybrid console and simplify gallery actions --- src/ConsoleWidget.cpp | 515 ++++++++++++++++++++++++++++++++++--- src/ConsoleWidget.h | 2 + src/ImageGalleryWidget.cpp | 23 +- src/ImageGalleryWidget.h | 3 - src/MainWindow.cpp | 86 ++++++- src/MainWindow.h | 2 + 6 files changed, 576 insertions(+), 55 deletions(-) diff --git a/src/ConsoleWidget.cpp b/src/ConsoleWidget.cpp index 5373b13..40504aa 100644 --- a/src/ConsoleWidget.cpp +++ b/src/ConsoleWidget.cpp @@ -7,14 +7,28 @@ #include #include #include +#include +#include +#include +#include #include #include +#include +#include +#include +#include #include +#include +#include #include #include +#include +#include #include +#include #include #include +#include namespace scopeone::ui { @@ -102,6 +116,22 @@ namespace scopeone::ui connect(m_autoScrollCheckBox, &QCheckBox::toggled, this, &ConsoleWidget::onAutoScrollToggled); connect(m_filterComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ConsoleWidget::onFilterChanged); + connect(m_searchInput, &QLineEdit::textChanged, this, + [this](const QString& text) + { + m_searchKeyword = text.trimmed(); + updateDisplay(); + }); + connect(m_runButton, &QPushButton::clicked, this, + [this]() { executeCommand(m_commandInput->text()); }); + m_consoleTextEdit->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_consoleTextEdit, &QTextEdit::customContextMenuRequested, + this, &ConsoleWidget::showContextMenu); + m_commandInput->installEventFilter(this); + + auto* clearShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+L")), this); + clearShortcut->setContext(Qt::WidgetWithChildrenShortcut); + connect(clearShortcut, &QShortcut::activated, this, &ConsoleWidget::clearMessages); updateDisplay(); } @@ -115,7 +145,7 @@ namespace scopeone::ui } } - // Build the console text view and filter controls + // Build the console text view and interactive command controls void ConsoleWidget::setupUI() { auto* mainLayout = new QVBoxLayout(this); @@ -139,38 +169,75 @@ namespace scopeone::ui "}" ); - auto* controlLayout = new QHBoxLayout(); + auto* topBarLayout = new QVBoxLayout(); + topBarLayout->setSpacing(4); + topBarLayout->setContentsMargins(0, 0, 0, 0); + + auto* row1Layout = new QHBoxLayout(); + row1Layout->setSpacing(4); + + m_searchInput = new QLineEdit(this); + m_searchInput->setPlaceholderText(tr("Search logs...")); + m_searchInput->setClearButtonEnabled(true); + m_searchInput->setMinimumWidth(80); + + m_filterComboBox = new QComboBox(this); + m_filterComboBox->addItems({tr("All"), + QStringLiteral("INFO"), + QStringLiteral("DEBUG"), + QStringLiteral("WARNING"), + QStringLiteral("ERROR")}); + m_filterComboBox->setFixedWidth(80); + + m_clearButton = new QPushButton(tr("Clear"), this); + m_clearButton->setFixedWidth(50); - m_clearButton = new QPushButton("Clear", this); - m_clearButton->setMaximumWidth(60); + row1Layout->addWidget(m_searchInput, 1); + row1Layout->addWidget(m_filterComboBox); + row1Layout->addWidget(m_clearButton); - m_showTimestampsCheckBox = new QCheckBox("Timestamps", this); + auto* row2Layout = new QHBoxLayout(); + row2Layout->setSpacing(8); + + m_showTimestampsCheckBox = new QCheckBox(tr("Timestamps"), this); m_showTimestampsCheckBox->setChecked(m_showTimestamps); - m_autoScrollCheckBox = new QCheckBox("Auto-scroll", this); + m_autoScrollCheckBox = new QCheckBox(tr("Auto-scroll"), this); m_autoScrollCheckBox->setChecked(m_autoScroll); - auto* filterLabel = new QLabel("Filter:", this); - m_filterComboBox = new QComboBox(this); - m_filterComboBox->addItem("All"); - m_filterComboBox->addItem("INFO"); - m_filterComboBox->addItem("DEBUG"); - m_filterComboBox->addItem("WARNING"); - m_filterComboBox->addItem("ERROR"); - m_filterComboBox->setMaximumWidth(120); - - m_messageCountLabel = new QLabel("Messages: 0", this); - - controlLayout->addWidget(m_clearButton); - controlLayout->addWidget(m_showTimestampsCheckBox); - controlLayout->addWidget(m_autoScrollCheckBox); - controlLayout->addWidget(filterLabel); - controlLayout->addWidget(m_filterComboBox); - controlLayout->addStretch(); - controlLayout->addWidget(m_messageCountLabel); + m_messageCountLabel = new QLabel(tr("Messages: 0"), this); + m_messageCountLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + + row2Layout->addWidget(m_showTimestampsCheckBox); + row2Layout->addWidget(m_autoScrollCheckBox); + row2Layout->addStretch(1); + row2Layout->addWidget(m_messageCountLabel); + topBarLayout->addLayout(row1Layout); + topBarLayout->addLayout(row2Layout); + + mainLayout->addLayout(topBarLayout, 0); mainLayout->addWidget(m_consoleTextEdit, 1); - mainLayout->addLayout(controlLayout, 0); + + auto* commandLayout = new QHBoxLayout(); + auto* promptLabel = new QLabel(QStringLiteral(">>> "), this); + promptLabel->setStyleSheet(QStringLiteral("color: #51cf66;")); + promptLabel->setFont(consoleFont); + + m_commandInput = new QLineEdit(this); + m_commandInput->setPlaceholderText(tr("Enter a command, then press Enter")); + m_commandInput->setFont(consoleFont); + m_commandInput->setStyleSheet( + QStringLiteral("QLineEdit { background: #1e1e1e; color: #f8f9fa; " + "border: 1px solid #495057; padding: 3px 6px; }")); + + m_runButton = new QPushButton(tr("Run"), this); + m_runButton->setFixedWidth(64); + + commandLayout->addWidget(promptLabel); + commandLayout->addWidget(m_commandInput, 1); + commandLayout->addWidget(m_runButton); + mainLayout->addLayout(commandLayout, 0); } // Append one message and trim old history @@ -189,9 +256,7 @@ namespace scopeone::ui m_messages.append(msg); - m_messageCountLabel->setText(QString("Messages: %1").arg(m_messages.size())); - - if (m_messageFilter.isEmpty() || m_messageFilter.contains(msg.type)) + if (messageMatchesCurrentFilter(msg)) { QString formattedMessage = formatMessage(msg); m_consoleTextEdit->append(formattedMessage); @@ -201,6 +266,14 @@ namespace scopeone::ui scrollToBottom(); } } + if (m_messageFilter.isEmpty() && m_searchKeyword.isEmpty()) + { + m_messageCountLabel->setText(tr("Messages: %1").arg(m_messages.size())); + } + else + { + updateDisplay(); + } } // Clear all stored and visible messages @@ -208,7 +281,7 @@ namespace scopeone::ui { m_messages.clear(); m_consoleTextEdit->clear(); - m_messageCountLabel->setText("Messages: 0"); + m_messageCountLabel->setText(tr("Messages: 0")); } // Toggle timestamp rendering for stored log entries @@ -263,15 +336,22 @@ namespace scopeone::ui QString htmlContent; htmlContent.reserve(m_messages.size() * 100); + int visibleCount = 0; for (const auto& msg : m_messages) { - if (m_messageFilter.isEmpty() || m_messageFilter.contains(msg.type)) + if (messageMatchesCurrentFilter(msg)) { + ++visibleCount; htmlContent += formatMessage(msg); htmlContent += "
"; } } + m_messageCountLabel->setText( + visibleCount == m_messages.size() + ? tr("Messages: %1").arg(m_messages.size()) + : tr("Messages: %1/%2").arg(visibleCount).arg(m_messages.size())); + if (!htmlContent.isEmpty()) { m_consoleTextEdit->setHtml(htmlContent); @@ -312,9 +392,25 @@ namespace scopeone::ui if (type == "ERROR") return "#ff6b6b"; if (type == "WARNING") return "#f59f00"; if (type == "DEBUG") return "#1581ed"; + if (type == "COMMAND") return "#51cf66"; + if (type == "API") return "#66d9ef"; return "#dee2e6"; } + bool ConsoleWidget::messageMatchesCurrentFilter(const ConsoleMessage& msg) const + { + if (!m_messageFilter.isEmpty() && !m_messageFilter.contains(msg.type)) + { + return false; + } + if (m_searchKeyword.isEmpty()) + { + return true; + } + return msg.message.contains(m_searchKeyword, Qt::CaseInsensitive) + || msg.type.contains(m_searchKeyword, Qt::CaseInsensitive); + } + // Scroll the console view to the newest message void ConsoleWidget::scrollToBottom() { @@ -342,7 +438,7 @@ namespace scopeone::ui { QString selectedFilter = m_filterComboBox->currentText(); - if (selectedFilter == "All") + if (selectedFilter == tr("All")) { m_messageFilter.clear(); } @@ -355,6 +451,363 @@ namespace scopeone::ui updateDisplay(); } + void ConsoleWidget::setApiDispatcher(ApiDispatcher dispatcher) + { + m_apiDispatcher = std::move(dispatcher); + } + + // Execute one shorthand command or raw Local API request + void ConsoleWidget::executeCommand(const QString& commandText) + { + const QString command = commandText.trimmed(); + if (command.isEmpty()) + { + return; + } + + m_commandHistory.append(command); + if (m_commandHistory.size() > 100) + { + m_commandHistory.removeFirst(); + } + m_historyIndex = m_commandHistory.size(); + m_commandInput->clear(); + addMessage(QStringLiteral(">>> ") + command, QStringLiteral("COMMAND")); + + const QStringList tokens = command.split( + QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts); + const QString verb = tokens.at(0).toLower(); + + if (verb == QStringLiteral("clear") || verb == QStringLiteral("cls")) + { + clearMessages(); + return; + } + if (verb == QStringLiteral("help") || verb == QStringLiteral("?")) + { + showHelp(); + return; + } + + QJsonObject request; + if (verb == QStringLiteral("snap")) + { + request.insert(QStringLiteral("type"), QStringLiteral("record")); + request.insert(QStringLiteral("frames"), 1); + if (tokens.size() > 1) + { + request.insert(QStringLiteral("camera"), tokens.at(1)); + } + } + else if (verb == QStringLiteral("preview") || verb == QStringLiteral("live")) + { + const QString action = tokens.value(1).toLower(); + if (action != QStringLiteral("start") + && action != QStringLiteral("on") + && action != QStringLiteral("stop") + && action != QStringLiteral("off")) + { + addMessage(tr("Usage: preview start|stop [camera]"), QStringLiteral("ERROR")); + return; + } + request.insert(QStringLiteral("type"), + action == QStringLiteral("stop") || action == QStringLiteral("off") + ? QStringLiteral("stop_preview") + : QStringLiteral("start_preview")); + if (tokens.size() > 2) + { + request.insert(QStringLiteral("camera"), tokens.at(2)); + } + } + else if (verb == QStringLiteral("exp") || verb == QStringLiteral("exposure")) + { + bool ok = false; + const double exposureMs = tokens.value(1).toDouble(&ok); + if (!ok || exposureMs <= 0.0) + { + addMessage(tr("Usage: exp [camera]"), QStringLiteral("ERROR")); + return; + } + request.insert(QStringLiteral("type"), QStringLiteral("set_exposure")); + request.insert(QStringLiteral("exposureMs"), exposureMs); + if (tokens.size() > 2) + { + request.insert(QStringLiteral("camera"), tokens.at(2)); + } + } + else if (verb == QStringLiteral("stage")) + { + const QString axis = tokens.value(1).toLower(); + if (axis == QStringLiteral("z")) + { + bool ok = false; + const double dz = tokens.value(2).toDouble(&ok); + if (!ok) + { + addMessage(tr("Usage: stage z "), QStringLiteral("ERROR")); + return; + } + request.insert(QStringLiteral("type"), QStringLiteral("move_z_relative")); + request.insert(QStringLiteral("dz"), dz); + } + else if (axis == QStringLiteral("xy")) + { + bool okX = false; + bool okY = false; + const double dx = tokens.value(2).toDouble(&okX); + const double dy = tokens.value(3).toDouble(&okY); + if (!okX || !okY) + { + addMessage(tr("Usage: stage xy "), QStringLiteral("ERROR")); + return; + } + request.insert(QStringLiteral("type"), QStringLiteral("move_xy_relative")); + request.insert(QStringLiteral("dx"), dx); + request.insert(QStringLiteral("dy"), dy); + } + else + { + addMessage(tr("Usage: stage z or stage xy "), + QStringLiteral("ERROR")); + return; + } + } + else if (verb == QStringLiteral("roi")) + { + const QString action = tokens.value(1).toLower(); + if (action == QStringLiteral("draw")) + { + request.insert(QStringLiteral("type"), QStringLiteral("draw_roi")); + } + else if (action == QStringLiteral("half")) + { + request.insert(QStringLiteral("type"), QStringLiteral("set_half_roi")); + } + else if (action == QStringLiteral("clear")) + { + request.insert(QStringLiteral("type"), QStringLiteral("clear_roi")); + } + else + { + addMessage(tr("Usage: roi draw|half|clear [camera]"), QStringLiteral("ERROR")); + return; + } + if (tokens.size() > 2) + { + request.insert(QStringLiteral("camera"), tokens.at(2)); + } + } + else if (verb == QStringLiteral("fit")) + { + request.insert(QStringLiteral("type"), QStringLiteral("set_fit_to_window")); + request.insert(QStringLiteral("enabled"), true); + } + else if (verb == QStringLiteral("zoom")) + { + bool ok = false; + const int zoomPercent = tokens.value(1).toInt(&ok); + if (!ok || zoomPercent <= 0) + { + addMessage(tr("Usage: zoom "), QStringLiteral("ERROR")); + return; + } + request.insert(QStringLiteral("type"), QStringLiteral("set_zoom")); + request.insert(QStringLiteral("zoomPercent"), zoomPercent); + } + else if (verb == QStringLiteral("auto")) + { + request.insert(QStringLiteral("type"), QStringLiteral("auto_layer_levels")); + } + else if (verb == QStringLiteral("status")) + { + request.insert(QStringLiteral("type"), QStringLiteral("status")); + } + else if (verb == QStringLiteral("api")) + { + const QString operationType = tokens.value(1); + if (operationType.isEmpty()) + { + addMessage(tr("Usage: api [json_payload]"), + QStringLiteral("ERROR")); + return; + } + const int payloadStart = command.indexOf(operationType) + operationType.size(); + const QString payload = command.mid(payloadStart).trimmed(); + if (!payload.isEmpty()) + { + QJsonParseError parseError; + const QJsonDocument document = QJsonDocument::fromJson( + payload.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError || !document.isObject()) + { + addMessage(tr("Invalid JSON: %1").arg(parseError.errorString()), + QStringLiteral("ERROR")); + return; + } + request = document.object(); + } + request.insert(QStringLiteral("type"), operationType); + } + else + { + addMessage(tr("Unknown command '%1'. Type 'help' for available commands.") + .arg(verb), + QStringLiteral("WARNING")); + return; + } + + m_apiDispatcher(request, + [this](const QJsonObject& response) + { + showCommandResponse(response); + }); + } + + // Print the command grammar in the console + void ConsoleWidget::showHelp() + { + addMessage(QStringLiteral( + "Commands:\n" + " help, ? Show this help\n" + " clear, cls Clear the console\n" + " snap [camera] Capture one frame\n" + " preview start|stop [camera] Control live preview\n" + " exp [camera] Set exposure\n" + " stage z Move focus stage\n" + " stage xy Move XY stage\n" + " roi draw|half|clear [camera] Control ROI\n" + " fit Fit preview to window\n" + " zoom Set preview zoom\n" + " auto Auto-stretch active layer\n" + " status Show application status\n" + " api [json] Send a raw Local API request"), + QStringLiteral("INFO")); + } + + // Show one Local API response in a readable form + void ConsoleWidget::showCommandResponse(const QJsonObject& response) + { + if (!response.value(QStringLiteral("ok")).toBool()) + { + addMessage(response.value(QStringLiteral("error")) + .toString(), + QStringLiteral("ERROR")); + return; + } + + const QString type = response.value(QStringLiteral("type")).toString(); + if (type == QStringLiteral("record")) + { + addMessage(tr("Snapshot captured"), QStringLiteral("INFO")); + return; + } + if (type == QStringLiteral("set_exposure") + && response.contains(QStringLiteral("exposureMs"))) + { + addMessage(tr("Exposure set to %1 ms") + .arg(response.value(QStringLiteral("exposureMs")).toDouble()), + QStringLiteral("INFO")); + return; + } + if (type == QStringLiteral("start_preview")) + { + addMessage(tr("Live preview started"), QStringLiteral("INFO")); + return; + } + if (type == QStringLiteral("stop_preview")) + { + addMessage(tr("Live preview stopped"), QStringLiteral("INFO")); + return; + } + + addMessage(QString::fromUtf8(QJsonDocument(response).toJson(QJsonDocument::Indented)), + QStringLiteral("API")); + } + + // Export the current visible console text + void ConsoleWidget::exportLogsToFile() + { + const QString filePath = QFileDialog::getSaveFileName( + this, tr("Export Console Logs"), QString(), tr("Log files (*.log);;All files (*.*)")); + if (filePath.isEmpty()) + { + return; + } + + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + addMessage(tr("Failed to export logs to %1").arg(filePath), QStringLiteral("ERROR")); + return; + } + QTextStream stream(&file); + stream << m_consoleTextEdit->toPlainText(); + addMessage(tr("Logs exported to %1").arg(filePath), QStringLiteral("INFO")); + } + + // Build the console context menu for copy, export, and clear actions + void ConsoleWidget::showContextMenu(const QPoint& position) + { + QMenu menu(this); + QAction* copySelection = menu.addAction(tr("Copy Selection")); + copySelection->setEnabled(m_consoleTextEdit->textCursor().hasSelection()); + connect(copySelection, &QAction::triggered, + m_consoleTextEdit, &QTextEdit::copy); + + QAction* copyAll = menu.addAction(tr("Copy All")); + connect(copyAll, &QAction::triggered, this, + [this]() + { + QGuiApplication::clipboard()->setText(m_consoleTextEdit->toPlainText()); + }); + + menu.addSeparator(); + QAction* exportAction = menu.addAction(tr("Export Logs to File...")); + connect(exportAction, &QAction::triggered, this, &ConsoleWidget::exportLogsToFile); + + menu.addSeparator(); + QAction* clearAction = menu.addAction(tr("Clear Console (Ctrl+L)")); + connect(clearAction, &QAction::triggered, this, &ConsoleWidget::clearMessages); + + menu.exec(m_consoleTextEdit->viewport()->mapToGlobal(position)); + } + + // Provide terminal-style history navigation in the command input + bool ConsoleWidget::eventFilter(QObject* object, QEvent* event) + { + if (object == m_commandInput && event->type() == QEvent::KeyPress) + { + auto* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Up) + { + if (!m_commandHistory.isEmpty()) + { + m_historyIndex = qMax(0, m_historyIndex - 1); + m_commandInput->setText(m_commandHistory.at(m_historyIndex)); + } + return true; + } + if (keyEvent->key() == Qt::Key_Down) + { + if (!m_commandHistory.isEmpty()) + { + m_historyIndex = qMin(m_commandHistory.size(), m_historyIndex + 1); + m_commandInput->setText( + m_historyIndex == m_commandHistory.size() + ? QString() + : m_commandHistory.at(m_historyIndex)); + } + return true; + } + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) + { + executeCommand(m_commandInput->text()); + return true; + } + } + return QWidget::eventFilter(object, event); + } + // Forward Qt log output into this widget void ConsoleWidget::installAsQtMessageSink(ConsoleWidget* sink) { diff --git a/src/ConsoleWidget.h b/src/ConsoleWidget.h index 6f2996f..54590cc 100644 --- a/src/ConsoleWidget.h +++ b/src/ConsoleWidget.h @@ -10,6 +10,7 @@ class QTextEdit; class QEvent; +class QPoint; class QLineEdit; class QPushButton; class QCheckBox; @@ -69,6 +70,7 @@ namespace scopeone::ui void updateDisplay(); QString formatMessage(const ConsoleMessage& msg) const; QString getTypeColor(const QString& type) const; + bool messageMatchesCurrentFilter(const ConsoleMessage& msg) const; void scrollToBottom(); void executeCommand(const QString& commandText); void showHelp(); diff --git a/src/ImageGalleryWidget.cpp b/src/ImageGalleryWidget.cpp index 54158f7..2613af7 100644 --- a/src/ImageGalleryWidget.cpp +++ b/src/ImageGalleryWidget.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -310,26 +310,16 @@ namespace scopeone::ui "QListWidget::item:selected { background: #31485d; }")); layout->addWidget(m_sessionList, 1); - auto* buttonLayout = new QGridLayout(); - buttonLayout->setHorizontalSpacing(6); - buttonLayout->setVerticalSpacing(6); - m_openButton = new QPushButton(QStringLiteral("Preview"), this); + auto* buttonLayout = new QHBoxLayout(); + buttonLayout->setSpacing(6); m_deleteButton = new QPushButton(QStringLiteral("Delete"), this); m_saveCheckedButton = new QPushButton(QStringLiteral("Save Checked"), this); - m_liveButton = new QPushButton(QStringLiteral("Live"), this); - m_openButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); m_saveCheckedButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); m_deleteButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - m_liveButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - m_openButton->setToolTip(QStringLiteral("Open the selected image preview")); m_deleteButton->setToolTip(QStringLiteral("Delete the selected gallery session")); m_saveCheckedButton->setToolTip(QStringLiteral("Save all checked unsaved sessions")); - m_liveButton->setToolTip(QStringLiteral("Return to the live preview")); - buttonLayout->addWidget(m_openButton, 0, 0); - buttonLayout->addWidget(m_deleteButton, 0, 1); - buttonLayout->addWidget(m_saveCheckedButton, 1, 0); - buttonLayout->addWidget(m_liveButton, 1, 1); - buttonLayout->setColumnStretch(0, 1); + buttonLayout->addWidget(m_saveCheckedButton, 1); + buttonLayout->addWidget(m_deleteButton); layout->addLayout(buttonLayout); connect(m_sessionList, &QListWidget::currentItemChanged, this, @@ -342,8 +332,6 @@ namespace scopeone::ui connect(m_sessionList, &QListWidget::itemChanged, this, [this](QListWidgetItem*) { updateButtons(); }); connect(m_sessionList, &QListWidget::customContextMenuRequested, this, &ImageGalleryWidget::showContextMenu); - connect(m_liveButton, &QPushButton::clicked, this, &ImageGalleryWidget::livePreviewRequested); - connect(m_openButton, &QPushButton::clicked, this, &ImageGalleryWidget::openCurrentSession); connect(m_saveCheckedButton, &QPushButton::clicked, this, &ImageGalleryWidget::saveCheckedSessions); connect(m_deleteButton, &QPushButton::clicked, this, &ImageGalleryWidget::deleteCurrentSession); @@ -363,7 +351,6 @@ namespace scopeone::ui { const auto session = currentSession(); const bool hasCurrent = session != nullptr; - m_openButton->setEnabled(canPreviewSession(session)); m_deleteButton->setEnabled(hasCurrent); m_saveCheckedButton->setEnabled(!checkedSessions().isEmpty()); } diff --git a/src/ImageGalleryWidget.h b/src/ImageGalleryWidget.h index 83ca3e9..7d4c0c7 100644 --- a/src/ImageGalleryWidget.h +++ b/src/ImageGalleryWidget.h @@ -27,7 +27,6 @@ namespace scopeone::ui QList> unsavedSessions() const; signals: - void livePreviewRequested(); void sessionOpenRequested(const std::shared_ptr& session); void sessionRemoved(const std::shared_ptr& session); void saveSessionAsRequested( @@ -54,8 +53,6 @@ namespace scopeone::ui scopeone::core::ScopeOneCore* m_core{nullptr}; QListWidget* m_sessionList{nullptr}; QLabel* m_emptyLabel{nullptr}; - QPushButton* m_liveButton{nullptr}; - QPushButton* m_openButton{nullptr}; QPushButton* m_saveCheckedButton{nullptr}; QPushButton* m_deleteButton{nullptr}; int m_nextSnapshotTitleIndex{1}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index c091bbd..7d645df 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include #include @@ -169,7 +171,87 @@ namespace scopeone::ui setupUI(); m_imageWorkspace->setLiveViewer(m_previewWidget); setupSignalWiring(); - new ScopeOneLocalApiServer(m_scopeonecore, m_previewWidget, m_imageWorkspace, this); + m_localApiServer = new ScopeOneLocalApiServer( + m_scopeonecore, m_previewWidget, m_imageWorkspace, this); + m_consoleWidget->setApiDispatcher( + [this](const QJsonObject& request, + std::function callback) + { + QJsonObject routedRequest = request; + const QString type = routedRequest.value(QStringLiteral("type")).toString(); + + if (type == QStringLiteral("set_fit_to_window")) + { + PreviewWidget* preview = m_imageWorkspace->activePreviewWidget(); + preview->setFitToWindow( + routedRequest.value(QStringLiteral("enabled")).toBool()); + QJsonObject response; + response.insert(QStringLiteral("type"), type); + response.insert(QStringLiteral("ok"), true); + response.insert(QStringLiteral("enabled"), + preview->isFitToWindow()); + callback(response); + return; + } + + if (type == QStringLiteral("set_zoom")) + { + PreviewWidget* preview = m_imageWorkspace->activePreviewWidget(); + preview->setFitToWindow(false); + preview->setZoomPercent( + routedRequest.value(QStringLiteral("zoomPercent")).toInt()); + QJsonObject response; + response.insert(QStringLiteral("type"), type); + response.insert(QStringLiteral("ok"), true); + response.insert(QStringLiteral("zoomPercent"), + preview->zoomPercent()); + callback(response); + return; + } + + if (type == QStringLiteral("draw_roi") + || type == QStringLiteral("set_half_roi") + || type == QStringLiteral("clear_roi")) + { + QString camera = routedRequest.value(QStringLiteral("camera")).toString(); + if (camera.isEmpty() + || camera.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) + { + camera = m_currentControlTarget; + if (camera.compare(QStringLiteral("All"), Qt::CaseInsensitive) == 0) + { + camera = scopeone::core::ScopeOneCore::sourceIdFromLayerKey( + m_imageWorkspace->activeLayerKey()); + } + routedRequest.insert(QStringLiteral("camera"), camera); + } + } + + if (type == QStringLiteral("draw_roi")) + { + m_imageWorkspace->activePreviewWidget()->startROIDrawing( + routedRequest.value(QStringLiteral("camera")).toString()); + QJsonObject response; + response.insert(QStringLiteral("type"), type); + response.insert(QStringLiteral("ok"), true); + callback(response); + return; + } + + if (type == QStringLiteral("auto_layer_levels") + && !routedRequest.contains(QStringLiteral("layerKey"))) + { + routedRequest.insert(QStringLiteral("layerKey"), + m_imageWorkspace->activeLayerKey()); + } + + m_localApiServer->dispatchRequest( + routedRequest, + [callback = std::move(callback)](QJsonObject response) + { + callback(response); + }); + }); logStartupSummary(); setWindowTitle("ScopeOne"); @@ -669,8 +751,6 @@ namespace scopeone::ui } m_imageWorkspace->openSession(session); }); - connect(m_imageGalleryWidget, &ImageGalleryWidget::livePreviewRequested, - this, &MainWindow::showLivePreview); connect(m_imageGalleryWidget, &ImageGalleryWidget::sessionRemoved, this, [this](const std::shared_ptr& session) diff --git a/src/MainWindow.h b/src/MainWindow.h index 513a9ac..7606f44 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -36,6 +36,7 @@ namespace scopeone::ui class ConsoleWidget; class DeviceControlWidget; class RecordingWidget; + class ScopeOneLocalApiServer; class MainWindow : public QMainWindow, public ScopeOneToolContext { Q_OBJECT @@ -124,6 +125,7 @@ namespace scopeone::ui scopeone::core::ImageSceneModel* m_imageSceneModel{nullptr}; PreviewWidget* m_previewWidget{nullptr}; ConsoleWidget* m_consoleWidget{nullptr}; + ScopeOneLocalApiServer* m_localApiServer{nullptr}; QDockWidget* m_propertyDockWidget{nullptr}; QDockWidget* m_configPresetDockWidget{nullptr}; From 0a4ba3560f50f3f54c5a724bab5e248327892bdc Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:46:00 +0200 Subject: [PATCH 19/37] Unify processing pipeline and histogram controls --- .../include/scopeone/ProcessingPlugin.h | 6 + ScopeOneCore/include/scopeone/ScopeOneCore.h | 5 + .../internal/ImageProcessingFramework.h | 1 + ScopeOneCore/src/ImageProcessingFramework.cpp | 27 +- ScopeOneCore/src/ScopeOneCore.cpp | 43 +++ src/DeviceControlWidget.cpp | 31 +- src/ImageGalleryWidget.cpp | 8 +- src/ImageProcessingWidget.cpp | 266 ++++++++++++------ src/ImageProcessingWidget.h | 24 +- src/MainWindow.cpp | 1 + src/ScopeOneLocalApiServer.cpp | 1 + 11 files changed, 299 insertions(+), 114 deletions(-) diff --git a/ScopeOneCore/include/scopeone/ProcessingPlugin.h b/ScopeOneCore/include/scopeone/ProcessingPlugin.h index 9516c86..1aa1a41 100644 --- a/ScopeOneCore/include/scopeone/ProcessingPlugin.h +++ b/ScopeOneCore/include/scopeone/ProcessingPlugin.h @@ -119,6 +119,9 @@ namespace scopeone::core 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; @@ -136,6 +139,9 @@ namespace scopeone::core } return process(std::get(input), processingBitDepth); } + + private: + bool m_enabled{true}; }; class ProcessingPlugin diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 7f74559..5b28378 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -456,16 +456,19 @@ namespace scopeone::core const QString& name() const { return m_name; } const QVariantMap& parameters() const { return m_parameters; } 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: QString m_id; QString m_name; QVariantMap m_parameters; ProcessingModuleDescriptor m_descriptor; + bool m_enabled{true}; }; class DevicePropertyInfo @@ -665,6 +668,8 @@ namespace scopeone::core QList processingModules() const; 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, diff --git a/ScopeOneCore/internal/ImageProcessingFramework.h b/ScopeOneCore/internal/ImageProcessingFramework.h index 76f8b56..da9f1ec 100644 --- a/ScopeOneCore/internal/ImageProcessingFramework.h +++ b/ScopeOneCore/internal/ImageProcessingFramework.h @@ -36,6 +36,7 @@ namespace scopeone::core::internal public: void addModule(std::unique_ptr module); bool removeModule(int index); + bool moveModule(int from, int to); std::shared_ptr createRuntime() const; void forEachModule(const std::function& visitor) const; bool withModule(int index, const std::function& visitor); diff --git a/ScopeOneCore/src/ImageProcessingFramework.cpp b/ScopeOneCore/src/ImageProcessingFramework.cpp index b51dddb..afaebbe 100644 --- a/ScopeOneCore/src/ImageProcessingFramework.cpp +++ b/ScopeOneCore/src/ImageProcessingFramework.cpp @@ -86,6 +86,10 @@ namespace scopeone::core::internal for (int moduleIndex = startIndex; moduleIndex < endIndex; ++moduleIndex) { ProcessingModule* module = m_modules[static_cast(moduleIndex)].get(); + if (!module->isEnabled()) + { + continue; + } ProcessingResult result = module->processValue(currentValue, processingBitDepth); if (!result.succeeded()) { @@ -141,6 +145,25 @@ namespace scopeone::core::internal return false; } + // Moves one configured module to a new pipeline position + bool ProcessingPipelineDefinition::moveModule(int from, int to) + { + QMutexLocker locker(&m_modulesMutex); + if (from < 0 || from >= static_cast(m_modules.size()) + || to < 0 || to >= static_cast(m_modules.size())) + { + return false; + } + if (from == to) + { + return true; + } + auto module = std::move(m_modules[static_cast(from)]); + m_modules.erase(m_modules.begin() + from); + m_modules.insert(m_modules.begin() + to, std::move(module)); + return true; + } + // Creates an independent runtime from the current definition std::shared_ptr ProcessingPipelineDefinition::createRuntime() const { @@ -149,7 +172,9 @@ namespace scopeone::core::internal modules.reserve(m_modules.size()); for (const auto& module : m_modules) { - modules.push_back(module->createRuntime()); + auto runtimeModule = module->createRuntime(); + runtimeModule->setEnabled(module->isEnabled()); + modules.push_back(std::move(runtimeModule)); } return std::make_shared(std::move(modules)); } diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index ee06d46..33be624 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -3947,6 +3947,7 @@ namespace scopeone::core info.setName(module->name()); info.setParameters(module->parameters()); info.setDescriptor(m_managers->processingModuleRegistry->descriptor(module->id())); + info.setEnabled(module->isEnabled()); out.append(std::move(info)); }); return out; @@ -3991,6 +3992,48 @@ namespace scopeone::core return true; } + // Move one editable processing module and rebuild runtime pipelines + bool ScopeOneCore::moveProcessingModule(int from, int to) + { + if (isRealTimeProcessingEnabled()) + { + return false; + } + ProcessingPipelineDefinition& definition = m_managers->imageProcessingManager->definition(); + if (!definition.moveModule(from, to)) + { + return false; + } + m_managers->imageProcessingManager->clearRuntimePipelines(); + emit processingModulesChanged(); + return true; + } + + // Enable or bypass one editable processing module + bool ScopeOneCore::setProcessingModuleEnabled(int index, bool enabled) + { + if (isRealTimeProcessingEnabled()) + { + return false; + } + ProcessingPipelineDefinition& definition = m_managers->imageProcessingManager->definition(); + bool updated = false; + if (!definition.withModule(index, [enabled, &updated](ProcessingModule* module) + { + updated = module->isEnabled() != enabled; + module->setEnabled(enabled); + })) + { + return false; + } + if (updated) + { + m_managers->imageProcessingManager->clearRuntimePipelines(); + emit processingModulesChanged(); + } + return true; + } + // Update module parameters and rebuild per camera runtime modules bool ScopeOneCore::setProcessingModuleParameters(int index, const QVariantMap& parameters) { diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index a8143fa..0cd44a1 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -616,7 +616,21 @@ namespace scopeone::ui histogramLayout->setContentsMargins(6, 6, 6, 6); m_layerHistogramWidget = new LayerHistogramWidget(m_layerHistogramGroup); histogramLayout->addWidget(m_layerHistogramWidget); + auto* histogramLevelsLayout = new QHBoxLayout; + m_layerAutoButton = new QPushButton(QStringLiteral("Auto"), m_layerHistogramGroup); + m_layerAutoButton->setToolTip(QStringLiteral("Set display levels from the current image")); + m_layerFullRangeButton = new QPushButton(QStringLiteral("Full Range"), m_layerHistogramGroup); + m_layerFullRangeButton->setToolTip(QStringLiteral("Show the complete intensity range")); + histogramLevelsLayout->addWidget(m_layerAutoButton); + histogramLevelsLayout->addWidget(m_layerFullRangeButton); + histogramLayout->addLayout(histogramLevelsLayout); + + m_layerAutoStretchCheckBox = new QCheckBox(QStringLiteral("Continuous Auto"), m_layerHistogramGroup); + m_layerAutoStretchCheckBox->setToolTip( + QStringLiteral("Update display levels continuously as images arrive")); + m_layerHistogramLogCheckBox = new QCheckBox(QStringLiteral("Log scale"), m_layerHistogramGroup); + histogramLayout->addWidget(m_layerAutoStretchCheckBox); histogramLayout->addWidget(m_layerHistogramLogCheckBox); connect(m_layerHistogramLogCheckBox, &QCheckBox::toggled, m_layerHistogramWidget, &LayerHistogramWidget::setLogScale); @@ -650,13 +664,6 @@ namespace scopeone::ui m_layerColormapComboBox = new QComboBox(m_layerSettingsGroup); m_layerBlendingComboBox = new QComboBox(m_layerSettingsGroup); - m_layerAutoButton = new QPushButton(QStringLiteral("Auto"), m_layerSettingsGroup); - m_layerAutoButton->setToolTip(QStringLiteral("Set display levels from the current image")); - m_layerFullRangeButton = new QPushButton(QStringLiteral("Full Range"), m_layerSettingsGroup); - m_layerFullRangeButton->setToolTip(QStringLiteral("Show the complete intensity range")); - m_layerAutoStretchCheckBox = new QCheckBox(QStringLiteral("Continuous Auto"), m_layerSettingsGroup); - m_layerAutoStretchCheckBox->setToolTip( - QStringLiteral("Update display levels continuously as images arrive")); m_clippingCheckBox = new QCheckBox(QStringLiteral("Hi-Lo Warn"), m_layerSettingsGroup); m_clippingCheckBox->setToolTip(QStringLiteral("Highlight saturated pixels in red and zero pixels in blue (Hotkey: C)")); m_scaleBarCheckBox = new QCheckBox(QStringLiteral("Scale Bar"), m_layerSettingsGroup); @@ -676,11 +683,8 @@ namespace scopeone::ui layerSettingsLayout->addWidget(m_layerColormapComboBox, 3, 1); layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Blend:"), m_layerSettingsGroup), 3, 2); layerSettingsLayout->addWidget(m_layerBlendingComboBox, 3, 3, 1, 3); - layerSettingsLayout->addWidget(m_layerAutoButton, 4, 0, 1, 2); - layerSettingsLayout->addWidget(m_layerFullRangeButton, 4, 2, 1, 2); - layerSettingsLayout->addWidget(m_layerAutoStretchCheckBox, 4, 4, 1, 2); - layerSettingsLayout->addWidget(m_clippingCheckBox, 5, 0, 1, 3); - layerSettingsLayout->addWidget(m_scaleBarCheckBox, 5, 3, 1, 3); + layerSettingsLayout->addWidget(m_clippingCheckBox, 4, 0, 1, 3); + layerSettingsLayout->addWidget(m_scaleBarCheckBox, 4, 3, 1, 3); auto* transformGroup = new QGroupBox(QStringLiteral("Transform"), this); auto* transformLayout = new QGridLayout(transformGroup); @@ -929,6 +933,9 @@ namespace scopeone::ui const QString layerKey = currentLayerKey(); const bool hasLayer = !layerKey.isEmpty() && m_layerRows.contains(layerKey); m_layerSettingsGroup->setEnabled(hasLayer); + m_layerAutoButton->setEnabled(hasLayer); + m_layerFullRangeButton->setEnabled(hasLayer); + m_layerAutoStretchCheckBox->setEnabled(hasLayer); if (!hasLayer) { m_selectedLayerLabel->setText(QStringLiteral("No layer selected")); diff --git a/src/ImageGalleryWidget.cpp b/src/ImageGalleryWidget.cpp index 2613af7..40ee203 100644 --- a/src/ImageGalleryWidget.cpp +++ b/src/ImageGalleryWidget.cpp @@ -295,7 +295,7 @@ namespace scopeone::ui m_emptyLabel = new QLabel(QStringLiteral("No captured images"), this); m_emptyLabel->setAlignment(Qt::AlignCenter); - layout->addWidget(m_emptyLabel); + layout->addWidget(m_emptyLabel, 1); m_sessionList = new QListWidget(this); m_sessionList->setSelectionMode(QAbstractItemView::SingleSelection); @@ -314,13 +314,13 @@ namespace scopeone::ui buttonLayout->setSpacing(6); m_deleteButton = new QPushButton(QStringLiteral("Delete"), this); m_saveCheckedButton = new QPushButton(QStringLiteral("Save Checked"), this); - m_saveCheckedButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - m_deleteButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + m_saveCheckedButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + m_deleteButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); m_deleteButton->setToolTip(QStringLiteral("Delete the selected gallery session")); m_saveCheckedButton->setToolTip(QStringLiteral("Save all checked unsaved sessions")); buttonLayout->addWidget(m_saveCheckedButton, 1); buttonLayout->addWidget(m_deleteButton); - layout->addLayout(buttonLayout); + layout->addLayout(buttonLayout, 0); connect(m_sessionList, &QListWidget::currentItemChanged, this, [this](QListWidgetItem*, QListWidgetItem*) diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index b3f4eaa..a9232d9 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -17,15 +17,17 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include +#include #include -#include #include #include #include @@ -548,75 +550,106 @@ namespace scopeone::ui content->setBackgroundRole(QPalette::Window); auto* contentLayout = new QVBoxLayout(content); contentLayout->setContentsMargins(5, 5, 5, 5); - auto* splitter = new QSplitter(Qt::Vertical, content); - splitter->setAutoFillBackground(true); - splitter->setBackgroundRole(QPalette::Window); QWidget* pipelineGroup = setupModuleList(); QWidget* parametersGroup = setupModuleConfig(); - setupRunControls(); - setupSourceControls(); - splitter->addWidget(pipelineGroup); - splitter->addWidget(parametersGroup); - splitter->setStretchFactor(0, 2); - splitter->setStretchFactor(1, 3); - contentLayout->addWidget(splitter, 1); - contentLayout->addWidget(m_runControlsWidget); - contentLayout->addWidget(m_sourceControlsWidget); + setupExecutionControls(); + setupInputControls(); + contentLayout->addWidget(m_inputControlsWidget); + contentLayout->addWidget(pipelineGroup); + contentLayout->addWidget(parametersGroup, 1); + contentLayout->addWidget(m_executionControlsWidget); scrollArea->setWidget(content); mainLayout->addWidget(scrollArea); } - void ImageProcessingWidget::setupSourceControls() + void ImageProcessingWidget::setupInputControls() { - m_sourceControlsWidget = new QGroupBox(tr("Single Run"), this); - auto* layout = new QGridLayout(m_sourceControlsWidget); - layout->addWidget(new QLabel(tr("Source"), m_sourceControlsWidget), 0, 0); - m_sourceCombo = new QComboBox(m_sourceControlsWidget); - layout->addWidget(m_sourceCombo, 0, 1, 1, 2); - layout->addWidget(new QLabel(tr("Range"), m_sourceControlsWidget), 1, 0); - m_offlineScopeCombo = new QComboBox(m_sourceControlsWidget); + m_inputControlsWidget = new QGroupBox(tr("Pipeline Input"), this); + auto* layout = new QGridLayout(m_inputControlsWidget); + m_liveModeRadio = new QRadioButton(tr("Live Stream"), m_inputControlsWidget); + m_staticModeRadio = new QRadioButton(tr("Image / Stack"), m_inputControlsWidget); + m_liveModeRadio->setChecked(true); + layout->addWidget(m_liveModeRadio, 0, 0); + layout->addWidget(m_staticModeRadio, 0, 1); + m_sourceLabel = new QLabel(m_inputControlsWidget); + layout->addWidget(m_sourceLabel, 1, 0); + m_liveSourceCombo = new QComboBox(m_inputControlsWidget); + m_liveSourceCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + layout->addWidget(m_liveSourceCombo, 1, 1, 1, 2); + m_sourceCombo = new QComboBox(m_inputControlsWidget); + m_sourceCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + layout->addWidget(m_sourceCombo, 1, 1, 1, 2); + m_rangeLabel = new QLabel(tr("Range"), m_inputControlsWidget); + layout->addWidget(m_rangeLabel, 2, 0); + m_offlineScopeCombo = new QComboBox(m_inputControlsWidget); m_offlineScopeCombo->addItem(tr("Current frame"), false); m_offlineScopeCombo->addItem(tr("Entire stack"), true); - layout->addWidget(m_offlineScopeCombo, 1, 1, 1, 2); - m_runOfflineButton = new QPushButton(tr("Run"), m_sourceControlsWidget); - m_cancelProcessingButton = new QPushButton(tr("Cancel"), m_sourceControlsWidget); - m_cancelProcessingButton->setEnabled(false); - connect(m_runOfflineButton, &QPushButton::clicked, - this, &ImageProcessingWidget::onRunOfflineProcessing); - connect(m_cancelProcessingButton, &QPushButton::clicked, - this, &ImageProcessingWidget::onCancelProcessing); + layout->addWidget(m_offlineScopeCombo, 2, 1, 1, 2); + layout->setColumnStretch(2, 1); + connect(m_liveModeRadio, &QRadioButton::toggled, + this, &ImageProcessingWidget::onInputModeChanged); + connect(m_staticModeRadio, &QRadioButton::toggled, + this, &ImageProcessingWidget::onInputModeChanged); + connect(m_liveSourceCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, [this]() + { + m_scopeonecore->setRealTimeProcessingSource( + m_liveSourceCombo->currentData().toString()); + updateRunButtons(); + }); connect(m_sourceCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { updateRunButtons(); }); connect(m_offlineScopeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { updateRunButtons(); }); - layout->addWidget(m_runOfflineButton, 2, 1); - layout->addWidget(m_cancelProcessingButton, 2, 2); - m_processingProgress = new QProgressBar(m_sourceControlsWidget); - m_processingProgress->setVisible(false); - layout->addWidget(m_processingProgress, 3, 0, 1, 3); + onInputModeChanged(); } - void ImageProcessingWidget::setupRunControls() + void ImageProcessingWidget::setupExecutionControls() { - m_runControlsWidget = new QGroupBox(tr("Real-time Processing"), this); - auto* layout = new QGridLayout(m_runControlsWidget); - m_liveProcessingCheckBox = new QCheckBox(tr("Enabled"), m_runControlsWidget); - m_liveSourceCombo = new QComboBox(m_runControlsWidget); - connect(m_liveProcessingCheckBox, &QCheckBox::toggled, - this, &ImageProcessingWidget::onLiveProcessingToggled); - connect(m_liveSourceCombo, QOverload::of(&QComboBox::currentIndexChanged), + m_executionControlsWidget = new QGroupBox(tr("Pipeline Output & Execution"), this); + auto* layout = new QGridLayout(m_executionControlsWidget); + layout->addWidget(new QLabel(tr("Bit depth"), m_executionControlsWidget), 0, 0); + m_processingBitDepthCombo = new QComboBox(m_executionControlsWidget); + m_processingBitDepthCombo->addItem( + tr("8-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit8)); + m_processingBitDepthCombo->addItem( + tr("16-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit16)); + connect(m_processingBitDepthCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &ImageProcessingWidget::onProcessingBitDepthChanged); + layout->addWidget(m_processingBitDepthCombo, 0, 1); + m_executeButton = new QPushButton(m_executionControlsWidget); + m_cancelProcessingButton = new QPushButton(tr("Cancel"), m_executionControlsWidget); + m_cancelProcessingButton->setEnabled(false); + connect(m_executeButton, &QPushButton::clicked, this, [this]() { - if (!m_scopeonecore->setRealTimeProcessingSource( - m_liveSourceCombo->currentData().toString())) + if (m_liveModeRadio->isChecked()) { - updateProcessingSettings(); + onLiveProcessingToggled(!m_scopeonecore->isRealTimeProcessingEnabled()); + } + else + { + onRunOfflineProcessing(); } }); - layout->addWidget(m_liveProcessingCheckBox, 0, 0); - layout->addWidget(new QLabel(tr("Source"), m_runControlsWidget), 0, 1); - layout->addWidget(m_liveSourceCombo, 0, 2); - layout->setColumnStretch(2, 1); + connect(m_cancelProcessingButton, &QPushButton::clicked, + this, &ImageProcessingWidget::onCancelProcessing); + layout->addWidget(m_executeButton, 1, 0, 1, 2); + layout->addWidget(m_cancelProcessingButton, 1, 2); + m_processingProgress = new QProgressBar(m_executionControlsWidget); + m_processingProgress->setVisible(false); + layout->addWidget(m_processingProgress, 2, 0, 1, 3); + } + + void ImageProcessingWidget::onInputModeChanged() + { + const bool liveMode = m_liveModeRadio->isChecked(); + m_sourceLabel->setText(liveMode ? tr("Target") : tr("Source")); + m_liveSourceCombo->setVisible(liveMode); + m_sourceCombo->setVisible(!liveMode); + m_rangeLabel->setVisible(!liveMode); + m_offlineScopeCombo->setVisible(!liveMode); + updateRunButtons(); } QWidget* ImageProcessingWidget::setupModuleList() @@ -626,6 +659,8 @@ namespace scopeone::ui m_moduleList = new QListWidget(group); connect(m_moduleList, &QListWidget::currentRowChanged, this, &ImageProcessingWidget::onModuleSelectionChanged); + connect(m_moduleList, &QListWidget::itemChanged, + this, &ImageProcessingWidget::onModuleItemChanged); layout->addWidget(m_moduleList); auto* controls = new QHBoxLayout; m_moduleTypeCombo = new QComboBox(group); @@ -636,22 +671,19 @@ namespace scopeone::ui controls->addWidget(m_moduleTypeCombo); m_addModuleButton = new QPushButton(tr("Add"), group); m_removeModuleButton = new QPushButton(tr("Remove"), group); + m_moveModuleUpButton = new QPushButton(tr("Up"), group); + m_moveModuleDownButton = new QPushButton(tr("Down"), group); connect(m_addModuleButton, &QPushButton::clicked, this, &ImageProcessingWidget::onAddModuleClicked); connect(m_removeModuleButton, &QPushButton::clicked, this, &ImageProcessingWidget::onRemoveModuleClicked); + connect(m_moveModuleUpButton, &QPushButton::clicked, + this, &ImageProcessingWidget::onMoveModuleUpClicked); + connect(m_moveModuleDownButton, &QPushButton::clicked, + this, &ImageProcessingWidget::onMoveModuleDownClicked); controls->addWidget(m_addModuleButton); + controls->addWidget(m_moveModuleUpButton); + controls->addWidget(m_moveModuleDownButton); controls->addWidget(m_removeModuleButton); layout->addLayout(controls); - auto* outputLayout = new QHBoxLayout; - outputLayout->addWidget(new QLabel(tr("Bit depth"), group)); - m_processingBitDepthCombo = new QComboBox(group); - m_processingBitDepthCombo->addItem( - tr("8-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit8)); - m_processingBitDepthCombo->addItem( - tr("16-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit16)); - connect(m_processingBitDepthCombo, QOverload::of(&QComboBox::currentIndexChanged), - this, &ImageProcessingWidget::onProcessingBitDepthChanged); - outputLayout->addWidget(m_processingBitDepthCombo, 1); - layout->addLayout(outputLayout); return group; } @@ -675,14 +707,25 @@ namespace scopeone::ui const auto modules = m_scopeonecore->processingModules(); const QSignalBlocker blocker(m_moduleList); m_moduleList->clear(); - for (const ProcessingModuleInfo& module : modules) + for (int index = 0; index < modules.size(); ++index) { - m_moduleList->addItem(module.name()); + const ProcessingModuleInfo& module = modules.at(index); + auto* item = new QListWidgetItem( + tr("%1. %2").arg(index + 1).arg(module.name()), m_moduleList); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(module.enabled() ? Qt::Checked : Qt::Unchecked); + item->setToolTip(module.enabled() + ? tr("Enabled") + : tr("Bypassed")); } if (!modules.isEmpty()) { m_moduleList->setCurrentRow(qBound(0, currentRow, modules.size() - 1)); } + else + { + m_moduleList->clearSelection(); + } } void ImageProcessingWidget::updateConfigWidget() @@ -710,28 +753,26 @@ namespace scopeone::ui const bool running = m_scopeonecore->isRealTimeProcessingEnabled(); const bool hasModules = !m_scopeonecore->processingModules().isEmpty(); const bool idle = m_offlineProcessingRequestId == 0; - { - const QSignalBlocker blocker(m_liveProcessingCheckBox); - m_liveProcessingCheckBox->setChecked(running); - } - m_liveProcessingCheckBox->setEnabled(idle && (running || hasModules)); - m_liveProcessingCheckBox->setToolTip( - !idle ? tr("A single run is in progress") - : !running && !hasModules ? tr("Add at least one pipeline step first") - : QString{}); m_processingBitDepthCombo->setEnabled(!running && idle); - m_liveSourceCombo->setEnabled(!running && idle); - m_moduleList->parentWidget()->setEnabled(!running && idle); - m_configStack->parentWidget()->setEnabled(!running && idle); + m_moduleList->setEnabled(!running && idle); + m_moduleTypeCombo->setEnabled(!running && idle); + m_addModuleButton->setEnabled(!running && idle); + m_removeModuleButton->setEnabled(!running && idle && m_moduleList->currentRow() >= 0); + m_moveModuleUpButton->setEnabled(!running && idle && m_moduleList->currentRow() > 0); + m_moveModuleDownButton->setEnabled(!running && idle + && m_moduleList->currentRow() >= 0 + && m_moduleList->currentRow() < m_moduleList->count() - 1); + m_configStack->setEnabled(!running && idle); + + const bool liveMode = m_liveModeRadio->isChecked(); const QString sourceType = m_sourceCombo->currentData(Qt::UserRole).toString(); - const bool hasPipeline = !m_scopeonecore->processingModules().isEmpty(); + const bool hasPipeline = hasModules; const QString sourceId = m_sourceCombo->currentData(Qt::UserRole + 1).toString(); const bool hasSource = !sourceType.isEmpty(); const bool sourceAvailable = hasSource && (sourceType == QStringLiteral("layer") || sourceType == QStringLiteral("stack") || m_workspace->document(sourceId).ready); - m_sourceCombo->setEnabled(!running && idle); const bool canProcessImage = sourceType == QStringLiteral("layer") || sourceType == QStringLiteral("document") || sourceType == QStringLiteral("document_stack"); @@ -743,36 +784,48 @@ namespace scopeone::ui m_offlineScopeCombo->setCurrentIndex(0); } const bool entireStack = m_offlineScopeCombo->currentData().toBool(); - m_offlineScopeCombo->setEnabled(!running && idle && canProcessStack); const bool validScope = entireStack ? canProcessStack : canProcessImage; const bool canRun = !running && idle && hasPipeline && sourceAvailable && validScope; - m_runOfflineButton->setEnabled(canRun); + m_liveSourceCombo->setEnabled(!running && idle && liveMode); + m_sourceCombo->setEnabled(!running && idle && !liveMode); + m_offlineScopeCombo->setEnabled(!running && idle && !liveMode && canProcessStack); + m_executeButton->setText(liveMode + ? running ? tr("Stop Live Pipeline") + : tr("Start Live Pipeline") + : tr("Run Processing")); + m_executeButton->setEnabled(liveMode + ? idle && (running || hasModules) + : canRun); QString runDisabledReason; - if (running) + if (!idle) + { + runDisabledReason = tr("A processing run is already in progress"); + } + else if (liveMode && !running && !hasModules) { - runDisabledReason = tr("Disable real-time processing before starting a single run"); + runDisabledReason = tr("Add at least one pipeline step first"); } - else if (!idle) + else if (!liveMode && running) { - runDisabledReason = tr("A single run is already in progress"); + runDisabledReason = tr("Stop live processing before running a static image"); } - else if (!hasPipeline) + else if (!liveMode && !hasPipeline) { runDisabledReason = tr("Add at least one pipeline step first"); } - else if (!hasSource) + else if (!liveMode && !hasSource) { runDisabledReason = tr("Select a source to process"); } - else if (!sourceAvailable) + else if (!liveMode && !sourceAvailable) { runDisabledReason = tr("The selected source is not available"); } - else if (!validScope) + else if (!liveMode && !validScope) { runDisabledReason = tr("The selected source does not support this range"); } - m_runOfflineButton->setToolTip(runDisabledReason); + m_executeButton->setToolTip(runDisabledReason); m_cancelProcessingButton->setEnabled(!idle); } @@ -953,13 +1006,44 @@ namespace scopeone::ui QMessageBox::warning(this, tr("Warning"), tr("Failed to remove processing module")); return; } - updateModuleList(); - updateConfigWidget(); } void ImageProcessingWidget::onModuleSelectionChanged() { updateConfigWidget(); + updateRunButtons(); + } + + void ImageProcessingWidget::onModuleItemChanged(QListWidgetItem* item) + { + const int row = m_moduleList->row(item); + m_scopeonecore->setProcessingModuleEnabled(row, item->checkState() == Qt::Checked); + } + + void ImageProcessingWidget::onMoveModuleUpClicked() + { + const int row = m_moduleList->currentRow(); + if (row <= 0) + { + return; + } + if (m_scopeonecore->moveProcessingModule(row, row - 1)) + { + m_moduleList->setCurrentRow(row - 1); + } + } + + void ImageProcessingWidget::onMoveModuleDownClicked() + { + const int row = m_moduleList->currentRow(); + if (row < 0 || row >= m_moduleList->count() - 1) + { + return; + } + if (m_scopeonecore->moveProcessingModule(row, row + 1)) + { + m_moduleList->setCurrentRow(row + 1); + } } void ImageProcessingWidget::onProcessingBitDepthChanged() @@ -976,8 +1060,8 @@ namespace scopeone::ui { if (!m_scopeonecore->setRealTimeProcessingEnabled(enabled)) { - QMessageBox::information( - this, tr("Information"), tr("Please add a processing module first")); + updateRunButtons(); + return; } updateRunButtons(); qInfo().noquote() << (m_scopeonecore->isRealTimeProcessingEnabled() diff --git a/src/ImageProcessingWidget.h b/src/ImageProcessingWidget.h index 87841f3..e903ac8 100644 --- a/src/ImageProcessingWidget.h +++ b/src/ImageProcessingWidget.h @@ -6,10 +6,13 @@ #include class QListWidget; +class QListWidgetItem; class QCheckBox; class QComboBox; +class QLabel; class QPushButton; class QProgressBar; +class QRadioButton; class QStackedWidget; namespace scopeone::ui @@ -36,15 +39,19 @@ namespace scopeone::ui private: void onAddModuleClicked(); void onRemoveModuleClicked(); + void onMoveModuleUpClicked(); + void onMoveModuleDownClicked(); + void onModuleItemChanged(QListWidgetItem* item); void onModuleSelectionChanged(); void onProcessingBitDepthChanged(); + void onInputModeChanged(); void onLiveProcessingToggled(bool enabled); void onRunOfflineProcessing(); void onCancelProcessing(); void setupUI(); - void setupRunControls(); - void setupSourceControls(); + void setupInputControls(); + void setupExecutionControls(); QWidget* setupModuleList(); QWidget* setupModuleConfig(); void updateProcessingSettings(); @@ -58,11 +65,14 @@ namespace scopeone::ui scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; ImageWorkspace* m_workspace{nullptr}; bool m_processingRunning{false}; - QWidget* m_runControlsWidget{nullptr}; - QWidget* m_sourceControlsWidget{nullptr}; + QWidget* m_inputControlsWidget{nullptr}; + QWidget* m_executionControlsWidget{nullptr}; QComboBox* m_sourceCombo{nullptr}; - QCheckBox* m_liveProcessingCheckBox{nullptr}; - QPushButton* m_runOfflineButton{nullptr}; + QRadioButton* m_liveModeRadio{nullptr}; + QRadioButton* m_staticModeRadio{nullptr}; + QLabel* m_sourceLabel{nullptr}; + QLabel* m_rangeLabel{nullptr}; + QPushButton* m_executeButton{nullptr}; QPushButton* m_cancelProcessingButton{nullptr}; QProgressBar* m_processingProgress{nullptr}; QComboBox* m_processingBitDepthCombo{nullptr}; @@ -71,6 +81,8 @@ namespace scopeone::ui QListWidget* m_moduleList{nullptr}; QPushButton* m_addModuleButton{nullptr}; QPushButton* m_removeModuleButton{nullptr}; + QPushButton* m_moveModuleUpButton{nullptr}; + QPushButton* m_moveModuleDownButton{nullptr}; QComboBox* m_moduleTypeCombo{nullptr}; QStackedWidget* m_configStack{nullptr}; QWidget* m_emptyConfigWidget{nullptr}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 7d645df..223968e 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -880,6 +880,7 @@ namespace scopeone::ui setupPropertyBrowser(); setupRecording(); setupImageGallery(); + setTabPosition(Qt::LeftDockWidgetArea, QTabWidget::North); updateDockWidgetMenu(); } diff --git a/src/ScopeOneLocalApiServer.cpp b/src/ScopeOneLocalApiServer.cpp index 7653bd8..65262a7 100644 --- a/src/ScopeOneLocalApiServer.cpp +++ b/src/ScopeOneLocalApiServer.cpp @@ -227,6 +227,7 @@ namespace scopeone::ui object.insert(QStringLiteral("index"), index); object.insert(QStringLiteral("kind"), info.id()); object.insert(QStringLiteral("name"), info.name()); + object.insert(QStringLiteral("enabled"), info.enabled()); object.insert(QStringLiteral("parameters"), QJsonObject::fromVariantMap(info.parameters())); return object; } From ae300c75712e71d7b7c2c770a904d3572edfd71d Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:05:17 +0200 Subject: [PATCH 20/37] Update --- README.md | 2 +- src/ImageProcessingWidget.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a6d75ce..acf41ef 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ScopeOne is open-source microscopy control software built with C++ and Qt. Hardw 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

diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index a9232d9..485f32c 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -673,6 +673,10 @@ namespace scopeone::ui m_removeModuleButton = new QPushButton(tr("Remove"), group); m_moveModuleUpButton = new QPushButton(tr("Up"), group); m_moveModuleDownButton = new QPushButton(tr("Down"), group); + m_addModuleButton->setFixedWidth(52); + m_moveModuleUpButton->setFixedWidth(42); + m_moveModuleDownButton->setFixedWidth(52); + m_removeModuleButton->setFixedWidth(64); connect(m_addModuleButton, &QPushButton::clicked, this, &ImageProcessingWidget::onAddModuleClicked); connect(m_removeModuleButton, &QPushButton::clicked, this, &ImageProcessingWidget::onRemoveModuleClicked); connect(m_moveModuleUpButton, &QPushButton::clicked, From ce14940e04773c4dede4aae52138df5b384c5d81 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:32:40 +0200 Subject: [PATCH 21/37] Migrate DAQ and tool plugins to Core SDK --- CMakeLists.txt | 2 +- README.md | 8 +- ScopeOneCore/CMakeLists.txt | 31 +- ScopeOneCore/README.md | 36 +- .../include/scopeone/CameraProvider.h | 2 +- ScopeOneCore/include/scopeone/DaqDevice.h | 192 ++++ .../include/scopeone/HardwareCapabilities.h | 12 +- .../include/scopeone/HardwareProvider.h | 3 +- ScopeOneCore/include/scopeone/HardwareTypes.h | 6 +- .../include/scopeone/PluginManifest.h | 6 +- ScopeOneCore/include/scopeone/PluginSDK.h | 5 +- .../include/scopeone/ProcessingPlugin.h | 4 +- .../include/scopeone/ScanImageAssembler.h | 51 + ScopeOneCore/include/scopeone/ScopeOneCore.h | 36 + ScopeOneCore/include/scopeone/SignalSource.h | 222 +++++ .../include/scopeone/SimulatorProvider.h | 1 + .../include/scopeone/ToolFrameStream.h | 4 +- ScopeOneCore/include/scopeone/ToolTask.h | 4 +- .../include/scopeone/scopeone_sdk_export.h | 15 + ScopeOneCore/internal/DaqDeviceManager.h | 50 + ScopeOneCore/internal/SignalSourceManager.h | 51 + ScopeOneCore/src/DaqDeviceManager.cpp | 264 ++++++ ScopeOneCore/src/ScanImageAssembler.cpp | 302 ++++++ ScopeOneCore/src/ScopeOneCore.cpp | 285 ++++++ ScopeOneCore/src/SignalSourceManager.cpp | 333 +++++++ plugins/CMakeLists.txt | 25 + plugins/NIDaqmx/CMakeLists.txt | 11 + plugins/NIDaqmx/NIDaqmxPlugin.cpp | 873 ++++++++++++++++++ plugins/NIDaqmx/NIDaqmxPlugin.h | 22 + plugins/NIDaqmx/plugin.json | 7 + plugins/PtuFile/CMakeLists.txt | 11 + plugins/PtuFile/PtuFilePlugin.cpp | 593 ++++++++++++ plugins/PtuFile/PtuFilePlugin.h | 22 + plugins/PtuFile/plugin.json | 7 + plugins/ScanningDaqTool/CMakeLists.txt | 14 + plugins/ScanningDaqTool/DaqControlWidget.cpp | 827 +++++++++++++++++ plugins/ScanningDaqTool/DaqControlWidget.h | 77 ++ .../ScanningDaqTool/ScanningDaqToolPlugin.cpp | 49 + .../ScanningDaqTool/SignalMonitorWidget.cpp | 683 ++++++++++++++ plugins/ScanningDaqTool/SignalMonitorWidget.h | 71 ++ plugins/ScanningDaqTool/plugin.json | 7 + plugins/examples/README.md | 6 +- scripts/build.ps1 | 20 +- src/PluginManagerDialog.cpp | 78 +- src/PluginManagerDialog.h | 1 + src/ScopeOneToolPlugin.cpp | 25 +- 46 files changed, 5266 insertions(+), 88 deletions(-) create mode 100644 ScopeOneCore/include/scopeone/DaqDevice.h create mode 100644 ScopeOneCore/include/scopeone/ScanImageAssembler.h create mode 100644 ScopeOneCore/include/scopeone/SignalSource.h create mode 100644 ScopeOneCore/include/scopeone/scopeone_sdk_export.h create mode 100644 ScopeOneCore/internal/DaqDeviceManager.h create mode 100644 ScopeOneCore/internal/SignalSourceManager.h create mode 100644 ScopeOneCore/src/DaqDeviceManager.cpp create mode 100644 ScopeOneCore/src/ScanImageAssembler.cpp create mode 100644 ScopeOneCore/src/SignalSourceManager.cpp create mode 100644 plugins/CMakeLists.txt create mode 100644 plugins/NIDaqmx/CMakeLists.txt create mode 100644 plugins/NIDaqmx/NIDaqmxPlugin.cpp create mode 100644 plugins/NIDaqmx/NIDaqmxPlugin.h create mode 100644 plugins/NIDaqmx/plugin.json create mode 100644 plugins/PtuFile/CMakeLists.txt create mode 100644 plugins/PtuFile/PtuFilePlugin.cpp create mode 100644 plugins/PtuFile/PtuFilePlugin.h create mode 100644 plugins/PtuFile/plugin.json create mode 100644 plugins/ScanningDaqTool/CMakeLists.txt create mode 100644 plugins/ScanningDaqTool/DaqControlWidget.cpp create mode 100644 plugins/ScanningDaqTool/DaqControlWidget.h create mode 100644 plugins/ScanningDaqTool/ScanningDaqToolPlugin.cpp create mode 100644 plugins/ScanningDaqTool/SignalMonitorWidget.cpp create mode 100644 plugins/ScanningDaqTool/SignalMonitorWidget.h create mode 100644 plugins/ScanningDaqTool/plugin.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 7712e03..e2039b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,7 +206,7 @@ if (SCOPEONE_CORE_RUNTIME_FILES) ) endif () -foreach (_plugin_kind IN ITEMS processing tools hardware) +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 diff --git a/README.md b/README.md index acf41ef..f1b4cb1 100644 --- a/README.md +++ b/README.md @@ -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/ @@ -73,15 +74,16 @@ ScopeWriter contains its filesystem Zarr V3 writer and carries libtiff, zlib, zs ### Plugin layout -ScopeOne loads external plugins from three directories beside the application: +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 a common manifest with `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. Minimal standalone projects are in `plugins/examples`. +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. Minimal standalone projects are in `plugins/examples`. -Micro-Manager remains the built-in hardware provider and continues to load its Device Adapters from `.cfg` files. Native devices that do not belong in Micro-Manager use the `HardwareProvider` plugin contract. +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. diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 1239b57..9fc449e 100644 --- a/ScopeOneCore/CMakeLists.txt +++ b/ScopeOneCore/CMakeLists.txt @@ -79,6 +79,7 @@ set(CORE_SOURCES src/ExperimentDocument.cpp src/AcquisitionEngine.cpp src/DriverHostProviderProxy.cpp + src/DaqDeviceManager.cpp src/HardwareProvider.cpp src/PluginManifest.cpp src/HardwareRuntime.cpp @@ -109,19 +110,31 @@ set(CORE_SOURCES src/NativeCameraBackend.cpp src/DriverHostCameraBackend.cpp src/ScopeOneCore.cpp + src/ScanImageAssembler.cpp + src/SignalSourceManager.cpp ) set(CORE_HEADERS include/scopeone/ExperimentDocument.h - internal/AcquisitionEngine.h include/scopeone/CameraProvider.h + include/scopeone/DaqDevice.h include/scopeone/DriverHostProviderPlugin.h - internal/CameraRuntimeControl.h include/scopeone/HardwareCapabilities.h - include/scopeone/HardwareTypes.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 @@ -133,14 +146,12 @@ set(CORE_HEADERS internal/ImageProcessingFramework.h internal/ProcessingModule.h internal/ProcessingModuleRegistry.h - include/scopeone/ProcessingPlugin.h include/scopeone/ProcessingPipeline.h - include/scopeone/ToolTask.h - include/scopeone/ToolFrameStream.h - include/scopeone/ToolPlugin.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 @@ -153,8 +164,6 @@ set(CORE_HEADERS 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 ) @@ -165,6 +174,10 @@ 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 diff --git a/ScopeOneCore/README.md b/ScopeOneCore/README.md index 36e1748..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,7 +18,7 @@ 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 @@ -31,7 +31,7 @@ ScopeOneCore/ 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,7 +42,8 @@ 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::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` | @@ -88,30 +89,21 @@ Outputs: - `build/Release/ScopeOneCore.dll` - `build/Release/ScopeOneCore.lib` - `build/Release/ScopeOne_DriverHost.exe` -- The external hardware example is built under `plugins/examples/hardware`. +- External plugins are built under `plugins`. - `build/ScopeOneCoreConfig.cmake` - `install/bin/ScopeOneCore.dll` - `install/bin/ScopeOne_DriverHost.exe` -- External hardware plugins are installed under `plugins/hardware`. +- 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. -- `HardwareProvider.h`, `HardwareCapabilities.h` and `CameraProvider.h` define provider discovery, device control and frame delivery. -- `DriverHostProviderPlugin.h` defines the module factory used to load external providers in isolated DriverHost processes. -- `ProcessingPlugin.h` defines processing module descriptors, runtime modules and the external processing plugin contract. -- `ToolPlugin.h` defines the restricted desktop tool context and external tool contract. -- `PluginManifest.h` validates the common plugin identity and API version metadata. -- `HardwareTypes.h` defines provider-independent device identity, state and endpoint metadata. -- `SimulatorProvider.h` provides an in-process reference provider. -- `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. @@ -121,12 +113,14 @@ Providers use ScopeOne logical device IDs and publish `ImageFrame` objects throu ## 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 and processing contracts are installed public Core APIs. Desktop tool plugins target the ScopeOne application UI contract. +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 diff --git a/ScopeOneCore/include/scopeone/CameraProvider.h b/ScopeOneCore/include/scopeone/CameraProvider.h index 08dca00..97a95f4 100644 --- a/ScopeOneCore/include/scopeone/CameraProvider.h +++ b/ScopeOneCore/include/scopeone/CameraProvider.h @@ -10,7 +10,7 @@ namespace scopeone::core { - class SCOPEONE_CORE_EXPORT CameraProvider : public DevicePropertyProvider + class SCOPEONE_SDK_EXPORT CameraProvider : public DevicePropertyProvider { public: using FrameSink = std::function; 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/HardwareCapabilities.h b/ScopeOneCore/include/scopeone/HardwareCapabilities.h index 0ff44cd..6930952 100644 --- a/ScopeOneCore/include/scopeone/HardwareCapabilities.h +++ b/ScopeOneCore/include/scopeone/HardwareCapabilities.h @@ -3,11 +3,11 @@ #include #include -#include "scopeone/scopeone_core_export.h" +#include "scopeone/scopeone_sdk_export.h" namespace scopeone::core { - class SCOPEONE_CORE_EXPORT DevicePropertyProvider + class SCOPEONE_SDK_EXPORT DevicePropertyProvider { public: virtual ~DevicePropertyProvider(); @@ -30,7 +30,7 @@ namespace scopeone::core virtual double getPropertyUpperLimit(const QString& deviceId, const QString& name) = 0; }; - class SCOPEONE_CORE_EXPORT StageProvider + class SCOPEONE_SDK_EXPORT StageProvider { public: virtual ~StageProvider(); @@ -60,7 +60,7 @@ namespace scopeone::core QString* errorMessage = nullptr) = 0; }; - class SCOPEONE_CORE_EXPORT ShutterProvider + class SCOPEONE_SDK_EXPORT ShutterProvider { public: virtual ~ShutterProvider(); @@ -73,7 +73,7 @@ namespace scopeone::core QString* errorMessage = nullptr) = 0; }; - class SCOPEONE_CORE_EXPORT StateProvider + class SCOPEONE_SDK_EXPORT StateProvider { public: virtual ~StateProvider(); @@ -87,7 +87,7 @@ namespace scopeone::core virtual QString stateLabel(const QString& deviceId, long state) const = 0; }; - class SCOPEONE_CORE_EXPORT ConfigurationProvider + class SCOPEONE_SDK_EXPORT ConfigurationProvider { public: virtual ~ConfigurationProvider(); diff --git a/ScopeOneCore/include/scopeone/HardwareProvider.h b/ScopeOneCore/include/scopeone/HardwareProvider.h index d6cf26c..351b38d 100644 --- a/ScopeOneCore/include/scopeone/HardwareProvider.h +++ b/ScopeOneCore/include/scopeone/HardwareProvider.h @@ -6,10 +6,11 @@ #include #include "scopeone/HardwareTypes.h" +#include "scopeone/scopeone_sdk_export.h" namespace scopeone::core { - class SCOPEONE_CORE_EXPORT HardwareProvider + class SCOPEONE_SDK_EXPORT HardwareProvider { public: virtual ~HardwareProvider(); diff --git a/ScopeOneCore/include/scopeone/HardwareTypes.h b/ScopeOneCore/include/scopeone/HardwareTypes.h index 0b0a267..19730af 100644 --- a/ScopeOneCore/include/scopeone/HardwareTypes.h +++ b/ScopeOneCore/include/scopeone/HardwareTypes.h @@ -4,7 +4,7 @@ #include #include -#include "scopeone/scopeone_core_export.h" +#include "scopeone/scopeone_sdk_export.h" namespace scopeone::core { @@ -44,14 +44,14 @@ namespace scopeone::core DriverHost }; - struct SCOPEONE_CORE_EXPORT HardwareProviderDescriptor + struct SCOPEONE_SDK_EXPORT HardwareProviderDescriptor { QString id; QString name; QString version; }; - struct SCOPEONE_CORE_EXPORT HardwareDeviceDescriptor + struct SCOPEONE_SDK_EXPORT HardwareDeviceDescriptor { QString logicalId; QString providerId; diff --git a/ScopeOneCore/include/scopeone/PluginManifest.h b/ScopeOneCore/include/scopeone/PluginManifest.h index 6868462..b7dd545 100644 --- a/ScopeOneCore/include/scopeone/PluginManifest.h +++ b/ScopeOneCore/include/scopeone/PluginManifest.h @@ -1,6 +1,6 @@ #pragma once -#include "scopeone/scopeone_core_export.h" +#include "scopeone/scopeone_sdk_export.h" #include #include @@ -26,8 +26,8 @@ namespace scopeone::core inline constexpr int ScopeOnePluginApiVersion = 1; - SCOPEONE_CORE_EXPORT QString pluginKindName(PluginKind kind); - SCOPEONE_CORE_EXPORT bool parsePluginManifest(const QJsonObject& metadata, + 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 index a8499cd..4cdb85a 100644 --- a/ScopeOneCore/include/scopeone/PluginSDK.h +++ b/ScopeOneCore/include/scopeone/PluginSDK.h @@ -1,12 +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/ProcessingPipeline.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/ProcessingPlugin.h b/ScopeOneCore/include/scopeone/ProcessingPlugin.h index 1aa1a41..d48b9d6 100644 --- a/ScopeOneCore/include/scopeone/ProcessingPlugin.h +++ b/ScopeOneCore/include/scopeone/ProcessingPlugin.h @@ -1,7 +1,7 @@ #pragma once #include "scopeone/ImageFrame.h" -#include "scopeone/scopeone_core_export.h" +#include "scopeone/scopeone_sdk_export.h" #include #include @@ -114,7 +114,7 @@ namespace scopeone::core } }; - class SCOPEONE_CORE_EXPORT ProcessingModule + class SCOPEONE_SDK_EXPORT ProcessingModule { public: virtual ~ProcessingModule() = default; 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 5b28378..347fa89 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -20,11 +20,13 @@ #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; @@ -558,6 +560,18 @@ 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; bool graphPixelValue(const QString& layerKey, const QPoint& imagePos, int& value) const; @@ -734,6 +748,21 @@ namespace scopeone::core void rawFramesAcquired(const QString& cameraId, quint64 frameCount); void previewRawFrameReady(const ImageFrame& frame); void previewStateChanged(bool running); + 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); @@ -877,6 +906,13 @@ namespace scopeone::core 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 flushProcessedFrames(); 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 index e00f3ca..c653e04 100644 --- a/ScopeOneCore/include/scopeone/SimulatorProvider.h +++ b/ScopeOneCore/include/scopeone/SimulatorProvider.h @@ -7,6 +7,7 @@ #include "scopeone/CameraProvider.h" #include "scopeone/HardwareProvider.h" +#include "scopeone/scopeone_core_export.h" namespace scopeone::core { diff --git a/ScopeOneCore/include/scopeone/ToolFrameStream.h b/ScopeOneCore/include/scopeone/ToolFrameStream.h index f3f25d7..1308627 100644 --- a/ScopeOneCore/include/scopeone/ToolFrameStream.h +++ b/ScopeOneCore/include/scopeone/ToolFrameStream.h @@ -1,7 +1,7 @@ #pragma once #include "scopeone/ImageFrame.h" -#include "scopeone/scopeone_core_export.h" +#include "scopeone/scopeone_sdk_export.h" #include #include @@ -13,7 +13,7 @@ namespace scopeone::core namespace scopeone::ui { - class SCOPEONE_CORE_EXPORT ScopeOneToolFrameStream final : public QObject + class SCOPEONE_SDK_EXPORT ScopeOneToolFrameStream final : public QObject { Q_OBJECT diff --git a/ScopeOneCore/include/scopeone/ToolTask.h b/ScopeOneCore/include/scopeone/ToolTask.h index 078facc..fa2e131 100644 --- a/ScopeOneCore/include/scopeone/ToolTask.h +++ b/ScopeOneCore/include/scopeone/ToolTask.h @@ -1,6 +1,6 @@ #pragma once -#include "scopeone/scopeone_core_export.h" +#include "scopeone/scopeone_sdk_export.h" #include #include @@ -10,7 +10,7 @@ namespace scopeone::ui { - class SCOPEONE_CORE_EXPORT ScopeOneToolTask final : public QObject + class SCOPEONE_SDK_EXPORT ScopeOneToolTask final : public QObject { Q_OBJECT 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/DaqDeviceManager.h b/ScopeOneCore/internal/DaqDeviceManager.h new file mode 100644 index 0000000..4bd4a22 --- /dev/null +++ b/ScopeOneCore/internal/DaqDeviceManager.h @@ -0,0 +1,50 @@ +#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; + QHash m_plugins; + QHash m_descriptors; + QHash> m_controllers; + QHash m_states; + QHash m_messages; + }; +} diff --git a/ScopeOneCore/internal/SignalSourceManager.h b/ScopeOneCore/internal/SignalSourceManager.h new file mode 100644 index 0000000..c3f1f8f --- /dev/null +++ b/ScopeOneCore/internal/SignalSourceManager.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "scopeone/SignalSource.h" + +class QPluginLoader; + +namespace scopeone::core::internal +{ + class SignalSourceManager final : public QObject + { + Q_OBJECT + + public: + explicit SignalSourceManager(QObject* parent = nullptr); + ~SignalSourceManager() override; + + QList sources() const; + bool startTrace(const SignalAcquisitionConfig& config, + QString* errorMessage = nullptr); + void stopTrace(const QString& sourceId); + SignalSourceState state(const QString& sourceId) const; + QString stateMessage(const QString& sourceId) const; + + signals: + void timeSeriesReady(const scopeone::core::TimeSeriesChunk& chunk); + void timestampedEventsReady(const scopeone::core::TimestampedEventChunk& chunk); + void sourceStateChanged(const QString& sourceId, + scopeone::core::SignalSourceState state, + const QString& message); + void sourceError(const QString& sourceId, const QString& errorMessage); + + private: + void loadPlugins(); + SignalSource* sourceInstance(const QString& sourceId, + QString* errorMessage); + + std::vector> m_loaders; + QHash m_plugins; + QHash m_descriptors; + QHash> m_sources; + QHash m_states; + QHash m_messages; + }; +} diff --git a/ScopeOneCore/src/DaqDeviceManager.cpp b/ScopeOneCore/src/DaqDeviceManager.cpp new file mode 100644 index 0000000..2ca7a2e --- /dev/null +++ b/ScopeOneCore/src/DaqDeviceManager.cpp @@ -0,0 +1,264 @@ +#include "internal/DaqDeviceManager.h" +#include "scopeone/PluginManifest.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace scopeone::core +{ + DaqController::DaqController(QObject* parent) + : QObject(parent) + { + } + + DaqController::~DaqController() = default; +} + +namespace scopeone::core::internal +{ + namespace + { + void expandRasterScan(const DaqRasterScanConfig& scan, + DaqSessionConfig& session) + { + const quint64 frameLines = static_cast(scan.activeLines) + + static_cast(scan.flybackLines); + const QString name = scan.name.trimmed().isEmpty() + ? QStringLiteral("Raster scan") + : scan.name.trimmed(); + + if (!scan.lineOutputTerminal.trimmed().isEmpty()) + { + session.routes.append({scan.lineClock, + scan.lineOutputTerminal, + false}); + } + + DaqPulseTaskConfig framePulse; + framePulse.name = name + QStringLiteral(" frame clock"); + framePulse.counter = scan.frameCounter; + framePulse.outputTerminal = scan.frameOutputTerminal; + framePulse.timebaseSource = scan.lineClock; + framePulse.lowTicks = static_cast(frameLines - 2); + framePulse.highTicks = 2; + session.pulseTasks.append(framePulse); + + DaqAnalogTaskConfig slowAxis; + slowAxis.name = name + QStringLiteral(" slow axis"); + slowAxis.direction = DaqTaskDirection::Output; + slowAxis.channels = {scan.yChannel}; + slowAxis.minimumVolts = std::min(scan.yStartVolts, scan.yEndVolts); + slowAxis.maximumVolts = std::max(scan.yStartVolts, scan.yEndVolts); + slowAxis.timing.sampleClock = scan.lineClock; + slowAxis.timing.sampleRateHz = scan.nominalLineRateHz; + slowAxis.timing.sampleEdge = DaqEdge::Falling; + slowAxis.timing.sampleMode = DaqSampleMode::Continuous; + slowAxis.timing.samplesPerChannel = frameLines; + slowAxis.timing.startTrigger = scan.frameOutputTerminal; + slowAxis.outputSamplesByScan.reserve(static_cast(frameLines)); + + for (quint32 line = 0; line < scan.activeLines; ++line) + { + const double t = static_cast(line) + / static_cast(scan.activeLines - 1); + slowAxis.outputSamplesByScan.append( + scan.yStartVolts + (scan.yEndVolts - scan.yStartVolts) * t); + } + for (quint32 line = 0; line < scan.flybackLines; ++line) + { + const double t = static_cast(line + 1) + / static_cast(scan.flybackLines); + const double smooth = t * t * (3.0 - 2.0 * t); + slowAxis.outputSamplesByScan.append( + scan.yEndVolts + (scan.yStartVolts - scan.yEndVolts) * smooth); + } + session.analogTasks.append(std::move(slowAxis)); + } + } + + DaqDeviceManager::DaqDeviceManager(QObject* parent) + : QObject(parent) + { + loadPlugins(); + } + + DaqDeviceManager::~DaqDeviceManager() + { + for (const QPointer& controller : std::as_const(m_controllers)) + { + delete controller.data(); + } + m_controllers.clear(); + m_loaders.clear(); + } + + QList DaqDeviceManager::devices() const + { + QList result = m_descriptors.values(); + std::sort(result.begin(), result.end(), + [](const DaqDeviceDescriptor& left, + const DaqDeviceDescriptor& right) + { + return left.name.compare(right.name, Qt::CaseInsensitive) < 0; + }); + return result; + } + + bool DaqDeviceManager::start(const DaqSessionConfig& config, + QString* errorMessage) + { + const QString deviceId = config.deviceId.trimmed(); + if (!m_descriptors.contains(deviceId)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Unknown DAQ device: %1").arg(deviceId); + } + return false; + } + DaqSessionConfig expanded = config; + expanded.deviceId = deviceId; + expanded.rasterScans.clear(); + for (const DaqRasterScanConfig& scan : config.rasterScans) + { + expandRasterScan(scan, expanded); + } + DaqController* deviceController = controller(deviceId, errorMessage); + return deviceController && deviceController->start(expanded, errorMessage); + } + + void DaqDeviceManager::stop(const QString& deviceId) + { + if (DaqController* deviceController = m_controllers.value(deviceId.trimmed())) + { + deviceController->stop(); + } + } + + DaqState DaqDeviceManager::state(const QString& deviceId) const + { + const QString id = deviceId.trimmed(); + const QPointer controller = m_controllers.value(id); + return controller ? controller->state() : m_states.value(id, DaqState::Idle); + } + + QString DaqDeviceManager::stateMessage(const QString& deviceId) const + { + const QString id = deviceId.trimmed(); + const QPointer controller = m_controllers.value(id); + return controller ? controller->stateMessage() + : m_messages.value(id, QStringLiteral("DAQ device is idle")); + } + + void DaqDeviceManager::loadPlugins() + { + const QStringList directories = { + QDir(QCoreApplication::applicationDirPath()) + .filePath(QStringLiteral("plugins/hardware")), + QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)) + .filePath(QStringLiteral("plugins/hardware"))}; + + for (const QString& path : directories) + { + const QDir directory(path); + for (const QFileInfo& file : directory.entryInfoList(QDir::Files, QDir::Name)) + { + if (!QLibrary::isLibrary(file.absoluteFilePath())) + { + continue; + } + auto loader = std::make_unique(file.absoluteFilePath()); + PluginManifest manifest; + QString manifestError; + if (!parsePluginManifest( + loader->metaData().value(QStringLiteral("MetaData")).toObject(), + PluginKind::Hardware, + manifest, + &manifestError)) + { + qWarning().noquote() + << QStringLiteral("Failed to load DAQ plugin %1: %2") + .arg(file.fileName(), manifestError); + continue; + } + auto* plugin = qobject_cast(loader->instance()); + if (!plugin) + { + qWarning().noquote() + << QStringLiteral("Failed to load DAQ plugin %1: %2") + .arg(file.fileName(), loader->errorString()); + continue; + } + + for (const DaqDeviceDescriptor& descriptor : plugin->devices()) + { + const QString deviceId = descriptor.id.trimmed(); + if (deviceId.isEmpty() || m_descriptors.contains(deviceId)) + { + continue; + } + m_descriptors.insert(deviceId, descriptor); + m_plugins.insert(deviceId, plugin); + m_states.insert(deviceId, DaqState::Idle); + m_messages.insert(deviceId, QStringLiteral("DAQ device is idle")); + } + m_loaders.push_back(std::move(loader)); + } + } + } + + DaqController* DaqDeviceManager::controller(const QString& deviceId, + QString* errorMessage) + { + const QString id = deviceId.trimmed(); + if (DaqController* existing = m_controllers.value(id)) + { + return existing; + } + DaqDevicePlugin* plugin = m_plugins.value(id); + if (!plugin) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Unknown DAQ device: %1").arg(id); + } + return nullptr; + } + DaqController* created = plugin->createController(id, this); + if (!created) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Failed to create DAQ controller: %1").arg(id); + } + return nullptr; + } + + connect(created, &DaqController::stateChanged, + this, [this, id](DaqState state, const QString& message) + { + m_states.insert(id, state); + m_messages.insert(id, message); + emit stateChanged(id, state, message); + }); + connect(created, &DaqController::controllerError, + this, [this, id](const QString& message) + { + emit deviceError(id, message); + }); + connect(created, &DaqController::inputDataReady, + this, &DaqDeviceManager::inputDataReady); + m_controllers.insert(id, created); + return created; + } +} diff --git a/ScopeOneCore/src/ScanImageAssembler.cpp b/ScopeOneCore/src/ScanImageAssembler.cpp new file mode 100644 index 0000000..acff2fc --- /dev/null +++ b/ScopeOneCore/src/ScanImageAssembler.cpp @@ -0,0 +1,302 @@ +#include "scopeone/ScanImageAssembler.h" + +#include +#include +#include +#include + +namespace scopeone::core +{ + namespace + { + bool matchesMarker(quint32 value, quint32 markerMask) + { + return markerMask != 0 && (value & markerMask) != 0; + } + } + + ScanImageAssembler::ScanImageAssembler(const QString& sourceId, + const ScanImageConfig& config) + : m_sourceId(sourceId.trimmed()) + , m_config(config) + { + reset(); + } + + bool ScanImageAssembler::isValid() const + { + const qint64 pixelCount = static_cast(m_config.width) + * static_cast(m_config.height); + return !m_sourceId.isEmpty() + && m_config.enabled + && m_config.width > 0 + && m_config.height > 0 + && m_config.gain > 0 + && m_config.averageFrames > 0 + && m_config.gain <= (std::numeric_limits::max)() + && pixelCount <= 64LL * 1024LL * 1024LL; + } + + void ScanImageAssembler::reset() + { + m_framePixels.clear(); + m_accumulatedPixels.clear(); + m_lineEventTicks.clear(); + m_readyFrames.clear(); + m_nextRow = 0; + m_accumulatedFrameCount = 0; + m_frameActive = false; + m_lineActive = false; + m_lineStartTick = 0; + m_lastLineDurationTicks = 0; + m_lastFrameTick = 0; + m_tickPeriodSeconds = 0.0; + if (isValid()) + { + m_framePixels.resize(m_config.width * m_config.height); + } + } + + QList ScanImageAssembler::append(const TimestampedEventChunk& chunk) + { + m_readyFrames.clear(); + if (!isValid() || chunk.sourceId != m_sourceId) + { + return {}; + } + m_tickPeriodSeconds = chunk.tickPeriodSeconds; + + qsizetype eventIndex = 0; + qsizetype markerIndex = 0; + while (eventIndex < chunk.eventTicks.size() + || markerIndex < chunk.markerTicks.size()) + { + if (markerIndex < chunk.markerTicks.size() + && (eventIndex >= chunk.eventTicks.size() + || chunk.markerTicks[markerIndex] <= chunk.eventTicks[eventIndex])) + { + handleMarker(chunk.markerTicks[markerIndex], + chunk.markerCodes[markerIndex]); + ++markerIndex; + } + else + { + appendEvent(chunk.eventTicks[eventIndex]); + ++eventIndex; + } + } + return std::exchange(m_readyFrames, QList{}); + } + + QList ScanImageAssembler::finish() + { + m_readyFrames.clear(); + if (m_lineActive && m_lastLineDurationTicks > 0) + { + finishLine(m_lineStartTick + m_lastLineDurationTicks); + } + if (m_accumulatedFrameCount > 0) + { + emitAveragedFrame(m_lastFrameTick); + } + m_frameActive = false; + m_lineActive = false; + m_lineEventTicks.clear(); + return std::exchange(m_readyFrames, QList{}); + } + + void ScanImageAssembler::handleMarker(quint64 tick, quint32 code) + { + if (matchesMarker(code, m_config.frameStartMarker)) + { + if (m_frameActive) + { + finishLine(tick); + if (m_nextRow > 0) + { + finishFrame(tick); + } + } + beginFrame(); + beginLine(tick); + } + + if (!m_frameActive) + { + if (m_config.frameStartMarker != 0) + { + return; + } + beginFrame(); + beginLine(tick); + } + if (matchesMarker(code, m_config.lineMarker)) + { + finishLine(tick); + beginLine(tick); + } + if (matchesMarker(code, m_config.frameEndMarker)) + { + finishLine(tick); + finishFrame(tick); + } + } + + void ScanImageAssembler::beginFrame() + { + m_framePixels.fill(0); + m_lineEventTicks.clear(); + m_nextRow = 0; + m_frameActive = true; + m_lineActive = false; + } + + void ScanImageAssembler::beginLine(quint64 tick) + { + if (!m_frameActive || m_nextRow >= m_config.height) + { + return; + } + m_lineEventTicks.clear(); + m_lineStartTick = tick; + m_lineActive = true; + } + + void ScanImageAssembler::finishLine(quint64 tick) + { + if (!m_lineActive) + { + return; + } + if (tick > m_lineStartTick && m_nextRow < m_config.height) + { + m_lastLineDurationTicks = tick - m_lineStartTick; + const bool reverse = m_config.mirrorHorizontal + != (m_config.serpentine && (m_nextRow % 2 == 1)); + const long double duration = static_cast(tick - m_lineStartTick); + for (quint64 eventTick : std::as_const(m_lineEventTicks)) + { + if (eventTick < m_lineStartTick || eventTick >= tick) + { + continue; + } + const long double phase = + static_cast(eventTick - m_lineStartTick) / duration; + const long double position = phase <= 0.5L + ? phase * 2.0L + : (1.0L - phase) * 2.0L; + const int x = std::min( + m_config.width - 1, + static_cast(position * m_config.width)); + const int outputX = reverse ? m_config.width - 1 - x : x; + quint16& pixel = m_framePixels[m_nextRow * m_config.width + outputX]; + const quint32 amplified = static_cast(pixel) + m_config.gain; + pixel = static_cast(std::min( + amplified, + static_cast((std::numeric_limits::max)()))); + } + ++m_nextRow; + } + m_lineEventTicks.clear(); + m_lineActive = false; + if (m_nextRow >= m_config.height) + { + finishFrame(tick); + } + } + + void ScanImageAssembler::finishFrame(quint64 tick) + { + if (!m_frameActive || m_nextRow <= 0) + { + m_frameActive = false; + m_lineActive = false; + return; + } + + m_lastFrameTick = tick; + if (m_config.averageFrames == 1) + { + emitFrame(m_framePixels, tick); + } + else + { + if (m_accumulatedPixels.isEmpty()) + { + m_accumulatedPixels.resize(m_framePixels.size()); + } + for (int index = 0; index < m_framePixels.size(); ++index) + { + m_accumulatedPixels[index] += m_framePixels[index]; + } + ++m_accumulatedFrameCount; + if (m_accumulatedFrameCount >= m_config.averageFrames) + { + emitAveragedFrame(tick); + } + } + m_frameActive = false; + m_lineActive = false; + m_lineEventTicks.clear(); + } + + void ScanImageAssembler::emitFrame(const QVector& pixels, + quint64 tick) + { + const qint64 byteCount = static_cast(m_config.width) + * static_cast(m_config.height) * sizeof(quint16); + ImageFrame frame; + frame.cameraId = m_sourceId; + frame.width = m_config.width; + frame.height = m_config.height; + frame.stride = m_config.width * static_cast(sizeof(quint16)); + frame.bitsPerSample = 16; + frame.pixelFormat = ImagePixelFormat::Mono16; + frame.frameIndex = m_nextFrameIndex++; + frame.timestampNs = m_tickPeriodSeconds > 0.0 + ? static_cast( + static_cast(tick) + * m_tickPeriodSeconds * 1.0e9) + : 0; + frame.bytes.resize(static_cast(byteCount)); + std::memcpy(frame.bytes.data(), + pixels.constData(), + static_cast(byteCount)); + m_readyFrames.append(std::move(frame)); + } + + void ScanImageAssembler::emitAveragedFrame(quint64 tick) + { + QVector pixels; + pixels.resize(m_framePixels.size()); + for (int index = 0; index < pixels.size(); ++index) + { + pixels[index] = static_cast( + m_accumulatedPixels[index] + / static_cast(m_accumulatedFrameCount)); + } + emitFrame(pixels, tick); + m_accumulatedPixels.fill(0); + m_accumulatedFrameCount = 0; + } + + void ScanImageAssembler::appendEvent(quint64 tick) + { + if (!m_frameActive) + { + if (m_config.frameStartMarker != 0) + { + return; + } + beginFrame(); + } + if (!m_lineActive) + { + beginLine(tick); + } + if (m_lineActive) + { + m_lineEventTicks.append(tick); + } + } +} diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 33be624..8d7ff56 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -1,5 +1,6 @@ #include "scopeone/ScopeOneCore.h" #include "scopeone/ImageSceneModel.h" +#include "scopeone/ScanImageAssembler.h" #include "internal/AcquisitionEngine.h" #include "internal/ImageProcessingFramework.h" @@ -12,6 +13,8 @@ #include "internal/ParticleAnalysis.h" #include "internal/RecordingManager.h" #include "internal/StageMosaicManager.h" +#include "internal/DaqDeviceManager.h" +#include "internal/SignalSourceManager.h" #include "MMCore.h" #include #include @@ -912,6 +915,16 @@ namespace scopeone::core ImageProcessingManager* imageProcessingManager{nullptr}; std::unique_ptr processingModuleRegistry; StageMosaicManager* stageMosaicManager{nullptr}; + internal::DaqDeviceManager* daqDeviceManager{nullptr}; + internal::SignalSourceManager* signalSourceManager{nullptr}; + struct ScanImageSessionState + { + std::shared_ptr session; + QString baseName; + quint64 lastTimestampNs{0}; + }; + QHash> scanImageAssemblers; + QHash scanImageSessions; QHash experiments; QHash> sessions; QString activeExperimentId; @@ -1146,6 +1159,17 @@ namespace scopeone::core qRegisterMetaType( "scopeone::core::ScopeOneCore::ParticleDetectionResult"); qRegisterMetaType("scopeone::core::ImageFrame"); + qRegisterMetaType( + "scopeone::core::SignalSourceState"); + qRegisterMetaType("scopeone::core::DaqState"); + qRegisterMetaType( + "scopeone::core::DaqInputChunk"); + qRegisterMetaType( + "scopeone::core::TimeSeriesChunk"); + qRegisterMetaType( + "scopeone::core::TimestampedEventChunk"); + qRegisterMetaType( + "scopeone::core::ScanImageConfig"); m_managers->processingModuleRegistry = std::make_unique(); const QStringList processingPluginErrors = m_managers->processingModuleRegistry->loadPlugins( @@ -1238,6 +1262,8 @@ namespace scopeone::core this, &ScopeOneCore::previewStateChanged); m_managers->hardwareRuntime->registerProvider(m_managers->microManagerProvider); m_managers->recordingManager = new RecordingManager(this); + m_managers->signalSourceManager = new internal::SignalSourceManager(this); + m_managers->daqDeviceManager = new internal::DaqDeviceManager(this); m_managers->imageProcessingManager = new ImageProcessingManager(this); m_managers->stageMosaicManager = new StageMosaicManager(this, this); m_managers->recordingWriterStatus.reset( @@ -1260,6 +1286,50 @@ namespace scopeone::core session.setPresentationState(layers, markups); }); + connect(m_managers->signalSourceManager, + &internal::SignalSourceManager::timeSeriesReady, + this, + &ScopeOneCore::handleSignalTimeSeries); + connect(m_managers->signalSourceManager, + &internal::SignalSourceManager::timestampedEventsReady, + this, + &ScopeOneCore::handleTimestampedSignalEvents); + connect(m_managers->signalSourceManager, + &internal::SignalSourceManager::sourceStateChanged, + this, + [this](const QString& sourceId, + SignalSourceState state, + const QString& message) + { + if (state == SignalSourceState::Idle + || state == SignalSourceState::Error) + { + finishScanImageSession( + sourceId, + state == SignalSourceState::Error + ? ExperimentRunState::Failed + : ExperimentRunState::Completed, + message); + } + emit signalSourceStateChanged(sourceId, state, message); + }); + connect(m_managers->signalSourceManager, + &internal::SignalSourceManager::sourceError, + this, + &ScopeOneCore::signalSourceError); + connect(m_managers->daqDeviceManager, + &internal::DaqDeviceManager::stateChanged, + this, + &ScopeOneCore::daqStateChanged); + connect(m_managers->daqDeviceManager, + &internal::DaqDeviceManager::deviceError, + this, + &ScopeOneCore::daqError); + connect(m_managers->daqDeviceManager, + &internal::DaqDeviceManager::inputDataReady, + this, + &ScopeOneCore::daqInputDataReady); + connect(m_managers->hardwareRuntime, &HardwareRuntime::frameReady, this, &ScopeOneCore::handleIncomingRawFrame); connect(m_managers->hardwareRuntime, &HardwareRuntime::frameReady, @@ -2129,6 +2199,221 @@ namespace scopeone::core return m_managers->cameraProvider->getROI(target, x, y, width, height); } + // List signal sources discovered through external source plugins + QList ScopeOneCore::signalSources() const + { + return m_managers->signalSourceManager->sources(); + } + + // Start one external signal source and optionally enable scan reconstruction + bool ScopeOneCore::startSignalTrace(const SignalAcquisitionConfig& config, + QString* errorMessage) + { + const QString sourceId = config.sourceId.trimmed(); + std::shared_ptr assembler; + if (config.scanImage.enabled) + { + assembler = std::make_shared(sourceId, config.scanImage); + if (!assembler->isValid()) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Invalid scan image configuration"); + } + return false; + } + } + + if (!m_managers->signalSourceManager->startTrace(config, errorMessage)) + { + return false; + } + + if (assembler) + { + m_managers->scanImageAssemblers.insert(sourceId, std::move(assembler)); + QString baseName = QFileInfo( + config.sourceSettings.value(QStringLiteral("filePath")).toString()) + .completeBaseName(); + if (baseName.isEmpty()) + { + baseName = QStringLiteral("scan_") + + QDateTime::currentDateTime().toString( + QStringLiteral("yyyyMMdd_hhmmss_zzz")); + } + else + { + baseName += QStringLiteral("_scan"); + } + Managers::ScanImageSessionState state; + state.baseName = baseName; + m_managers->scanImageSessions.insert(sourceId, std::move(state)); + } + else + { + m_managers->scanImageAssemblers.remove(sourceId); + m_managers->scanImageSessions.remove(sourceId); + } + return true; + } + + void ScopeOneCore::stopSignalTrace(const QString& sourceId) + { + m_managers->signalSourceManager->stopTrace(sourceId); + } + + SignalSourceState ScopeOneCore::signalSourceState(const QString& sourceId) const + { + return m_managers->signalSourceManager->state(sourceId); + } + + QString ScopeOneCore::signalSourceStateMessage(const QString& sourceId) const + { + return m_managers->signalSourceManager->stateMessage(sourceId); + } + + QList ScopeOneCore::daqDevices() const + { + return m_managers->daqDeviceManager->devices(); + } + + bool ScopeOneCore::startDaqSession(const DaqSessionConfig& config, + QString* errorMessage) + { + return m_managers->daqDeviceManager->start(config, errorMessage); + } + + void ScopeOneCore::stopDaqSession(const QString& deviceId) + { + m_managers->daqDeviceManager->stop(deviceId); + } + + DaqState ScopeOneCore::daqState(const QString& deviceId) const + { + return m_managers->daqDeviceManager->state(deviceId); + } + + QString ScopeOneCore::daqStateMessage(const QString& deviceId) const + { + return m_managers->daqDeviceManager->stateMessage(deviceId); + } + + void ScopeOneCore::handleSignalTimeSeries(const TimeSeriesChunk& chunk) + { + emit signalTimeSeriesReady(chunk); + } + + void ScopeOneCore::handleTimestampedSignalEvents(const TimestampedEventChunk& chunk) + { + const auto assembler = m_managers->scanImageAssemblers.value(chunk.sourceId); + if (assembler) + { + publishScanFrames(chunk.sourceId, assembler->append(chunk)); + } + emit timestampedSignalEventsReady(chunk); + } + + void ScopeOneCore::publishScanFrames(const QString& sourceId, + const QList& frames) + { + if (frames.isEmpty()) + { + return; + } + + auto stateIt = m_managers->scanImageSessions.find(sourceId); + if (stateIt == m_managers->scanImageSessions.end()) + { + return; + } + + const QString scanSourceId = QStringLiteral("scan:%1").arg(sourceId); + QList normalizedFrames; + normalizedFrames.reserve(frames.size()); + for (const ImageFrame& sourceFrame : frames) + { + ImageFrame frame = sourceFrame; + frame.cameraId = scanSourceId; + publishStaticFrame(scanSourceId, frame, + QStringLiteral("Scan %1").arg(sourceId)); + normalizedFrames.append(std::move(frame)); + } + + if (!stateIt->session) + { + ExperimentPlan plan; + plan.streamToDisk = false; + plan.baseName = stateIt->baseName; + stateIt->session = createFrameSession(normalizedFrames, plan); + if (!stateIt->session) + { + return; + } + stateIt->session->setRunState(ExperimentRunState::Running); + } + else + { + const QString cameraId = normalizedFrames.constFirst().cameraId; + quint64 sequenceIndex = static_cast( + stateIt->session->recordedFrameCount(cameraId)); + for (const ImageFrame& frame : normalizedFrames) + { + if (!stateIt->session->appendImageFrame(frame)) + { + continue; + } + AcquisitionEventRecord record; + record.event.sequenceIndex = sequenceIndex; + record.event.timeIndex = static_cast(sequenceIndex); + record.event.cameraIds = {cameraId}; + record.startedTimestampNs = frame.timestampNs; + record.completedTimestampNs = frame.timestampNs; + record.succeeded = true; + record.frames.insert(cameraId, frameRecordFromImageFrame(frame)); + stateIt->session->appendEventRecord(record); + ++sequenceIndex; + } + ExperimentPlan plan = stateIt->session->capturePlan(); + plan.framesPerBurst = static_cast( + stateIt->session->recordedFrameCount(cameraId)); + stateIt->session->setCapturePlan(plan); + } + stateIt->lastTimestampNs = normalizedFrames.constLast().timestampNs; + } + + void ScopeOneCore::finishScanImageSession(const QString& sourceId, + ExperimentRunState finalState, + const QString& message) + { + const auto assembler = m_managers->scanImageAssemblers.value(sourceId); + if (assembler) + { + publishScanFrames(sourceId, assembler->finish()); + } + + auto stateIt = m_managers->scanImageSessions.find(sourceId); + if (stateIt == m_managers->scanImageSessions.end()) + { + return; + } + if (!stateIt->session) + { + m_managers->scanImageSessions.erase(stateIt); + m_managers->scanImageAssemblers.remove(sourceId); + return; + } + + const auto session = stateIt->session; + session->setRunState( + finalState, + stateIt->lastTimestampNs, + finalState == ExperimentRunState::Failed ? message : QString()); + registerRecordingSession(session); + m_managers->scanImageSessions.erase(stateIt); + m_managers->scanImageAssemblers.remove(sourceId); + emit scanImageSessionReady(session); + } + // Track the active line profile request for future frames void ScopeOneCore::setLineProfile(const QString& cameraId, const QPoint& start, diff --git a/ScopeOneCore/src/SignalSourceManager.cpp b/ScopeOneCore/src/SignalSourceManager.cpp new file mode 100644 index 0000000..cd3220f --- /dev/null +++ b/ScopeOneCore/src/SignalSourceManager.cpp @@ -0,0 +1,333 @@ +#include "internal/SignalSourceManager.h" +#include "scopeone/PluginManifest.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace scopeone::core +{ + EventCountBinner::EventCountBinner(const QString& sourceId, + const QString& quantity, + const QString& unit, + double tickPeriodSeconds, + double sampleIntervalSeconds) + : m_sourceId(sourceId) + , m_quantity(quantity) + , m_unit(unit) + , m_tickPeriodSeconds(tickPeriodSeconds) + , m_ticksPerSample(std::max( + 1, + static_cast(std::llround( + sampleIntervalSeconds / tickPeriodSeconds)))) + , m_nextSampleTick(m_ticksPerSample) + , m_sampleIntervalSeconds( + static_cast(m_ticksPerSample) * tickPeriodSeconds) + { + } + + void EventCountBinner::addEvent(quint64 tick) + { + const quint64 bin = binForTick(tick); + advanceToBin(bin); + if (bin == m_currentSample) + { + ++m_currentEventCount; + ++m_totalInputEvents; + } + } + + void EventCountBinner::addMarker(quint64 tick, quint32 code) + { + advanceToTick(tick); + m_pendingMarkers.append({static_cast(tick) * m_tickPeriodSeconds, code}); + ++m_totalMarkers; + } + + void EventCountBinner::advanceToTick(quint64 tick) + { + advanceToBin(binForTick(tick)); + } + + void EventCountBinner::advanceToElapsedSeconds(double elapsedSeconds) + { + if (elapsedSeconds > 0.0) + { + advanceToBin(static_cast(elapsedSeconds / m_sampleIntervalSeconds)); + } + } + + bool EventCountBinner::hasReadyChunks() const + { + return !m_readyChunks.isEmpty(); + } + + QList EventCountBinner::takeReadyChunks() + { + return std::exchange(m_readyChunks, QList{}); + } + + QList EventCountBinner::takeCompletedChunks() + { + finishChunk(); + return takeReadyChunks(); + } + + void EventCountBinner::appendCompletedValue(double value) + { + constexpr qsizetype kMaximumChunkSamples = 65536; + m_completedValues.append(value); + if (m_completedValues.size() >= kMaximumChunkSamples) + { + finishChunk(); + } + } + + void EventCountBinner::finishChunk() + { + if (m_completedValues.isEmpty()) + { + return; + } + TimeSeriesChunk chunk; + chunk.sourceId = m_sourceId; + chunk.quantity = m_quantity; + chunk.unit = m_unit; + chunk.startTimeSeconds = static_cast(m_firstCompletedSample) + * m_sampleIntervalSeconds; + chunk.sampleIntervalSeconds = m_sampleIntervalSeconds; + chunk.values.swap(m_completedValues); + const double chunkEndTime = static_cast( + m_firstCompletedSample + static_cast(chunk.values.size())) + * m_sampleIntervalSeconds; + int markerCount = 0; + while (markerCount < m_pendingMarkers.size() + && m_pendingMarkers[markerCount].timeSeconds < chunkEndTime) + { + ++markerCount; + } + if (markerCount > 0) + { + chunk.markers = m_pendingMarkers.mid(0, markerCount); + m_pendingMarkers.remove(0, markerCount); + } + chunk.totalInputEvents = m_totalInputEvents; + chunk.totalMarkers = m_totalMarkers; + m_firstCompletedSample += static_cast(chunk.values.size()); + m_readyChunks.append(std::move(chunk)); + } + + quint64 EventCountBinner::binForTick(quint64 tick) const + { + if (tick >= m_currentSampleTick && tick < m_nextSampleTick) + { + return m_currentSample; + } + return tick / m_ticksPerSample; + } + + void EventCountBinner::advanceToBin(quint64 targetBin) + { + if (targetBin <= m_currentSample) + { + return; + } + while (m_currentSample < targetBin) + { + appendCompletedValue(static_cast(m_currentEventCount)); + m_currentEventCount = 0; + ++m_currentSample; + } + m_currentSampleTick = m_currentSample * m_ticksPerSample; + m_nextSampleTick = m_currentSampleTick + m_ticksPerSample; + } + + SignalSource::SignalSource(QObject* parent) + : QObject(parent) + { + } + + SignalSource::~SignalSource() = default; +} + +namespace scopeone::core::internal +{ + SignalSourceManager::SignalSourceManager(QObject* parent) + : QObject(parent) + { + loadPlugins(); + } + + SignalSourceManager::~SignalSourceManager() + { + for (const QPointer& source : std::as_const(m_sources)) + { + delete source.data(); + } + m_sources.clear(); + m_loaders.clear(); + } + + QList SignalSourceManager::sources() const + { + QList result = m_descriptors.values(); + std::sort(result.begin(), result.end(), + [](const SignalSourceDescriptor& left, + const SignalSourceDescriptor& right) + { + return left.name.compare(right.name, Qt::CaseInsensitive) < 0; + }); + return result; + } + + bool SignalSourceManager::startTrace(const SignalAcquisitionConfig& config, + QString* errorMessage) + { + const QString sourceId = config.sourceId.trimmed(); + if (!m_descriptors.contains(sourceId)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Unknown signal source: %1").arg(sourceId); + } + return false; + } + SignalSource* source = sourceInstance(sourceId, errorMessage); + return source && source->start(config, errorMessage); + } + + void SignalSourceManager::stopTrace(const QString& sourceId) + { + if (SignalSource* source = m_sources.value(sourceId.trimmed())) + { + source->stop(); + } + } + + SignalSourceState SignalSourceManager::state(const QString& sourceId) const + { + const QString id = sourceId.trimmed(); + const QPointer source = m_sources.value(id); + return source ? source->state() : m_states.value(id, SignalSourceState::Idle); + } + + QString SignalSourceManager::stateMessage(const QString& sourceId) const + { + const QString id = sourceId.trimmed(); + const QPointer source = m_sources.value(id); + return source ? source->stateMessage() + : m_messages.value(id, QStringLiteral("Signal source is idle")); + } + + void SignalSourceManager::loadPlugins() + { + const QStringList directories = { + QDir(QCoreApplication::applicationDirPath()) + .filePath(QStringLiteral("plugins/hardware")), + QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)) + .filePath(QStringLiteral("plugins/hardware"))}; + + for (const QString& path : directories) + { + const QDir directory(path); + for (const QFileInfo& file : directory.entryInfoList(QDir::Files, QDir::Name)) + { + if (!QLibrary::isLibrary(file.absoluteFilePath())) + { + continue; + } + auto loader = std::make_unique(file.absoluteFilePath()); + PluginManifest manifest; + QString manifestError; + if (!parsePluginManifest( + loader->metaData().value(QStringLiteral("MetaData")).toObject(), + PluginKind::Hardware, + manifest, + &manifestError)) + { + qWarning().noquote() + << QStringLiteral("Failed to load signal source plugin %1: %2") + .arg(file.fileName(), manifestError); + continue; + } + auto* plugin = qobject_cast(loader->instance()); + if (!plugin) + { + qWarning().noquote() + << QStringLiteral("Failed to load signal source plugin %1: %2") + .arg(file.fileName(), loader->errorString()); + continue; + } + + for (const SignalSourceDescriptor& descriptor : plugin->signalSources()) + { + const QString sourceId = descriptor.id.trimmed(); + if (sourceId.isEmpty() || m_descriptors.contains(sourceId)) + { + continue; + } + m_descriptors.insert(sourceId, descriptor); + m_plugins.insert(sourceId, plugin); + m_states.insert(sourceId, SignalSourceState::Idle); + m_messages.insert(sourceId, QStringLiteral("Signal source is idle")); + } + m_loaders.push_back(std::move(loader)); + } + } + } + + SignalSource* SignalSourceManager::sourceInstance(const QString& sourceId, + QString* errorMessage) + { + const QString id = sourceId.trimmed(); + if (SignalSource* existing = m_sources.value(id)) + { + return existing; + } + SignalSourcePlugin* plugin = m_plugins.value(id); + if (!plugin) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Unknown signal source: %1").arg(id); + } + return nullptr; + } + SignalSource* source = plugin->createSignalSource(id, this); + if (!source) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Failed to create signal source: %1").arg(id); + } + return nullptr; + } + + connect(source, &SignalSource::timeSeriesReady, + this, &SignalSourceManager::timeSeriesReady); + connect(source, &SignalSource::timestampedEventsReady, + this, &SignalSourceManager::timestampedEventsReady); + connect(source, &SignalSource::stateChanged, + this, [this, id](SignalSourceState state, const QString& message) + { + m_states.insert(id, state); + m_messages.insert(id, message); + emit sourceStateChanged(id, state, message); + }); + connect(source, &SignalSource::sourceError, + this, [this, id](const QString& message) + { + emit sourceError(id, message); + }); + m_sources.insert(id, source); + return source; + } +} diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100644 index 0000000..68f767a --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.23) +project(ScopeOnePlugins LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +find_package(ScopeOneCore CONFIG REQUIRED) + +function(scopeone_add_plugin target kind) + set_target_properties(${target} PROPERTIES + PREFIX "" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/${kind}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/${kind}" + ) + install(TARGETS ${target} + RUNTIME DESTINATION "plugins/${kind}" + LIBRARY DESTINATION "plugins/${kind}" + ) +endfunction() + +add_subdirectory(NIDaqmx) +add_subdirectory(PtuFile) +add_subdirectory(ScanningDaqTool) +add_subdirectory(examples) diff --git a/plugins/NIDaqmx/CMakeLists.txt b/plugins/NIDaqmx/CMakeLists.txt new file mode 100644 index 0000000..92c4a2f --- /dev/null +++ b/plugins/NIDaqmx/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(ScopeOneNIDaqmx MODULE + NIDaqmxPlugin.cpp + NIDaqmxPlugin.h + plugin.json +) +set_target_properties(ScopeOneNIDaqmx PROPERTIES OUTPUT_NAME "NIDaqmxPlugin") +target_link_libraries(ScopeOneNIDaqmx PRIVATE + scopeone::PluginSDK + Qt6::Core +) +scopeone_add_plugin(ScopeOneNIDaqmx hardware) diff --git a/plugins/NIDaqmx/NIDaqmxPlugin.cpp b/plugins/NIDaqmx/NIDaqmxPlugin.cpp new file mode 100644 index 0000000..bbc0f49 --- /dev/null +++ b/plugins/NIDaqmx/NIDaqmxPlugin.cpp @@ -0,0 +1,873 @@ +#include "NIDaqmxPlugin.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace scopeone::plugins +{ + using namespace scopeone::core; + + namespace + { +#ifdef Q_OS_WIN +#define SCOPEONE_DAQMX_CALL __stdcall + constexpr auto kLibraryName = "nicaiu.dll"; +#else +#define SCOPEONE_DAQMX_CALL + constexpr auto kLibraryName = "libnidaqmx.so"; +#endif + + using Int32 = qint32; + using UInt32 = quint32; + using UInt64 = quint64; + using Bool32 = quint32; + using TaskHandle = void*; + + constexpr Int32 kRising = 10280; + constexpr Int32 kFalling = 10171; + constexpr Int32 kHertz = 10373; + constexpr Int32 kLow = 10214; + constexpr Int32 kContinuousSamples = 10123; + constexpr Int32 kFiniteSamples = 10178; + constexpr Int32 kDefaultTerminalConfiguration = -1; + constexpr Int32 kVolts = 10348; + constexpr Int32 kChannelForAllLines = 1; + constexpr Int32 kGroupByScanNumber = 1; + constexpr Int32 kDoNotInvert = 0; + constexpr Int32 kInvertPolarity = 1; + + struct DaqmxApi + { + using GetString = Int32 (SCOPEONE_DAQMX_CALL*)(char*, UInt32); + using GetDeviceString = Int32 (SCOPEONE_DAQMX_CALL*)(const char*, char*, UInt32); + using CreateTask = Int32 (SCOPEONE_DAQMX_CALL*)(const char*, TaskHandle*); + using ClearTask = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle); + using StartTask = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle); + using StopTask = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle); + using CreatePulse = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + const char*, + const char*, + Int32, + Int32, + double, + double, + double); + using CreatePulseTicks = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + const char*, + const char*, + const char*, + Int32, + UInt32, + UInt32, + UInt32); + using ConfigureImplicit = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, Int32, UInt64); + using ConfigureTrigger = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, const char*, Int32); + using SetChannelString = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, const char*, const char*); + using RouteTerminal = Int32 (SCOPEONE_DAQMX_CALL*)(const char*, const char*, Int32); + using DisconnectTerminal = Int32 (SCOPEONE_DAQMX_CALL*)(const char*, const char*); + using CreateAiVoltage = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + const char*, + const char*, + Int32, + double, + double, + Int32, + const char*); + using CreateAoVoltage = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + const char*, + const char*, + double, + double, + Int32, + const char*); + using CreateDigital = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + const char*, + const char*, + Int32); + using ConfigureSampleClock = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + const char*, + double, + Int32, + Int32, + UInt64); + using WriteAnalog = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + Int32, + Bool32, + double, + Int32, + const double*, + Int32*, + void*); + using WriteDigital = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + Int32, + Bool32, + double, + Int32, + const UInt32*, + Int32*, + void*); + using AvailableSamples = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, UInt32*); + using ReadAnalog = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + Int32, + double, + Int32, + double*, + UInt32, + Int32*, + void*); + using ReadDigital = Int32 (SCOPEONE_DAQMX_CALL*)(TaskHandle, + Int32, + double, + Int32, + UInt32*, + UInt32, + Int32*, + void*); + + QLibrary library{QString::fromLatin1(kLibraryName)}; + GetString getSystemDeviceNames{nullptr}; + GetString getExtendedErrorInfo{nullptr}; + GetDeviceString getProductType{nullptr}; + GetDeviceString getAiChannels{nullptr}; + GetDeviceString getAoChannels{nullptr}; + GetDeviceString getDiLines{nullptr}; + GetDeviceString getDoLines{nullptr}; + GetDeviceString getCiChannels{nullptr}; + GetDeviceString getCoChannels{nullptr}; + GetDeviceString getTerminals{nullptr}; + CreateTask createTask{nullptr}; + ClearTask clearTask{nullptr}; + StartTask startTask{nullptr}; + StopTask stopTask{nullptr}; + CreatePulse createPulse{nullptr}; + CreatePulseTicks createPulseTicks{nullptr}; + ConfigureImplicit configureImplicit{nullptr}; + ConfigureTrigger configureTrigger{nullptr}; + SetChannelString setChannelString{nullptr}; + RouteTerminal routeTerminal{nullptr}; + DisconnectTerminal disconnectTerminal{nullptr}; + CreateAiVoltage createAiVoltage{nullptr}; + CreateAoVoltage createAoVoltage{nullptr}; + CreateDigital createDi{nullptr}; + CreateDigital createDo{nullptr}; + ConfigureSampleClock configureSampleClock{nullptr}; + WriteAnalog writeAnalog{nullptr}; + WriteDigital writeDigital{nullptr}; + AvailableSamples availableSamples{nullptr}; + ReadAnalog readAnalog{nullptr}; + ReadDigital readDigital{nullptr}; + + template + bool resolve(Function& function, const char* name) + { + function = reinterpret_cast(library.resolve(name)); + return function != nullptr; + } + + bool load(bool controllerFunctions, QString* errorMessage = nullptr) + { + if (!library.isLoaded() && !library.load()) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("NI-DAQmx runtime is unavailable: %1") + .arg(library.errorString()); + } + return false; + } + bool ok = resolve(getSystemDeviceNames, "DAQmxGetSysDevNames") + && resolve(getExtendedErrorInfo, "DAQmxGetExtendedErrorInfo") + && resolve(getProductType, "DAQmxGetDevProductType") + && resolve(getAiChannels, "DAQmxGetDevAIPhysicalChans") + && resolve(getAoChannels, "DAQmxGetDevAOPhysicalChans") + && resolve(getDiLines, "DAQmxGetDevDILines") + && resolve(getDoLines, "DAQmxGetDevDOLines") + && resolve(getCiChannels, "DAQmxGetDevCIPhysicalChans") + && resolve(getCoChannels, "DAQmxGetDevCOPhysicalChans") + && resolve(getTerminals, "DAQmxGetDevTerminals"); + if (controllerFunctions) + { + ok = ok + && resolve(createTask, "DAQmxCreateTask") + && resolve(clearTask, "DAQmxClearTask") + && resolve(startTask, "DAQmxStartTask") + && resolve(stopTask, "DAQmxStopTask") + && resolve(createPulse, "DAQmxCreateCOPulseChanFreq") + && resolve(createPulseTicks, "DAQmxCreateCOPulseChanTicks") + && resolve(configureImplicit, "DAQmxCfgImplicitTiming") + && resolve(configureTrigger, "DAQmxCfgDigEdgeStartTrig") + && resolve(setChannelString, "DAQmxSetCOPulseTerm") + && resolve(routeTerminal, "DAQmxConnectTerms") + && resolve(disconnectTerminal, "DAQmxDisconnectTerms") + && resolve(createAiVoltage, "DAQmxCreateAIVoltageChan") + && resolve(createAoVoltage, "DAQmxCreateAOVoltageChan") + && resolve(createDi, "DAQmxCreateDIChan") + && resolve(createDo, "DAQmxCreateDOChan") + && resolve(configureSampleClock, "DAQmxCfgSampClkTiming") + && resolve(writeAnalog, "DAQmxWriteAnalogF64") + && resolve(writeDigital, "DAQmxWriteDigitalU32") + && resolve(availableSamples, "DAQmxGetReadAvailSampPerChan") + && resolve(readAnalog, "DAQmxReadAnalogF64") + && resolve(readDigital, "DAQmxReadDigitalU32"); + } + if (!ok && errorMessage) + { + *errorMessage = QStringLiteral("NI-DAQmx runtime is missing required functions"); + } + return ok; + } + + QString error(Int32 code) const + { + std::array buffer{}; + if (getExtendedErrorInfo) + { + getExtendedErrorInfo(buffer.data(), static_cast(buffer.size())); + } + const QString detail = QString::fromLocal8Bit(buffer.data()).trimmed(); + return detail.isEmpty() + ? QStringLiteral("NI-DAQmx error %1").arg(code) + : detail; + } + }; + + QStringList splitNames(const QByteArray& value) + { + QStringList names; + for (const QString& name : QString::fromLocal8Bit(value).split( + QLatin1Char(','), Qt::SkipEmptyParts)) + { + names.append(name.trimmed()); + } + return names; + } + + QString readSystemString(DaqmxApi::GetString function) + { + const Int32 size = function(nullptr, 0); + if (size <= 0) + { + return {}; + } + QByteArray buffer(size, '\0'); + return function(buffer.data(), static_cast(buffer.size())) < 0 + ? QString() + : QString::fromLocal8Bit(buffer.constData()); + } + + QString readDeviceString(DaqmxApi::GetDeviceString function, + const QString& device) + { + const QByteArray encoded = device.toLocal8Bit(); + const Int32 size = function(encoded.constData(), nullptr, 0); + if (size <= 0) + { + return {}; + } + QByteArray buffer(size, '\0'); + return function(encoded.constData(), buffer.data(), + static_cast(buffer.size())) < 0 + ? QString() + : QString::fromLocal8Bit(buffer.constData()); + } + + QString nativeDeviceId(const QString& id) + { + return id.startsWith(QStringLiteral("ni:")) ? id.mid(3) : id; + } + + QString absoluteTerminal(const QString& device, const QString& terminal) + { + const QString value = terminal.trimmed(); + if (value.isEmpty() || value.startsWith(QLatin1Char('/'))) + { + return value; + } + return value.startsWith(device + QLatin1Char('/'), Qt::CaseInsensitive) + ? QLatin1Char('/') + value + : QStringLiteral("/%1/%2").arg(device, value); + } + + class NIDaqmxController final : public DaqController + { + public: + explicit NIDaqmxController(QString device, QObject* parent) + : DaqController(parent) + , m_device(std::move(device)) + { + } + + ~NIDaqmxController() override + { + stop(); + } + + bool start(const DaqSessionConfig& config, + QString* errorMessage) override + { + if (m_state == DaqState::Armed || m_state == DaqState::Running) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("DAQ device is already active"); + } + return false; + } + + QString loadError; + if (!m_api.load(true, &loadError)) + { + return fail(loadError, errorMessage); + } + emitState(DaqState::Armed, QStringLiteral("Configuring hardware tasks")); + + for (const DaqTerminalRoute& route : config.routes) + { + const QByteArray source = absoluteTerminal(m_device, route.source).toLocal8Bit(); + const QByteArray destination = absoluteTerminal(m_device, route.destination).toLocal8Bit(); + const Int32 result = m_api.routeTerminal( + source.constData(), + destination.constData(), + route.inverted ? kInvertPolarity : kDoNotInvert); + if (result < 0) + { + return fail(m_api.error(result), errorMessage); + } + m_routes.push_back({source, destination}); + } + + QString taskError; + for (int index = 0; index < config.pulseTasks.size(); ++index) + { + if (!createPulseTask(config.pulseTasks[index], index, taskError)) + { + return fail(taskError, errorMessage); + } + } + for (int index = 0; index < config.analogTasks.size(); ++index) + { + if (!createAnalogTask(config.analogTasks[index], index, taskError)) + { + return fail(taskError, errorMessage); + } + } + for (int index = 0; index < config.digitalTasks.size(); ++index) + { + if (!createDigitalTask(config.digitalTasks[index], index, taskError)) + { + return fail(taskError, errorMessage); + } + } + if (m_tasks.empty()) + { + return fail(QStringLiteral("DAQ session has no tasks"), errorMessage); + } + + for (ActiveTask& task : m_tasks) + { + const Int32 result = m_api.startTask(task.handle); + if (result < 0) + { + return fail(m_api.error(result), errorMessage); + } + } + if (std::any_of(m_tasks.cbegin(), m_tasks.cend(), + [](const ActiveTask& task) + { + return task.inputKind != InputKind::None; + })) + { + m_reading.store(true); + m_reader = std::thread([this]() { readInputs(); }); + } + emitState(DaqState::Running, QStringLiteral("DAQ session is active")); + return true; + } + + void stop() override + { + releaseHardware(); + if (m_state != DaqState::Idle) + { + emitState(DaqState::Idle, QStringLiteral("DAQ device is idle")); + } + } + + DaqState state() const override + { + return m_state.load(); + } + + QString stateMessage() const override + { + return m_message; + } + + private: + enum class InputKind + { + None, + Analog, + Digital + }; + + struct ActiveTask + { + TaskHandle handle{nullptr}; + InputKind inputKind{InputKind::None}; + QString name; + QStringList channels; + quint64 nextSample{0}; + double sampleRateHz{0.0}; + }; + + bool configureTiming(TaskHandle handle, + const DaqTaskTiming& timing, + QString& errorMessage) + { + const QByteArray clock = absoluteTerminal(m_device, timing.sampleClock).toLocal8Bit(); + Int32 result = m_api.configureSampleClock( + handle, + clock.constData(), + timing.sampleRateHz, + timing.sampleEdge == DaqEdge::Rising ? kRising : kFalling, + timing.sampleMode == DaqSampleMode::Continuous + ? kContinuousSamples + : kFiniteSamples, + timing.samplesPerChannel); + if (result >= 0 && !timing.startTrigger.trimmed().isEmpty()) + { + const QByteArray trigger = absoluteTerminal(m_device, + timing.startTrigger).toLocal8Bit(); + result = m_api.configureTrigger( + handle, + trigger.constData(), + timing.startEdge == DaqEdge::Rising ? kRising : kFalling); + } + if (result < 0) + { + errorMessage = m_api.error(result); + return false; + } + return true; + } + + bool createPulseTask(const DaqPulseTaskConfig& config, + int index, + QString& errorMessage) + { + TaskHandle handle = nullptr; + const QString name = config.name.trimmed().isEmpty() + ? QStringLiteral("Pulse Task %1").arg(index + 1) + : config.name.trimmed(); + Int32 result = m_api.createTask(name.toLocal8Bit().constData(), &handle); + const QByteArray counter = absoluteTerminal(m_device, config.counter).toLocal8Bit(); + const QByteArray output = absoluteTerminal(m_device, config.outputTerminal).toLocal8Bit(); + if (result >= 0 && !config.timebaseSource.trimmed().isEmpty()) + { + const QByteArray timebase = absoluteTerminal(m_device, + config.timebaseSource).toLocal8Bit(); + result = m_api.createPulseTicks( + handle, + counter.constData(), + nullptr, + timebase.constData(), + kLow, + config.initialDelayTicks, + config.lowTicks, + config.highTicks); + } + else if (result >= 0) + { + result = m_api.createPulse( + handle, + counter.constData(), + nullptr, + kHertz, + kLow, + config.initialDelaySeconds, + config.frequencyHz, + config.dutyCycle); + } + if (result >= 0 && !config.outputTerminal.trimmed().isEmpty()) + { + result = m_api.setChannelString(handle, + counter.constData(), + output.constData()); + } + if (result >= 0) + { + result = m_api.configureImplicit( + handle, + kContinuousSamples, + 1000); + } + if (result >= 0 && !config.startTrigger.trimmed().isEmpty()) + { + const QByteArray trigger = absoluteTerminal(m_device, + config.startTrigger).toLocal8Bit(); + result = m_api.configureTrigger( + handle, + trigger.constData(), + config.startEdge == DaqEdge::Rising ? kRising : kFalling); + } + if (result < 0) + { + errorMessage = m_api.error(result); + if (handle) + { + m_api.clearTask(handle); + } + return false; + } + m_tasks.push_back({handle, + InputKind::None, + name, + {}, + 0, + 0.0}); + return true; + } + + bool createAnalogTask(const DaqAnalogTaskConfig& config, + int index, + QString& errorMessage) + { + TaskHandle handle = nullptr; + const QString name = config.name.trimmed().isEmpty() + ? QStringLiteral("Analog Task %1").arg(index + 1) + : config.name.trimmed(); + Int32 result = m_api.createTask(name.toLocal8Bit().constData(), &handle); + const QByteArray channels = config.channels.join(QLatin1Char(',')).toLocal8Bit(); + if (result >= 0 && config.direction == DaqTaskDirection::Input) + { + result = m_api.createAiVoltage( + handle, + channels.constData(), + "", + kDefaultTerminalConfiguration, + config.minimumVolts, + config.maximumVolts, + kVolts, + nullptr); + } + else if (result >= 0) + { + result = m_api.createAoVoltage( + handle, + channels.constData(), + "", + config.minimumVolts, + config.maximumVolts, + kVolts, + nullptr); + } + if (result >= 0 && !configureTiming(handle, config.timing, errorMessage)) + { + result = -1; + } + if (result >= 0 && config.direction == DaqTaskDirection::Output) + { + Int32 written = 0; + result = m_api.writeAnalog( + handle, + static_cast(config.timing.samplesPerChannel), + 0, + 10.0, + kGroupByScanNumber, + config.outputSamplesByScan.constData(), + &written, + nullptr); + } + if (result < 0) + { + if (errorMessage.isEmpty()) + { + errorMessage = m_api.error(result); + } + if (handle) + { + m_api.clearTask(handle); + } + return false; + } + m_tasks.push_back({handle, + config.direction == DaqTaskDirection::Input + ? InputKind::Analog + : InputKind::None, + name, + config.channels, + 0, + config.timing.sampleRateHz}); + return true; + } + + bool createDigitalTask(const DaqDigitalTaskConfig& config, + int index, + QString& errorMessage) + { + TaskHandle handle = nullptr; + const QString name = config.name.trimmed().isEmpty() + ? QStringLiteral("Digital Task %1").arg(index + 1) + : config.name.trimmed(); + Int32 result = m_api.createTask(name.toLocal8Bit().constData(), &handle); + const QByteArray lines = config.lines.join(QLatin1Char(',')).toLocal8Bit(); + if (result >= 0) + { + result = config.direction == DaqTaskDirection::Input + ? m_api.createDi( + handle, + lines.constData(), + "", + kChannelForAllLines) + : m_api.createDo( + handle, + lines.constData(), + "", + kChannelForAllLines); + } + if (result >= 0 && !configureTiming(handle, config.timing, errorMessage)) + { + result = -1; + } + if (result >= 0 && config.direction == DaqTaskDirection::Output) + { + Int32 written = 0; + result = m_api.writeDigital( + handle, + static_cast(config.timing.samplesPerChannel), + 0, + 10.0, + kGroupByScanNumber, + config.outputSamplesByScan.constData(), + &written, + nullptr); + } + if (result < 0) + { + if (errorMessage.isEmpty()) + { + errorMessage = m_api.error(result); + } + if (handle) + { + m_api.clearTask(handle); + } + return false; + } + m_tasks.push_back({handle, + config.direction == DaqTaskDirection::Input + ? InputKind::Digital + : InputKind::None, + name, + config.lines, + 0, + config.timing.sampleRateHz}); + return true; + } + + void readInputs() + { + constexpr UInt32 kSamplesPerRead = 65536; + while (m_reading.load()) + { + for (ActiveTask& task : m_tasks) + { + if (task.inputKind == InputKind::None) + { + continue; + } + UInt32 available = 0; + Int32 result = m_api.availableSamples(task.handle, &available); + if (result < 0) + { + reportReadError(m_api.error(result)); + return; + } + if (available == 0) + { + continue; + } + const UInt32 samples = std::min(available, kSamplesPerRead); + DaqInputChunk chunk; + chunk.deviceId = QStringLiteral("ni:%1").arg(m_device); + chunk.taskName = task.name; + chunk.channels = task.channels; + chunk.firstSample = task.nextSample; + chunk.nominalSampleRateHz = task.sampleRateHz; + Int32 samplesRead = 0; + if (task.inputKind == InputKind::Analog) + { + chunk.analogSamplesByScan.resize( + static_cast(samples * task.channels.size())); + result = m_api.readAnalog( + task.handle, + static_cast(samples), + 0.0, + kGroupByScanNumber, + chunk.analogSamplesByScan.data(), + static_cast(chunk.analogSamplesByScan.size()), + &samplesRead, + nullptr); + chunk.analogSamplesByScan.resize( + static_cast(samplesRead * task.channels.size())); + } + else + { + chunk.digitalSamplesByScan.resize(static_cast(samples)); + result = m_api.readDigital( + task.handle, + static_cast(samples), + 0.0, + kGroupByScanNumber, + chunk.digitalSamplesByScan.data(), + static_cast(chunk.digitalSamplesByScan.size()), + &samplesRead, + nullptr); + chunk.digitalSamplesByScan.resize(samplesRead); + } + if (result < 0) + { + reportReadError(m_api.error(result)); + return; + } + if (samplesRead > 0) + { + task.nextSample += static_cast(samplesRead); + emit inputDataReady(chunk); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } + + void releaseHardware() + { + m_reading.store(false); + if (m_reader.joinable()) + { + m_reader.join(); + } + for (auto it = m_tasks.rbegin(); it != m_tasks.rend(); ++it) + { + m_api.stopTask(it->handle); + m_api.clearTask(it->handle); + } + m_tasks.clear(); + for (auto it = m_routes.rbegin(); it != m_routes.rend(); ++it) + { + m_api.disconnectTerminal(it->first.constData(), it->second.constData()); + } + m_routes.clear(); + } + + void reportReadError(const QString& message) + { + QMetaObject::invokeMethod(this, [this, message]() + { + if (m_state == DaqState::Armed || m_state == DaqState::Running) + { + fail(message, nullptr); + } + }, Qt::QueuedConnection); + } + + bool fail(const QString& message, QString* errorMessage) + { + releaseHardware(); + if (errorMessage) + { + *errorMessage = message; + } + emitState(DaqState::Error, message); + emit controllerError(message); + return false; + } + + void emitState(DaqState state, const QString& message) + { + m_state = state; + m_message = message; + emit stateChanged(state, message); + } + + QString m_device; + DaqmxApi m_api; + std::vector m_tasks; + std::vector> m_routes; + std::atomic_bool m_reading{false}; + std::thread m_reader; + std::atomic m_state{DaqState::Idle}; + QString m_message{QStringLiteral("DAQ device is idle")}; + }; + + void appendChannels(DaqDeviceDescriptor& descriptor, + const QString& names, + DaqChannelType type) + { + for (const QString& name : splitNames(names.toLocal8Bit())) + { + descriptor.channels.append({name, type}); + } + } + } + + QList NIDaqmxPlugin::devices() const + { + DaqmxApi api; + if (!api.load(false)) + { + return {}; + } + + QList result; + for (const QString& device : splitNames( + readSystemString(api.getSystemDeviceNames).toLocal8Bit())) + { + DaqDeviceDescriptor descriptor; + descriptor.id = QStringLiteral("ni:%1").arg(device); + descriptor.name = device; + descriptor.provider = QStringLiteral("National Instruments"); + descriptor.product = readDeviceString(api.getProductType, device); + appendChannels(descriptor, + readDeviceString(api.getAiChannels, device), + DaqChannelType::AnalogInput); + appendChannels(descriptor, + readDeviceString(api.getAoChannels, device), + DaqChannelType::AnalogOutput); + appendChannels(descriptor, + readDeviceString(api.getDiLines, device), + DaqChannelType::DigitalInput); + appendChannels(descriptor, + readDeviceString(api.getDoLines, device), + DaqChannelType::DigitalOutput); + appendChannels(descriptor, + readDeviceString(api.getCiChannels, device), + DaqChannelType::CounterInput); + appendChannels(descriptor, + readDeviceString(api.getCoChannels, device), + DaqChannelType::CounterOutput); + descriptor.terminals = splitNames( + readDeviceString(api.getTerminals, device).toLocal8Bit()); + result.append(std::move(descriptor)); + } + return result; + } + + DaqController* NIDaqmxPlugin::createController(const QString& deviceId, + QObject* parent) + { + const QString device = nativeDeviceId(deviceId.trimmed()); + return new NIDaqmxController(device, parent); + } +} diff --git a/plugins/NIDaqmx/NIDaqmxPlugin.h b/plugins/NIDaqmx/NIDaqmxPlugin.h new file mode 100644 index 0000000..de9a5b5 --- /dev/null +++ b/plugins/NIDaqmx/NIDaqmxPlugin.h @@ -0,0 +1,22 @@ +#pragma once + +#include "scopeone/DaqDevice.h" + +#include + +namespace scopeone::plugins +{ + class NIDaqmxPlugin final : public QObject, + public scopeone::core::DaqDevicePlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID SCOPEONE_DAQ_DEVICE_PLUGIN_IID FILE "plugin.json") + Q_INTERFACES(scopeone::core::DaqDevicePlugin) + + public: + QList devices() const override; + scopeone::core::DaqController* createController( + const QString& deviceId, + QObject* parent = nullptr) override; + }; +} diff --git a/plugins/NIDaqmx/plugin.json b/plugins/NIDaqmx/plugin.json new file mode 100644 index 0000000..35e6102 --- /dev/null +++ b/plugins/NIDaqmx/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "scopeone.nidaqmx", + "name": "NI-DAQmx Device Plugin", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "hardware" +} diff --git a/plugins/PtuFile/CMakeLists.txt b/plugins/PtuFile/CMakeLists.txt new file mode 100644 index 0000000..eb7394a --- /dev/null +++ b/plugins/PtuFile/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(ScopeOnePtuFile MODULE + PtuFilePlugin.cpp + PtuFilePlugin.h + plugin.json +) +set_target_properties(ScopeOnePtuFile PROPERTIES OUTPUT_NAME "PtuFilePlugin") +target_link_libraries(ScopeOnePtuFile PRIVATE + scopeone::PluginSDK + Qt6::Core +) +scopeone_add_plugin(ScopeOnePtuFile hardware) diff --git a/plugins/PtuFile/PtuFilePlugin.cpp b/plugins/PtuFile/PtuFilePlugin.cpp new file mode 100644 index 0000000..dc8fbbe --- /dev/null +++ b/plugins/PtuFile/PtuFilePlugin.cpp @@ -0,0 +1,593 @@ +#include "PtuFilePlugin.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace scopeone::plugins +{ + using namespace scopeone::core; + + namespace + { + constexpr auto kSourceId = "ptu:file"; + constexpr qsizetype kTagSize = 48; + constexpr qsizetype kRecordsPerChunk = 65536; + + constexpr quint32 kTagEmpty = 0xffff0008U; + constexpr quint32 kTagBool = 0x00000008U; + constexpr quint32 kTagInt = 0x10000008U; + constexpr quint32 kTagBitSet = 0x11000008U; + constexpr quint32 kTagColor = 0x12000008U; + constexpr quint32 kTagFloat = 0x20000008U; + constexpr quint32 kTagDateTime = 0x21000008U; + constexpr quint32 kTagFloatArray = 0x2001ffffU; + constexpr quint32 kTagAnsiString = 0x4001ffffU; + constexpr quint32 kTagWideString = 0x4002ffffU; + constexpr quint32 kTagBinaryBlob = 0xffffffffU; + + constexpr quint32 kPicoHarpT3 = 0x00010303U; + constexpr quint32 kPicoHarpT2 = 0x00010203U; + constexpr quint32 kHydraHarpT3 = 0x00010304U; + constexpr quint32 kHydraHarpT2 = 0x00010204U; + constexpr quint32 kHydraHarp2T3 = 0x01010304U; + constexpr quint32 kHydraHarp2T2 = 0x01010204U; + constexpr quint32 kTimeHarp260NT3 = 0x00010305U; + constexpr quint32 kTimeHarp260NT2 = 0x00010205U; + constexpr quint32 kTimeHarp260PT3 = 0x00010306U; + constexpr quint32 kTimeHarp260PT2 = 0x00010206U; + constexpr quint32 kGenericT3 = 0x00010307U; + constexpr quint32 kGenericT2 = 0x00010207U; + + enum class Decoder + { + PicoT2, + PicoT3, + HydraT2V1, + HydraT3V1, + HydraT2V2, + HydraT3V2 + }; + + struct PtuHeader + { + quint64 recordCount{0}; + quint32 recordType{0}; + double globalResolutionSeconds{0.0}; + Decoder decoder{Decoder::PicoT2}; + }; + + struct PtuSettings + { + QString filePath; + int detectorChannel{1}; + double sampleIntervalSeconds{0.01}; + bool publishEvents{false}; + }; + + QString tagIdentifier(const QByteArray& bytes) + { + const int terminator = bytes.indexOf('\0'); + return QString::fromLatin1(terminator >= 0 ? bytes.first(terminator) : bytes); + } + + double tagDouble(qint64 value) + { + const quint64 bits = static_cast(value); + double result = 0.0; + std::memcpy(&result, &bits, sizeof(result)); + return result; + } + + bool decoderForRecordType(quint32 recordType, Decoder& decoder) + { + switch (recordType) + { + case kPicoHarpT2: + decoder = Decoder::PicoT2; + return true; + case kPicoHarpT3: + decoder = Decoder::PicoT3; + return true; + case kHydraHarpT2: + decoder = Decoder::HydraT2V1; + return true; + case kHydraHarpT3: + decoder = Decoder::HydraT3V1; + return true; + case kHydraHarp2T2: + case kTimeHarp260NT2: + case kTimeHarp260PT2: + case kGenericT2: + decoder = Decoder::HydraT2V2; + return true; + case kHydraHarp2T3: + case kTimeHarp260NT3: + case kTimeHarp260PT3: + case kGenericT3: + decoder = Decoder::HydraT3V2; + return true; + default: + return false; + } + } + + bool skipTagPayload(QFile& file, qint64 byteCount, QString& errorMessage) + { + if (byteCount < 0 || byteCount > file.size() - file.pos() + || !file.seek(file.pos() + byteCount)) + { + errorMessage = QStringLiteral("Invalid PTU tag payload length"); + return false; + } + return true; + } + + bool readHeader(QFile& file, PtuHeader& header, QString& errorMessage) + { + const QByteArray magic = file.read(8); + const QByteArray version = file.read(8); + if (magic.size() != 8 || version.size() != 8 + || !magic.startsWith("PQTTTR")) + { + errorMessage = QStringLiteral("The selected file is not a PTU file"); + return false; + } + + qint64 recordCount = -1; + qint64 recordType = -1; + double globalResolution = 0.0; + while (true) + { + const QByteArray rawTag = file.read(kTagSize); + if (rawTag.size() != kTagSize) + { + errorMessage = QStringLiteral("Incomplete PTU header"); + return false; + } + const auto* bytes = reinterpret_cast(rawTag.constData()); + const QString identifier = tagIdentifier(rawTag.first(32)); + const quint32 type = qFromLittleEndian(bytes + 36); + const qint64 value = qFromLittleEndian(bytes + 40); + if (identifier == QStringLiteral("TTResult_NumberOfRecords")) + { + recordCount = value; + } + else if (identifier == QStringLiteral("TTResultFormat_TTTRRecType")) + { + recordType = value; + } + else if (identifier == QStringLiteral("MeasDesc_GlobalResolution")) + { + globalResolution = tagDouble(value); + } + + if (type == kTagFloatArray || type == kTagAnsiString + || type == kTagWideString || type == kTagBinaryBlob) + { + if (!skipTagPayload(file, value, errorMessage)) + { + return false; + } + } + else if (type != kTagEmpty && type != kTagBool && type != kTagInt + && type != kTagBitSet && type != kTagColor + && type != kTagFloat && type != kTagDateTime) + { + errorMessage = QStringLiteral("Unsupported PTU tag type 0x%1") + .arg(type, 8, 16, QLatin1Char('0')); + return false; + } + if (identifier == QStringLiteral("Header_End")) + { + break; + } + } + + Decoder decoder; + if (recordCount < 0 || recordType < 0 + || !std::isfinite(globalResolution) + || globalResolution <= 0.0 + || !decoderForRecordType(static_cast(recordType), decoder)) + { + errorMessage = QStringLiteral("Unsupported or incomplete PTU measurement header"); + return false; + } + header.recordCount = static_cast(recordCount); + header.recordType = static_cast(recordType); + header.globalResolutionSeconds = globalResolution; + header.decoder = decoder; + return true; + } + + SignalSourceDescriptor sourceDescriptor() + { + SignalSourceDescriptor descriptor; + descriptor.id = QString::fromLatin1(kSourceId); + descriptor.name = QStringLiteral("PTU File"); + descriptor.provider = QStringLiteral("PicoQuant"); + descriptor.quantity = QStringLiteral("Photon count"); + descriptor.unit = QStringLiteral("photons"); + descriptor.streamType = SignalStreamType::TimestampedEvents; + + SignalParameterDescriptor file; + file.key = QStringLiteral("filePath"); + file.name = QStringLiteral("PTU file"); + file.type = SignalParameterType::File; + file.fileFilter = QStringLiteral("PicoQuant PTU files (*.ptu);;All files (*)"); + descriptor.parameters.append(file); + + SignalParameterDescriptor channel; + channel.key = QStringLiteral("detectorChannel"); + channel.name = QStringLiteral("Detector channel"); + channel.type = SignalParameterType::Integer; + channel.defaultValue = 1; + channel.hasRange = true; + channel.minimum = 0; + channel.maximum = 64; + descriptor.parameters.append(channel); + return descriptor; + } + + class PtuFileSource final : public SignalSource + { + public: + explicit PtuFileSource(QObject* parent = nullptr) + : SignalSource(parent) + { + } + + ~PtuFileSource() override + { + std::lock_guard lock(m_threadMutex); + if (m_worker.joinable()) + { + m_worker.request_stop(); + m_worker.join(); + } + } + + bool start(const SignalAcquisitionConfig& config, + QString* errorMessage) override + { + const QString filePath = config.sourceSettings + .value(QStringLiteral("filePath")).toString().trimmed(); + if (config.sourceId.trimmed() != QString::fromLatin1(kSourceId) + || !QFileInfo::exists(filePath) + || !QFileInfo(filePath).isFile()) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Select an existing PTU file"); + } + return false; + } + + std::lock_guard lock(m_threadMutex); + if (m_state == SignalSourceState::Starting + || m_state == SignalSourceState::Running + || m_state == SignalSourceState::Stopping) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Signal source is already active"); + } + return false; + } + if (m_worker.joinable()) + { + m_worker.join(); + } + + PtuSettings settings; + settings.filePath = filePath; + settings.detectorChannel = config.sourceSettings + .value(QStringLiteral("detectorChannel"), 1).toInt(); + settings.sampleIntervalSeconds = config.sampleIntervalSeconds; + settings.publishEvents = config.publishTimestampedEvents + || config.scanImage.enabled; + setState(SignalSourceState::Starting, QStringLiteral("Opening PTU file")); + m_worker = std::jthread([this, settings](std::stop_token stopToken) + { + run(stopToken, settings); + }); + return true; + } + + void stop() override + { + std::lock_guard lock(m_threadMutex); + if (m_state == SignalSourceState::Starting + || m_state == SignalSourceState::Running) + { + setState(SignalSourceState::Stopping, QStringLiteral("Stopping PTU read")); + m_worker.request_stop(); + } + } + + SignalSourceState state() const override + { + return m_state.load(); + } + + QString stateMessage() const override + { + std::lock_guard lock(m_stateMutex); + return m_stateMessage; + } + + private: + void setState(SignalSourceState state, const QString& message) + { + { + std::lock_guard lock(m_stateMutex); + m_stateMessage = message; + m_state.store(state); + } + emit stateChanged(state, message); + } + + void fail(const QString& message) + { + setState(SignalSourceState::Error, message); + emit sourceError(message); + } + + void run(std::stop_token stopToken, const PtuSettings& settings) + { + QFile file(settings.filePath); + if (!file.open(QIODevice::ReadOnly)) + { + fail(QStringLiteral("Failed to open PTU file: %1").arg(file.errorString())); + return; + } + + PtuHeader header; + QString errorMessage; + if (!readHeader(file, header, errorMessage)) + { + fail(errorMessage); + return; + } + setState(SignalSourceState::Running, + QStringLiteral("Reading %1 PTU records").arg(header.recordCount)); + + EventCountBinner binner(QString::fromLatin1(kSourceId), + QStringLiteral("Photon count"), + QStringLiteral("photons"), + header.globalResolutionSeconds, + settings.sampleIntervalSeconds); + quint64 overflowCorrection = 0; + quint64 processedRecords = 0; + quint64 photonCount = 0; + quint64 markerCount = 0; + quint64 lastTick = 0; + bool hasTick = false; + + while (processedRecords < header.recordCount + && !stopToken.stop_requested()) + { + const qsizetype count = static_cast(std::min( + kRecordsPerChunk, header.recordCount - processedRecords)); + const QByteArray rawRecords = file.read(count * 4); + if (rawRecords.size() != count * 4) + { + fail(QStringLiteral("Unexpected end of PTU record data")); + return; + } + + TimestampedEventChunk events; + if (settings.publishEvents) + { + events.sourceId = QString::fromLatin1(kSourceId); + events.tickPeriodSeconds = header.globalResolutionSeconds; + events.eventTicks.reserve(count); + events.eventCodes.reserve(count); + } + + const auto addPhoton = [&](quint64 tick, quint32 channel) + { + lastTick = tick; + hasTick = true; + if (static_cast(channel) == settings.detectorChannel) + { + binner.addEvent(tick); + ++photonCount; + if (settings.publishEvents) + { + events.eventTicks.append(tick); + events.eventCodes.append(channel); + } + } + else + { + binner.advanceToTick(tick); + } + }; + const auto addMarker = [&](quint64 tick, quint32 code) + { + lastTick = tick; + hasTick = true; + binner.addMarker(tick, code); + ++markerCount; + if (settings.publishEvents) + { + events.markerTicks.append(tick); + events.markerCodes.append(code); + } + }; + const auto advanceOverflow = [&](quint64 tick) + { + lastTick = tick; + hasTick = true; + binner.advanceToTick(tick); + }; + + const auto* bytes = reinterpret_cast(rawRecords.constData()); + for (qsizetype index = 0; index < count; ++index) + { + const quint32 record = qFromLittleEndian(bytes + index * 4); + switch (header.decoder) + { + case Decoder::PicoT2: + { + constexpr quint64 wraparound = 210698240ULL; + const quint32 channel = record >> 28; + const quint32 time = record & 0x0fffffffU; + if (channel != 0x0fU) + { + addPhoton(overflowCorrection + time, channel); + } + else if ((time & 0x0fU) == 0) + { + overflowCorrection += wraparound; + advanceOverflow(overflowCorrection); + } + else + { + addMarker(overflowCorrection + (time & 0x0ffffff0U), + time & 0x0fU); + } + break; + } + case Decoder::PicoT3: + { + constexpr quint64 wraparound = 65536ULL; + const quint32 channel = record >> 28; + const quint32 nsync = record & 0x0000ffffU; + const quint32 dtime = (record >> 16) & 0x00000fffU; + if (channel != 0x0fU) + { + addPhoton(overflowCorrection + nsync, channel); + } + else if (dtime == 0) + { + overflowCorrection += wraparound; + advanceOverflow(overflowCorrection); + } + else + { + addMarker(overflowCorrection + nsync, dtime); + } + break; + } + case Decoder::HydraT2V1: + case Decoder::HydraT2V2: + { + constexpr quint64 wraparoundV1 = 33552000ULL; + constexpr quint64 wraparoundV2 = 33554432ULL; + const bool version1 = header.decoder == Decoder::HydraT2V1; + const bool special = (record & 0x80000000U) != 0; + const quint32 channel = (record >> 25) & 0x3fU; + const quint32 time = record & 0x01ffffffU; + if (!special) + { + addPhoton(overflowCorrection + time, channel + 1); + } + else if (channel == 0x3fU) + { + const quint64 overflows = version1 || time == 0 ? 1 : time; + overflowCorrection += overflows + * (version1 ? wraparoundV1 : wraparoundV2); + advanceOverflow(overflowCorrection); + } + else if (channel >= 1 && channel <= 15) + { + addMarker(overflowCorrection + time, channel); + } + else if (channel == 0) + { + addPhoton(overflowCorrection + time, 0); + } + break; + } + case Decoder::HydraT3V1: + case Decoder::HydraT3V2: + { + constexpr quint64 wraparound = 1024ULL; + const bool version1 = header.decoder == Decoder::HydraT3V1; + const bool special = (record & 0x80000000U) != 0; + const quint32 channel = (record >> 25) & 0x3fU; + const quint32 nsync = record & 0x000003ffU; + if (!special) + { + addPhoton(overflowCorrection + nsync, channel); + } + else if (channel == 0x3fU) + { + const quint64 overflows = version1 || nsync == 0 ? 1 : nsync; + overflowCorrection += overflows * wraparound; + advanceOverflow(overflowCorrection); + } + else if (channel >= 1 && channel <= 15) + { + addMarker(overflowCorrection + nsync, channel); + } + break; + } + } + if (binner.hasReadyChunks()) + { + for (const TimeSeriesChunk& chunk : binner.takeReadyChunks()) + { + emit timeSeriesReady(chunk); + } + } + } + processedRecords += static_cast(count); + if (events.isValid()) + { + emit timestampedEventsReady(events); + } + } + + if (stopToken.stop_requested()) + { + setState(SignalSourceState::Idle, QStringLiteral("PTU read stopped")); + return; + } + if (hasTick) + { + binner.advanceToElapsedSeconds( + static_cast(lastTick) * header.globalResolutionSeconds + + settings.sampleIntervalSeconds); + } + for (const TimeSeriesChunk& chunk : binner.takeCompletedChunks()) + { + emit timeSeriesReady(chunk); + } + setState(SignalSourceState::Idle, + QStringLiteral("PTU read complete: %1 photons, %2 markers") + .arg(photonCount) + .arg(markerCount)); + } + + mutable std::mutex m_stateMutex; + std::mutex m_threadMutex; + std::jthread m_worker; + std::atomic m_state{SignalSourceState::Idle}; + QString m_stateMessage{QStringLiteral("PTU file source is idle")}; + }; + } + + QList PtuFilePlugin::signalSources() const + { + return {sourceDescriptor()}; + } + + SignalSource* PtuFilePlugin::createSignalSource(const QString& sourceId, + QObject* parent) + { + return sourceId.trimmed() == QString::fromLatin1(kSourceId) + ? new PtuFileSource(parent) + : nullptr; + } +} diff --git a/plugins/PtuFile/PtuFilePlugin.h b/plugins/PtuFile/PtuFilePlugin.h new file mode 100644 index 0000000..21aa99b --- /dev/null +++ b/plugins/PtuFile/PtuFilePlugin.h @@ -0,0 +1,22 @@ +#pragma once + +#include "scopeone/SignalSource.h" + +#include + +namespace scopeone::plugins +{ + class PtuFilePlugin final : public QObject, + public scopeone::core::SignalSourcePlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID SCOPEONE_SIGNAL_SOURCE_PLUGIN_IID FILE "plugin.json") + Q_INTERFACES(scopeone::core::SignalSourcePlugin) + + public: + QList signalSources() const override; + scopeone::core::SignalSource* createSignalSource( + const QString& sourceId, + QObject* parent = nullptr) override; + }; +} diff --git a/plugins/PtuFile/plugin.json b/plugins/PtuFile/plugin.json new file mode 100644 index 0000000..bf13605 --- /dev/null +++ b/plugins/PtuFile/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "scopeone.ptu-file", + "name": "PicoQuant PTU Source", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "hardware" +} diff --git a/plugins/ScanningDaqTool/CMakeLists.txt b/plugins/ScanningDaqTool/CMakeLists.txt new file mode 100644 index 0000000..54ecbb4 --- /dev/null +++ b/plugins/ScanningDaqTool/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(ScopeOneScanningDaqTool MODULE + DaqControlWidget.cpp + DaqControlWidget.h + SignalMonitorWidget.cpp + SignalMonitorWidget.h + ScanningDaqToolPlugin.cpp + plugin.json +) +set_target_properties(ScopeOneScanningDaqTool PROPERTIES OUTPUT_NAME "ScanningDaqTool") +target_link_libraries(ScopeOneScanningDaqTool PRIVATE + scopeone::PluginSDK + Qt6::Widgets +) +scopeone_add_plugin(ScopeOneScanningDaqTool tools) diff --git a/plugins/ScanningDaqTool/DaqControlWidget.cpp b/plugins/ScanningDaqTool/DaqControlWidget.cpp new file mode 100644 index 0000000..2bb0521 --- /dev/null +++ b/plugins/ScanningDaqTool/DaqControlWidget.cpp @@ -0,0 +1,827 @@ +#include "DaqControlWidget.h" + +#include "scopeone/ScopeOneCore.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace scopeone::plugins +{ + namespace + { + QComboBox* editableCombo(const QStringList& values, QWidget* parent) + { + auto* combo = new QComboBox(parent); + combo->setEditable(true); + combo->addItem(QString()); + combo->addItems(values); + return combo; + } + + QDoubleSpinBox* realSpin(double minimum, + double maximum, + double value, + int decimals, + const QString& suffix, + QWidget* parent) + { + auto* spin = new QDoubleSpinBox(parent); + spin->setRange(minimum, maximum); + spin->setDecimals(decimals); + spin->setValue(value); + spin->setSuffix(suffix); + return spin; + } + + QStringList commaSeparated(const QString& text) + { + QStringList result; + for (const QString& item : text.split(QLatin1Char(','), Qt::SkipEmptyParts)) + { + const QString trimmed = item.trimmed(); + if (!trimmed.isEmpty()) + { + result.append(trimmed); + } + } + return result; + } + } + + DaqControlWidget::DaqControlWidget(scopeone::core::ScopeOneCore* core, + QWidget* parent) + : QWidget(parent) + , m_core(core) + { + auto* layout = new QVBoxLayout(this); + auto* deviceForm = new QFormLayout(); + m_deviceCombo = new QComboBox(this); + deviceForm->addRow(tr("Device"), m_deviceCombo); + m_productLabel = new QLabel(this); + m_productLabel->setWordWrap(true); + deviceForm->addRow(tr("Hardware"), m_productLabel); + m_resourcesLabel = new QLabel(this); + m_resourcesLabel->setWordWrap(true); + deviceForm->addRow(tr("Resources"), m_resourcesLabel); + layout->addLayout(deviceForm); + + m_rasterGroup = new QGroupBox(tr("Raster scan timing"), this); + m_rasterGroup->setCheckable(true); + m_rasterGroup->setChecked(false); + auto* rasterForm = new QFormLayout(m_rasterGroup); + m_lineClockCombo = editableCombo({}, m_rasterGroup); + rasterForm->addRow(tr("Line clock input"), m_lineClockCombo); + m_lineRateSpin = realSpin(0.001, 100000000.0, 1000.0, 3, + tr(" Hz"), m_rasterGroup); + rasterForm->addRow(tr("Nominal line rate"), m_lineRateSpin); + m_activeLinesSpin = new QSpinBox(m_rasterGroup); + m_activeLinesSpin->setRange(2, 1000000); + m_activeLinesSpin->setValue(512); + rasterForm->addRow(tr("Active lines"), m_activeLinesSpin); + m_flybackLinesSpin = new QSpinBox(m_rasterGroup); + m_flybackLinesSpin->setRange(2, 1000000); + m_flybackLinesSpin->setValue(16); + rasterForm->addRow(tr("Flyback lines"), m_flybackLinesSpin); + m_yChannelCombo = new QComboBox(m_rasterGroup); + rasterForm->addRow(tr("Y analog output"), m_yChannelCombo); + m_yStartSpin = realSpin(-1000.0, 1000.0, -1.0, 6, + tr(" V"), m_rasterGroup); + rasterForm->addRow(tr("Y start"), m_yStartSpin); + m_yEndSpin = realSpin(-1000.0, 1000.0, 1.0, 6, + tr(" V"), m_rasterGroup); + rasterForm->addRow(tr("Y end"), m_yEndSpin); + m_frameCounterCombo = new QComboBox(m_rasterGroup); + rasterForm->addRow(tr("Frame counter"), m_frameCounterCombo); + m_lineOutputCombo = editableCombo({}, m_rasterGroup); + rasterForm->addRow(tr("Line output"), m_lineOutputCombo); + m_frameOutputCombo = editableCombo({}, m_rasterGroup); + rasterForm->addRow(tr("Frame output"), m_frameOutputCombo); + layout->addWidget(m_rasterGroup); + + layout->addWidget(new QLabel(tr("Counter pulse tasks"), this)); + m_pulseTable = new QTableWidget(0, 7, this); + m_pulseTable->setHorizontalHeaderLabels( + {tr("Counter"), tr("Output terminal"), tr("Frequency"), tr("Duty"), tr("Delay"), + tr("Start trigger"), tr("Edge")}); + m_pulseTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + m_pulseTable->horizontalHeader()->setStretchLastSection(true); + m_pulseTable->verticalHeader()->hide(); + m_pulseTable->setSelectionBehavior(QAbstractItemView::SelectRows); + layout->addWidget(m_pulseTable); + + auto* pulseButtons = new QHBoxLayout(); + m_addPulseButton = new QPushButton(tr("Add pulse"), this); + m_removePulseButton = new QPushButton(tr("Remove"), this); + pulseButtons->addWidget(m_addPulseButton); + pulseButtons->addWidget(m_removePulseButton); + pulseButtons->addStretch(); + layout->addLayout(pulseButtons); + + layout->addWidget(new QLabel(tr("Buffered hardware tasks"), this)); + m_bufferedTable = new QTableWidget(0, 12, this); + m_bufferedTable->setHorizontalHeaderLabels( + {tr("Type"), tr("Channels"), tr("Minimum"), tr("Maximum"), + tr("Sample clock"), tr("Clock edge"), tr("Rate"), tr("Samples"), + tr("Mode"), tr("Start trigger"), tr("Trigger edge"), + tr("Output data")}); + m_bufferedTable->horizontalHeader()->setSectionResizeMode( + QHeaderView::ResizeToContents); + m_bufferedTable->horizontalHeader()->setStretchLastSection(true); + m_bufferedTable->verticalHeader()->hide(); + m_bufferedTable->setSelectionBehavior(QAbstractItemView::SelectRows); + layout->addWidget(m_bufferedTable); + + auto* bufferedButtons = new QHBoxLayout(); + m_addBufferedButton = new QPushButton(tr("Add task"), this); + m_removeBufferedButton = new QPushButton(tr("Remove"), this); + bufferedButtons->addWidget(m_addBufferedButton); + bufferedButtons->addWidget(m_removeBufferedButton); + bufferedButtons->addStretch(); + layout->addLayout(bufferedButtons); + + layout->addWidget(new QLabel(tr("Terminal routes"), this)); + m_routeTable = new QTableWidget(0, 3, this); + m_routeTable->setHorizontalHeaderLabels( + {tr("Source"), tr("Destination"), tr("Polarity")}); + m_routeTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_routeTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + m_routeTable->verticalHeader()->hide(); + m_routeTable->setSelectionBehavior(QAbstractItemView::SelectRows); + layout->addWidget(m_routeTable); + + auto* routeButtons = new QHBoxLayout(); + m_addRouteButton = new QPushButton(tr("Add route"), this); + m_removeRouteButton = new QPushButton(tr("Remove"), this); + routeButtons->addWidget(m_addRouteButton); + routeButtons->addWidget(m_removeRouteButton); + routeButtons->addStretch(); + layout->addLayout(routeButtons); + + auto* runButtons = new QHBoxLayout(); + m_startButton = new QPushButton(tr("Arm and start"), this); + m_startButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + m_stopButton = new QPushButton(tr("Stop"), this); + m_stopButton->setIcon(style()->standardIcon(QStyle::SP_MediaStop)); + m_stopButton->setEnabled(false); + runButtons->addWidget(m_startButton); + runButtons->addWidget(m_stopButton); + layout->addLayout(runButtons); + + m_statusLabel = new QLabel(tr("No DAQ devices available"), this); + m_statusLabel->setWordWrap(true); + layout->addWidget(m_statusLabel); + + for (const auto& device : m_core->daqDevices()) + { + m_deviceCombo->addItem(device.name, device.id); + m_deviceCombo->setItemData( + m_deviceCombo->count() - 1, + QStringLiteral("%1, %2").arg(device.provider, device.product), + Qt::ToolTipRole); + } + + connect(m_deviceCombo, &QComboBox::currentIndexChanged, + this, &DaqControlWidget::refreshDevice); + connect(m_addPulseButton, &QPushButton::clicked, + this, &DaqControlWidget::addPulseRow); + connect(m_removePulseButton, &QPushButton::clicked, this, [this]() + { + removeSelectedRow(m_pulseTable); + }); + connect(m_addBufferedButton, &QPushButton::clicked, + this, &DaqControlWidget::addBufferedRow); + connect(m_removeBufferedButton, &QPushButton::clicked, this, [this]() + { + removeSelectedRow(m_bufferedTable); + }); + connect(m_addRouteButton, &QPushButton::clicked, + this, &DaqControlWidget::addRouteRow); + connect(m_removeRouteButton, &QPushButton::clicked, this, [this]() + { + removeSelectedRow(m_routeTable); + }); + connect(m_startButton, &QPushButton::clicked, + this, &DaqControlWidget::startSession); + connect(m_stopButton, &QPushButton::clicked, this, [this]() + { + m_core->stopDaqSession(m_activeDeviceId); + }); + connect(m_core, &scopeone::core::ScopeOneCore::daqStateChanged, + this, &DaqControlWidget::handleStateChanged); + connect(m_core, &scopeone::core::ScopeOneCore::daqError, + this, [](const QString&, const QString& message) + { + qCritical().noquote() << message; + }); + connect(m_core, &scopeone::core::ScopeOneCore::daqInputDataReady, + this, [this](const scopeone::core::DaqInputChunk& chunk) + { + if (chunk.deviceId != m_activeDeviceId) + { + return; + } + if (m_inputStatusTimer.isValid() + && m_inputStatusTimer.elapsed() < 100) + { + return; + } + m_inputStatusTimer.restart(); + const qsizetype values = !chunk.analogSamplesByScan.isEmpty() + ? chunk.analogSamplesByScan.size() + : chunk.digitalSamplesByScan.size(); + m_statusLabel->setText( + tr("%1: received %2 values").arg(chunk.taskName).arg(values)); + }); + + refreshDevice(); + } + + scopeone::core::DaqDeviceDescriptor DaqControlWidget::currentDevice() const + { + const QString id = m_deviceCombo->currentData().toString(); + for (const auto& device : m_core->daqDevices()) + { + if (device.id == id) + { + return device; + } + } + return {}; + } + + QStringList DaqControlWidget::terminalChoices( + const scopeone::core::DaqDeviceDescriptor& device) const + { + QStringList choices = device.terminals; + choices.removeDuplicates(); + std::sort(choices.begin(), choices.end(), + [](const QString& left, const QString& right) + { + return left.compare(right, Qt::CaseInsensitive) < 0; + }); + return choices; + } + + void DaqControlWidget::refreshDevice() + { + m_pulseTable->setRowCount(0); + m_bufferedTable->setRowCount(0); + m_routeTable->setRowCount(0); + const auto device = currentDevice(); + const bool available = !device.id.isEmpty(); + const QStringList terminals = terminalChoices(device); + for (QComboBox* combo : {m_lineClockCombo, m_lineOutputCombo, + m_frameOutputCombo}) + { + combo->clear(); + combo->addItem(QString()); + combo->addItems(terminals); + } + m_yChannelCombo->clear(); + m_frameCounterCombo->clear(); + for (const auto& channel : device.channels) + { + if (channel.type == scopeone::core::DaqChannelType::AnalogOutput) + { + m_yChannelCombo->addItem(channel.physicalName); + } + else if (channel.type == scopeone::core::DaqChannelType::CounterOutput) + { + m_frameCounterCombo->addItem(channel.physicalName); + } + } + m_productLabel->setText(available + ? QStringLiteral("%1, %2") + .arg(device.provider, device.product) + : tr("DAQ device not found")); + if (available) + { + int counts[6]{}; + QStringList channelNames; + for (const auto& channel : device.channels) + { + ++counts[static_cast(channel.type)]; + channelNames.append(channel.physicalName); + } + m_resourcesLabel->setText( + tr("AI %1 AO %2 DI %3 DO %4 CI %5 CO %6 Terminals %7") + .arg(counts[static_cast(scopeone::core::DaqChannelType::AnalogInput)]) + .arg(counts[static_cast(scopeone::core::DaqChannelType::AnalogOutput)]) + .arg(counts[static_cast(scopeone::core::DaqChannelType::DigitalInput)]) + .arg(counts[static_cast(scopeone::core::DaqChannelType::DigitalOutput)]) + .arg(counts[static_cast(scopeone::core::DaqChannelType::CounterInput)]) + .arg(counts[static_cast(scopeone::core::DaqChannelType::CounterOutput)]) + .arg(device.terminals.size())); + m_resourcesLabel->setToolTip(channelNames.join(QLatin1Char('\n'))); + } + else + { + m_resourcesLabel->clear(); + m_resourcesLabel->setToolTip(QString()); + } + m_statusLabel->setText(available + ? m_core->daqStateMessage(device.id) + : tr("Install a DAQ device plugin and driver")); + setControlsEnabled(available); + m_stopButton->setEnabled(false); + } + + void DaqControlWidget::addBufferedRow() + { + const auto device = currentDevice(); + const int row = m_bufferedTable->rowCount(); + m_bufferedTable->insertRow(row); + + auto* type = new QComboBox(m_bufferedTable); + type->addItem(tr("Analog input"), QStringLiteral("AI")); + type->addItem(tr("Analog output"), QStringLiteral("AO")); + type->addItem(tr("Digital input"), QStringLiteral("DI")); + type->addItem(tr("Digital output"), QStringLiteral("DO")); + m_bufferedTable->setCellWidget(row, 0, type); + + auto* channels = new QComboBox(m_bufferedTable); + channels->setEditable(true); + m_bufferedTable->setCellWidget(row, 1, channels); + m_bufferedTable->setCellWidget( + row, 2, realSpin(-1000.0, 1000.0, -10.0, 3, + tr(" V"), m_bufferedTable)); + m_bufferedTable->setCellWidget( + row, 3, realSpin(-1000.0, 1000.0, 10.0, 3, + tr(" V"), m_bufferedTable)); + m_bufferedTable->setCellWidget( + row, 4, editableCombo(terminalChoices(device), m_bufferedTable)); + auto* sampleEdge = new QComboBox(m_bufferedTable); + sampleEdge->addItem(tr("Rising"), static_cast(scopeone::core::DaqEdge::Rising)); + sampleEdge->addItem(tr("Falling"), static_cast(scopeone::core::DaqEdge::Falling)); + m_bufferedTable->setCellWidget(row, 5, sampleEdge); + m_bufferedTable->setCellWidget( + row, 6, realSpin(0.001, 1000000000.0, 1000.0, 3, + tr(" Hz"), m_bufferedTable)); + auto* samples = new QSpinBox(m_bufferedTable); + samples->setRange(1, std::numeric_limits::max()); + samples->setValue(1); + m_bufferedTable->setCellWidget(row, 7, samples); + auto* mode = new QComboBox(m_bufferedTable); + mode->addItem(tr("Finite"), static_cast(scopeone::core::DaqSampleMode::Finite)); + mode->addItem(tr("Continuous"), + static_cast(scopeone::core::DaqSampleMode::Continuous)); + m_bufferedTable->setCellWidget(row, 8, mode); + m_bufferedTable->setCellWidget( + row, 9, editableCombo(terminalChoices(device), m_bufferedTable)); + auto* triggerEdge = new QComboBox(m_bufferedTable); + triggerEdge->addItem(tr("Rising"), static_cast(scopeone::core::DaqEdge::Rising)); + triggerEdge->addItem(tr("Falling"), static_cast(scopeone::core::DaqEdge::Falling)); + m_bufferedTable->setCellWidget(row, 10, triggerEdge); + auto* outputData = new QLineEdit(m_bufferedTable); + auto* waveformAction = outputData->addAction( + QIcon::fromTheme(QStringLiteral("office-chart-line"), + style()->standardIcon(QStyle::SP_FileDialogDetailedView)), + QLineEdit::TrailingPosition); + waveformAction->setToolTip(tr("Generate analog waveform")); + outputData->setPlaceholderText(tr("Comma-separated samples")); + connect(outputData, &QLineEdit::textEdited, outputData, + [outputData]() + { + outputData->setProperty("generatedAnalogSamples", QVariant()); + }); + connect(waveformAction, &QAction::triggered, this, + [this, outputData]() + { + for (int currentRow = 0; + currentRow < m_bufferedTable->rowCount(); ++currentRow) + { + if (m_bufferedTable->cellWidget(currentRow, 11) == outputData) + { + configureAnalogWaveform(currentRow, outputData); + return; + } + } + }); + m_bufferedTable->setCellWidget(row, 11, outputData); + + const auto updateChannels = [device, type, channels, outputData, + waveformAction, this]() + { + int row = -1; + for (int currentRow = 0; + currentRow < m_bufferedTable->rowCount(); ++currentRow) + { + if (m_bufferedTable->cellWidget(currentRow, 11) == outputData) + { + row = currentRow; + break; + } + } + if (row < 0) + { + return; + } + const QString taskType = type->currentData().toString(); + const bool analog = taskType == QStringLiteral("AI") + || taskType == QStringLiteral("AO"); + const bool output = taskType == QStringLiteral("AO") + || taskType == QStringLiteral("DO"); + const auto channelType = taskType == QStringLiteral("AI") + ? scopeone::core::DaqChannelType::AnalogInput + : taskType == QStringLiteral("AO") + ? scopeone::core::DaqChannelType::AnalogOutput + : taskType == QStringLiteral("DI") + ? scopeone::core::DaqChannelType::DigitalInput + : scopeone::core::DaqChannelType::DigitalOutput; + const QString current = channels->currentText(); + channels->clear(); + for (const auto& channel : device.channels) + { + if (channel.type == channelType) + { + channels->addItem(channel.physicalName); + } + } + if (!current.isEmpty()) + { + channels->setCurrentText(current); + } + m_bufferedTable->cellWidget(row, 2)->setEnabled(analog); + m_bufferedTable->cellWidget(row, 3)->setEnabled(analog); + m_bufferedTable->cellWidget(row, 11)->setEnabled(output); + waveformAction->setVisible(analog && output); + }; + connect(type, &QComboBox::currentIndexChanged, this, updateChannels); + updateChannels(); + } + + void DaqControlWidget::configureAnalogWaveform(int row, QLineEdit* outputData) + { + QDialog dialog(this); + dialog.setWindowTitle(tr("Analog Waveform")); + auto* layout = new QFormLayout(&dialog); + + auto* shape = new QComboBox(&dialog); + shape->addItems({tr("Constant"), tr("Square"), tr("Sine"), + tr("Triangle"), tr("Ramp")}); + shape->setCurrentIndex(1); + layout->addRow(tr("Shape"), shape); + + auto* low = realSpin(-1000.0, 1000.0, 0.0, 6, tr(" V"), &dialog); + auto* high = realSpin(-1000.0, 1000.0, 0.1, 6, tr(" V"), &dialog); + layout->addRow(tr("Low"), low); + layout->addRow(tr("High"), high); + + auto* frequency = realSpin(0.001, 100000000.0, 10.0, 3, + tr(" Hz"), &dialog); + layout->addRow(tr("Frequency"), frequency); + auto* duty = realSpin(0.001, 99.999, 50.0, 3, tr(" %"), &dialog); + layout->addRow(tr("Duty cycle"), duty); + auto* points = new QSpinBox(&dialog); + points->setRange(2, 100000); + points->setValue(100); + layout->addRow(tr("Samples per cycle"), points); + + connect(shape, &QComboBox::currentIndexChanged, &dialog, + [shape, low, frequency, duty]() + { + const int index = shape->currentIndex(); + low->setEnabled(index != 0); + frequency->setEnabled(index != 0); + duty->setEnabled(index == 1); + }); + + auto* buttons = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog); + connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + layout->addRow(buttons); + if (dialog.exec() != QDialog::Accepted) + { + return; + } + + const int count = points->value(); + const double lower = low->value(); + const double upper = high->value(); + const double range = upper - lower; + const double dutyFraction = duty->value() / 100.0; + QVariantList samples; + samples.reserve(count); + const double twoPi = 2.0 * std::acos(-1.0); + for (int index = 0; index < count; ++index) + { + const double phase = static_cast(index) / count; + double value = upper; + switch (shape->currentIndex()) + { + case 1: + value = phase < 1.0 - dutyFraction ? lower : upper; + break; + case 2: + value = lower + range * (0.5 - 0.5 * std::cos(twoPi * phase)); + break; + case 3: + value = lower + range * (1.0 - std::abs(2.0 * phase - 1.0)); + break; + case 4: + value = lower + range * index / (count - 1); + break; + default: + break; + } + samples.append(value); + } + outputData->setProperty("generatedAnalogSamples", samples); + outputData->setText(tr("%1, %2 to %3 V, %4 Hz") + .arg(shape->currentText()) + .arg(lower, 0, 'g', 6) + .arg(upper, 0, 'g', 6) + .arg(frequency->value(), 0, 'g', 6)); + + qobject_cast(m_bufferedTable->cellWidget(row, 6)) + ->setValue(frequency->value() * count); + qobject_cast(m_bufferedTable->cellWidget(row, 7))->setValue(count); + auto* mode = qobject_cast(m_bufferedTable->cellWidget(row, 8)); + mode->setCurrentIndex(mode->findData( + static_cast(scopeone::core::DaqSampleMode::Continuous))); + } + + void DaqControlWidget::addPulseRow() + { + const auto device = currentDevice(); + QStringList counters; + for (const auto& channel : device.channels) + { + if (channel.type == scopeone::core::DaqChannelType::CounterOutput) + { + counters.append(channel.physicalName); + } + } + const int row = m_pulseTable->rowCount(); + m_pulseTable->insertRow(row); + auto* counter = new QComboBox(m_pulseTable); + counter->addItems(counters); + m_pulseTable->setCellWidget(row, 0, counter); + m_pulseTable->setCellWidget( + row, 1, editableCombo(terminalChoices(device), m_pulseTable)); + m_pulseTable->setCellWidget( + row, 2, realSpin(0.001, 100000000.0, 1000.0, 3, + tr(" Hz"), m_pulseTable)); + m_pulseTable->setCellWidget( + row, 3, realSpin(0.001, 99.999, 50.0, 3, + tr(" %"), m_pulseTable)); + m_pulseTable->setCellWidget( + row, 4, realSpin(0.0, 1000000.0, 0.0, 3, + tr(" ms"), m_pulseTable)); + m_pulseTable->setCellWidget( + row, 5, editableCombo(terminalChoices(device), m_pulseTable)); + auto* edge = new QComboBox(m_pulseTable); + edge->addItem(tr("Rising"), static_cast(scopeone::core::DaqEdge::Rising)); + edge->addItem(tr("Falling"), static_cast(scopeone::core::DaqEdge::Falling)); + m_pulseTable->setCellWidget(row, 6, edge); + } + + void DaqControlWidget::addRouteRow() + { + const QStringList terminals = terminalChoices(currentDevice()); + const int row = m_routeTable->rowCount(); + m_routeTable->insertRow(row); + m_routeTable->setCellWidget(row, 0, editableCombo(terminals, m_routeTable)); + m_routeTable->setCellWidget(row, 1, editableCombo(terminals, m_routeTable)); + auto* polarity = new QComboBox(m_routeTable); + polarity->addItem(tr("Normal"), false); + polarity->addItem(tr("Inverted"), true); + m_routeTable->setCellWidget(row, 2, polarity); + } + + void DaqControlWidget::removeSelectedRow(QTableWidget* table) + { + const int row = table->currentRow(); + if (row >= 0) + { + table->removeRow(row); + } + } + + void DaqControlWidget::startSession() + { + scopeone::core::DaqSessionConfig config; + config.deviceId = m_deviceCombo->currentData().toString(); + if (m_rasterGroup->isChecked()) + { + scopeone::core::DaqRasterScanConfig scan; + scan.name = QStringLiteral("ScopeOne raster scan"); + scan.lineClock = m_lineClockCombo->currentText(); + scan.nominalLineRateHz = m_lineRateSpin->value(); + scan.activeLines = static_cast(m_activeLinesSpin->value()); + scan.flybackLines = static_cast(m_flybackLinesSpin->value()); + scan.yChannel = m_yChannelCombo->currentText(); + scan.yStartVolts = m_yStartSpin->value(); + scan.yEndVolts = m_yEndSpin->value(); + scan.frameCounter = m_frameCounterCombo->currentText(); + scan.lineOutputTerminal = m_lineOutputCombo->currentText(); + scan.frameOutputTerminal = m_frameOutputCombo->currentText(); + config.rasterScans.append(scan); + } + for (int row = 0; row < m_pulseTable->rowCount(); ++row) + { + scopeone::core::DaqPulseTaskConfig pulse; + pulse.name = QStringLiteral("ScopeOne Pulse %1").arg(row + 1); + pulse.counter = qobject_cast( + m_pulseTable->cellWidget(row, 0))->currentText(); + pulse.outputTerminal = qobject_cast( + m_pulseTable->cellWidget(row, 1))->currentText(); + pulse.frequencyHz = qobject_cast( + m_pulseTable->cellWidget(row, 2))->value(); + pulse.dutyCycle = qobject_cast( + m_pulseTable->cellWidget(row, 3))->value() / 100.0; + pulse.initialDelaySeconds = qobject_cast( + m_pulseTable->cellWidget(row, 4))->value() + / 1000.0; + pulse.startTrigger = qobject_cast( + m_pulseTable->cellWidget(row, 5))->currentText(); + pulse.startEdge = static_cast( + qobject_cast(m_pulseTable->cellWidget(row, 6)) + ->currentData().toInt()); + config.pulseTasks.append(pulse); + } + for (int row = 0; row < m_routeTable->rowCount(); ++row) + { + scopeone::core::DaqTerminalRoute route; + route.source = qobject_cast( + m_routeTable->cellWidget(row, 0))->currentText(); + route.destination = qobject_cast( + m_routeTable->cellWidget(row, 1))->currentText(); + route.inverted = qobject_cast( + m_routeTable->cellWidget(row, 2))->currentData().toBool(); + config.routes.append(route); + } + for (int row = 0; row < m_bufferedTable->rowCount(); ++row) + { + const QString type = qobject_cast( + m_bufferedTable->cellWidget(row, 0)) + ->currentData().toString(); + const QStringList channels = commaSeparated( + qobject_cast(m_bufferedTable->cellWidget(row, 1)) + ->currentText()); + scopeone::core::DaqTaskTiming timing; + timing.sampleClock = qobject_cast( + m_bufferedTable->cellWidget(row, 4))->currentText(); + timing.sampleEdge = static_cast( + qobject_cast(m_bufferedTable->cellWidget(row, 5)) + ->currentData().toInt()); + timing.sampleRateHz = qobject_cast( + m_bufferedTable->cellWidget(row, 6))->value(); + timing.samplesPerChannel = static_cast( + qobject_cast(m_bufferedTable->cellWidget(row, 7))->value()); + timing.sampleMode = static_cast( + qobject_cast(m_bufferedTable->cellWidget(row, 8)) + ->currentData().toInt()); + timing.startTrigger = qobject_cast( + m_bufferedTable->cellWidget(row, 9))->currentText(); + timing.startEdge = static_cast( + qobject_cast(m_bufferedTable->cellWidget(row, 10)) + ->currentData().toInt()); + auto* outputData = qobject_cast( + m_bufferedTable->cellWidget(row, 11)); + const QStringList sampleText = commaSeparated(outputData->text()); + + if (type == QStringLiteral("AI") || type == QStringLiteral("AO")) + { + scopeone::core::DaqAnalogTaskConfig task; + task.name = QStringLiteral("ScopeOne %1 %2").arg(type).arg(row + 1); + task.direction = type == QStringLiteral("AI") + ? scopeone::core::DaqTaskDirection::Input + : scopeone::core::DaqTaskDirection::Output; + task.channels = channels; + task.minimumVolts = qobject_cast( + m_bufferedTable->cellWidget(row, 2))->value(); + task.maximumVolts = qobject_cast( + m_bufferedTable->cellWidget(row, 3))->value(); + task.timing = timing; + if (task.direction == scopeone::core::DaqTaskDirection::Output) + { + const QVariantList generatedSamples = outputData + ->property("generatedAnalogSamples") + .toList(); + if (!generatedSamples.isEmpty()) + { + for (const QVariant& value : generatedSamples) + { + task.outputSamplesByScan.append(value.toDouble()); + } + } + else + { + for (const QString& value : sampleText) + { + bool valid = false; + const double sample = value.toDouble(&valid); + if (!valid) + { + m_statusLabel->setText( + tr("Invalid analog output sample: %1").arg(value)); + return; + } + task.outputSamplesByScan.append(sample); + } + } + } + config.analogTasks.append(task); + } + else + { + scopeone::core::DaqDigitalTaskConfig task; + task.name = QStringLiteral("ScopeOne %1 %2").arg(type).arg(row + 1); + task.direction = type == QStringLiteral("DI") + ? scopeone::core::DaqTaskDirection::Input + : scopeone::core::DaqTaskDirection::Output; + task.lines = channels; + task.timing = timing; + if (task.direction == scopeone::core::DaqTaskDirection::Output) + { + for (const QString& value : sampleText) + { + bool valid = false; + const quint32 sample = value.toUInt(&valid, 0); + if (!valid) + { + m_statusLabel->setText( + tr("Invalid digital output sample: %1").arg(value)); + return; + } + task.outputSamplesByScan.append(sample); + } + } + config.digitalTasks.append(task); + } + } + + QString errorMessage; + if (!m_core->startDaqSession(config, &errorMessage)) + { + m_statusLabel->setText(errorMessage); + qWarning().noquote() << errorMessage; + return; + } + m_activeDeviceId = config.deviceId; + } + + void DaqControlWidget::setControlsEnabled(bool enabled) + { + m_deviceCombo->setEnabled(enabled); + m_rasterGroup->setEnabled(enabled); + m_pulseTable->setEnabled(enabled); + m_bufferedTable->setEnabled(enabled); + m_routeTable->setEnabled(enabled); + m_addPulseButton->setEnabled(enabled); + m_removePulseButton->setEnabled(enabled); + m_addBufferedButton->setEnabled(enabled); + m_removeBufferedButton->setEnabled(enabled); + m_addRouteButton->setEnabled(enabled); + m_removeRouteButton->setEnabled(enabled); + m_startButton->setEnabled(enabled && m_deviceCombo->count() > 0); + } + + void DaqControlWidget::handleStateChanged(const QString& deviceId, + scopeone::core::DaqState state, + const QString& message) + { + if (deviceId != m_activeDeviceId + && deviceId != m_deviceCombo->currentData().toString()) + { + return; + } + const bool active = state == scopeone::core::DaqState::Armed + || state == scopeone::core::DaqState::Running; + m_statusLabel->setText(message); + setControlsEnabled(!active); + m_stopButton->setEnabled(active); + if (active) + { + m_activeDeviceId = deviceId; + } + else + { + m_activeDeviceId.clear(); + } + } +} diff --git a/plugins/ScanningDaqTool/DaqControlWidget.h b/plugins/ScanningDaqTool/DaqControlWidget.h new file mode 100644 index 0000000..0978edc --- /dev/null +++ b/plugins/ScanningDaqTool/DaqControlWidget.h @@ -0,0 +1,77 @@ +#pragma once + +#include "scopeone/DaqDevice.h" + +#include +#include + +class QComboBox; +class QDoubleSpinBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QSpinBox; +class QTableWidget; + +namespace scopeone::core +{ + class ScopeOneCore; +} + +namespace scopeone::plugins +{ + class DaqControlWidget final : public QWidget + { + Q_OBJECT + + public: + explicit DaqControlWidget(scopeone::core::ScopeOneCore* core, + QWidget* parent = nullptr); + + private: + scopeone::core::DaqDeviceDescriptor currentDevice() const; + QStringList terminalChoices(const scopeone::core::DaqDeviceDescriptor& device) const; + void refreshDevice(); + void addPulseRow(); + void addBufferedRow(); + void configureAnalogWaveform(int row, QLineEdit* outputData); + void addRouteRow(); + void removeSelectedRow(QTableWidget* table); + void startSession(); + void setControlsEnabled(bool enabled); + void handleStateChanged(const QString& deviceId, + scopeone::core::DaqState state, + const QString& message); + + scopeone::core::ScopeOneCore* m_core{nullptr}; + QComboBox* m_deviceCombo{nullptr}; + QLabel* m_productLabel{nullptr}; + QLabel* m_resourcesLabel{nullptr}; + QGroupBox* m_rasterGroup{nullptr}; + QComboBox* m_lineClockCombo{nullptr}; + QDoubleSpinBox* m_lineRateSpin{nullptr}; + QSpinBox* m_activeLinesSpin{nullptr}; + QSpinBox* m_flybackLinesSpin{nullptr}; + QComboBox* m_yChannelCombo{nullptr}; + QDoubleSpinBox* m_yStartSpin{nullptr}; + QDoubleSpinBox* m_yEndSpin{nullptr}; + QComboBox* m_frameCounterCombo{nullptr}; + QComboBox* m_lineOutputCombo{nullptr}; + QComboBox* m_frameOutputCombo{nullptr}; + QTableWidget* m_pulseTable{nullptr}; + QTableWidget* m_bufferedTable{nullptr}; + QTableWidget* m_routeTable{nullptr}; + QPushButton* m_addPulseButton{nullptr}; + QPushButton* m_removePulseButton{nullptr}; + QPushButton* m_addBufferedButton{nullptr}; + QPushButton* m_removeBufferedButton{nullptr}; + QPushButton* m_addRouteButton{nullptr}; + QPushButton* m_removeRouteButton{nullptr}; + QPushButton* m_startButton{nullptr}; + QPushButton* m_stopButton{nullptr}; + QLabel* m_statusLabel{nullptr}; + QString m_activeDeviceId; + QElapsedTimer m_inputStatusTimer; + }; +} diff --git a/plugins/ScanningDaqTool/ScanningDaqToolPlugin.cpp b/plugins/ScanningDaqTool/ScanningDaqToolPlugin.cpp new file mode 100644 index 0000000..597c4a7 --- /dev/null +++ b/plugins/ScanningDaqTool/ScanningDaqToolPlugin.cpp @@ -0,0 +1,49 @@ +#include "DaqControlWidget.h" +#include "SignalMonitorWidget.h" + +#include "scopeone/ToolPlugin.h" + +#include + +namespace scopeone::plugins +{ + class ScanningDaqToolPlugin final : public QObject, + public scopeone::ui::ScopeOneToolPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneToolPlugin_iid FILE "plugin.json") + Q_INTERFACES(scopeone::ui::ScopeOneToolPlugin) + + public: + QList tools() const override + { + return {{QStringLiteral("scopeone.daq_control"), + QStringLiteral("Scanning and DAQ Control"), + QStringLiteral("Acquisition"), + scopeone::ui::ToolWindowMode::ModelessSingleton, + false}, + {QStringLiteral("scopeone.signal_monitor"), + QStringLiteral("Signal Monitor"), + QStringLiteral("Acquisition"), + scopeone::ui::ToolWindowMode::ModelessSingleton, + false}}; + } + + QWidget* createTool(const QString& toolId, + scopeone::ui::ScopeOneToolContext& context, + QWidget* parent) override + { + if (toolId == QStringLiteral("scopeone.daq_control")) + { + return new DaqControlWidget(&context.core(), parent); + } + if (toolId == QStringLiteral("scopeone.signal_monitor")) + { + return new SignalMonitorWidget(&context.core(), context, parent); + } + return nullptr; + } + }; +} + +#include "ScanningDaqToolPlugin.moc" diff --git a/plugins/ScanningDaqTool/SignalMonitorWidget.cpp b/plugins/ScanningDaqTool/SignalMonitorWidget.cpp new file mode 100644 index 0000000..fb5a10a --- /dev/null +++ b/plugins/ScanningDaqTool/SignalMonitorWidget.cpp @@ -0,0 +1,683 @@ +#include "SignalMonitorWidget.h" + +#include "scopeone/ScopeOneCore.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace scopeone::plugins +{ + namespace + { + QString formattedValue(double value, const QString& unit) + { + return QStringLiteral("%1 %2").arg(value, 0, 'g', 5).arg(unit); + } + + QString settingKey(const QString& sourceId, const QString& parameterKey) + { + return QStringLiteral("signalSources/%1/%2").arg(sourceId, parameterKey); + } + } + + class SignalTracePlot final : public QWidget + { + public: + explicit SignalTracePlot(QWidget* parent = nullptr) + : QWidget(parent) + { + setMinimumHeight(220); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + } + + double appendChunk(const scopeone::core::TimeSeriesChunk& chunk, + double windowSeconds) + { + m_windowSeconds = windowSeconds; + for (int index = 0; index < chunk.values.size(); ++index) + { + m_times.append(chunk.startTimeSeconds + + (static_cast(index) + 0.5) + * chunk.sampleIntervalSeconds); + m_values.append(chunk.values[index]); + } + trim(); + update(); + return m_values.constLast(); + } + + void setWindowSeconds(double windowSeconds) + { + m_windowSeconds = windowSeconds; + trim(); + update(); + } + + void clear() + { + m_times.clear(); + m_values.clear(); + update(); + } + + protected: + void paintEvent(QPaintEvent*) override + { + QPainter painter(this); + painter.fillRect(rect(), palette().color(QPalette::Base)); + + const QRectF plotRect = QRectF(rect()).adjusted(62.0, 12.0, -12.0, -30.0); + if (plotRect.width() <= 1.0 || plotRect.height() <= 1.0) + { + return; + } + painter.setPen(palette().color(QPalette::Mid)); + painter.drawLine(plotRect.bottomLeft(), plotRect.bottomRight()); + painter.drawLine(plotRect.bottomLeft(), plotRect.topLeft()); + + const QColor textColor = palette().color(QPalette::Text); + if (m_times.isEmpty() || m_values.isEmpty()) + { + painter.setPen(textColor); + painter.drawText(plotRect, Qt::AlignCenter, tr("No signal data")); + return; + } + + const double endTime = m_times.constLast(); + const double startTime = std::max(0.0, endTime - m_windowSeconds); + const double displayedDuration = std::max( + endTime - startTime, (std::numeric_limits::epsilon)()); + const auto first = std::lower_bound(m_times.cbegin(), m_times.cend(), startTime); + const int firstIndex = static_cast(std::distance(m_times.cbegin(), first)); + const int lastIndex = std::min(m_times.size(), m_values.size()); + if (firstIndex >= lastIndex) + { + return; + } + + double minimum = (std::numeric_limits::max)(); + double maximum = (std::numeric_limits::lowest)(); + for (int index = firstIndex; index < lastIndex; ++index) + { + minimum = std::min(minimum, m_values[index]); + maximum = std::max(maximum, m_values[index]); + } + const bool nonnegative = minimum >= 0.0; + const bool nonpositive = maximum <= 0.0; + if (nonnegative) + { + minimum = 0.0; + } + if (nonpositive) + { + maximum = 0.0; + } + if (minimum == maximum) + { + maximum = minimum + 1.0; + } + const double padding = (maximum - minimum) * 0.05; + if (!nonnegative) + { + minimum -= padding; + } + if (!nonpositive) + { + maximum += padding; + } + + const auto mapX = [&](double time) + { + return plotRect.left() + + (time - startTime) / displayedDuration * plotRect.width(); + }; + const auto mapY = [&](double value) + { + return plotRect.bottom() + - (value - minimum) / (maximum - minimum) * plotRect.height(); + }; + + painter.setPen(textColor); + painter.drawText(QRectF(0.0, plotRect.top() - 6.0, + plotRect.left() - 6.0, 20.0), + Qt::AlignRight | Qt::AlignVCenter, + QString::number(maximum, 'g', 5)); + painter.drawText(QRectF(0.0, plotRect.bottom() - 10.0, + plotRect.left() - 6.0, 20.0), + Qt::AlignRight | Qt::AlignVCenter, + QString::number(minimum, 'g', 5)); + painter.drawText(QRectF(plotRect.left(), plotRect.bottom() + 4.0, + plotRect.width(), 20.0), + Qt::AlignLeft | Qt::AlignVCenter, + QStringLiteral("%1 s").arg(startTime, 0, 'g', 4)); + painter.drawText(QRectF(plotRect.left(), plotRect.bottom() + 4.0, + plotRect.width(), 20.0), + Qt::AlignRight | Qt::AlignVCenter, + QStringLiteral("%1 s").arg(endTime, 0, 'g', 4)); + + painter.setPen(QPen(palette().color(QPalette::Highlight), 1.5)); + const int visiblePoints = lastIndex - firstIndex; + const int pixelColumns = std::max(1, static_cast(plotRect.width())); + if (visiblePoints <= pixelColumns * 2) + { + painter.setRenderHint(QPainter::Antialiasing, true); + QPainterPath path; + path.moveTo(mapX(m_times[firstIndex]), mapY(m_values[firstIndex])); + for (int index = firstIndex + 1; index < lastIndex; ++index) + { + path.lineTo(mapX(m_times[index]), mapY(m_values[index])); + } + painter.drawPath(path); + return; + } + + QVector minima(pixelColumns, maximum); + QVector maxima(pixelColumns, minimum); + QVector populated(pixelColumns, false); + // Preserve extrema when samples outnumber horizontal pixels + for (int index = firstIndex; index < lastIndex; ++index) + { + const int column = std::clamp( + static_cast((m_times[index] - startTime) + / displayedDuration * pixelColumns), + 0, + pixelColumns - 1); + minima[column] = std::min(minima[column], m_values[index]); + maxima[column] = std::max(maxima[column], m_values[index]); + populated[column] = true; + } + for (int column = 0; column < pixelColumns; ++column) + { + if (populated[column]) + { + const double x = plotRect.left() + column; + painter.drawLine(QPointF(x, mapY(minima[column])), + QPointF(x, mapY(maxima[column]))); + } + } + } + + private: + void trim() + { + if (m_times.isEmpty()) + { + return; + } + const double cutoff = m_times.constLast() - m_windowSeconds; + const auto first = std::lower_bound(m_times.cbegin(), m_times.cend(), cutoff); + const int removeCount = static_cast(std::distance(m_times.cbegin(), first)); + if (removeCount > 0) + { + m_times.remove(0, removeCount); + m_values.remove(0, removeCount); + } + } + + QVector m_times; + QVector m_values; + double m_windowSeconds{10.0}; + }; + + SignalMonitorWidget::SignalMonitorWidget(scopeone::core::ScopeOneCore* core, + scopeone::ui::ScopeOneToolContext& context, + QWidget* parent) + : QWidget(parent) + , m_core(core) + , m_context(context) + { + auto* layout = new QVBoxLayout(this); + auto* sourceForm = new QFormLayout(); + m_sourceCombo = new QComboBox(this); + sourceForm->addRow(tr("Source"), m_sourceCombo); + layout->addLayout(sourceForm); + + m_sourceForm = new QFormLayout(); + layout->addLayout(m_sourceForm); + + auto* traceForm = new QFormLayout(); + m_sampleIntervalSpin = new QDoubleSpinBox(this); + m_sampleIntervalSpin->setRange(0.001, 10000.0); + m_sampleIntervalSpin->setDecimals(3); + m_sampleIntervalSpin->setSuffix(tr(" ms")); + m_sampleIntervalSpin->setValue(10.0); + traceForm->addRow(tr("Interval"), m_sampleIntervalSpin); + + m_windowDurationSpin = new QDoubleSpinBox(this); + m_windowDurationSpin->setRange(0.1, 60.0); + m_windowDurationSpin->setDecimals(1); + m_windowDurationSpin->setSuffix(tr(" s")); + m_windowDurationSpin->setValue(10.0); + traceForm->addRow(tr("Window"), m_windowDurationSpin); + + m_scanImageCheck = new QCheckBox(tr("Build scan image"), this); + traceForm->addRow(tr("Scan"), m_scanImageCheck); + m_scanWidthSpin = new QSpinBox(this); + m_scanWidthSpin->setRange(1, 8192); + m_scanWidthSpin->setValue(256); + traceForm->addRow(tr("Width"), m_scanWidthSpin); + m_scanHeightSpin = new QSpinBox(this); + m_scanHeightSpin->setRange(1, 8192); + m_scanHeightSpin->setValue(256); + traceForm->addRow(tr("Height"), m_scanHeightSpin); + m_scanGainSpin = new QSpinBox(this); + m_scanGainSpin->setRange(1, 65535); + m_scanGainSpin->setValue(1000); + m_scanGainSpin->setSuffix(tr(" x")); + traceForm->addRow(tr("Gain"), m_scanGainSpin); + + m_scanAverageFramesSpin = new QSpinBox(this); + m_scanAverageFramesSpin->setRange(1, 256); + m_scanAverageFramesSpin->setValue(1); + m_scanAverageFramesSpin->setToolTip( + tr("Average this many complete scan images before publishing one result")); + traceForm->addRow(tr("Average frames"), m_scanAverageFramesSpin); + const auto markerCombo = [this](quint32 value) + { + auto* combo = new QComboBox(this); + combo->addItem(tr("Off"), 0U); + combo->addItem(tr("Marker 1"), 1U); + combo->addItem(tr("Marker 2"), 2U); + combo->addItem(tr("Marker 3"), 4U); + combo->addItem(tr("Marker 4"), 8U); + combo->setCurrentIndex(combo->findData(value)); + return combo; + }; + m_frameStartMarkerCombo = markerCombo(1); + traceForm->addRow(tr("Frame start"), m_frameStartMarkerCombo); + m_lineMarkerCombo = markerCombo(2); + m_lineMarkerCombo->setToolTip( + tr("One marker completes one full back-and-forth scan line")); + traceForm->addRow(tr("Line marker"), m_lineMarkerCombo); + m_frameEndMarkerCombo = markerCombo(0); + traceForm->addRow(tr("Frame end"), m_frameEndMarkerCombo); + m_serpentineCheck = new QCheckBox(tr("Reverse alternate lines"), this); + traceForm->addRow(tr("Scan order"), m_serpentineCheck); + m_mirrorHorizontalCheck = new QCheckBox(tr("Mirror left/right"), this); + traceForm->addRow(tr("Image orientation"), m_mirrorHorizontalCheck); + layout->addLayout(traceForm); + + auto* buttonLayout = new QHBoxLayout(); + m_startButton = new QPushButton(tr("Start"), this); + m_startButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); + m_stopButton = new QPushButton(tr("Stop"), this); + m_stopButton->setIcon(style()->standardIcon(QStyle::SP_MediaStop)); + m_stopButton->setEnabled(false); + buttonLayout->addWidget(m_startButton); + buttonLayout->addWidget(m_stopButton); + layout->addLayout(buttonLayout); + + m_valueLabel = new QLabel(tr("Value: -"), this); + m_statusLabel = new QLabel(tr("No signal sources available"), this); + m_statusLabel->setWordWrap(true); + layout->addWidget(m_valueLabel); + layout->addWidget(m_statusLabel); + m_plot = new SignalTracePlot(this); + layout->addWidget(m_plot, 1); + + connect(m_sourceCombo, &QComboBox::currentIndexChanged, + this, &SignalMonitorWidget::rebuildSourceParameters); + connect(m_startButton, &QPushButton::clicked, + this, &SignalMonitorWidget::startAcquisition); + connect(m_stopButton, &QPushButton::clicked, this, [this]() + { + m_core->stopSignalTrace(m_activeSourceId); + }); + connect(m_windowDurationSpin, &QDoubleSpinBox::valueChanged, + m_plot, &SignalTracePlot::setWindowSeconds); + connect(m_scanImageCheck, &QCheckBox::toggled, this, [this](bool enabled) + { + const bool editable = enabled && m_scanImageCheck->isEnabled(); + m_scanWidthSpin->setEnabled(editable); + m_scanHeightSpin->setEnabled(editable); + m_scanGainSpin->setEnabled(editable); + m_scanAverageFramesSpin->setEnabled(editable); + m_frameStartMarkerCombo->setEnabled(editable); + m_lineMarkerCombo->setEnabled(editable); + m_frameEndMarkerCombo->setEnabled(editable); + m_serpentineCheck->setEnabled(editable); + m_mirrorHorizontalCheck->setEnabled(editable); + }); + connect(m_core, &scopeone::core::ScopeOneCore::signalTimeSeriesReady, + this, &SignalMonitorWidget::handleTimeSeries); + connect(m_core, &scopeone::core::ScopeOneCore::signalSourceStateChanged, + this, &SignalMonitorWidget::handleStateChanged); + connect(m_core, &scopeone::core::ScopeOneCore::signalSourceError, + this, [](const QString&, const QString& message) + { + qCritical().noquote() << message; + }); + connect(m_core, &scopeone::core::ScopeOneCore::scanImageSessionReady, + this, [this](const std::shared_ptr& session) + { + m_statusLabel->setText( + tr("Scan session ready: %1 frames") + .arg(session->recordedFrameCount())); + m_context.presentSession(session, tr("Reconstructed Scan")); + }); + + m_scanImageCheck->setChecked(false); + m_scanWidthSpin->setEnabled(false); + m_scanHeightSpin->setEnabled(false); + m_scanGainSpin->setEnabled(false); + m_scanAverageFramesSpin->setEnabled(false); + m_frameStartMarkerCombo->setEnabled(false); + m_lineMarkerCombo->setEnabled(false); + m_frameEndMarkerCombo->setEnabled(false); + m_serpentineCheck->setEnabled(false); + m_mirrorHorizontalCheck->setEnabled(false); + + refreshSources(); + } + + void SignalMonitorWidget::refreshSources() + { + const QString selectedId = m_sourceCombo->currentData().toString(); + const QList sources = m_core->signalSources(); + { + const QSignalBlocker blocker(m_sourceCombo); + m_sourceCombo->clear(); + for (const auto& source : sources) + { + m_sourceCombo->addItem(source.name, source.id); + m_sourceCombo->setItemData( + m_sourceCombo->count() - 1, + QStringLiteral("%1, %2 [%3]") + .arg(source.provider, source.quantity, source.unit), + Qt::ToolTipRole); + } + const int selectedIndex = m_sourceCombo->findData(selectedId); + if (selectedIndex >= 0) + { + m_sourceCombo->setCurrentIndex(selectedIndex); + } + } + const bool available = !sources.isEmpty(); + if (available) + { + const QString sourceId = m_sourceCombo->currentData().toString(); + const auto state = m_core->signalSourceState(sourceId); + const bool active = state == scopeone::core::SignalSourceState::Starting + || state == scopeone::core::SignalSourceState::Running + || state == scopeone::core::SignalSourceState::Stopping; + m_startButton->setEnabled(!active); + m_stopButton->setEnabled(state == scopeone::core::SignalSourceState::Starting + || state == scopeone::core::SignalSourceState::Running); + m_statusLabel->setText(m_core->signalSourceStateMessage(sourceId)); + } + else + { + m_startButton->setEnabled(false); + m_stopButton->setEnabled(false); + m_statusLabel->setText(tr("No signal source plugins found")); + } + rebuildSourceParameters(); + } + + void SignalMonitorWidget::rebuildSourceParameters() + { + while (m_sourceForm->rowCount() > 0) + { + m_sourceForm->removeRow(0); + } + m_parameterEditors.clear(); + + const auto descriptor = currentDescriptor(); + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + for (const auto& parameter : descriptor.parameters) + { + const QVariant storedValue = settings.value( + settingKey(descriptor.id, parameter.key), parameter.defaultValue); + QWidget* editor = nullptr; + if (parameter.type == scopeone::core::SignalParameterType::Integer) + { + auto* spin = new QSpinBox(this); + if (parameter.hasRange) + { + spin->setRange(static_cast(parameter.minimum), + static_cast(parameter.maximum)); + } + spin->setSuffix(parameter.suffix); + spin->setValue(storedValue.toInt()); + editor = spin; + } + else if (parameter.type == scopeone::core::SignalParameterType::Real) + { + auto* spin = new QDoubleSpinBox(this); + if (parameter.hasRange) + { + spin->setRange(parameter.minimum, parameter.maximum); + } + spin->setSuffix(parameter.suffix); + spin->setValue(storedValue.toDouble()); + editor = spin; + } + else if (parameter.type == scopeone::core::SignalParameterType::Choice) + { + auto* combo = new QComboBox(this); + for (int index = 0; index < parameter.choices.size(); ++index) + { + const QString name = index < parameter.choiceNames.size() + ? parameter.choiceNames[index] + : parameter.choices[index].toString(); + combo->addItem(name, parameter.choices[index]); + } + combo->setCurrentIndex(std::max(0, combo->findData(storedValue))); + editor = combo; + } + else + { + auto* edit = new QLineEdit(storedValue.toString(), this); + editor = edit; + if (parameter.type == scopeone::core::SignalParameterType::File) + { + auto* row = new QWidget(this); + auto* rowLayout = new QHBoxLayout(row); + rowLayout->setContentsMargins(0, 0, 0, 0); + rowLayout->setSpacing(4); + auto* browse = new QToolButton(row); + browse->setIcon(style()->standardIcon(QStyle::SP_DirOpenIcon)); + browse->setToolTip(tr("Select file")); + rowLayout->addWidget(edit, 1); + rowLayout->addWidget(browse); + connect(browse, &QToolButton::clicked, this, + [this, edit, parameter]() + { + const QString path = QFileDialog::getOpenFileName( + this, + parameter.name, + QFileInfo(edit->text()).absolutePath(), + parameter.fileFilter); + if (!path.isEmpty()) + { + edit->setText(QDir::toNativeSeparators(path)); + } + }); + m_sourceForm->addRow(parameter.name, row); + m_parameterEditors.insert(parameter.key, edit); + continue; + } + } + m_sourceForm->addRow(parameter.name, editor); + m_parameterEditors.insert(parameter.key, editor); + } + m_sampleIntervalSpin->setEnabled( + descriptor.streamType == scopeone::core::SignalStreamType::TimestampedEvents); + } + + void SignalMonitorWidget::startAcquisition() + { + const auto descriptor = currentDescriptor(); + if (descriptor.id.isEmpty()) + { + return; + } + + scopeone::core::SignalAcquisitionConfig config; + config.sourceId = descriptor.id; + config.sampleIntervalSeconds = m_sampleIntervalSpin->value() / 1000.0; + config.scanImage.enabled = m_scanImageCheck->isChecked(); + config.scanImage.width = m_scanWidthSpin->value(); + config.scanImage.height = m_scanHeightSpin->value(); + config.scanImage.gain = static_cast(m_scanGainSpin->value()); + config.scanImage.averageFrames = m_scanAverageFramesSpin->value(); + config.scanImage.frameStartMarker = + m_frameStartMarkerCombo->currentData().toUInt(); + config.scanImage.lineMarker = + m_lineMarkerCombo->currentData().toUInt(); + config.scanImage.frameEndMarker = + m_frameEndMarkerCombo->currentData().toUInt(); + config.scanImage.serpentine = m_serpentineCheck->isChecked(); + config.scanImage.mirrorHorizontal = m_mirrorHorizontalCheck->isChecked(); + config.sourceSettings = sourceSettings(); + + QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + for (auto it = config.sourceSettings.constBegin(); + it != config.sourceSettings.constEnd(); ++it) + { + settings.setValue(settingKey(descriptor.id, it.key()), it.value()); + } + + m_plot->clear(); + m_valueLabel->setText(QStringLiteral("%1: -").arg(descriptor.quantity)); + QString errorMessage; + if (!m_core->startSignalTrace(config, &errorMessage)) + { + m_statusLabel->setText(errorMessage); + qWarning().noquote() << errorMessage; + return; + } + m_activeSourceId = descriptor.id; + } + + void SignalMonitorWidget::handleTimeSeries( + const scopeone::core::TimeSeriesChunk& chunk) + { + if (!chunk.isValid() || chunk.sourceId != m_activeSourceId) + { + return; + } + const double latestValue = m_plot->appendChunk( + chunk, m_windowDurationSpin->value()); + m_valueLabel->setText(QStringLiteral("%1: %2") + .arg(chunk.quantity, formattedValue(latestValue, chunk.unit))); + m_valueLabel->setToolTip(tr("Input events: %1\nMarkers: %2") + .arg(chunk.totalInputEvents) + .arg(chunk.totalMarkers)); + } + + void SignalMonitorWidget::handleStateChanged( + const QString& sourceId, + scopeone::core::SignalSourceState state, + const QString& message) + { + if (sourceId != m_activeSourceId + && sourceId != m_sourceCombo->currentData().toString()) + { + return; + } + m_statusLabel->setText(message); + const bool active = state == scopeone::core::SignalSourceState::Starting + || state == scopeone::core::SignalSourceState::Running + || state == scopeone::core::SignalSourceState::Stopping; + setSourceControlsEnabled(!active); + m_startButton->setEnabled(!active); + m_stopButton->setEnabled(state == scopeone::core::SignalSourceState::Starting + || state == scopeone::core::SignalSourceState::Running); + if (active) + { + m_activeSourceId = sourceId; + } + else if (m_activeSourceId == sourceId) + { + m_activeSourceId.clear(); + } + } + + void SignalMonitorWidget::setSourceControlsEnabled(bool enabled) + { + m_sourceCombo->setEnabled(enabled); + for (QWidget* editor : m_parameterEditors) + { + editor->setEnabled(enabled); + if (QWidget* row = editor->parentWidget(); row && row != this) + { + row->setEnabled(enabled); + } + } + m_sampleIntervalSpin->setEnabled( + enabled && currentDescriptor().streamType + == scopeone::core::SignalStreamType::TimestampedEvents); + m_scanImageCheck->setEnabled(enabled); + const bool scanEditable = enabled && m_scanImageCheck->isChecked(); + m_scanWidthSpin->setEnabled(scanEditable); + m_scanHeightSpin->setEnabled(scanEditable); + m_scanGainSpin->setEnabled(scanEditable); + m_scanAverageFramesSpin->setEnabled(scanEditable); + m_frameStartMarkerCombo->setEnabled(scanEditable); + m_lineMarkerCombo->setEnabled(scanEditable); + m_frameEndMarkerCombo->setEnabled(scanEditable); + m_serpentineCheck->setEnabled(scanEditable); + m_mirrorHorizontalCheck->setEnabled(scanEditable); + } + + scopeone::core::SignalSourceDescriptor SignalMonitorWidget::currentDescriptor() const + { + const QString sourceId = m_sourceCombo->currentData().toString(); + for (const auto& descriptor : m_core->signalSources()) + { + if (descriptor.id == sourceId) + { + return descriptor; + } + } + return {}; + } + + QVariantMap SignalMonitorWidget::sourceSettings() const + { + QVariantMap values; + for (auto it = m_parameterEditors.constBegin(); + it != m_parameterEditors.constEnd(); ++it) + { + if (const auto* spin = qobject_cast(it.value())) + { + values.insert(it.key(), spin->value()); + } + else if (const auto* spin = qobject_cast(it.value())) + { + values.insert(it.key(), spin->value()); + } + else if (const auto* combo = qobject_cast(it.value())) + { + values.insert(it.key(), combo->currentData()); + } + else if (const auto* edit = qobject_cast(it.value())) + { + values.insert(it.key(), edit->text().trimmed()); + } + } + return values; + } +} diff --git a/plugins/ScanningDaqTool/SignalMonitorWidget.h b/plugins/ScanningDaqTool/SignalMonitorWidget.h new file mode 100644 index 0000000..dfcc7c7 --- /dev/null +++ b/plugins/ScanningDaqTool/SignalMonitorWidget.h @@ -0,0 +1,71 @@ +#pragma once + +#include "scopeone/SignalSource.h" +#include "scopeone/ToolPlugin.h" + +#include +#include + +class QComboBox; +class QCheckBox; +class QDoubleSpinBox; +class QFormLayout; +class QLabel; +class QPushButton; +class QSpinBox; + +namespace scopeone::core +{ + class ScopeOneCore; +} + +namespace scopeone::plugins +{ + class SignalTracePlot; + + class SignalMonitorWidget final : public QWidget + { + Q_OBJECT + + public: + explicit SignalMonitorWidget(scopeone::core::ScopeOneCore* core, + scopeone::ui::ScopeOneToolContext& context, + QWidget* parent = nullptr); + + private: + void refreshSources(); + void rebuildSourceParameters(); + void startAcquisition(); + void handleTimeSeries(const scopeone::core::TimeSeriesChunk& chunk); + void handleStateChanged(const QString& sourceId, + scopeone::core::SignalSourceState state, + const QString& message); + void setSourceControlsEnabled(bool enabled); + scopeone::core::SignalSourceDescriptor currentDescriptor() const; + QVariantMap sourceSettings() const; + + scopeone::core::ScopeOneCore* m_core{nullptr}; + scopeone::ui::ScopeOneToolContext& m_context; + QComboBox* m_sourceCombo{nullptr}; + QFormLayout* m_sourceForm{nullptr}; + QHash m_parameterEditors; + QDoubleSpinBox* m_sampleIntervalSpin{nullptr}; + QDoubleSpinBox* m_windowDurationSpin{nullptr}; + QCheckBox* m_scanImageCheck{nullptr}; + QSpinBox* m_scanWidthSpin{nullptr}; + QSpinBox* m_scanHeightSpin{nullptr}; + QSpinBox* m_scanGainSpin{nullptr}; + QSpinBox* m_scanAverageFramesSpin{nullptr}; + QComboBox* m_frameStartMarkerCombo{nullptr}; + QComboBox* m_lineMarkerCombo{nullptr}; + QComboBox* m_frameEndMarkerCombo{nullptr}; + QCheckBox* m_serpentineCheck{nullptr}; + QCheckBox* m_mirrorHorizontalCheck{nullptr}; + QPushButton* m_startButton{nullptr}; + QPushButton* m_stopButton{nullptr}; + QLabel* m_valueLabel{nullptr}; + QLabel* m_statusLabel{nullptr}; + SignalTracePlot* m_plot{nullptr}; + QString m_activeSourceId; + }; +} diff --git a/plugins/ScanningDaqTool/plugin.json b/plugins/ScanningDaqTool/plugin.json new file mode 100644 index 0000000..3a17f0f --- /dev/null +++ b/plugins/ScanningDaqTool/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "scopeone.scanning-daq-tool", + "name": "Scanning and DAQ Tools", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "tool" +} diff --git a/plugins/examples/README.md b/plugins/examples/README.md index a4cd7b0..cab0399 100644 --- a/plugins/examples/README.md +++ b/plugins/examples/README.md @@ -1,6 +1,6 @@ # ScopeOne plugin examples -These projects use the installed `scopeone::PluginSDK` target and do not depend on ScopeOne source files. Include `scopeone/PluginSDK.h` as the public SDK entry point. +These projects use the installed `scopeone::PluginSDK` target and do not depend on ScopeOne source files. The public headers live in `ScopeOneCore/include/scopeone`; include `scopeone/PluginSDK.h` as the SDK entry point. The same plugin build also contains the external NI-DAQmx, PTU and scanning tool plugins. The normal ScopeOne build script builds and installs these examples automatically during a full build: @@ -14,9 +14,9 @@ To build only the examples: .\scripts\build.ps1 --target plugins ``` -Each example plugin declares `id`, `name`, `version`, `scopeOneApi`, and `kind` in `plugin.json`. Processing plugins implement `ProcessingPlugin` and add modules to the shared pipeline. Hardware providers implement `DriverHostProviderPlugin` and run in `ScopeOne_DriverHost`. Tool plugins implement `ScopeOneToolPlugin` and own an independent workflow window. `ExampleHardwarePlugin` wraps the public `SimulatorProvider` to demonstrate a complete external hardware plugin. +Each example plugin declares `id`, `name`, `version`, `scopeOneApi`, and `kind` in `plugin.json`. Processing plugins implement `ProcessingPlugin` and add modules to the shared pipeline. Hardware providers implement `DriverHostProviderPlugin` and run in `ScopeOne_DriverHost`. DAQ plugins implement `DaqDevicePlugin`, signal sources implement `SignalSourcePlugin`, and tool plugins implement `ScopeOneToolPlugin`. -Install processing plugins under `plugins/processing`, tool plugins under `plugins/tools`, and hardware plugins under `plugins/hardware`. +Install processing plugins under `plugins/processing`, tool plugins under `plugins/tools`, and hardware, DAQ and signal source plugins under `plugins/hardware`. Tool plugins can create an independent processing pipeline without changing the main Process panel: diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 80d61de..97b0d97 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -236,7 +236,7 @@ $writerBuildDir = Join-Path $writerBuildRoot "standalone" $writerInstallDir = Join-Path $writerSourceDir "install" $writerConsumerSourceDir = Join-Path $writerSourceDir "tests\consumer" $writerConsumerBuildDir = Join-Path $writerBuildRoot "consumer" -$pluginSourceDir = Join-Path $repoRoot "plugins\examples" +$pluginSourceDir = Join-Path $repoRoot "plugins" $pluginBuildDir = Join-Path $repoRoot "build\plugins" $pluginInstallDir = Join-Path $coreInstallDir "bin" $config = "Release" @@ -283,6 +283,16 @@ $needPluginConfigure = $configure -or -not (Test-Path $pluginCachePath) -or -not $pluginBuildFilesExist $pluginCachedInstallPrefix = Normalize-CMakePath ( Get-CMakeCacheValue -CachePath $pluginCachePath -Key "CMAKE_INSTALL_PREFIX") +$pluginCachedSourceDir = Normalize-CMakePath ( + Get-CMakeCacheValue -CachePath $pluginCachePath -Key "CMAKE_HOME_DIRECTORY") +if ($pluginCachedSourceDir -and + $pluginCachedSourceDir -ne (Normalize-CMakePath $pluginSourceDir)) { + $needPluginConfigure = $true + if (Test-Path $pluginBuildDir) { + Write-Step "Removing stale plugin build directory" + Remove-Item -LiteralPath $pluginBuildDir -Recurse -Force + } +} if ($pluginCachedInstallPrefix -and $pluginCachedInstallPrefix -ne (Normalize-CMakePath $pluginInstallDir)) { $needPluginConfigure = $true @@ -446,14 +456,14 @@ if ($target -in @("all", "plugins")) { $pluginConfigureArgs += "-DOpenCV_DIR=$coreOpenCvDir" } Invoke-Step ` - -Label "Configuring ScopeOne plugin examples" ` + -Label "Configuring ScopeOne plugins" ` -FilePath $cmake ` -Arguments $pluginConfigureArgs ` -WorkingDirectory $repoRoot } Invoke-Step ` - -Label "Building ScopeOne plugin examples ($config)" ` + -Label "Building ScopeOne plugins ($config)" ` -FilePath $cmake ` -Arguments @( "--build", $pluginBuildDir, @@ -463,7 +473,7 @@ if ($target -in @("all", "plugins")) { -WorkingDirectory $repoRoot Invoke-Step ` - -Label "Installing ScopeOne plugin examples" ` + -Label "Installing ScopeOne plugins" ` -FilePath $cmake ` -Arguments @( "--install", $pluginBuildDir, @@ -548,7 +558,7 @@ if ($target -eq "scopewriter") { else { Write-Host "ScopeOneCore install: $coreInstallDir" if ($target -in @("all", "plugins")) { - Write-Host "Plugin examples: $pluginBuildDir" + Write-Host "Plugins: $pluginBuildDir" } } if ($target -ne "scopewriter") { diff --git a/src/PluginManagerDialog.cpp b/src/PluginManagerDialog.cpp index bdce7c6..b8766f4 100644 --- a/src/PluginManagerDialog.cpp +++ b/src/PluginManagerDialog.cpp @@ -1,8 +1,10 @@ #include "PluginManagerDialog.h" #include "scopeone/PluginManifest.h" +#include "scopeone/DaqDevice.h" #include "scopeone/DriverHostProviderPlugin.h" #include "scopeone/ProcessingPlugin.h" +#include "scopeone/SignalSource.h" #include "scopeone/ScopeOneCore.h" #include "scopeone/ToolPlugin.h" @@ -33,16 +35,21 @@ namespace scopeone::ui { constexpr int kIdRole = Qt::UserRole; constexpr int kKindRole = Qt::UserRole + 1; + constexpr int kStatusRole = Qt::UserRole + 2; + constexpr int kMetadataRole = Qt::UserRole + 3; struct DiscoveredPlugin { scopeone::core::PluginManifest manifest; scopeone::core::PluginKind expectedKind{scopeone::core::PluginKind::Processing}; QString path; + QString interfaceId; + QJsonObject metadata; QString error; + QString loadError; }; - QString pluginInterfaceId(scopeone::core::PluginKind kind); + QStringList pluginInterfaceIds(scopeone::core::PluginKind kind); QList discoverPlugins() { @@ -75,17 +82,30 @@ namespace scopeone::ui plugin.expectedKind = kind; plugin.path = file.absoluteFilePath(); const QJsonObject loaderMetadata = loader.metaData(); + plugin.interfaceId = loaderMetadata.value(QStringLiteral("IID")).toString(); + plugin.metadata = loaderMetadata.value(QStringLiteral("MetaData")).toObject(); scopeone::core::parsePluginManifest( - loaderMetadata.value(QStringLiteral("MetaData")).toObject(), + plugin.metadata, kind, plugin.manifest, &plugin.error); if (plugin.error.isEmpty() - && loaderMetadata.value(QStringLiteral("IID")).toString() - != pluginInterfaceId(kind)) + && !pluginInterfaceIds(kind).contains( + loaderMetadata.value(QStringLiteral("IID")).toString())) { plugin.error = QStringLiteral("plugin interface does not match its kind"); } + if (plugin.error.isEmpty()) + { + if (!loader.load()) + { + plugin.loadError = loader.errorString(); + } + else + { + loader.unload(); + } + } plugins.append(std::move(plugin)); } } @@ -105,16 +125,19 @@ namespace scopeone::ui : scopeone::core::pluginKindName(kind); } - QString pluginInterfaceId(scopeone::core::PluginKind kind) + QStringList pluginInterfaceIds(scopeone::core::PluginKind kind) { switch (kind) { case scopeone::core::PluginKind::Processing: - return QStringLiteral(ScopeOneProcessingPlugin_iid); + return {QStringLiteral(ScopeOneProcessingPlugin_iid)}; case scopeone::core::PluginKind::Tool: - return QStringLiteral(ScopeOneToolPlugin_iid); + return {QStringLiteral(ScopeOneToolPlugin_iid)}; case scopeone::core::PluginKind::Hardware: - return QStringLiteral(ScopeOneDriverHostProviderPlugin_iid); + return { + QStringLiteral(ScopeOneDriverHostProviderPlugin_iid), + QStringLiteral(SCOPEONE_DAQ_DEVICE_PLUGIN_IID), + QStringLiteral(SCOPEONE_SIGNAL_SOURCE_PLUGIN_IID)}; } return {}; } @@ -130,10 +153,15 @@ namespace scopeone::ui { continue; } - if (!plugin.error.isEmpty()) + if (plugin.interfaceId != QStringLiteral(ScopeOneDriverHostProviderPlugin_iid)) + { + continue; + } + if (!plugin.error.isEmpty() || !plugin.loadError.isEmpty()) { errors.append(QStringLiteral("%1: %2") - .arg(QFileInfo(plugin.path).fileName(), plugin.error)); + .arg(QFileInfo(plugin.path).fileName(), + plugin.error.isEmpty() ? plugin.loadError : plugin.error)); continue; } @@ -173,7 +201,7 @@ namespace scopeone::ui : QDialog(parent) { setWindowTitle(tr("Plugin Manager")); - resize(820, 460); + resize(900, 600); auto* layout = new QVBoxLayout(this); m_table = new QTableWidget(this); @@ -188,6 +216,12 @@ namespace scopeone::ui m_table->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch); layout->addWidget(m_table, 1); + layout->addWidget(new QLabel(tr("Plugin metadata and diagnostics"), this)); + m_detailsEdit = new QPlainTextEdit(this); + m_detailsEdit->setReadOnly(true); + m_detailsEdit->setMaximumHeight(160); + layout->addWidget(m_detailsEdit); + layout->addWidget(new QLabel(tr("Hardware options (JSON)"), this)); m_optionsEdit = new QPlainTextEdit(this); m_optionsEdit->setMaximumHeight(100); @@ -220,7 +254,14 @@ namespace scopeone::ui auto* enabled = new QTableWidgetItem(); enabled->setData(kIdRole, plugin.manifest.id); enabled->setData(kKindRole, static_cast(plugin.expectedKind)); - if (hardware && plugin.error.isEmpty()) + const QString status = plugin.error.isEmpty() + ? (plugin.loadError.isEmpty() ? tr("Ready") : plugin.loadError) + : plugin.error; + enabled->setData(kStatusRole, status); + enabled->setData( + kMetadataRole, + QString::fromUtf8(QJsonDocument(plugin.metadata).toJson(QJsonDocument::Indented))); + if (hardware && plugin.error.isEmpty() && plugin.loadError.isEmpty()) { enabled->setFlags(enabled->flags() | Qt::ItemIsUserCheckable); const QString key = settingsKey(plugin.manifest.id, QStringLiteral("enabled")); @@ -238,7 +279,7 @@ namespace scopeone::ui scopeone::core::pluginKindName(plugin.expectedKind))); m_table->setItem(row, 3, new QTableWidgetItem(plugin.manifest.version)); m_table->setItem(row, 4, new QTableWidgetItem( - plugin.error.isEmpty() ? tr("Compatible") : plugin.error)); + status)); m_table->setItem(row, 5, new QTableWidgetItem(plugin.path)); } if (m_table->rowCount() > 0) @@ -259,7 +300,14 @@ namespace scopeone::ui const QTableWidgetItem* item = m_table->item(row, 0); const auto kind = static_cast(item->data(kKindRole).toInt()); const QString id = item->data(kIdRole).toString(); - const bool hardware = kind == scopeone::core::PluginKind::Hardware && !id.isEmpty(); + const QString status = item->data(kStatusRole).toString(); + m_detailsEdit->setPlainText( + item->data(kMetadataRole).toString() + + QStringLiteral("\n\n") + + tr("Status: %1").arg(status)); + const bool hardware = kind == scopeone::core::PluginKind::Hardware + && !id.isEmpty() + && status == tr("Ready"); m_optionsEdit->setEnabled(hardware); if (!hardware) { @@ -305,7 +353,7 @@ namespace scopeone::ui QMessageBox::warning(this, tr("Plugin Manager"), error); return; } - if (loaderMetadata.value(QStringLiteral("IID")).toString() != pluginInterfaceId(kind)) + if (!pluginInterfaceIds(kind).contains(loaderMetadata.value(QStringLiteral("IID")).toString())) { QMessageBox::warning(this, tr("Plugin Manager"), tr("The plugin interface does not match its declared type.")); diff --git a/src/PluginManagerDialog.h b/src/PluginManagerDialog.h index ffcb54c..04acec6 100644 --- a/src/PluginManagerDialog.h +++ b/src/PluginManagerDialog.h @@ -29,6 +29,7 @@ namespace scopeone::ui void saveHardwareSettings(); QTableWidget* m_table{nullptr}; + QPlainTextEdit* m_detailsEdit{nullptr}; QPlainTextEdit* m_optionsEdit{nullptr}; }; } diff --git a/src/ScopeOneToolPlugin.cpp b/src/ScopeOneToolPlugin.cpp index ffa7054..e78940c 100644 --- a/src/ScopeOneToolPlugin.cpp +++ b/src/ScopeOneToolPlugin.cpp @@ -187,6 +187,14 @@ namespace scopeone::ui void ToolRegistry::openTool(const QString& toolId, QWidget* parent) { + const auto centerTool = [parent](QWidget* tool) + { + QWidget* host = parent->window(); + const QPoint position = host->frameGeometry().center() + - tool->frameGeometry().center(); + tool->move(position); + }; + for (const auto& entry : m_entries) { if (entry->descriptor.id != toolId) @@ -215,20 +223,17 @@ namespace scopeone::ui delete tool; return; } - tool->setWindowFlag(Qt::Window, true); + tool->setParent(nullptr, Qt::Window); tool->setAttribute(Qt::WA_DeleteOnClose); entry->instance = tool; + tool->adjustSize(); tool->show(); - QTimer::singleShot(0, tool, [tool, parent]() + tool->raise(); + tool->activateWindow(); + QTimer::singleShot(0, tool, [tool, centerTool]() { - QWidget* host = parent ? parent->window() : nullptr; - if (!host) - { - return; - } - const QPoint position = host->frameGeometry().center() - - tool->frameGeometry().center(); - tool->move(position); + tool->adjustSize(); + centerTool(tool); }); } } From 766490f0b352a744522e3aa9d40a256696d6cbc8 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:13:52 +0200 Subject: [PATCH 22/37] Add standard colormap LUTs --- ScopeOneCore/src/ImageSceneModel.cpp | 3 + plugins/examples/CMakeLists.txt | 2 +- plugins/examples/tool/CMakeLists.txt | 1 + plugins/examples/tool/DhmReconstruction.cpp | 596 +++++++++++++----- plugins/examples/tool/DhmReconstruction.h | 56 +- plugins/examples/tool/DhmToolPlugin.cpp | 640 +++++++++++++++----- resources/luts/Jet.lut | Bin 0 -> 768 bytes resources/luts/README.md | 3 + resources/luts/Rainbow.lut | Bin 0 -> 768 bytes resources/luts/Turbo.lut | 4 + resources/resources.qrc | 3 + 11 files changed, 995 insertions(+), 313 deletions(-) create mode 100644 resources/luts/Jet.lut create mode 100644 resources/luts/Rainbow.lut create mode 100644 resources/luts/Turbo.lut diff --git a/ScopeOneCore/src/ImageSceneModel.cpp b/ScopeOneCore/src/ImageSceneModel.cpp index 799eba1..e8ea56e 100644 --- a/ScopeOneCore/src/ImageSceneModel.cpp +++ b/ScopeOneCore/src/ImageSceneModel.cpp @@ -522,6 +522,9 @@ namespace scopeone::core QStringLiteral("Inferno"), QStringLiteral("Magma"), QStringLiteral("Cividis"), + QStringLiteral("Jet"), + QStringLiteral("Rainbow"), + QStringLiteral("Turbo"), }; } diff --git a/plugins/examples/CMakeLists.txt b/plugins/examples/CMakeLists.txt index fdde06f..e36a8a2 100644 --- a/plugins/examples/CMakeLists.txt +++ b/plugins/examples/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_AUTOMOC ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) find_package(ScopeOneCore CONFIG REQUIRED) -find_package(OpenCV REQUIRED COMPONENTS imgcodecs imgproc) +find_package(OpenCV REQUIRED COMPONENTS core imgcodecs imgproc) function(scopeone_example_plugin target kind) set_target_properties(${target} PROPERTIES diff --git a/plugins/examples/tool/CMakeLists.txt b/plugins/examples/tool/CMakeLists.txt index 020f40d..8e33a03 100644 --- a/plugins/examples/tool/CMakeLists.txt +++ b/plugins/examples/tool/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(ScopeOneDhmTool MODULE target_compile_features(ScopeOneDhmTool PRIVATE cxx_std_20) target_link_libraries(ScopeOneDhmTool PRIVATE scopeone::PluginSDK + opencv_core opencv_imgcodecs opencv_imgproc Qt6::Widgets diff --git a/plugins/examples/tool/DhmReconstruction.cpp b/plugins/examples/tool/DhmReconstruction.cpp index 20cab6e..b6f1fe7 100644 --- a/plugins/examples/tool/DhmReconstruction.cpp +++ b/plugins/examples/tool/DhmReconstruction.cpp @@ -1,251 +1,525 @@ #include "DhmReconstruction.h" +#include + #include #include -#include -#include -#include -#include +#include +#include namespace { - using Complex = std::complex; + using scopeone::core::ImageFrame; + using scopeone::dhm::DhmParameters; + constexpr float Pi = 3.14159265358979323846f; + constexpr float TwoPi = 2.0f * Pi; + + struct SpectrumData + { + cv::Mat shiftedSpectrum; + cv::Mat logMagnitude; + int roiWidth{0}; + int roiHeight{0}; + }; + + cv::Mat inputAsFloat(const ImageFrame& input) + { + const int depth = input.isMono16() ? CV_16UC1 : CV_8UC1; + const cv::Mat view(input.height, + input.width, + depth, + const_cast(input.bytes.constData()), + static_cast(input.stride)); + cv::Mat result; + view.convertTo(result, CV_32F); + return result; + } - int nextPowerOfTwo(int value) + cv::Mat applyRoi(const cv::Mat& input, const DhmParameters& params) { - int size = 1; - while (size < value) + if (params.roiMode == scopeone::dhm::DhmRoiMode::FullFrame) { - size <<= 1; + return input.clone(); } - return size; + + const int size = std::min(params.roiSize, std::min(input.cols, input.rows)); + const int x = (input.cols - size) / 2; + const int y = (input.rows - size) / 2; + return input(cv::Rect(x, y, size, size)).clone(); + } + + void fftShift(cv::Mat& input) + { + const int halfWidth = input.cols / 2; + const int halfHeight = input.rows / 2; + cv::Mat topLeft(input, cv::Rect(0, 0, halfWidth, halfHeight)); + cv::Mat topRight(input, cv::Rect(input.cols - halfWidth, + 0, + halfWidth, + halfHeight)); + cv::Mat bottomLeft(input, cv::Rect(0, + input.rows - halfHeight, + halfWidth, + halfHeight)); + cv::Mat bottomRight(input, cv::Rect(input.cols - halfWidth, + input.rows - halfHeight, + halfWidth, + halfHeight)); + + cv::Mat temporary; + topLeft.copyTo(temporary); + bottomRight.copyTo(topLeft); + temporary.copyTo(bottomRight); + topRight.copyTo(temporary); + bottomLeft.copyTo(topRight); + temporary.copyTo(bottomLeft); } - int wrapIndex(int value, int size) + SpectrumData buildSpectrum(const ImageFrame& input, const DhmParameters& params) { - value %= size; - return value < 0 ? value + size : value; + SpectrumData result; + const cv::Mat roi = applyRoi(inputAsFloat(input), params); + result.roiWidth = roi.cols; + result.roiHeight = roi.rows; + + const int fftWidth = cv::getOptimalDFTSize(roi.cols); + const int fftHeight = cv::getOptimalDFTSize(roi.rows); + cv::Mat padded; + cv::copyMakeBorder(roi, + padded, + 0, + fftHeight - roi.rows, + 0, + fftWidth - roi.cols, + cv::BORDER_CONSTANT, + cv::Scalar::all(0)); + + cv::dft(padded, result.shiftedSpectrum, cv::DFT_COMPLEX_OUTPUT); + fftShift(result.shiftedSpectrum); + + cv::Mat planes[2]; + cv::split(result.shiftedSpectrum, planes); + cv::magnitude(planes[0], planes[1], result.logMagnitude); + result.logMagnitude += 1.0f; + cv::log(result.logMagnitude, result.logMagnitude); + return result; + } + + ImageFrame normalizedFrame(const cv::Mat& values, + const ImageFrame& source, + const QString& cameraId) + { + cv::Mat normalized; + cv::normalize(values, normalized, 0.0, 65535.0, cv::NORM_MINMAX, CV_16U); + normalized = normalized.clone(); + + ImageFrame frame; + frame.cameraId = cameraId; + frame.width = normalized.cols; + frame.height = normalized.rows; + frame.stride = static_cast(normalized.step); + frame.bitsPerSample = 16; + frame.pixelFormat = scopeone::core::ImagePixelFormat::Mono16; + frame.frameIndex = source.frameIndex; + frame.timestampNs = source.timestampNs; + frame.bytes = QByteArray(reinterpret_cast(normalized.data), + static_cast(normalized.total() + * normalized.elemSize())); + return frame; + } + + QPoint detectedOffset(const cv::Mat& logMagnitude) + { + const cv::Point point = scopeone::dhm::autoDetectSideband(logMagnitude); + return {point.x - logMagnitude.cols / 2, + point.y - logMagnitude.rows / 2}; } - void fft1d(std::vector& values, bool inverse) + cv::Mat circularMask(int width, + int height, + const cv::Point& center, + int radius, + bool softEdge, + double sigma) { - const int size = static_cast(values.size()); - for (int i = 1, j = 0; i < size; ++i) + cv::Mat mask = cv::Mat::zeros(height, width, CV_32F); + cv::circle(mask, center, radius, cv::Scalar(1.0f), cv::FILLED, cv::LINE_AA); + if (softEdge) { - int bit = size >> 1; - for (; j & bit; bit >>= 1) - { - j ^= bit; - } - j ^= bit; - if (i < j) + const int kernelSize = std::max(3, (static_cast(std::ceil(sigma * 6.0)) | 1)); + cv::GaussianBlur(mask, + mask, + cv::Size(kernelSize, kernelSize), + sigma, + sigma, + cv::BORDER_REPLICATE); + double maximum = 0.0; + cv::minMaxLoc(mask, nullptr, &maximum); + mask /= static_cast(maximum); + } + return mask; + } + + void circularShift(const cv::Mat& source, cv::Mat& destination, int shiftX, int shiftY) + { + destination.create(source.size(), source.type()); + for (int y = 0; y < source.rows; ++y) + { + const int sourceY = (y - shiftY + source.rows) % source.rows; + for (int x = 0; x < source.cols; ++x) { - std::swap(values[i], values[j]); + const int sourceX = (x - shiftX + source.cols) % source.cols; + destination.at(y, x) = source.at(sourceY, sourceX); } } + } - for (int length = 2; length <= size; length <<= 1) + void applyMask(cv::Mat& spectrum, const cv::Mat& mask) + { + for (int y = 0; y < spectrum.rows; ++y) { - const float angle = 2.0f * Pi / static_cast(length) - * (inverse ? 1.0f : -1.0f); - const Complex step(std::cos(angle), std::sin(angle)); - for (int start = 0; start < size; start += length) + auto* row = spectrum.ptr(y); + const auto* maskRow = mask.ptr(y); + for (int x = 0; x < spectrum.cols; ++x) { - Complex factor(1.0f, 0.0f); - const int half = length / 2; - for (int index = 0; index < half; ++index) - { - const Complex even = values[start + index]; - const Complex odd = factor * values[start + index + half]; - values[start + index] = even + odd; - values[start + index + half] = even - odd; - factor *= step; - } + row[x] *= maskRow[x]; } } + } - if (inverse) + void applyPropagation(cv::Mat& shiftedSpectrum, + double wavelength, + double pixelSize, + double z) + { + const double waveNumber = 2.0 * CV_PI / wavelength; + const int centerX = shiftedSpectrum.cols / 2; + const int centerY = shiftedSpectrum.rows / 2; + for (int y = 0; y < shiftedSpectrum.rows; ++y) { - const float scale = 1.0f / static_cast(size); - for (Complex& value : values) + const double fy = static_cast(y - centerY) + / (pixelSize * shiftedSpectrum.rows); + const double ky = 2.0 * CV_PI * fy; + auto* row = shiftedSpectrum.ptr(y); + for (int x = 0; x < shiftedSpectrum.cols; ++x) { - value *= scale; + const double fx = static_cast(x - centerX) + / (pixelSize * shiftedSpectrum.cols); + const double kx = 2.0 * CV_PI * fx; + const double kz = std::sqrt(std::max(0.0, + waveNumber * waveNumber + - kx * kx + - ky * ky)); + const float real = static_cast(std::cos(kz * z)); + const float imaginary = static_cast(std::sin(kz * z)); + const cv::Vec2f value = row[x]; + row[x][0] = value[0] * real - value[1] * imaginary; + row[x][1] = value[0] * imaginary + value[1] * real; } } } - void fft2d(std::vector& values, - int width, - int height, - bool inverse, - const std::atomic_bool& cancel, - const std::function& progress, - int progressStart, - int progressEnd) + float wrappedDifference(float value, float reference) { - std::vector line(static_cast(std::max(width, height))); - for (int y = 0; y < height; ++y) + float difference = value - reference; + while (difference > Pi) { - if (cancel) + difference -= TwoPi; + } + while (difference < -Pi) + { + difference += TwoPi; + } + return difference; + } + + cv::Mat unwrapPhase2D(const cv::Mat& wrapped, + const std::atomic_bool& cancel) + { + struct Node + { + float quality; + int x; + int y; + + bool operator<(const Node& other) const + { + return quality < other.quality; + } + }; + + cv::Mat reliability = cv::Mat::zeros(wrapped.size(), CV_32F); + for (int y = 0; y < wrapped.rows; ++y) + { + for (int x = 0; x < wrapped.cols; ++x) { - return; + const float horizontal = x + 1 < wrapped.cols + ? std::abs(wrappedDifference( + wrapped.at(y, x + 1), + wrapped.at(y, x))) + : 0.0f; + const float vertical = y + 1 < wrapped.rows + ? std::abs(wrappedDifference( + wrapped.at(y + 1, x), + wrapped.at(y, x))) + : 0.0f; + reliability.at(y, x) = 1.0f / (horizontal + vertical + 1.0e-6f); } - line.assign(values.begin() + static_cast(y) * width, - values.begin() + static_cast(y + 1) * width); - fft1d(line, inverse); - std::copy(line.begin(), line.begin() + width, - values.begin() + static_cast(y) * width); - progress(progressStart + (progressEnd - progressStart) * y / (2 * height)); } - line.resize(height); - for (int x = 0; x < width; ++x) + double maximum = 0.0; + cv::Point seed; + cv::minMaxLoc(reliability, nullptr, &maximum, nullptr, &seed); + + cv::Mat unwrapped = cv::Mat::zeros(wrapped.size(), CV_32F); + cv::Mat visited = cv::Mat::zeros(wrapped.size(), CV_8U); + std::priority_queue queue; + queue.push({reliability.at(seed.y, seed.x), seed.x, seed.y}); + unwrapped.at(seed.y, seed.x) = wrapped.at(seed.y, seed.x); + + constexpr int dx[] = {1, -1, 0, 0}; + constexpr int dy[] = {0, 0, 1, -1}; + while (!queue.empty()) { if (cancel) { - return; + return {}; } - for (int y = 0; y < height; ++y) + + const Node node = queue.top(); + queue.pop(); + if (visited.at(node.y, node.x)) { - line[y] = values[static_cast(y) * width + x]; + continue; } - fft1d(line, inverse); - for (int y = 0; y < height; ++y) + visited.at(node.y, node.x) = 1; + + for (int direction = 0; direction < 4; ++direction) { - values[static_cast(y) * width + x] = line[y]; + const int nx = node.x + dx[direction]; + const int ny = node.y + dy[direction]; + if (nx < 0 || nx >= wrapped.cols || ny < 0 || ny >= wrapped.rows + || visited.at(ny, nx)) + { + continue; + } + + const float reference = unwrapped.at(node.y, node.x); + const float value = wrapped.at(ny, nx); + unwrapped.at(ny, nx) + = value + TwoPi * std::round((reference - value) / TwoPi); + queue.push({reliability.at(ny, nx), nx, ny}); } - progress(progressStart + (progressEnd - progressStart) - * (height + x) / (height + width)); } + return unwrapped; } - std::vector toComplex(const scopeone::core::ImageFrame& frame, - int width, - int height) + void removeTilt(cv::Mat& phase) { - std::vector values(static_cast(width) * height); - for (int y = 0; y < frame.height; ++y) + const double centerX = (phase.cols - 1) / 2.0; + const double centerY = (phase.rows - 1) / 2.0; + const double scaleX = std::max(1.0, centerX); + const double scaleY = std::max(1.0, centerY); + + double sums[3][3]{}; + double vector[3]{}; + for (int y = 0; y < phase.rows; ++y) { - const auto* row = reinterpret_cast(frame.bytes.constData() - + y * frame.stride); - for (int x = 0; x < frame.width; ++x) + const double normalizedY = (y - centerY) / scaleY; + for (int x = 0; x < phase.cols; ++x) { - const float value = frame.isMono16() - ? static_cast(reinterpret_cast(row)[x]) - : static_cast(row[x]); - values[static_cast(y) * width + x] = Complex(value, 0.0f); + const double normalizedX = (x - centerX) / scaleX; + const double terms[3] = {normalizedX, normalizedY, 1.0}; + const double value = phase.at(y, x); + for (int row = 0; row < 3; ++row) + { + vector[row] += terms[row] * value; + for (int column = 0; column < 3; ++column) + { + sums[row][column] += terms[row] * terms[column]; + } + } } } - return values; - } - scopeone::core::ImageFrame phaseFrame(const scopeone::core::ImageFrame& input, - const std::vector& field, - int width) - { - scopeone::core::ImageFrame output; - output.cameraId = QStringLiteral("dhm.phase"); - output.width = input.width; - output.height = input.height; - output.stride = input.width * static_cast(sizeof(unsigned short)); - output.bitsPerSample = 16; - output.pixelFormat = scopeone::core::ImagePixelFormat::Mono16; - output.frameIndex = input.frameIndex; - output.timestampNs = input.timestampNs; - output.bytes.resize(output.stride * output.height); + cv::Mat matrix(3, 3, CV_64F, sums); + cv::Mat rightHandSide(3, 1, CV_64F, vector); + cv::Mat coefficients; + cv::solve(matrix, rightHandSide, coefficients, cv::DECOMP_CHOLESKY); - for (int y = 0; y < input.height; ++y) + for (int y = 0; y < phase.rows; ++y) { - auto* row = reinterpret_cast(output.bytes.data() - + y * output.stride); - for (int x = 0; x < input.width; ++x) + const double normalizedY = (y - centerY) / scaleY; + for (int x = 0; x < phase.cols; ++x) { - float phase = std::arg(field[static_cast(y) * width + x]); - phase = (phase + Pi) / (2.0f * Pi); - row[x] = static_cast(std::clamp(phase, 0.0f, 1.0f) - * 65535.0f); + const double normalizedX = (x - centerX) / scaleX; + const double plane = coefficients.at(0) * normalizedX + + coefficients.at(1) * normalizedY + + coefficients.at(2); + phase.at(y, x) -= static_cast(plane); } } - return output; } } namespace scopeone::dhm { - scopeone::core::ImageFrame reconstructPhase( - const scopeone::core::ImageFrame& input, - int sidebandX, - int sidebandY, - int radius, - const std::atomic_bool& cancel, - const std::function& progress) + cv::Point autoDetectSideband(const cv::Mat& logMagSpectrum) + { + cv::Mat blurred; + cv::GaussianBlur(logMagSpectrum, blurred, cv::Size(0, 0), 2.0, 2.0); + + const cv::Point center(logMagSpectrum.cols / 2, logMagSpectrum.rows / 2); + const int dcRadius = static_cast( + 0.06 * std::min(logMagSpectrum.cols, logMagSpectrum.rows)); + cv::Mat mask = cv::Mat::zeros(logMagSpectrum.size(), CV_8U); + cv::rectangle(mask, + cv::Rect(0, 0, logMagSpectrum.cols, std::max(1, center.y)), + cv::Scalar(255), + cv::FILLED); + cv::circle(mask, center, dcRadius, cv::Scalar(0), cv::FILLED); + + cv::Point peak; + cv::minMaxLoc(blurred, nullptr, nullptr, nullptr, &peak, mask); + return peak; + } + + std::pair computeSpectrum(const ImageFrame& input, + const DhmParameters& params) + { + const SpectrumData spectrum = buildSpectrum(input, params); + const ImageFrame frame = normalizedFrame( + spectrum.logMagnitude, input, QStringLiteral("dhm.spectrum")); + return {frame, detectedOffset(spectrum.logMagnitude)}; + } + + DhmResult reconstruct(const ImageFrame& input, + const DhmParameters& params, + const std::atomic_bool& cancel, + const std::function& progress) { - if (!input.isValid() || (!input.isMono8() && !input.isMono16())) + const SpectrumData spectrum = buildSpectrum(input, params); + const QPoint detected = detectedOffset(spectrum.logMagnitude); + const QPoint selected = params.autoDetectSideband + ? detected + : QPoint(params.sidebandX, params.sidebandY); + const int maximumX = spectrum.shiftedSpectrum.cols / 2 - 1; + const int maximumY = spectrum.shiftedSpectrum.rows / 2 - 1; + const int sidebandX = std::clamp(selected.x(), -maximumX, maximumX); + const int sidebandY = std::clamp(selected.y(), -maximumY, maximumY); + const int radius = std::clamp( + params.radius, + 1, + std::min(spectrum.shiftedSpectrum.cols, spectrum.shiftedSpectrum.rows) / 2); + + DhmResult result; + result.detectedSidebandX = detected.x(); + result.detectedSidebandY = detected.y(); + result.spectrumFrame = normalizedFrame( + spectrum.logMagnitude, input, QStringLiteral("dhm.spectrum")); + progress(10); + + if (params.outputMode == DhmOutputMode::Spectrum) { - return {}; + result.outputFrame = result.spectrumFrame; + progress(100); + return result; } - const int width = nextPowerOfTwo(input.width); - const int height = nextPowerOfTwo(input.height); - const int safeRadius = std::clamp(radius, 1, std::min(width, height) / 2); - const int effectiveSidebandX = std::clamp(sidebandX, -width / 2, width / 2 - 1); - const int effectiveSidebandY = std::clamp(sidebandY, -height / 2, height / 2 - 1); - const int centerX = width / 2 + effectiveSidebandX; - const int centerY = height / 2 + effectiveSidebandY; - auto field = toComplex(input, width, height); - progress(5); + cv::Mat filtered = spectrum.shiftedSpectrum.clone(); + const cv::Point sideband(spectrum.shiftedSpectrum.cols / 2 + sidebandX, + spectrum.shiftedSpectrum.rows / 2 + sidebandY); + const cv::Mat mask = circularMask(spectrum.shiftedSpectrum.cols, + spectrum.shiftedSpectrum.rows, + sideband, + radius, + params.softEdge, + params.softEdgeSigma); + applyMask(filtered, mask); + progress(30); - fft2d(field, width, height, false, cancel, progress, 5, 45); - if (cancel) + cv::Mat demodulated; + circularShift(filtered, demodulated, -sidebandX, -sidebandY); + if (params.z != 0.0) { - return {}; + applyPropagation(demodulated, + params.wavelength, + params.pixelSize, + params.z); } + progress(48); - for (int y = 0; y < height; ++y) + fftShift(demodulated); + cv::Mat complexField; + cv::idft(demodulated, + complexField, + cv::DFT_SCALE | cv::DFT_COMPLEX_OUTPUT); + cv::Mat fieldRoi(complexField, + cv::Rect(0, 0, spectrum.roiWidth, spectrum.roiHeight)); + + cv::Mat planes[2]; + cv::split(fieldRoi, planes); + cv::Mat amplitude; + cv::Mat phase; + cv::magnitude(planes[0], planes[1], amplitude); + cv::phase(planes[0], planes[1], phase, false); + const cv::Mat wrappedPhase = phase.clone(); + progress(62); + + if (params.unwrapPhase) { - for (int x = 0; x < width; ++x) + phase = unwrapPhase2D(phase, cancel); + if (phase.empty()) { - const int shiftedX = (x + width / 2) % width; - const int shiftedY = (y + height / 2) % height; - const int dx = shiftedX - centerX; - const int dy = shiftedY - centerY; - if (dx * dx + dy * dy > safeRadius * safeRadius) - { - field[static_cast(y) * width + x] = Complex(0.0f, 0.0f); - } + return {}; } } + if (cancel) + { + return {}; + } + progress(78); - // Move the selected carrier sideband to the zero-frequency origin. - std::vector demodulated(field.size(), Complex(0.0f, 0.0f)); - for (int y = 0; y < height; ++y) + if (params.removeTilt) { - for (int x = 0; x < width; ++x) + removeTilt(phase); + } + progress(88); + + switch (params.outputMode) + { + case DhmOutputMode::QuantitativePhase: + { + const cv::Mat quantitativePhase = phase * (params.wavelength / TwoPi); + result.outputFrame = normalizedFrame( + quantitativePhase, input, QStringLiteral("dhm.phase")); + break; + } + case DhmOutputMode::WrappedPhase: + { + cv::Mat displayPhase = wrappedPhase.clone(); + for (int y = 0; y < displayPhase.rows; ++y) { - const int shiftedX = (x + width / 2) % width; - const int shiftedY = (y + height / 2) % height; - const int dx = shiftedX - centerX; - const int dy = shiftedY - centerY; - if (dx * dx + dy * dy > safeRadius * safeRadius) + for (int x = 0; x < displayPhase.cols; ++x) { - continue; + displayPhase.at(y, x) + = (displayPhase.at(y, x) + Pi) / TwoPi; } - const int targetX = wrapIndex(x - effectiveSidebandX, width); - const int targetY = wrapIndex(y - effectiveSidebandY, height); - demodulated[static_cast(targetY) * width + targetX] - = field[static_cast(y) * width + x]; } + result.outputFrame = normalizedFrame( + displayPhase, input, QStringLiteral("dhm.wrapped_phase")); + break; } - field.swap(demodulated); - progress(55); - - fft2d(field, width, height, true, cancel, progress, 55, 95); - if (cancel) - { - return {}; + case DhmOutputMode::Amplitude: + result.outputFrame = normalizedFrame( + amplitude, input, QStringLiteral("dhm.amplitude")); + break; + case DhmOutputMode::Spectrum: + break; } + progress(100); - return phaseFrame(input, field, width); + return result; } } diff --git a/plugins/examples/tool/DhmReconstruction.h b/plugins/examples/tool/DhmReconstruction.h index e3c0009..cf129f4 100644 --- a/plugins/examples/tool/DhmReconstruction.h +++ b/plugins/examples/tool/DhmReconstruction.h @@ -2,16 +2,64 @@ #include "scopeone/ImageFrame.h" +#include +#include + #include #include +#include namespace scopeone::dhm { - scopeone::core::ImageFrame reconstructPhase( + enum class DhmRoiMode + { + FullFrame, + CenterCrop + }; + + enum class DhmOutputMode + { + QuantitativePhase, + WrappedPhase, + Amplitude, + Spectrum + }; + + struct DhmParameters + { + int sidebandX{48}; + int sidebandY{-32}; + int radius{24}; + bool autoDetectSideband{true}; + bool softEdge{true}; + double softEdgeSigma{2.0}; + double wavelength{632.8e-9}; + double pixelSize{5.5e-6}; + double z{0.0}; + bool unwrapPhase{false}; + bool removeTilt{false}; + DhmRoiMode roiMode{DhmRoiMode::FullFrame}; + int roiSize{512}; + DhmOutputMode outputMode{DhmOutputMode::QuantitativePhase}; + }; + + struct DhmResult + { + scopeone::core::ImageFrame outputFrame; + scopeone::core::ImageFrame spectrumFrame; + int detectedSidebandX{0}; + int detectedSidebandY{0}; + }; + + DhmResult reconstruct( const scopeone::core::ImageFrame& input, - int sidebandX, - int sidebandY, - int radius, + const DhmParameters& params, const std::atomic_bool& cancel, const std::function& progress); + + std::pair computeSpectrum( + const scopeone::core::ImageFrame& input, + const DhmParameters& params); + + cv::Point autoDetectSideband(const cv::Mat& logMagSpectrum); } diff --git a/plugins/examples/tool/DhmToolPlugin.cpp b/plugins/examples/tool/DhmToolPlugin.cpp index af48da0..04e316e 100644 --- a/plugins/examples/tool/DhmToolPlugin.cpp +++ b/plugins/examples/tool/DhmToolPlugin.cpp @@ -1,16 +1,24 @@ #include "DhmReconstruction.h" + #include "scopeone/ToolFrameStream.h" #include "scopeone/ToolPlugin.h" #include "scopeone/ToolTask.h" -#include #include +#include +#include #include +#include +#include #include +#include #include -#include +#include +#include +#include #include -#include +#include +#include #include #include #include @@ -19,119 +27,271 @@ #include #include +#include #include +#include namespace { + using scopeone::core::ImageFrame; + using scopeone::dhm::DhmOutputMode; + using scopeone::dhm::DhmParameters; + using scopeone::dhm::DhmRoiMode; + + class SpectrumView final : public QWidget + { + public: + explicit SpectrumView(QWidget* parent = nullptr) + : QWidget(parent) + { + setMinimumSize(320, 320); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setMouseTracking(true); + } + + void setFrame(const ImageFrame& frame) + { + m_frame = frame; + if (!frame.isValid()) + { + m_image = {}; + update(); + return; + } + + const cv::Mat source(frame.height, + frame.width, + CV_16UC1, + const_cast(reinterpret_cast( + frame.bytes.constData())), + static_cast(frame.stride)); + cv::Mat eightBit; + source.convertTo(eightBit, CV_8U, 1.0 / 256.0); + cv::Mat colored; + cv::applyColorMap(eightBit, colored, cv::COLORMAP_JET); + cv::cvtColor(colored, colored, cv::COLOR_BGR2RGB); + m_image = QImage(colored.data, + colored.cols, + colored.rows, + static_cast(colored.step), + QImage::Format_RGB888) + .copy(); + update(); + } + + void setSelection(const QPoint& offset, int radius) + { + m_selection = offset; + m_radius = radius; + update(); + } + + void setSelectionChanged(std::function callback) + { + m_selectionChanged = std::move(callback); + } + + protected: + void paintEvent(QPaintEvent*) override + { + QPainter painter(this); + painter.fillRect(rect(), QColor(QStringLiteral("#171b20"))); + if (m_image.isNull()) + { + painter.setPen(QColor(QStringLiteral("#8d98a4"))); + painter.drawText(rect(), Qt::AlignCenter, tr("Spectrum unavailable")); + return; + } + + const QRectF imageRect = fittedImageRect(); + painter.drawImage(imageRect, m_image); + + const QPoint center(m_frame.width / 2, m_frame.height / 2); + const QPoint selected(center.x() + m_selection.x(), + center.y() + m_selection.y()); + const QPointF selectedPoint = imagePoint(selected, imageRect); + const QPointF centerPoint = imagePoint(center, imageRect); + const double scaleX = imageRect.width() / m_frame.width; + const double scaleY = imageRect.height() / m_frame.height; + const double radius = m_radius * (scaleX + scaleY) / 2.0; + + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(QPen(QColor(QStringLiteral("#ffffff")), 1.5)); + painter.drawLine(selectedPoint.x() - 8, + selectedPoint.y(), + selectedPoint.x() + 8, + selectedPoint.y()); + painter.drawLine(selectedPoint.x(), + selectedPoint.y() - 8, + selectedPoint.x(), + selectedPoint.y() + 8); + painter.setPen(QPen(QColor(QStringLiteral("#ffd166")), 2.0)); + painter.drawEllipse(selectedPoint, radius, radius); + painter.setPen(QPen(QColor(QStringLiteral("#ffffff")), 1.0, Qt::DashLine)); + painter.drawEllipse(centerPoint, 4.0, 4.0); + } + + void mousePressEvent(QMouseEvent* event) override + { + if (event->button() != Qt::LeftButton || m_image.isNull()) + { + return; + } + + const QRectF imageRect = fittedImageRect(); + if (!imageRect.contains(event->position())) + { + return; + } + const int x = std::clamp( + static_cast((event->position().x() - imageRect.left()) + * m_frame.width / imageRect.width()), + 0, + m_frame.width - 1); + const int y = std::clamp( + static_cast((event->position().y() - imageRect.top()) + * m_frame.height / imageRect.height()), + 0, + m_frame.height - 1); + const QPoint center(m_frame.width / 2, m_frame.height / 2); + m_selection = QPoint(x - center.x(), y - center.y()); + update(); + m_selectionChanged(m_selection); + } + + private: + QRectF fittedImageRect() const + { + const QSizeF imageSize = m_image.size(); + const QSizeF available = size(); + const double scale = std::min(available.width() / imageSize.width(), + available.height() / imageSize.height()); + const QSizeF scaled = imageSize * scale; + return QRectF((available.width() - scaled.width()) / 2.0, + (available.height() - scaled.height()) / 2.0, + scaled.width(), + scaled.height()); + } + + QPointF imagePoint(const QPoint& point, const QRectF& imageRect) const + { + return {imageRect.left() + imageRect.width() * point.x() / m_frame.width, + imageRect.top() + imageRect.height() * point.y() / m_frame.height}; + } + + ImageFrame m_frame; + QImage m_image; + QPoint m_selection; + int m_radius{24}; + std::function m_selectionChanged; + }; + class DhmTool final : public QWidget { public: DhmTool(scopeone::ui::ScopeOneToolContext& context, QWidget* parent) - : QWidget(parent), m_context(context) + : QWidget(parent) + , m_context(context) { setWindowFlag(Qt::Window, true); setWindowTitle(QStringLiteral("DHM Reconstruction")); - resize(640, 430); + resize(1180, 820); auto* layout = new QVBoxLayout(this); auto* previews = new QHBoxLayout(); - m_inputPreview = createPreview(QStringLiteral("Hologram input")); - m_resultPreview = createPreview(QStringLiteral("Reconstructed phase")); - previews->addWidget(m_inputPreview); - previews->addWidget(m_resultPreview); - layout->addLayout(previews); + m_inputPreview = createPreview(QStringLiteral("Input hologram")); + m_spectrumView = new SpectrumView(this); + m_resultPreview = createPreview(QStringLiteral("Reconstructed result")); + previews->addWidget(m_inputPreview, 1); + previews->addWidget(m_spectrumView, 1); + previews->addWidget(m_resultPreview, 1); + layout->addLayout(previews, 1); - auto* form = new QFormLayout(); - m_offsetX = createSpinBox(-10000, 10000, 48); - m_offsetY = createSpinBox(-10000, 10000, 32); - m_radius = createSpinBox(1, 10000, 24); - form->addRow(QStringLiteral("Sideband X"), m_offsetX); - form->addRow(QStringLiteral("Sideband Y"), m_offsetY); - form->addRow(QStringLiteral("Radius"), m_radius); - layout->addLayout(form); + auto* controls = new QHBoxLayout(); + controls->addLayout(createSidebandForm(), 1); + controls->addLayout(createOpticsForm(), 1); + controls->addLayout(createOutputForm(), 1); + layout->addLayout(controls); - m_liveCheckBox = new QCheckBox(QStringLiteral("Live reconstruction"), this); - m_liveCheckBox->setChecked(true); - layout->addWidget(m_liveCheckBox); - - auto* buttons = new QHBoxLayout(); + auto* actions = new QHBoxLayout(); m_loadButton = new QPushButton(QStringLiteral("Load hologram"), this); + m_autoDetectButton = new QPushButton(QStringLiteral("Auto detect +1"), this); m_reconstructButton = new QPushButton(QStringLiteral("Reconstruct once"), this); m_cancelButton = new QPushButton(QStringLiteral("Cancel"), this); m_cancelButton->setEnabled(false); - buttons->addWidget(m_loadButton); - buttons->addWidget(m_reconstructButton); - buttons->addWidget(m_cancelButton); - layout->addLayout(buttons); + actions->addWidget(m_loadButton); + actions->addWidget(m_autoDetectButton); + actions->addWidget(m_reconstructButton); + actions->addWidget(m_cancelButton); + actions->addStretch(); + m_liveCheckBox = new QCheckBox(QStringLiteral("Live reconstruction"), this); + m_liveCheckBox->setChecked(true); + actions->addWidget(m_liveCheckBox); + layout->addLayout(actions); + auto* statusLayout = new QHBoxLayout(); + m_progress = new QProgressBar(this); + m_progress->setRange(0, 100); + m_progress->setValue(0); m_status = new QLabel(QStringLiteral("Ready"), this); - layout->addWidget(m_status); + statusLayout->addWidget(m_progress, 1); + statusLayout->addWidget(m_status); + layout->addLayout(statusLayout); m_stream = new scopeone::ui::ScopeOneToolFrameStream(m_context.core(), this); - connect(m_reconstructButton, &QPushButton::clicked, this, - [this]() { startReconstruction(); }); - connect(m_loadButton, &QPushButton::clicked, this, [this]() + m_spectrumView->setSelectionChanged([this](const QPoint& offset) { - const QString path = QFileDialog::getOpenFileName( - this, QStringLiteral("Load hologram"), QString(), - QStringLiteral("Images (*.png *.jpg *.jpeg *.bmp *.tif *.tiff)")); - if (path.isEmpty()) - { - return; - } - m_fileInput = imageFrame(path); - m_liveCheckBox->setChecked(false); - m_sourceId = m_fileInput.cameraId; - startReconstruction(); + setManualSideband(offset); }); + connect(m_autoDetectButton, &QPushButton::clicked, this, + [this]() + { + m_autoDetectCheckBox->setChecked(true); + startReconstruction(); + }); + connect(m_reconstructButton, &QPushButton::clicked, this, + [this]() { startReconstruction(); }); + connect(m_loadButton, &QPushButton::clicked, this, + [this]() { loadHologram(); }); connect(m_cancelButton, &QPushButton::clicked, this, [this]() { m_liveCheckBox->setChecked(false); - if (m_task) + m_task->cancel(); + }); + connect(m_liveCheckBox, &QCheckBox::toggled, this, + [this](bool enabled) + { + m_stream->setEnabled(enabled); + if (!enabled) { - m_task->cancel(); + m_status->setText(m_task ? QStringLiteral("Finishing current frame") + : QStringLiteral("Ready")); + return; + } + m_fileInput = {}; + const ImageFrame frame = m_context.currentFrame(); + if (frame.isValid()) + { + m_sourceId = frame.cameraId; + m_stream->setSourceId(m_sourceId); + queueFrame(frame); + } + }); + connect(m_stream, &scopeone::ui::ScopeOneToolFrameStream::frameReady, this, + [this](const ImageFrame& frame) + { + if (m_liveCheckBox->isChecked()) + { + m_sourceId = frame.cameraId; + m_stream->setSourceId(m_sourceId); + queueFrame(frame); } }); - connect(m_stream, &scopeone::ui::ScopeOneToolFrameStream::frameReady, - this, [this](const scopeone::core::ImageFrame& frame) - { - if (!m_liveCheckBox->isChecked() || !frame.isValid()) - { - return; - } - if (m_sourceId.isEmpty()) - { - m_sourceId = frame.cameraId; - m_stream->setSourceId(m_sourceId); - } - queueFrame(frame); - }); - connect(m_liveCheckBox, &QCheckBox::toggled, this, [this](bool enabled) - { - m_stream->setEnabled(enabled); - if (!enabled) - { - m_status->setText(m_task ? QStringLiteral("Finishing current frame") - : QStringLiteral("Ready")); - return; - } - m_fileInput = {}; - const auto frame = m_context.currentFrame(); - if (frame.isValid() - && scopeone::core::ScopeOneCore::isRawLayerKey( - m_context.currentLayerKey())) - { - m_sourceId = frame.cameraId; - m_stream->setSourceId(m_sourceId); - queueFrame(frame); - } - }); - const auto frame = m_context.currentFrame(); - if (frame.isValid() - && scopeone::core::ScopeOneCore::isRawLayerKey(m_context.currentLayerKey())) + const ImageFrame frame = m_context.currentFrame(); + if (frame.isValid()) { m_sourceId = frame.cameraId; m_stream->setSourceId(m_sourceId); @@ -140,6 +300,90 @@ namespace } private: + QFormLayout* createSidebandForm() + { + auto* form = new QFormLayout(); + m_autoDetectCheckBox = new QCheckBox(QStringLiteral("Auto detect sideband"), this); + m_autoDetectCheckBox->setChecked(true); + m_sidebandX = createSpinBox(-4096, 4096, 48); + m_sidebandY = createSpinBox(-4096, 4096, -32); + m_radius = createSpinBox(1, 4096, 24); + m_softEdgeCheckBox = new QCheckBox(QStringLiteral("Soft circular edge"), this); + m_softEdgeCheckBox->setChecked(true); + m_softEdgeSigma = createDoubleSpinBox(0.1, 30.0, 2.0, 1); + form->addRow(m_autoDetectCheckBox); + form->addRow(QStringLiteral("Sideband X"), m_sidebandX); + form->addRow(QStringLiteral("Sideband Y"), m_sidebandY); + form->addRow(QStringLiteral("Filter radius"), m_radius); + form->addRow(m_softEdgeCheckBox); + form->addRow(QStringLiteral("Soft edge sigma"), m_softEdgeSigma); + connect(m_sidebandX, &QSpinBox::valueChanged, this, + [this]() { m_autoDetectCheckBox->setChecked(false); }); + connect(m_sidebandY, &QSpinBox::valueChanged, this, + [this]() { m_autoDetectCheckBox->setChecked(false); }); + connect(m_radius, &QSpinBox::valueChanged, this, + [this](int radius) { m_spectrumView->setSelection(currentOffset(), radius); }); + return form; + } + + QFormLayout* createOpticsForm() + { + auto* form = new QFormLayout(); + m_roiMode = new QComboBox(this); + m_roiMode->addItem(QStringLiteral("Full frame"), + static_cast(DhmRoiMode::FullFrame)); + m_roiMode->addItem(QStringLiteral("Center crop 512"), + static_cast(DhmRoiMode::CenterCrop)); + m_roiMode->addItem(QStringLiteral("Center crop 1024"), + static_cast(DhmRoiMode::CenterCrop)); + m_roiSize = createSpinBox(32, 4096, 512); + m_wavelength = createDoubleSpinBox(100.0, 2000.0, 632.8, 1); + m_pixelSize = createDoubleSpinBox(0.01, 100.0, 5.5, 3); + m_z = createDoubleSpinBox(-100.0, 100.0, 0.0, 4); + m_wavelength->setSuffix(QStringLiteral(" nm")); + m_pixelSize->setSuffix(QStringLiteral(" um")); + m_z->setSuffix(QStringLiteral(" mm")); + form->addRow(QStringLiteral("ROI"), m_roiMode); + form->addRow(QStringLiteral("Crop size"), m_roiSize); + form->addRow(QStringLiteral("Wavelength"), m_wavelength); + form->addRow(QStringLiteral("Pixel size"), m_pixelSize); + form->addRow(QStringLiteral("Propagation z"), m_z); + connect(m_roiMode, qOverload(&QComboBox::currentIndexChanged), this, + [this](int index) + { + m_roiSize->setEnabled(index != 0); + if (index == 1) + { + m_roiSize->setValue(512); + } + if (index == 2) + { + m_roiSize->setValue(1024); + } + }); + return form; + } + + QFormLayout* createOutputForm() + { + auto* form = new QFormLayout(); + m_unwrapCheckBox = new QCheckBox(QStringLiteral("Quality-guided unwrap"), this); + m_tiltCheckBox = new QCheckBox(QStringLiteral("Remove tilt plane"), this); + m_outputMode = new QComboBox(this); + m_outputMode->addItem(QStringLiteral("Quantitative phase"), + static_cast(DhmOutputMode::QuantitativePhase)); + m_outputMode->addItem(QStringLiteral("Wrapped phase"), + static_cast(DhmOutputMode::WrappedPhase)); + m_outputMode->addItem(QStringLiteral("Amplitude"), + static_cast(DhmOutputMode::Amplitude)); + m_outputMode->addItem(QStringLiteral("Spectrum"), + static_cast(DhmOutputMode::Spectrum)); + form->addRow(m_unwrapCheckBox); + form->addRow(m_tiltCheckBox); + form->addRow(QStringLiteral("Output"), m_outputMode); + return form; + } + static QSpinBox* createSpinBox(int minimum, int maximum, int value) { auto* spinBox = new QSpinBox(); @@ -148,17 +392,30 @@ namespace return spinBox; } + static QDoubleSpinBox* createDoubleSpinBox(double minimum, + double maximum, + double value, + int decimals) + { + auto* spinBox = new QDoubleSpinBox(); + spinBox->setRange(minimum, maximum); + spinBox->setDecimals(decimals); + spinBox->setValue(value); + return spinBox; + } + static QLabel* createPreview(const QString& title) { auto* preview = new QLabel(title); preview->setAlignment(Qt::AlignCenter); - preview->setMinimumSize(280, 180); + preview->setMinimumSize(300, 300); + preview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); preview->setFrameShape(QFrame::Box); preview->setStyleSheet(QStringLiteral("background: #202020; color: #aaaaaa;")); return preview; } - static scopeone::core::ImageFrame imageFrame(const QString& source) + static ImageFrame imageFrame(const QString& source) { const cv::Mat image = cv::imread(source.toStdString(), cv::IMREAD_UNCHANGED); cv::Mat gray; @@ -170,12 +427,17 @@ namespace { cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); } - if (gray.depth() != CV_16U) + if (gray.depth() == CV_8U) { gray.convertTo(gray, CV_16U, 257.0); } + else if (gray.depth() != CV_16U) + { + gray.convertTo(gray, CV_16U); + } gray = gray.clone(); - scopeone::core::ImageFrame frame; + + ImageFrame frame; frame.cameraId = source; frame.width = gray.cols; frame.height = gray.rows; @@ -187,112 +449,184 @@ namespace return frame; } - static void showPreview(QLabel* preview, - const scopeone::core::ImageFrame& frame) + static void showPreview(QLabel* preview, const ImageFrame& frame) { const QImage image(reinterpret_cast(frame.bytes.constData()), - frame.width, frame.height, - static_cast(frame.stride), + frame.width, + frame.height, + frame.stride, QImage::Format_Grayscale16); preview->setPixmap(QPixmap::fromImage(image).scaled( preview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } + QPoint currentOffset() const + { + return {m_sidebandX->value(), m_sidebandY->value()}; + } + + void setManualSideband(const QPoint& offset) + { + QSignalBlocker blockX(m_sidebandX); + QSignalBlocker blockY(m_sidebandY); + m_sidebandX->setValue(offset.x()); + m_sidebandY->setValue(offset.y()); + m_autoDetectCheckBox->setChecked(false); + m_spectrumView->setSelection(offset, m_radius->value()); + } + + void loadHologram() + { + const QString path = QFileDialog::getOpenFileName( + this, + QStringLiteral("Load hologram"), + QString(), + QStringLiteral("Images (*.png *.jpg *.jpeg *.bmp *.tif *.tiff)")); + if (path.isEmpty()) + { + return; + } + m_fileInput = imageFrame(path); + m_liveCheckBox->setChecked(false); + m_sourceId = path; + startReconstruction(); + } + void startReconstruction() { - const auto input = m_fileInput.isValid() ? m_fileInput : m_context.currentFrame(); + const ImageFrame input = m_fileInput.isValid() + ? m_fileInput + : m_context.currentFrame(); if (!input.isValid()) { - m_status->setText(QStringLiteral("No image loaded")); + m_status->setText(QStringLiteral("No hologram loaded")); return; } - m_sourceId = input.cameraId; m_stream->setSourceId(m_sourceId); queueFrame(input); } - void queueFrame(const scopeone::core::ImageFrame& input) + DhmParameters parameters() const + { + DhmParameters params; + params.autoDetectSideband = m_autoDetectCheckBox->isChecked(); + params.sidebandX = m_sidebandX->value(); + params.sidebandY = m_sidebandY->value(); + params.radius = m_radius->value(); + params.softEdge = m_softEdgeCheckBox->isChecked(); + params.softEdgeSigma = m_softEdgeSigma->value(); + params.wavelength = m_wavelength->value() * 1.0e-9; + params.pixelSize = m_pixelSize->value() * 1.0e-6; + params.z = m_z->value() * 1.0e-3; + params.unwrapPhase = m_unwrapCheckBox->isChecked(); + params.removeTilt = m_tiltCheckBox->isChecked(); + params.roiMode = static_cast(m_roiMode->currentData().toInt()); + params.roiSize = m_roiSize->value(); + params.outputMode = static_cast(m_outputMode->currentData().toInt()); + return params; + } + + void queueFrame(const ImageFrame& input) { showPreview(m_inputPreview, input); m_stream->setProcessing(true); - const int offsetX = m_offsetX->value(); - const int offsetY = m_offsetY->value(); - const int radius = std::min(m_radius->value(), - std::max(1, std::min(input.width, input.height) / 2)); - m_result.reset(); - + const DhmParameters params = parameters(); + auto result = std::make_shared(); m_task = new scopeone::ui::ScopeOneToolTask( - [this, input, offsetX, offsetY, radius](const std::atomic_bool& cancel, - const std::function& progress) + [input, params, result](const std::atomic_bool& cancel, + const std::function& progress) { - auto result = scopeone::dhm::reconstructPhase(input, - offsetX, - offsetY, - radius, - cancel, - progress); - if (result.isValid()) - { - m_result = std::make_shared(std::move(result)); - } + *result = scopeone::dhm::reconstruct(input, params, cancel, progress); }, this); - - connect(m_task, &scopeone::ui::ScopeOneToolTask::progressChanged, - this, [this](int percent) - { - m_status->setText(QStringLiteral("Reconstructing %1%%").arg(percent)); - }); + connect(m_task, &scopeone::ui::ScopeOneToolTask::progressChanged, this, + [this](int percent) + { + m_progress->setValue(percent); + m_status->setText(QStringLiteral("Reconstructing %1%%").arg(percent)); + }); connect(m_task, &scopeone::ui::ScopeOneToolTask::finished, this, - [this]() { publishResult(); }); + [this, result]() { publishResult(*result); }); connect(m_task, &scopeone::ui::ScopeOneToolTask::canceled, this, [this]() { finishTask(QStringLiteral("Canceled")); }); connect(m_task, &scopeone::ui::ScopeOneToolTask::failed, this, [this](const QString& message) { finishTask(message); }); m_reconstructButton->setEnabled(false); + m_autoDetectButton->setEnabled(false); + m_loadButton->setEnabled(false); m_cancelButton->setEnabled(true); + m_progress->setValue(0); m_status->setText(QStringLiteral("Reconstructing 0%")); m_task->start(); } - void publishResult() + void publishResult(const scopeone::dhm::DhmResult& result) { - if (!m_result) + if (!result.outputFrame.isValid()) { - finishTask(QStringLiteral("Reconstruction failed")); + finishTask(QStringLiteral("Reconstruction produced no output")); return; } - showPreview(m_resultPreview, *m_result); - const auto stored = m_context.publishToolStreamFrame( - QStringLiteral("dhm.phase"), *m_result, QStringLiteral("DHM Phase")); - if (stored.isValid()) + + m_spectrumView->setFrame(result.spectrumFrame); + if (m_autoDetectCheckBox->isChecked()) { - if (!m_layersPresented) - { - QStringList layers; - const QString rawLayer = scopeone::core::ScopeOneCore::rawLayerKey(m_sourceId); - if (m_context.core().graphFrame(rawLayer).isValid()) - { - layers.append(rawLayer); - } - layers.append(scopeone::core::ScopeOneCore::toolLayerKey(stored.cameraId)); - m_context.showLayers(layers, layers.size() > 1); - m_layersPresented = true; - } - finishTask(QStringLiteral("DHM phase ready")); + QSignalBlocker blockX(m_sidebandX); + QSignalBlocker blockY(m_sidebandY); + m_sidebandX->setValue(result.detectedSidebandX); + m_sidebandY->setValue(result.detectedSidebandY); } - else + m_spectrumView->setSelection(currentOffset(), m_radius->value()); + showPreview(m_resultPreview, result.outputFrame); + + const QString sourceId = result.outputFrame.cameraId; + const QString displayName = outputName(); + const ImageFrame stored = m_context.publishToolStreamFrame( + sourceId, + result.outputFrame, + displayName); + if (!stored.isValid()) + { + finishTask(QStringLiteral("Failed to publish DHM output")); + return; + } + + QStringList layers; + const QString rawLayer = scopeone::core::ScopeOneCore::rawLayerKey(m_sourceId); + if (m_context.core().graphFrame(rawLayer).isValid()) { - finishTask(QStringLiteral("Failed to publish DHM phase")); + layers.append(rawLayer); } + layers.append(scopeone::core::ScopeOneCore::toolLayerKey(stored.cameraId)); + m_context.showLayers(layers, layers.size() > 1); + finishTask(QStringLiteral("DHM output ready")); + } + + QString outputName() const + { + switch (static_cast(m_outputMode->currentData().toInt())) + { + case DhmOutputMode::QuantitativePhase: + return QStringLiteral("DHM Quantitative Phase"); + case DhmOutputMode::WrappedPhase: + return QStringLiteral("DHM Wrapped Phase"); + case DhmOutputMode::Amplitude: + return QStringLiteral("DHM Amplitude"); + case DhmOutputMode::Spectrum: + return QStringLiteral("DHM Spectrum"); + } + return QStringLiteral("DHM Output"); } void finishTask(const QString& status) { + m_progress->setValue(status == QStringLiteral("DHM output ready") ? 100 : m_progress->value()); m_status->setText(status); m_reconstructButton->setEnabled(true); + m_autoDetectButton->setEnabled(true); + m_loadButton->setEnabled(true); m_cancelButton->setEnabled(false); if (m_task) { @@ -307,22 +641,34 @@ namespace } scopeone::ui::ScopeOneToolContext& m_context; - QSpinBox* m_offsetX{nullptr}; - QSpinBox* m_offsetY{nullptr}; - QSpinBox* m_radius{nullptr}; QLabel* m_inputPreview{nullptr}; + SpectrumView* m_spectrumView{nullptr}; QLabel* m_resultPreview{nullptr}; - QCheckBox* m_liveCheckBox{nullptr}; + QCheckBox* m_autoDetectCheckBox{nullptr}; + QSpinBox* m_sidebandX{nullptr}; + QSpinBox* m_sidebandY{nullptr}; + QSpinBox* m_radius{nullptr}; + QCheckBox* m_softEdgeCheckBox{nullptr}; + QDoubleSpinBox* m_softEdgeSigma{nullptr}; + QComboBox* m_roiMode{nullptr}; + QSpinBox* m_roiSize{nullptr}; + QDoubleSpinBox* m_wavelength{nullptr}; + QDoubleSpinBox* m_pixelSize{nullptr}; + QDoubleSpinBox* m_z{nullptr}; + QCheckBox* m_unwrapCheckBox{nullptr}; + QCheckBox* m_tiltCheckBox{nullptr}; + QComboBox* m_outputMode{nullptr}; QPushButton* m_loadButton{nullptr}; + QPushButton* m_autoDetectButton{nullptr}; QPushButton* m_reconstructButton{nullptr}; QPushButton* m_cancelButton{nullptr}; + QCheckBox* m_liveCheckBox{nullptr}; + QProgressBar* m_progress{nullptr}; QLabel* m_status{nullptr}; scopeone::ui::ScopeOneToolTask* m_task{nullptr}; scopeone::ui::ScopeOneToolFrameStream* m_stream{nullptr}; - std::shared_ptr m_result; QString m_sourceId; - scopeone::core::ImageFrame m_fileInput; - bool m_layersPresented{false}; + ImageFrame m_fileInput; }; class DhmToolPlugin final : public QObject, diff --git a/resources/luts/Jet.lut b/resources/luts/Jet.lut new file mode 100644 index 0000000000000000000000000000000000000000..1f3eda166b201979b97122706915d245cf8d66e2 GIT binary patch literal 768 zcmZQzpe*|{{ZPvU+%T}%1 zv~AbEL&r{?yL9c=y+_Ypz5Dd-*T4T1fdwLJGN|Cvtr4D zIWwkA=;>%_An1D*4jus!2^j?y4IKj$3mXR)51)XLh?suPJN pt12tX%Sua%iwXhZ=m829004QaZO;Gz literal 0 HcmV?d00001 diff --git a/resources/luts/README.md b/resources/luts/README.md index 89d3f62..c8208b9 100644 --- a/resources/luts/README.md +++ b/resources/luts/README.md @@ -7,3 +7,6 @@ Gray and the single-channel or dual-channel tables are linear maps. `Fire.lut` follows the ImageJ Fire map used by Micro-Manager, and `Ice.lut` follows the ImageJ Ice map. The Viridis, Inferno, Magma, and Cividis tables use the canonical listed colormaps published by Matplotlib. + +`Jet.lut` is the classic MATLAB/ImageJ jet map, `Rainbow.lut` is a continuous +rainbow map, and `Turbo.lut` is Google's perceptually improved jet replacement. diff --git a/resources/luts/Rainbow.lut b/resources/luts/Rainbow.lut new file mode 100644 index 0000000000000000000000000000000000000000..77919106348444480631d745dc8498e018089997 GIT binary patch literal 768 zcmezWpB~`Pk1royKYMuR`lYic4)5E!W&Nt93uaH7*wfxrTTz^snGzor8sP2bXk%`q ztD!6>DZ7P7p=G+B~m#<=AV&&lC;TIASla!WIP*&5>(lszLF}Jj_b98a@@b(J`3XO=0 ziBC#N%go6uC@!t2s%>a$Y47Ukn>c0KjM?)RELyr^)tdF2wrtzEXWxOt$4;C&d*RZR z>$mRQfB59ti`VZyeE$05*Pp-t{*e~U|Nj2@^_8?B<6>uJW+XMt2CI5TCT3PHei2DI zWer^;a~nrD?|{&#_>|1N;)>d)_MVB;W-nN}YW??@AABBCDDDEEEFFFFFGGGGGGFFFFFEEDCBA@>=;:87531/.,*('%#"  "%'*,/258@CFIKNQTVY\^adfiknqsvx{}€‚…‡ŠŒ‘”–™›ž £¥¨«­¯²´·¹¼¾ÀÃÅÇÉËÍÐÒÔÕ×ÙÛÝÞàâãäæçéêëìîïðñòóôõö÷øøùúúûüüýýþþþþÿÿÿÿÿÿÿþþþýýüüûûúùø÷öõôóñðïíìêéçåäâàßÝÛÙ×ÕÓÑÏÍËÉÇÅÃÁ¾¼º¸¶³±®¬©§¤¡ž›™–“Ї„~{xurolifc`][XUSPNKIGECA?=;97531/-+*(&%#!  + ;CJQX_fmsy€†‹‘—œ¢§¬±µº¿ÃÇËÏÓÖÚÝàãæéëîðòôöøúûüýþþÿÿÿþþýüûúø÷õôòðîëéçäâßÝÚØÕÒÐÍÊÈÅÂÀ½»¹¶´²¯¬ª§¤¡ž›˜”‘ŽŠ‡„€}zvsolifb_\YVSQNKIGDB@?=<:98766554444444445556677788999:::::::::9998766543210/-,+*)'&%#"! + +  \ No newline at end of file diff --git a/resources/resources.qrc b/resources/resources.qrc index 33b072f..148a9dd 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -16,5 +16,8 @@ luts/Inferno.lut luts/Magma.lut luts/Cividis.lut + luts/Jet.lut + luts/Rainbow.lut + luts/Turbo.lut From 62d844bfa09506bec0ac4ef178cc20f075282158 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:34:32 +0200 Subject: [PATCH 23/37] Add 3D surface view to PreviewWidget and refine DHM unwrapping --- VERSION | 2 +- plugins/examples/tool/DhmReconstruction.cpp | 7 +- src/DeviceControlWidget.cpp | 113 +++++ src/DeviceControlWidget.h | 7 + src/ImageWorkspace.cpp | 42 ++ src/ImageWorkspace.h | 3 + src/MainWindow.cpp | 36 ++ src/MainWindow.h | 3 + src/PreviewWidget.cpp | 506 ++++++++++++++++++-- src/PreviewWidget.h | 40 ++ src/main.cpp | 2 +- 11 files changed, 718 insertions(+), 43 deletions(-) diff --git a/VERSION b/VERSION index e7985f3..4bb0a7a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.901 +1.5.902 diff --git a/plugins/examples/tool/DhmReconstruction.cpp b/plugins/examples/tool/DhmReconstruction.cpp index b6f1fe7..7c0854a 100644 --- a/plugins/examples/tool/DhmReconstruction.cpp +++ b/plugins/examples/tool/DhmReconstruction.cpp @@ -277,6 +277,7 @@ namespace std::priority_queue queue; queue.push({reliability.at(seed.y, seed.x), seed.x, seed.y}); unwrapped.at(seed.y, seed.x) = wrapped.at(seed.y, seed.x); + visited.at(seed.y, seed.x) = 1; constexpr int dx[] = {1, -1, 0, 0}; constexpr int dy[] = {0, 0, 1, -1}; @@ -289,11 +290,6 @@ namespace const Node node = queue.top(); queue.pop(); - if (visited.at(node.y, node.x)) - { - continue; - } - visited.at(node.y, node.x) = 1; for (int direction = 0; direction < 4; ++direction) { @@ -309,6 +305,7 @@ namespace const float value = wrapped.at(ny, nx); unwrapped.at(ny, nx) = value + TwoPi * std::round((reference - value) / TwoPi); + visited.at(ny, nx) = 1; queue.push({reliability.at(ny, nx), nx, ny}); } } diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 0cd44a1..4d6a0ac 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -582,6 +583,48 @@ namespace scopeone::ui connect(m_previewWidget, &PreviewWidget::scaleBarVisibilityChanged, m_scaleBarCheckBox, &QCheckBox::setChecked); } + { + const QSignalBlocker blocker(m_viewDimensionCombo); + m_viewDimensionCombo->setCurrentIndex( + m_previewWidget->viewDimensionMode() == PreviewWidget::ViewDimensionMode::ThreeDimensional + ? 1 + : 0); + } + { + const QSignalBlocker blocker(m_3dZScaleSlider); + m_3dZScaleSlider->setValue(qRound(m_previewWidget->get3dZScale() * 10.0f)); + } + { + const QSignalBlocker blocker(m_3dZScaleSpinBox); + m_3dZScaleSpinBox->setValue(m_previewWidget->get3dZScale()); + } + { + const QSignalBlocker blocker(m_3dWireframeCheckBox); + m_3dWireframeCheckBox->setChecked(m_previewWidget->is3dWireframeEnabled()); + } + connect(m_previewWidget, &PreviewWidget::viewDimensionModeChanged, + this, [this](PreviewWidget::ViewDimensionMode mode) + { + const QSignalBlocker blocker(m_viewDimensionCombo); + m_viewDimensionCombo->setCurrentIndex( + mode == PreviewWidget::ViewDimensionMode::ThreeDimensional ? 1 : 0); + }); + connect(m_previewWidget, &PreviewWidget::threeDimensionalZScaleChanged, + this, [this](float scale) + { + { + const QSignalBlocker sliderBlocker(m_3dZScaleSlider); + m_3dZScaleSlider->setValue(qRound(scale * 10.0f)); + } + const QSignalBlocker spinBlocker(m_3dZScaleSpinBox); + m_3dZScaleSpinBox->setValue(scale); + }); + connect(m_previewWidget, &PreviewWidget::threeDimensionalWireframeChanged, + this, [this](bool enabled) + { + const QSignalBlocker blocker(m_3dWireframeCheckBox); + m_3dWireframeCheckBox->setChecked(enabled); + }); } // Builds layer and alignment controls @@ -731,10 +774,80 @@ namespace scopeone::ui m_alignXLabel->setMinimumWidth(20); m_alignYLabel->setMinimumWidth(20); m_alignZoomLabel->setMinimumWidth(60); + + m_surfaceViewGroup = new QGroupBox(QStringLiteral("Surface View"), this); + auto* surfaceViewLayout = new QGridLayout(m_surfaceViewGroup); + surfaceViewLayout->setContentsMargins(6, 6, 6, 6); + surfaceViewLayout->setHorizontalSpacing(6); + surfaceViewLayout->setVerticalSpacing(4); + + m_viewDimensionCombo = new QComboBox(m_surfaceViewGroup); + m_viewDimensionCombo->addItem(QStringLiteral("2D Flat Map")); + m_viewDimensionCombo->addItem(QStringLiteral("3D Surface")); + m_viewDimensionCombo->setToolTip(QStringLiteral("Switch between the flat image and a displaced surface")); + + m_3dZScaleSlider = new QSlider(Qt::Horizontal, m_surfaceViewGroup); + m_3dZScaleSlider->setRange(1, 100); + m_3dZScaleSlider->setValue(10); + m_3dZScaleSlider->setToolTip(QStringLiteral("Height exaggeration from 0.1x to 10.0x")); + + m_3dZScaleSpinBox = new QDoubleSpinBox(m_surfaceViewGroup); + m_3dZScaleSpinBox->setRange(0.1, 10.0); + m_3dZScaleSpinBox->setSingleStep(0.1); + m_3dZScaleSpinBox->setDecimals(1); + m_3dZScaleSpinBox->setSuffix(QStringLiteral("x")); + m_3dZScaleSpinBox->setValue(1.0); + m_3dZScaleSpinBox->setFixedWidth(64); + m_3dZScaleSpinBox->setKeyboardTracking(false); + + m_3dWireframeCheckBox = new QCheckBox(QStringLiteral("Wireframe"), m_surfaceViewGroup); + m_3dResetButton = new QPushButton(QStringLiteral("Reset View"), m_surfaceViewGroup); + m_3dResetButton->setMaximumWidth(84); + + surfaceViewLayout->addWidget(new QLabel(QStringLiteral("Mode:"), m_surfaceViewGroup), 0, 0); + surfaceViewLayout->addWidget(m_viewDimensionCombo, 0, 1, 1, 3); + surfaceViewLayout->addWidget(new QLabel(QStringLiteral("Z-Scale:"), m_surfaceViewGroup), 1, 0); + surfaceViewLayout->addWidget(m_3dZScaleSlider, 1, 1); + surfaceViewLayout->addWidget(m_3dZScaleSpinBox, 1, 2); + surfaceViewLayout->addWidget(m_3dWireframeCheckBox, 2, 0, 1, 2); + surfaceViewLayout->addWidget(m_3dResetButton, 2, 2, 1, 2, Qt::AlignLeft); + + connect(m_viewDimensionCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, [this](int index) + { + m_previewWidget->setViewDimensionMode( + index == 1 ? PreviewWidget::ViewDimensionMode::ThreeDimensional + : PreviewWidget::ViewDimensionMode::TwoDimensional); + }); + connect(m_3dZScaleSlider, &QSlider::valueChanged, this, + [this](int value) + { + const float scale = static_cast(value) / 10.0f; + { + const QSignalBlocker blocker(m_3dZScaleSpinBox); + m_3dZScaleSpinBox->setValue(scale); + } + m_previewWidget->set3dZScale(scale); + }); + connect(m_3dZScaleSpinBox, QOverload::of(&QDoubleSpinBox::valueChanged), this, + [this](double value) + { + { + const QSignalBlocker blocker(m_3dZScaleSlider); + m_3dZScaleSlider->setValue(qRound(value * 10.0)); + } + m_previewWidget->set3dZScale(static_cast(value)); + }); + connect(m_3dWireframeCheckBox, &QCheckBox::toggled, + this, [this](bool enabled) { m_previewWidget->set3dWireframeEnabled(enabled); }); + connect(m_3dResetButton, &QPushButton::clicked, + this, [this]() { m_previewWidget->reset3dCamera(); }); + controlLayout->addWidget(m_layerTable, 0, 0, 1, 6); controlLayout->addWidget(m_layerHistogramGroup, 1, 0, 1, 6); controlLayout->addWidget(m_layerSettingsGroup, 2, 0, 1, 6); controlLayout->addWidget(transformGroup, 3, 0, 1, 6); + controlLayout->addWidget(m_surfaceViewGroup, 4, 0, 1, 6); controlLayout->setColumnStretch(5, 1); diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index a3c7b40..d5f61bd 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -22,6 +22,7 @@ class QLabel; class QLineEdit; class QPushButton; class QScrollArea; +class QSlider; class QSpinBox; class QTableWidget; @@ -152,6 +153,12 @@ namespace scopeone::ui LayerHistogramWidget* m_layerHistogramWidget{nullptr}; QCheckBox* m_layerHistogramLogCheckBox{nullptr}; QGroupBox* m_layerHistogramGroup{nullptr}; + QGroupBox* m_surfaceViewGroup{nullptr}; + QComboBox* m_viewDimensionCombo{nullptr}; + QSlider* m_3dZScaleSlider{nullptr}; + QDoubleSpinBox* m_3dZScaleSpinBox{nullptr}; + QCheckBox* m_3dWireframeCheckBox{nullptr}; + QPushButton* m_3dResetButton{nullptr}; QLabel* m_alignXLabel{nullptr}; QSpinBox* m_alignXSpinBox{nullptr}; QLabel* m_alignYLabel{nullptr}; diff --git a/src/ImageWorkspace.cpp b/src/ImageWorkspace.cpp index 797eef7..e442826 100644 --- a/src/ImageWorkspace.cpp +++ b/src/ImageWorkspace.cpp @@ -530,6 +530,33 @@ namespace scopeone::ui } }); + m_viewerToolbar->addSeparator(); + m_dimensionAction = m_viewerToolbar->addAction(tr("3D Surface")); + m_dimensionAction->setCheckable(true); + m_dimensionAction->setToolTip(tr("Switch between flat 2D and 3D surface view")); + connect(m_dimensionAction, &QAction::toggled, this, + [this](bool enabled) + { + if (PreviewWidget* preview = activePreviewWidget()) + { + preview->setViewDimensionMode( + enabled ? PreviewWidget::ViewDimensionMode::ThreeDimensional + : PreviewWidget::ViewDimensionMode::TwoDimensional); + } + updateViewerToolbar(); + }); + + m_reset3dAction = m_viewerToolbar->addAction(tr("Reset 3D")); + m_reset3dAction->setToolTip(tr("Reset the 3D camera view")); + connect(m_reset3dAction, &QAction::triggered, this, + [this]() + { + if (PreviewWidget* preview = activePreviewWidget()) + { + preview->reset3dCamera(); + } + }); + m_compareSeparator = m_viewerToolbar->addSeparator(); m_compareAction = m_viewerToolbar->addAction(tr("Compare")); m_compareAction->setCheckable(true); @@ -607,6 +634,7 @@ namespace scopeone::ui m_fitToWindowAction->setEnabled(preview != nullptr); m_oneToOneAction->setEnabled(preview != nullptr); m_zoomCombo->setEnabled(preview != nullptr); + m_dimensionAction->setEnabled(preview != nullptr); const bool staticDocument = activeDocument.isValid(); m_compareSeparator->setVisible(staticDocument); m_compareAction->setVisible(staticDocument); @@ -614,6 +642,8 @@ namespace scopeone::ui m_compareDocumentCombo->setVisible(staticDocument); m_compareDocumentCombo->setEnabled(staticDocument); m_linkFramesAction->setVisible(staticDocument && comparisonActive()); + m_reset3dAction->setEnabled( + preview && preview->viewDimensionMode() == PreviewWidget::ViewDimensionMode::ThreeDimensional); if (preview) { { @@ -632,6 +662,11 @@ namespace scopeone::ui m_layoutCombo->setCurrentIndex( preview->layerLayoutMode() == PreviewWidget::LayerLayoutMode::Overlay ? 1 : 0); } + { + const QSignalBlocker blocker(m_dimensionAction); + m_dimensionAction->setChecked( + preview->viewDimensionMode() == PreviewWidget::ViewDimensionMode::ThreeDimensional); + } } { @@ -1651,6 +1686,13 @@ namespace scopeone::ui this, [this](bool) { updateViewerToolbar(); }); connect(preview, &PreviewWidget::layerLayoutModeChanged, this, [this](PreviewWidget::LayerLayoutMode) { updateViewerToolbar(); }); + connect(preview, &PreviewWidget::viewDimensionModeChanged, + this, [this](PreviewWidget::ViewDimensionMode mode) + { + Q_UNUSED(mode); + emit viewDimensionModeChanged(); + updateViewerToolbar(); + }); connect(scene, &scopeone::core::ImageSceneModel::layersChanged, this, [this, documentId]() { syncActiveLayer(documentId); }); if (documentId.isEmpty()) diff --git a/src/ImageWorkspace.h b/src/ImageWorkspace.h index a9822af..49f5e6e 100644 --- a/src/ImageWorkspace.h +++ b/src/ImageWorkspace.h @@ -98,6 +98,7 @@ namespace scopeone::ui void documentsChanged(); void activeDocumentChanged(const QString& documentId); void activeViewerChanged(); + void viewDimensionModeChanged(); void activeLayerChanged(const QString& layerKey); void activeFrameChanged(); void histogramReady(const QString& layerKey, @@ -156,6 +157,8 @@ namespace scopeone::ui QAction* m_compareSeparator{nullptr}; QAction* m_compareAction{nullptr}; QAction* m_linkFramesAction{nullptr}; + QAction* m_dimensionAction{nullptr}; + QAction* m_reset3dAction{nullptr}; QComboBox* m_layoutCombo{nullptr}; QComboBox* m_zoomCombo{nullptr}; QComboBox* m_compareDocumentCombo{nullptr}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 223968e..2d53018 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -652,7 +653,10 @@ namespace scopeone::ui m_deviceControlWidget->setPreviewWidget( m_imageWorkspace->activePreviewWidget()); m_deviceControlWidget->setViewerContext(liveViewer); + updateDimensionViewActions(); }); + connect(m_imageWorkspace, &ImageWorkspace::viewDimensionModeChanged, + this, &MainWindow::updateDimensionViewActions); connect(m_imageWorkspace, &ImageWorkspace::activeDocumentChanged, this, [this](const QString& documentId) { @@ -1113,6 +1117,24 @@ namespace scopeone::ui }); addAction(m_toggleLayoutAction); + m_toggleDimensionAction = m_viewMenu->addAction(tr("3D Surface View")); + m_toggleDimensionAction->setCheckable(true); + m_toggleDimensionAction->setShortcuts({QKeySequence(QStringLiteral("Ctrl+3")), + QKeySequence(Qt::Key_D)}); + m_toggleDimensionAction->setShortcutContext(Qt::ApplicationShortcut); + connect(m_toggleDimensionAction, &QAction::toggled, this, [this](bool enabled) + { + m_imageWorkspace->activePreviewWidget()->setViewDimensionMode( + enabled ? PreviewWidget::ViewDimensionMode::ThreeDimensional + : PreviewWidget::ViewDimensionMode::TwoDimensional); + }); + addAction(m_toggleDimensionAction); + + m_reset3dAction = m_viewMenu->addAction(tr("Reset 3D Camera")); + connect(m_reset3dAction, &QAction::triggered, this, + [this]() { m_imageWorkspace->activePreviewWidget()->reset3dCamera(); }); + m_reset3dAction->setEnabled(false); + m_viewMenu->addSeparator(); m_dockWidgetsMenu = m_viewMenu->addMenu(tr("&Dock Widgets")); @@ -1498,9 +1520,23 @@ namespace scopeone::ui m_consoleWidget->addMessage( QStringLiteral("External device adapters: %1").arg(adapterPaths.join(QStringLiteral("; ")))); } + updateDimensionViewActions(); showStatusMessage(tr("ScopeOne ready"), 3000); } + void MainWindow::updateDimensionViewActions() + { + PreviewWidget* preview = m_imageWorkspace->activePreviewWidget(); + const bool threeDimensional = + preview->viewDimensionMode() == PreviewWidget::ViewDimensionMode::ThreeDimensional; + { + const QSignalBlocker blocker(m_toggleDimensionAction); + m_toggleDimensionAction->setChecked(threeDimensional); + } + m_toggleDimensionAction->setEnabled(true); + m_reset3dAction->setEnabled(threeDimensional); + } + // Show one transient status message without disturbing persistent fields void MainWindow::showStatusMessage(const QString& message, int timeoutMs) { diff --git a/src/MainWindow.h b/src/MainWindow.h index 7606f44..719c3fe 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -93,6 +93,7 @@ namespace scopeone::ui void clearCursorStatus(); void refreshPreviewCursorStatus(); void schedulePreviewCursorStatusRefresh(); + void updateDimensionViewActions(); void showMeasurementLine(const QString& layerKey, const QPoint& start, const QPoint& end); @@ -165,6 +166,8 @@ namespace scopeone::ui QAction* m_scaleBarAction{nullptr}; QAction* m_clippingAction{nullptr}; QAction* m_toggleLayoutAction{nullptr}; + QAction* m_toggleDimensionAction{nullptr}; + QAction* m_reset3dAction{nullptr}; QAction* m_togglePreviewAction{nullptr}; QAction* m_snapAction{nullptr}; QAction* m_autoContrastAction{nullptr}; diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index 8460a6c..d175820 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -10,11 +10,14 @@ #include #include #include +#include #include #include +#include #include #include #include +#include #include namespace scopeone::ui @@ -972,6 +975,52 @@ namespace scopeone::ui return m_clippingWarning; } + void PreviewWidget::setViewDimensionMode(ViewDimensionMode mode) + { + if (m_viewDimensionMode == mode) + { + return; + } + m_viewDimensionMode = mode; + m_surfaceOrbiting = false; + m_surfacePanning = false; + unsetCursor(); + emit viewDimensionModeChanged(m_viewDimensionMode); + update(); + } + + void PreviewWidget::set3dZScale(float scale) + { + const float nextScale = qBound(0.1f, scale, 10.0f); + if (qFuzzyCompare(m_zScale, nextScale)) + { + return; + } + m_zScale = nextScale; + emit threeDimensionalZScaleChanged(m_zScale); + update(); + } + + void PreviewWidget::reset3dCamera() + { + m_cameraPitch = 35.0f; + m_cameraYaw = 45.0f; + m_cameraDistance = 2.8f; + m_cameraPan = QVector2D(0.0f, 0.0f); + update(); + } + + void PreviewWidget::set3dWireframeEnabled(bool enabled) + { + if (m_wireframe3d == enabled) + { + return; + } + m_wireframe3d = enabled; + emit threeDimensionalWireframeChanged(m_wireframe3d); + update(); + } + void PreviewWidget::setActiveLayerKey(const QString& key) { if (m_activeLayerKey == key) @@ -1993,6 +2042,75 @@ namespace scopeone::ui } } + // Draws the active image layer as a GPU-displaced surface + void PreviewWidget::draw3dSurface(const RenderItem& item) + { + const FrameSourceState& frameState = *item.info->frameState; + const ImageFrame& frame = item.processed ? frameState.processedFrame : frameState.rawFrame; + const quint64 revision = item.processed ? frameState.processedRevision : frameState.rawRevision; + const GLuint texture = ensureFrameTexture(item.layerKey, frame, revision); + const GLint internalFormat = frame.isMono16() ? GL_R16 : GL_R8; + const float sampleMax = internalFormat == GL_R16 ? 65535.0f : 255.0f; + const float bitMax = static_cast(qMax(1, frame.maxValue())); + const float levelDomain = static_cast(qMax(1, item.display.levelDomainMax)); + + QMatrix4x4 projection; + projection.perspective(45.0f, + static_cast(width()) / static_cast(height()), + 0.1f, + 100.0f); + QMatrix4x4 view; + view.translate(m_cameraPan.x(), m_cameraPan.y(), -m_cameraDistance); + view.rotate(m_cameraPitch, 1.0f, 0.0f, 0.0f); + view.rotate(m_cameraYaw, 0.0f, 0.0f, 1.0f); + + m_prog3d.bind(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + m_prog3d.setUniformValue(m_u3dTex, 0); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_colormapTexture); + m_prog3d.setUniformValue(m_u3dColormapLut, 1); + glActiveTexture(GL_TEXTURE0); + + m_prog3d.setUniformValue(m_u3dMvp, projection * view); + m_prog3d.setUniformValue(m_u3dMinNorm, + static_cast(item.display.levelMin) / levelDomain); + m_prog3d.setUniformValue(m_u3dMaxNorm, + static_cast(item.display.levelMax) / levelDomain); + m_prog3d.setUniformValue(m_u3dTexNormScale, sampleMax / bitMax); + m_prog3d.setUniformValue(m_u3dZScale, m_zScale); + m_prog3d.setUniformValue(m_u3dGamma, static_cast(item.display.gamma)); + m_prog3d.setUniformValue(m_u3dColormap, item.display.colormapIndex); + m_prog3d.setUniformValue(m_u3dShowClipping, m_clippingWarning ? 1 : 0); + m_prog3d.setUniformValue(m_u3dUvScale, frameState.flipX ? -1.0f : 1.0f, + frameState.flipY ? 1.0f : -1.0f); + m_prog3d.setUniformValue(m_u3dUvOffset, frameState.flipX ? 1.0f : 0.0f, + frameState.flipY ? 0.0f : 1.0f); + m_prog3d.setUniformValue(m_u3dLightDirection, QVector3D(-0.4f, 0.5f, 1.0f)); + + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glDepthFunc(GL_LESS); + m_gridVao.bind(); + if (m_wireframe3d) + { + m_gl3dFunctions.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + glDrawElements(GL_TRIANGLES, + m_gridElementCount, + GL_UNSIGNED_INT, + nullptr); + if (m_wireframe3d) + { + m_gl3dFunctions.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + m_gridVao.release(); + glDisable(GL_DEPTH_TEST); + m_prog3d.release(); + } + // Updates the layer info summary text void PreviewWidget::updateLayerInfoDisplay() { @@ -2204,6 +2322,7 @@ namespace scopeone::ui void PreviewWidget::initializeGL() { initializeOpenGLFunctions(); + m_gl3dFunctions.initializeOpenGLFunctions(); const QSurfaceFormat format = context()->format(); QString profile = QStringLiteral("No profile"); @@ -2395,7 +2514,8 @@ namespace scopeone::ui } glClearColor(0.1f, 0.1f, 0.1f, 1.0f); applyViewportForRect(rect()); - glClear(GL_COLOR_BUFFER_BIT); + glDepthMask(GL_TRUE); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); bool canGpu = m_glInited && m_prog.isLinked(); QMap frameSources; @@ -2417,6 +2537,51 @@ namespace scopeone::ui } m_placeholderLabel->hide(); + if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional) + { + const RenderItem* surfaceItem = &renderItems.back(); + for (const RenderItem& item : renderItems) + { + if (item.layerKey == m_activeLayerKey) + { + surfaceItem = &item; + break; + } + } + + glDisable(GL_SCISSOR_TEST); + applyViewportForRect(rect()); + draw3dSurface(*surfaceItem); + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing, true); + QFont font = p.font(); + font.setBold(true); + p.setFont(font); + const QString title = tr("3D Surface: %1").arg(layerName(surfaceItem->layerKey)); + const QString detail = tr("Z-Scale %1x%2").arg(m_zScale, 0, 'f', 1) + .arg(m_wireframe3d ? tr(" | Wireframe") : QString()); + const QRect panelRect(12, 12, 230, 52); + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 170)); + p.drawRoundedRect(panelRect, 5, 5); + p.setPen(Qt::white); + p.drawText(panelRect.adjusted(10, 7, -10, -26), + Qt::AlignLeft | Qt::AlignVCenter, + title); + font.setBold(false); + p.setFont(font); + p.drawText(panelRect.adjusted(10, 25, -10, -7), + Qt::AlignLeft | Qt::AlignVCenter, + detail); + p.setPen(QColor(220, 225, 230, 190)); + p.drawText(QRect(12, height() - 28, width() - 24, 18), + Qt::AlignRight | Qt::AlignVCenter, + tr("Left drag: orbit Right drag: pan Wheel: zoom")); + return; + } + + glDisable(GL_DEPTH_TEST); for (const auto& item : renderItems) { drawRenderItem(item); @@ -2555,6 +2720,152 @@ namespace scopeone::ui m_uUvOffset = m_prog.uniformLocation("uUvOffset"); m_uShowClipping = m_prog.uniformLocation("uShowClipping"); + const char* vs3d = R"( + #version 330 core + layout (location = 0) in vec2 aPos; + layout (location = 1) in vec2 aUV; + out vec2 vUV; + out float vHeight; + uniform sampler2D uTex; + uniform mat4 uMvp; + uniform float uMinNorm; + uniform float uMaxNorm; + uniform float uTexNormScale; + uniform float uGamma; + uniform float uZScale; + uniform vec2 uUvScale; + uniform vec2 uUvOffset; + float heightAt(vec2 uv) { + float value = texture(uTex, uv * uUvScale + uUvOffset).r * uTexNormScale; + float normalized = clamp((value - uMinNorm) / max(uMaxNorm - uMinNorm, 1e-6), 0.0, 1.0); + return pow(normalized, 1.0 / max(uGamma, 1e-3)); + } + void main() { + vUV = aUV * uUvScale + uUvOffset; + vHeight = heightAt(aUV); + gl_Position = uMvp * vec4(aPos, vHeight * uZScale, 1.0); + } + )"; + const char* fs3d = R"( + #version 330 core + in vec2 vUV; + in float vHeight; + out vec4 FragColor; + uniform sampler2D uTex; + uniform float uMinNorm; + uniform float uMaxNorm; + uniform float uTexNormScale; + uniform float uGamma; + uniform int uColormap; + uniform int uShowClipping; + uniform sampler2D uColormapLut; + uniform vec3 uLightDirection; + vec3 applyColormap(float t, int map) { + vec2 lutSize = vec2(textureSize(uColormapLut, 0)); + float column = (t * (lutSize.x - 1.0) + 0.5) / lutSize.x; + float row = (float(map) + 0.5) / lutSize.y; + return texture(uColormapLut, vec2(column, row)).rgb; + } + void main() { + float value = texture(uTex, vUV).r * uTexNormScale; + if (uShowClipping == 1 && value >= 0.999) { + FragColor = vec4(1.0, 0.0, 0.0, 1.0); + return; + } + if (uShowClipping == 1 && value <= 0.0001) { + FragColor = vec4(0.0, 0.2, 1.0, 1.0); + return; + } + vec2 texel = 1.0 / vec2(textureSize(uTex, 0)); + float dx = texture(uTex, clamp(vUV + vec2(texel.x, 0.0), 0.0, 1.0)).r + - texture(uTex, clamp(vUV - vec2(texel.x, 0.0), 0.0, 1.0)).r; + float dy = texture(uTex, clamp(vUV + vec2(0.0, texel.y), 0.0, 1.0)).r + - texture(uTex, clamp(vUV - vec2(0.0, texel.y), 0.0, 1.0)).r; + vec3 normal = normalize(vec3(-dx * 4.0, -dy * 4.0, 1.0)); + float diffuse = max(dot(normal, normalize(uLightDirection)), 0.0); + float lighting = 0.28 + 0.72 * diffuse; + float normalized = clamp((value - uMinNorm) / max(uMaxNorm - uMinNorm, 1e-6), 0.0, 1.0); + normalized = pow(normalized, 1.0 / max(uGamma, 1e-3)); + FragColor = vec4(applyColormap(normalized, uColormap) * lighting, 1.0); + } + )"; + if (!m_prog3d.addShaderFromSourceCode(QOpenGLShader::Vertex, vs3d) + || !m_prog3d.addShaderFromSourceCode(QOpenGLShader::Fragment, fs3d) + || !m_prog3d.link()) + { + qCritical() << "PreviewWidget: 3D shader setup FAILED" << m_prog3d.log(); + return; + } + + constexpr int gridSize = 256; + std::vector gridVertices; + gridVertices.reserve(gridSize * gridSize * 4); + for (int y = 0; y < gridSize; ++y) + { + const float v = static_cast(y) / static_cast(gridSize - 1); + for (int x = 0; x < gridSize; ++x) + { + const float u = static_cast(x) / static_cast(gridSize - 1); + gridVertices.push_back(u * 2.0f - 1.0f); + gridVertices.push_back(v * 2.0f - 1.0f); + gridVertices.push_back(u); + gridVertices.push_back(1.0f - v); + } + } + + std::vector gridIndices; + gridIndices.reserve((gridSize - 1) * (gridSize - 1) * 6); + for (int y = 0; y < gridSize - 1; ++y) + { + for (int x = 0; x < gridSize - 1; ++x) + { + const GLuint topLeft = static_cast(y * gridSize + x); + const GLuint topRight = topLeft + 1; + const GLuint bottomLeft = static_cast((y + 1) * gridSize + x); + const GLuint bottomRight = bottomLeft + 1; + gridIndices.insert(gridIndices.end(), + {topLeft, bottomLeft, topRight, + topRight, bottomLeft, bottomRight}); + } + } + + m_gridVao.create(); + glGenBuffers(1, &m_gridVbo); + glGenBuffers(1, &m_gridIbo); + m_gridVao.bind(); + glBindBuffer(GL_ARRAY_BUFFER, m_gridVbo); + glBufferData(GL_ARRAY_BUFFER, + static_cast(gridVertices.size() * sizeof(float)), + gridVertices.data(), + GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_gridIbo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + static_cast(gridIndices.size() * sizeof(GLuint)), + gridIndices.data(), + GL_STATIC_DRAW); + m_prog3d.bind(); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + m_prog3d.release(); + m_gridVao.release(); + glBindBuffer(GL_ARRAY_BUFFER, 0); + m_gridElementCount = static_cast(gridIndices.size()); + m_u3dTex = m_prog3d.uniformLocation("uTex"); + m_u3dMvp = m_prog3d.uniformLocation("uMvp"); + m_u3dMinNorm = m_prog3d.uniformLocation("uMinNorm"); + m_u3dMaxNorm = m_prog3d.uniformLocation("uMaxNorm"); + m_u3dTexNormScale = m_prog3d.uniformLocation("uTexNormScale"); + m_u3dZScale = m_prog3d.uniformLocation("uZScale"); + m_u3dGamma = m_prog3d.uniformLocation("uGamma"); + m_u3dColormap = m_prog3d.uniformLocation("uColormap"); + m_u3dColormapLut = m_prog3d.uniformLocation("uColormapLut"); + m_u3dShowClipping = m_prog3d.uniformLocation("uShowClipping"); + m_u3dUvScale = m_prog3d.uniformLocation("uUvScale"); + m_u3dUvOffset = m_prog3d.uniformLocation("uUvOffset"); + m_u3dLightDirection = m_prog3d.uniformLocation("uLightDirection"); + // Uploads all colormaps once for shader lookup glGenTextures(1, &m_colormapTexture); glActiveTexture(GL_TEXTURE1); @@ -2587,6 +2898,50 @@ namespace scopeone::ui if (m_uUvOffset >= 0) m_prog.setUniformValue(m_uUvOffset, ox, oy); } + GLuint PreviewWidget::ensureFrameTexture(const QString& textureKey, + const ImageFrame& frame, + quint64 frameRevision) + { + GLenum uploadType = GL_UNSIGNED_BYTE; + GLint internalFormat = GL_R8; + int unpackAlign = 1; + if (frame.isMono16()) + { + uploadType = GL_UNSIGNED_SHORT; + internalFormat = GL_R16; + unpackAlign = 2; + } + + const GLuint texId = getOrCreateTexture(textureKey, + frame.width, + frame.height, + internalFormat); + CachedTexture& cachedTexture = m_textureCache[textureKey]; + glBindTexture(GL_TEXTURE_2D, texId); + if (cachedTexture.uploadedRevision == frameRevision) + { + return texId; + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlign); + const int bytesPerPixel = (uploadType == GL_UNSIGNED_SHORT) ? 2 : 1; + if (frame.stride > 0) + { + const int rowPixels = frame.stride / bytesPerPixel; + if (rowPixels != frame.width) + { + glPixelStorei(GL_UNPACK_ROW_LENGTH, rowPixels); + } + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + frame.width, frame.height, + GL_RED, uploadType, frame.bytes.constData()); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + cachedTexture.uploadedRevision = frameRevision; + return texId; + } + // Uploads and draws one image frame into a target rectangle void PreviewWidget::drawFrameInRect(const QString& textureKey, const ImageFrame& frame, @@ -2602,45 +2957,13 @@ namespace scopeone::ui ensureGlPipeline(); - GLenum uploadType = GL_UNSIGNED_BYTE; - GLint internalFormat = GL_R8; - int unpackAlign = 1; - - if (frame.isMono16()) - { - uploadType = GL_UNSIGNED_SHORT; - internalFormat = GL_R16; - unpackAlign = 2; - } - else if (!frame.isMono8()) + if (!frame.isMono8() && !frame.isMono16()) { return; } - GLuint texId = getOrCreateTexture(textureKey, frame.width, frame.height, internalFormat); - CachedTexture& cachedTexture = m_textureCache[textureKey]; - - glBindTexture(GL_TEXTURE_2D, texId); - - if (cachedTexture.uploadedRevision != frameRevision) - { - glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlign); - const int bytesPerPixel = (uploadType == GL_UNSIGNED_SHORT) ? 2 : 1; - if (frame.stride > 0) - { - const int rowPixels = frame.stride / bytesPerPixel; - if (rowPixels != frame.width) - { - glPixelStorei(GL_UNPACK_ROW_LENGTH, rowPixels); - } - } - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, - frame.width, frame.height, - GL_RED, uploadType, frame.bytes.constData()); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - cachedTexture.uploadedRevision = frameRevision; - } + const GLint internalFormat = frame.isMono16() ? GL_R16 : GL_R8; + const GLuint texId = ensureFrameTexture(textureKey, frame, frameRevision); m_prog.bind(); glActiveTexture(GL_TEXTURE0); @@ -2835,6 +3158,23 @@ namespace scopeone::ui glDeleteTextures(1, &m_colormapTexture); m_colormapTexture = 0; } + if (m_gridVbo != 0) + { + glDeleteBuffers(1, &m_gridVbo); + m_gridVbo = 0; + } + if (m_gridIbo != 0) + { + glDeleteBuffers(1, &m_gridIbo); + m_gridIbo = 0; + } + if (m_vbo != 0) + { + glDeleteBuffers(1, &m_vbo); + m_vbo = 0; + } + m_gridVao.destroy(); + m_vao.destroy(); doneCurrent(); } @@ -2946,6 +3286,30 @@ namespace scopeone::ui { emit activated(); emit mousePositionChanged(event->pos()); + if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional) + { + if (event->button() == Qt::LeftButton) + { + m_surfaceOrbiting = true; + m_surfacePanning = false; + m_surfaceDragStart = event->pos(); + m_surfaceStartPitch = m_cameraPitch; + m_surfaceStartYaw = m_cameraYaw; + setCursor(Qt::ClosedHandCursor); + event->accept(); + return; + } + if (event->button() == Qt::RightButton) + { + m_surfacePanning = true; + m_surfaceOrbiting = false; + m_surfaceDragStart = event->pos(); + m_surfaceStartPan = m_cameraPan; + setCursor(Qt::SizeAllCursor); + event->accept(); + return; + } + } if (event->button() == Qt::MiddleButton) { PreviewInteractionTarget target; @@ -3089,6 +3453,13 @@ namespace scopeone::ui // Toggles visibility of the layer under the double click void PreviewWidget::mouseDoubleClickEvent(QMouseEvent* event) { + if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional + && event->button() == Qt::LeftButton) + { + reset3dCamera(); + event->accept(); + return; + } if (event->button() == Qt::LeftButton) { PreviewInteractionTarget target; @@ -3123,6 +3494,28 @@ namespace scopeone::ui { emit mousePositionChanged(event->pos()); update(); + if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional) + { + if (m_surfaceOrbiting) + { + const QPoint delta = event->pos() - m_surfaceDragStart; + m_cameraYaw = m_surfaceStartYaw + static_cast(delta.x()) * 0.5f; + m_cameraPitch = qBound(-85.0f, + m_surfaceStartPitch + static_cast(delta.y()) * 0.5f, + 85.0f); + update(); + return; + } + if (m_surfacePanning) + { + const QPoint delta = event->pos() - m_surfaceDragStart; + m_cameraPan = m_surfaceStartPan + + QVector2D(static_cast(delta.x()) / static_cast(width()), + -static_cast(delta.y()) / static_cast(height())); + update(); + return; + } + } if (m_measurementLineDrawingMode && m_measurementLineDragging) { m_measurementLineEnd = event->pos(); @@ -3242,6 +3635,23 @@ namespace scopeone::ui void PreviewWidget::mouseReleaseEvent(QMouseEvent* event) { emit mousePositionChanged(event->pos()); + if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional) + { + if (m_surfaceOrbiting && event->button() == Qt::LeftButton) + { + m_surfaceOrbiting = false; + unsetCursor(); + event->accept(); + return; + } + if (m_surfacePanning && event->button() == Qt::RightButton) + { + m_surfacePanning = false; + unsetCursor(); + event->accept(); + return; + } + } if (m_viewPanning && event->button() == Qt::MiddleButton) { m_viewPanning = false; @@ -3473,6 +3883,23 @@ namespace scopeone::ui // Handles control wheel zoom around the cursor anchor void PreviewWidget::wheelEvent(QWheelEvent* event) { + if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional) + { + const int deltaY = event->angleDelta().y(); + if (deltaY != 0) + { + const int steps = (deltaY / 120 != 0) + ? (deltaY / 120) + : ((deltaY > 0) ? 1 : -1); + m_cameraDistance = qBound(0.8f, + m_cameraDistance * std::pow(0.88f, + static_cast(steps)), + 20.0f); + } + update(); + event->accept(); + return; + } if (!(event->modifiers() & Qt::ControlModifier)) { QOpenGLWidget::wheelEvent(event); @@ -3538,6 +3965,13 @@ namespace scopeone::ui // Cancels active drawing modes from keyboard input void PreviewWidget::keyPressEvent(QKeyEvent* event) { + if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional + && event->key() == Qt::Key_R) + { + reset3dCamera(); + event->accept(); + return; + } if (m_measurementLineDrawingMode && event->key() == Qt::Key_Escape) { cancelMeasurementLineDrawing(); diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index 3316d44..75e07ab 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include #include "scopeone/ImageSceneModel.h" @@ -38,6 +40,7 @@ namespace scopeone::ui public: enum class LayerLayoutMode { SideBySide, Overlay }; + enum class ViewDimensionMode { TwoDimensional, ThreeDimensional }; struct PreviewInteractionTarget { @@ -85,6 +88,13 @@ namespace scopeone::ui bool isScaleBarVisible() const; void setClippingWarningEnabled(bool enabled); bool isClippingWarningEnabled() const; + void setViewDimensionMode(ViewDimensionMode mode); + ViewDimensionMode viewDimensionMode() const { return m_viewDimensionMode; } + void set3dZScale(float scale); + float get3dZScale() const { return m_zScale; } + void reset3dCamera(); + void set3dWireframeEnabled(bool enabled); + bool is3dWireframeEnabled() const { return m_wireframe3d; } void setActiveLayerKey(const QString& key); QString activeLayerKey() const { return m_activeLayerKey; } void setPixelSizeCallback(std::function callback); @@ -108,6 +118,9 @@ namespace scopeone::ui void fitToWindowChanged(bool enabled); void scaleBarVisibilityChanged(bool visible); void clippingWarningChanged(bool enabled); + void viewDimensionModeChanged(ViewDimensionMode mode); + void threeDimensionalZScaleChanged(float scale); + void threeDimensionalWireframeChanged(bool enabled); void stageStepRequested(double dxScale, double dyScale, bool big); void stageZStepRequested(double dzScale, bool big); void layerClicked(const QString& layerKey); @@ -236,13 +249,30 @@ namespace scopeone::ui QString m_placeholderText{QStringLiteral("No image loaded")}; bool m_glInited{false}; + QOpenGLFunctions_3_3_Core m_gl3dFunctions; QOpenGLVertexArrayObject m_vao; GLuint m_vbo{0}; GLuint m_colormapTexture{0}; QOpenGLShaderProgram m_prog; + QOpenGLShaderProgram m_prog3d; + QOpenGLVertexArrayObject m_gridVao; + GLuint m_gridVbo{0}; + GLuint m_gridIbo{0}; + int m_gridElementCount{0}; GLint m_uTex{-1}, m_uMinNorm{-1}, m_uMaxNorm{-1}, m_uTexNormScale{-1}, m_uAlpha{-1}; GLint m_uGamma{-1}, m_uColormap{-1}, m_uColormapLut{-1}; GLint m_uUvScale{-1}, m_uUvOffset{-1}, m_uShowClipping{-1}; + GLint m_u3dTex{-1}, m_u3dMvp{-1}, m_u3dMinNorm{-1}, m_u3dMaxNorm{-1}; + GLint m_u3dTexNormScale{-1}, m_u3dZScale{-1}, m_u3dGamma{-1}; + GLint m_u3dColormap{-1}, m_u3dColormapLut{-1}, m_u3dShowClipping{-1}; + GLint m_u3dUvScale{-1}, m_u3dUvOffset{-1}, m_u3dLightDirection{-1}; + ViewDimensionMode m_viewDimensionMode{ViewDimensionMode::TwoDimensional}; + float m_cameraPitch{35.0f}; + float m_cameraYaw{45.0f}; + float m_cameraDistance{2.8f}; + QVector2D m_cameraPan{0.0f, 0.0f}; + float m_zScale{1.0f}; + bool m_wireframe3d{false}; bool m_scaleBarVisible{true}; bool m_clippingWarning{false}; QString m_activeLayerKey; @@ -287,6 +317,12 @@ namespace scopeone::ui QPoint m_panStartWidgetPos; QPoint m_panStartOffset; QString m_panLayerKey; + bool m_surfaceOrbiting{false}; + bool m_surfacePanning{false}; + QPoint m_surfaceDragStart; + float m_surfaceStartPitch{35.0f}; + float m_surfaceStartYaw{45.0f}; + QVector2D m_surfaceStartPan{0.0f, 0.0f}; void updateImageDisplay(); void updateLayerFps(const QString& layerKey, quint64 frameCount = 1); void updateFrameRates(); @@ -357,7 +393,11 @@ namespace scopeone::ui MarkupEditMode& outEditMode) const; void clearSelectedMarkups(); void drawRenderItem(const RenderItem& item); + void draw3dSurface(const RenderItem& item); void ensureGlPipeline(); + GLuint ensureFrameTexture(const QString& textureKey, + const scopeone::core::ImageFrame& frame, + quint64 frameRevision); void drawFrameInRect(const QString& textureKey, const scopeone::core::ImageFrame& frame, quint64 frameRevision, diff --git a/src/main.cpp b/src/main.cpp index 1437ae1..2cc896d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,7 @@ int main(int argc, char *argv[]) format.setVersion(4, 1); format.setProfile(QSurfaceFormat::CoreProfile); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); - format.setDepthBufferSize(0); + format.setDepthBufferSize(24); QSurfaceFormat::setDefaultFormat(format); QApplication app(argc, argv); From f8d0648ac856296652043a6e10db0d4c7681397e Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:45:57 +0200 Subject: [PATCH 24/37] Apply widget style settings and simplify plugin status --- src/MainWindow.cpp | 20 ++++++++++++++++++++ src/PluginManagerDialog.cpp | 23 ++++------------------- src/SettingsDialog.cpp | 21 ++++++++++++++++++++- src/SettingsDialog.h | 3 +++ 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 2d53018..45f44d2 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -119,6 +120,15 @@ namespace scopeone::ui return {}; } + // Apply the requested Qt widget style + void applyWidgetStyle(const QString& widgetStyle) + { + if (widgetStyle != QStringLiteral("default") && !widgetStyle.isEmpty()) + { + QApplication::setStyle(QStyleFactory::create(widgetStyle)); + } + } + // Apply the requested Qt color scheme void applyColorScheme(const QString& colorScheme) { @@ -1481,6 +1491,9 @@ namespace scopeone::ui { constexpr qint64 kDefaultRecordedMaxBytes = 16ll * 1024 * 1024 * 1024; QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + applyWidgetStyle(settings.value( + QStringLiteral("Appearance/Style"), + QStringLiteral("default")).toString()); applyColorScheme(settings.value( QStringLiteral("Appearance/ColorScheme"), QStringLiteral("system")).toString()); @@ -1659,6 +1672,9 @@ namespace scopeone::ui constexpr qint64 kDefaultRecordedMaxBytes = 16ll * 1024 * 1024 * 1024; const qint64 currentValue = m_scopeonecore->recordingMaxPendingWriteBytes(); QSettings settings(QStringLiteral("ScopeOne"), QStringLiteral("ScopeOne")); + const QString widgetStyle = settings.value( + QStringLiteral("Appearance/Style"), + QStringLiteral("default")).toString(); const QString colorScheme = settings.value( QStringLiteral("Appearance/ColorScheme"), QStringLiteral("system")).toString(); @@ -1666,6 +1682,7 @@ namespace scopeone::ui const QStringList adapterPaths = m_scopeonecore->additionalDeviceAdapterSearchPaths(); SettingsDialog dialog(currentValue > 0 ? currentValue : kDefaultRecordedMaxBytes, adapterPaths.value(0), + widgetStyle, colorScheme, this); if (dialog.exec() != QDialog::Accepted) @@ -1683,11 +1700,14 @@ namespace scopeone::ui tr("The device adapter directory could not be updated.")); return; } + const QString selectedWidgetStyle = dialog.widgetStyle(); const QString selectedColorScheme = dialog.colorScheme(); settings.setValue(QStringLiteral("Recording/MaxPendingWriteBytes"), recordedMaxBytes); settings.setValue(QStringLiteral("Hardware/MicroManagerDirectory"), microManagerDirectory); + settings.setValue(QStringLiteral("Appearance/Style"), selectedWidgetStyle); settings.setValue(QStringLiteral("Appearance/ColorScheme"), selectedColorScheme); m_scopeonecore->setRecordingMaxPendingWriteBytes(recordedMaxBytes); + applyWidgetStyle(selectedWidgetStyle); applyColorScheme(selectedColorScheme); showStatusMessage( tr("Settings updated"), diff --git a/src/PluginManagerDialog.cpp b/src/PluginManagerDialog.cpp index b8766f4..49b22dc 100644 --- a/src/PluginManagerDialog.cpp +++ b/src/PluginManagerDialog.cpp @@ -46,7 +46,6 @@ namespace scopeone::ui QString interfaceId; QJsonObject metadata; QString error; - QString loadError; }; QStringList pluginInterfaceIds(scopeone::core::PluginKind kind); @@ -95,17 +94,6 @@ namespace scopeone::ui { plugin.error = QStringLiteral("plugin interface does not match its kind"); } - if (plugin.error.isEmpty()) - { - if (!loader.load()) - { - plugin.loadError = loader.errorString(); - } - else - { - loader.unload(); - } - } plugins.append(std::move(plugin)); } } @@ -157,11 +145,10 @@ namespace scopeone::ui { continue; } - if (!plugin.error.isEmpty() || !plugin.loadError.isEmpty()) + if (!plugin.error.isEmpty()) { errors.append(QStringLiteral("%1: %2") - .arg(QFileInfo(plugin.path).fileName(), - plugin.error.isEmpty() ? plugin.loadError : plugin.error)); + .arg(QFileInfo(plugin.path).fileName(), plugin.error)); continue; } @@ -254,14 +241,12 @@ namespace scopeone::ui auto* enabled = new QTableWidgetItem(); enabled->setData(kIdRole, plugin.manifest.id); enabled->setData(kKindRole, static_cast(plugin.expectedKind)); - const QString status = plugin.error.isEmpty() - ? (plugin.loadError.isEmpty() ? tr("Ready") : plugin.loadError) - : plugin.error; + const QString status = plugin.error.isEmpty() ? tr("Ready") : plugin.error; enabled->setData(kStatusRole, status); enabled->setData( kMetadataRole, QString::fromUtf8(QJsonDocument(plugin.metadata).toJson(QJsonDocument::Indented))); - if (hardware && plugin.error.isEmpty() && plugin.loadError.isEmpty()) + if (hardware && plugin.error.isEmpty()) { enabled->setFlags(enabled->flags() | Qt::ItemIsUserCheckable); const QString key = settingsKey(plugin.manifest.id, QStringLiteral("enabled")); diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp index eea71d9..59b60dc 100644 --- a/src/SettingsDialog.cpp +++ b/src/SettingsDialog.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ namespace scopeone::ui // Create the application settings dialog SettingsDialog::SettingsDialog(qint64 maxPendingWriteBytes, const QString& microManagerDirectory, + const QString& widgetStyle, const QString& colorScheme, QWidget* parent) : QDialog(parent) @@ -34,13 +36,24 @@ namespace scopeone::ui auto* layout = new QVBoxLayout(this); auto* formLayout = new QFormLayout(); + + m_widgetStyleComboBox = new QComboBox(this); + m_widgetStyleComboBox->addItem(QStringLiteral("Default"), QStringLiteral("default")); + for (const QString& styleKey : QStyleFactory::keys()) + { + m_widgetStyleComboBox->addItem(styleKey, styleKey); + } + const int styleIndex = m_widgetStyleComboBox->findData(widgetStyle); + m_widgetStyleComboBox->setCurrentIndex(styleIndex >= 0 ? styleIndex : 0); + formLayout->addRow(QStringLiteral("Widget style"), m_widgetStyleComboBox); + m_colorSchemeComboBox = new QComboBox(this); m_colorSchemeComboBox->addItem(QStringLiteral("System"), QStringLiteral("system")); m_colorSchemeComboBox->addItem(QStringLiteral("Light"), QStringLiteral("light")); m_colorSchemeComboBox->addItem(QStringLiteral("Dark"), QStringLiteral("dark")); const int colorSchemeIndex = m_colorSchemeComboBox->findData(colorScheme); m_colorSchemeComboBox->setCurrentIndex(colorSchemeIndex >= 0 ? colorSchemeIndex : 0); - formLayout->addRow(QStringLiteral("Theme"), m_colorSchemeComboBox); + formLayout->addRow(QStringLiteral("Color scheme"), m_colorSchemeComboBox); m_recordingBufferLimitEdit = new QLineEdit(this); auto* validator = new QDoubleValidator(m_recordingBufferLimitEdit); @@ -130,6 +143,12 @@ namespace scopeone::ui return directory.isEmpty() ? QString() : QDir::cleanPath(directory); } + // Return the selected application widget style + QString SettingsDialog::widgetStyle() const + { + return m_widgetStyleComboBox->currentData().toString(); + } + // Return the selected application color scheme QString SettingsDialog::colorScheme() const { diff --git a/src/SettingsDialog.h b/src/SettingsDialog.h index d36bdf1..3b52970 100644 --- a/src/SettingsDialog.h +++ b/src/SettingsDialog.h @@ -15,16 +15,19 @@ namespace scopeone::ui public: explicit SettingsDialog(qint64 maxPendingWriteBytes, const QString& microManagerDirectory, + const QString& widgetStyle, const QString& colorScheme, QWidget* parent = nullptr); qint64 maxPendingWriteBytes() const; QString microManagerDirectory() const; + QString widgetStyle() const; QString colorScheme() const; private: QLineEdit* m_recordingBufferLimitEdit{nullptr}; QLineEdit* m_microManagerDirectoryEdit{nullptr}; + QComboBox* m_widgetStyleComboBox{nullptr}; QComboBox* m_colorSchemeComboBox{nullptr}; }; } From 29d6faf2c517f6b2c3773b09ff0c24f426eb4da5 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:20:35 +0200 Subject: [PATCH 25/37] Unify static image import as layers and modernize DHM input pipeline --- ScopeOneCore/CMakeLists.txt | 4 +- ScopeOneCore/include/scopeone/ScopeOneCore.h | 3 + ScopeOneCore/src/ScopeOneCore.cpp | 98 +++++++++++++++ plugins/examples/tool/DhmToolPlugin.cpp | 122 +++++++++---------- src/DeviceControlWidget.cpp | 25 ++++ src/DeviceControlWidget.h | 2 + src/MainWindow.cpp | 43 +++++++ src/MainWindow.h | 3 + src/PreviewWidget.cpp | 35 ++++++ src/PreviewWidget.h | 5 + 10 files changed, 276 insertions(+), 64 deletions(-) diff --git a/ScopeOneCore/CMakeLists.txt b/ScopeOneCore/CMakeLists.txt index 9fc449e..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 "") diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 347fa89..8f0c7a0 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -581,6 +581,9 @@ 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); ImageFrame publishToolStreamFrame(const QString& sourceId, const ImageFrame& frame, const QString& displayName = QString()); diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 8d7ff56..f8500a6 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -35,7 +35,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -2830,6 +2833,101 @@ namespace scopeone::core return storedFrame; } + // Import an external image file as a static frame layer + ImageFrame ScopeOneCore::importImageAsStaticLayer(const QString& filePath, + QString* outLayerKey, + QString* errorMessage) + { + const QString cleanedPath = QDir::cleanPath(filePath.trimmed()); + if (cleanedPath.isEmpty() || !QFileInfo::exists(cleanedPath)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("File does not exist: %1").arg(filePath); + } + return {}; + } + + const cv::Mat image = cv::imread(cleanedPath.toStdString(), cv::IMREAD_UNCHANGED); + if (image.empty()) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Failed to read image file: %1").arg(filePath); + } + return {}; + } + + ImageFrame frame; + frame.width = image.cols; + frame.height = image.rows; + + cv::Mat gray; + if (image.channels() == 1) + { + gray = image; + } + else if (image.channels() == 4) + { + cv::cvtColor(image, gray, cv::COLOR_BGRA2GRAY); + } + else + { + cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); + } + + frame.width = gray.cols; + frame.height = gray.rows; + + if (gray.depth() == CV_8U) + { + frame.bitsPerSample = 8; + frame.pixelFormat = ImagePixelFormat::Mono8; + frame.stride = static_cast(gray.step); + frame.bytes = QByteArray(reinterpret_cast(gray.data), + static_cast(gray.total() * gray.elemSize())); + } + else + { + cv::Mat gray16; + if (gray.depth() == CV_16U) + { + gray16 = gray; + } + else + { + gray.convertTo(gray16, CV_16U); + } + frame.bitsPerSample = 16; + frame.pixelFormat = ImagePixelFormat::Mono16; + frame.stride = static_cast(gray16.step); + frame.bytes = QByteArray(reinterpret_cast(gray16.data), + static_cast(gray16.total() * gray16.elemSize())); + } + + static std::atomic s_importCounter{1}; + const QFileInfo fileInfo(cleanedPath); + const QString sourceId = QStringLiteral("imported:%1_%2") + .arg(fileInfo.completeBaseName()) + .arg(s_importCounter.fetch_add(1)); + frame.cameraId = sourceId; + + const ImageFrame published = publishStaticFrame(sourceId, frame, fileInfo.fileName()); + if (published.isValid()) + { + const QString layerKey = staticLayerKey(sourceId); + if (outLayerKey) + { + *outLayerKey = layerKey; + } + if (m_imageSceneModel) + { + m_imageSceneModel->setLayerVisible(layerKey, true); + } + } + return published; + } + // Publish the latest frame of a tool-owned realtime stream ImageFrame ScopeOneCore::publishToolStreamFrame(const QString& sourceId, const ImageFrame& frame, diff --git a/plugins/examples/tool/DhmToolPlugin.cpp b/plugins/examples/tool/DhmToolPlugin.cpp index 04e316e..8b7041e 100644 --- a/plugins/examples/tool/DhmToolPlugin.cpp +++ b/plugins/examples/tool/DhmToolPlugin.cpp @@ -3,11 +3,12 @@ #include "scopeone/ToolFrameStream.h" #include "scopeone/ToolPlugin.h" #include "scopeone/ToolTask.h" +#include "scopeone/ScopeOneCore.h" +#include "scopeone/ImageSceneModel.h" #include #include #include -#include #include #include #include @@ -214,12 +215,15 @@ namespace layout->addLayout(controls); auto* actions = new QHBoxLayout(); - m_loadButton = new QPushButton(QStringLiteral("Load hologram"), this); + auto* layerLabel = new QLabel(QStringLiteral("Input:"), this); + m_layerComboBox = new QComboBox(this); + m_layerComboBox->setMinimumWidth(140); m_autoDetectButton = new QPushButton(QStringLiteral("Auto detect +1"), this); m_reconstructButton = new QPushButton(QStringLiteral("Reconstruct once"), this); m_cancelButton = new QPushButton(QStringLiteral("Cancel"), this); m_cancelButton->setEnabled(false); - actions->addWidget(m_loadButton); + actions->addWidget(layerLabel); + actions->addWidget(m_layerComboBox); actions->addWidget(m_autoDetectButton); actions->addWidget(m_reconstructButton); actions->addWidget(m_cancelButton); @@ -240,6 +244,23 @@ namespace m_stream = new scopeone::ui::ScopeOneToolFrameStream(m_context.core(), this); + refreshLayerComboBox(); + connect(m_context.core().imageSceneModel(), + &scopeone::core::ImageSceneModel::layersChanged, + this, + [this]() { refreshLayerComboBox(); }); + connect(m_layerComboBox, &QComboBox::currentIndexChanged, this, + [this]() + { + const QString layerKey = m_layerComboBox->currentData().toString(); + m_sourceId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey); + m_stream->setSourceId(m_sourceId); + if (!m_liveCheckBox->isChecked()) + { + startReconstruction(); + } + }); + m_spectrumView->setSelectionChanged([this](const QPoint& offset) { setManualSideband(offset); @@ -252,8 +273,6 @@ namespace }); connect(m_reconstructButton, &QPushButton::clicked, this, [this]() { startReconstruction(); }); - connect(m_loadButton, &QPushButton::clicked, this, - [this]() { loadHologram(); }); connect(m_cancelButton, &QPushButton::clicked, this, [this]() { @@ -270,11 +289,13 @@ namespace : QStringLiteral("Ready")); return; } - m_fileInput = {}; - const ImageFrame frame = m_context.currentFrame(); + const QString layerKey = m_layerComboBox->currentData().toString(); + const ImageFrame frame = !layerKey.isEmpty() + ? m_context.core().graphFrame(layerKey) + : m_context.currentFrame(); if (frame.isValid()) { - m_sourceId = frame.cameraId; + m_sourceId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey); m_stream->setSourceId(m_sourceId); queueFrame(frame); } @@ -415,40 +436,6 @@ namespace return preview; } - static ImageFrame imageFrame(const QString& source) - { - const cv::Mat image = cv::imread(source.toStdString(), cv::IMREAD_UNCHANGED); - cv::Mat gray; - if (image.channels() == 1) - { - gray = image; - } - else - { - cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); - } - if (gray.depth() == CV_8U) - { - gray.convertTo(gray, CV_16U, 257.0); - } - else if (gray.depth() != CV_16U) - { - gray.convertTo(gray, CV_16U); - } - gray = gray.clone(); - - ImageFrame frame; - frame.cameraId = source; - frame.width = gray.cols; - frame.height = gray.rows; - frame.stride = static_cast(gray.step); - frame.bitsPerSample = 16; - frame.pixelFormat = scopeone::core::ImagePixelFormat::Mono16; - frame.bytes = QByteArray(reinterpret_cast(gray.data), - static_cast(gray.total() * gray.elemSize())); - return frame; - } - static void showPreview(QLabel* preview, const ImageFrame& frame) { const QImage image(reinterpret_cast(frame.bytes.constData()), @@ -475,34 +462,46 @@ namespace m_spectrumView->setSelection(offset, m_radius->value()); } - void loadHologram() + void refreshLayerComboBox() { - const QString path = QFileDialog::getOpenFileName( - this, - QStringLiteral("Load hologram"), - QString(), - QStringLiteral("Images (*.png *.jpg *.jpeg *.bmp *.tif *.tiff)")); - if (path.isEmpty()) + const QString currentSelection = m_layerComboBox->currentData().toString(); + const QString activeKey = m_context.currentLayerKey(); + const QSignalBlocker blocker(m_layerComboBox); + m_layerComboBox->clear(); + const QStringList layerIds = m_context.core().imageSceneModel()->layerIds(); + for (const QString& layerKey : layerIds) { - return; + QString name = layerKey; + scopeone::core::DocumentLayer layer; + if (m_context.core().imageSceneModel()->findLayer(layerKey, layer) && !layer.name.isEmpty()) + { + name = layer.name; + } + m_layerComboBox->addItem(name, layerKey); + } + int index = m_layerComboBox->findData(currentSelection); + if (index < 0 && !activeKey.isEmpty()) + { + index = m_layerComboBox->findData(activeKey); + } + if (index >= 0) + { + m_layerComboBox->setCurrentIndex(index); } - m_fileInput = imageFrame(path); - m_liveCheckBox->setChecked(false); - m_sourceId = path; - startReconstruction(); } void startReconstruction() { - const ImageFrame input = m_fileInput.isValid() - ? m_fileInput + const QString layerKey = m_layerComboBox->currentData().toString(); + const ImageFrame input = !layerKey.isEmpty() + ? m_context.core().graphFrame(layerKey) : m_context.currentFrame(); if (!input.isValid()) { - m_status->setText(QStringLiteral("No hologram loaded")); + m_status->setText(QStringLiteral("No hologram layer available")); return; } - m_sourceId = input.cameraId; + m_sourceId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey); m_stream->setSourceId(m_sourceId); queueFrame(input); } @@ -555,7 +554,7 @@ namespace m_reconstructButton->setEnabled(false); m_autoDetectButton->setEnabled(false); - m_loadButton->setEnabled(false); + m_layerComboBox->setEnabled(false); m_cancelButton->setEnabled(true); m_progress->setValue(0); m_status->setText(QStringLiteral("Reconstructing 0%")); @@ -626,7 +625,7 @@ namespace m_status->setText(status); m_reconstructButton->setEnabled(true); m_autoDetectButton->setEnabled(true); - m_loadButton->setEnabled(true); + m_layerComboBox->setEnabled(true); m_cancelButton->setEnabled(false); if (m_task) { @@ -658,7 +657,7 @@ namespace QCheckBox* m_unwrapCheckBox{nullptr}; QCheckBox* m_tiltCheckBox{nullptr}; QComboBox* m_outputMode{nullptr}; - QPushButton* m_loadButton{nullptr}; + QComboBox* m_layerComboBox{nullptr}; QPushButton* m_autoDetectButton{nullptr}; QPushButton* m_reconstructButton{nullptr}; QPushButton* m_cancelButton{nullptr}; @@ -668,7 +667,6 @@ namespace scopeone::ui::ScopeOneToolTask* m_task{nullptr}; scopeone::ui::ScopeOneToolFrameStream* m_stream{nullptr}; QString m_sourceId; - ImageFrame m_fileInput; }; class DhmToolPlugin final : public QObject, diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 4d6a0ac..706a196 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -691,6 +692,8 @@ namespace scopeone::ui m_layerMoveDownButton = new QPushButton(QStringLiteral("Down"), m_layerSettingsGroup); m_layerRemoveButton = new QPushButton(QStringLiteral("Remove"), m_layerSettingsGroup); m_layerRemoveButton->setMaximumWidth(68); + m_layerImportButton = new QPushButton(QStringLiteral("Import..."), m_layerSettingsGroup); + m_layerImportButton->setMaximumWidth(68); m_layerOpacitySpinBox = new QSpinBox(m_layerSettingsGroup); m_layerOpacitySpinBox->setRange(0, 100); @@ -718,6 +721,7 @@ namespace scopeone::ui layerSettingsLayout->addWidget(m_layerMoveUpButton, 1, 1, Qt::AlignLeft); layerSettingsLayout->addWidget(m_layerMoveDownButton, 1, 2, Qt::AlignLeft); layerSettingsLayout->addWidget(m_layerRemoveButton, 1, 3, Qt::AlignLeft); + layerSettingsLayout->addWidget(m_layerImportButton, 1, 4, Qt::AlignLeft); layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Opacity:"), m_layerSettingsGroup), 2, 0); layerSettingsLayout->addWidget(m_layerOpacitySpinBox, 2, 1, Qt::AlignLeft); layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Gamma:"), m_layerSettingsGroup), 2, 2); @@ -859,6 +863,8 @@ namespace scopeone::ui this, &DeviceControlWidget::onPreviewLayerMoveDownClicked); connect(m_layerRemoveButton, &QPushButton::clicked, this, &DeviceControlWidget::onPreviewLayerRemoveClicked); + connect(m_layerImportButton, &QPushButton::clicked, + this, &DeviceControlWidget::onPreviewLayerImportClicked); connect(m_layerOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), this, &DeviceControlWidget::onPreviewLayerOpacityChanged); connect(m_layerGammaSpinBox, QOverload::of(&QDoubleSpinBox::valueChanged), @@ -1250,6 +1256,25 @@ namespace scopeone::ui scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey())); } + // Open file dialog and import image files as static layers + void DeviceControlWidget::onPreviewLayerImportClicked() + { + const QStringList filePaths = QFileDialog::getOpenFileNames( + this, + tr("Import Image as Layer"), + QString(), + tr("Images (*.tif *.tiff *.png *.jpg *.jpeg *.bmp)")); + QString lastLayerKey; + for (const QString& filePath : filePaths) + { + m_scopeonecore->importImageAsStaticLayer(filePath, &lastLayerKey); + } + if (!lastLayerKey.isEmpty() && m_workspace) + { + m_workspace->setActiveLayerKey(lastLayerKey); + } + } + void DeviceControlWidget::onLayerHistogramReady( const QString& layerKey, const scopeone::core::ScopeOneCore::HistogramStats& stats) diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index d5f61bd..b0853e1 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -109,6 +109,7 @@ namespace scopeone::ui void onPreviewLayerMoveUpClicked(); void onPreviewLayerMoveDownClicked(); void onPreviewLayerRemoveClicked(); + void onPreviewLayerImportClicked(); void onLayerHistogramReady(const QString& layerKey, const scopeone::core::ScopeOneCore::HistogramStats& stats); void refreshLayerHistogram(); @@ -141,6 +142,7 @@ namespace scopeone::ui QPushButton* m_layerMoveUpButton{nullptr}; QPushButton* m_layerMoveDownButton{nullptr}; QPushButton* m_layerRemoveButton{nullptr}; + QPushButton* m_layerImportButton{nullptr}; QSpinBox* m_layerOpacitySpinBox{nullptr}; QDoubleSpinBox* m_layerGammaSpinBox{nullptr}; QComboBox* m_layerColormapComboBox{nullptr}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 45f44d2..a026778 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -419,6 +419,8 @@ namespace scopeone::ui this, &MainWindow::handlePreviewMousePosition); connect(m_previewWidget, &PreviewWidget::roiDrawn, this, &MainWindow::handleRoiDrawn); + connect(m_previewWidget, &PreviewWidget::imageFilesDropped, + this, &MainWindow::importImages); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::rawFramesAcquired, m_previewWidget, &PreviewWidget::trackRawFrameRate); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::processedFramesCompleted, @@ -1046,6 +1048,9 @@ namespace scopeone::ui refreshRecentConfigurationsMenu(); m_unloadConfigurationAction = m_fileMenu->addAction(tr("&Unload Configuration")); m_fileMenu->addSeparator(); + m_importImageAction = m_fileMenu->addAction(tr("&Import Image as Layer...")); + m_importImageAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+I"))); + connect(m_importImageAction, &QAction::triggered, this, &MainWindow::openImportImageDialog); m_saveImageAsAction = m_fileMenu->addAction(tr("Save Image &As...")); m_saveImageAsAction->setEnabled(false); connect(m_saveImageAsAction, &QAction::triggered, @@ -1714,6 +1719,44 @@ namespace scopeone::ui 5000); } + // Open file dialog and import image files as static layers + void MainWindow::openImportImageDialog() + { + const QStringList filePaths = QFileDialog::getOpenFileNames( + this, + tr("Import Image as Layer"), + QString(), + tr("Images (*.tif *.tiff *.png *.jpg *.jpeg *.bmp)")); + importImages(filePaths); + } + + // Import multiple image files as static layers into the workspace + void MainWindow::importImages(const QStringList& filePaths) + { + QString lastLayerKey; + for (const QString& filePath : filePaths) + { + QString layerKey; + QString error; + const scopeone::core::ImageFrame frame = + m_scopeonecore->importImageAsStaticLayer(filePath, &layerKey, &error); + if (frame.isValid()) + { + lastLayerKey = layerKey; + } + else if (!error.isEmpty()) + { + showStatusMessage(error, 5000); + } + } + if (!lastLayerKey.isEmpty()) + { + m_imageWorkspace->setActiveLayerKey(lastLayerKey); + m_saveImageAsAction->setEnabled(true); + showStatusMessage(tr("Imported %1 image layer(s)").arg(filePaths.size()), 3000); + } + } + // Display image coordinates and pixel value under the cursor void MainWindow::handlePreviewMousePosition(const QPoint& pos) { diff --git a/src/MainWindow.h b/src/MainWindow.h index 719c3fe..af3e8e5 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -87,6 +87,8 @@ namespace scopeone::ui void applyStoredApplicationSettings(); void logStartupSummary(); void openSettingsDialog(); + void openImportImageDialog(); + void importImages(const QStringList& filePaths); void connectPropertyPanels(); void showStatusMessage(const QString& message, int timeoutMs = 0); void setCursorStatus(const QString& text); @@ -174,6 +176,7 @@ namespace scopeone::ui QAction* m_loadConfigurationAction{nullptr}; QAction* m_unloadConfigurationAction{nullptr}; QAction* m_saveImageAsAction{nullptr}; + QAction* m_importImageAction{nullptr}; QAction* m_settingsAction{nullptr}; QAction* m_aboutAction{nullptr}; QAction* m_aboutQtAction{nullptr}; diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index d175820..7b0a8af 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -3,8 +3,11 @@ #include "scopeone/ImageSceneModel.h" #include "scopeone/ScopeOneCore.h" #include +#include +#include #include #include +#include #include #include #include @@ -13,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -246,6 +250,7 @@ namespace scopeone::ui setMinimumSize(256, 256); setMouseTracking(true); setFocusPolicy(Qt::StrongFocus); + setAcceptDrops(true); m_placeholderLabel = new QLabel(m_placeholderText, this); m_placeholderLabel->setAlignment(Qt::AlignCenter); @@ -4000,6 +4005,8 @@ namespace scopeone::ui return; } + + if (event->key() == Qt::Key_Escape) { m_sceneModel->selectOnly(QString()); @@ -4047,4 +4054,32 @@ namespace scopeone::ui QOpenGLWidget::keyPressEvent(event); } + + // Accept file drag operations containing image files + void PreviewWidget::dragEnterEvent(QDragEnterEvent* event) + { + if (event->mimeData()->hasUrls()) + { + event->acceptProposedAction(); + } + } + + // Process dropped files and emit image paths + void PreviewWidget::dropEvent(QDropEvent* event) + { + const QList urls = event->mimeData()->urls(); + QStringList filePaths; + for (const QUrl& url : urls) + { + if (url.isLocalFile()) + { + filePaths.append(url.toLocalFile()); + } + } + if (!filePaths.isEmpty()) + { + emit imageFilesDropped(filePaths); + event->acceptProposedAction(); + } + } } // namespace scopeone::ui diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index 75e07ab..843abc4 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -22,6 +22,8 @@ #include "scopeone/ImageSceneModel.h" #include "scopeone/ImageFrame.h" +class QDragEnterEvent; +class QDropEvent; class QEvent; class QKeyEvent; class QLabel; @@ -138,6 +140,7 @@ namespace scopeone::ui const QPoint& start, const QPoint& end); void measurementLineCleared(); + void imageFilesDropped(const QStringList& filePaths); protected: void initializeGL() override; @@ -150,6 +153,8 @@ namespace scopeone::ui void leaveEvent(QEvent* event) override; void wheelEvent(QWheelEvent* event) override; void keyPressEvent(QKeyEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; private: struct FpsState From 4df567521de6feb5875639aaf982189c9ab6e5ba Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Thu, 3 Sep 2026 20:38:41 +0200 Subject: [PATCH 26/37] Update --- .../examples/example_raw_stream_process.py | 69 +++++++++++++++++++ VERSION | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 ScopeOneCore/python/scopeone/examples/example_raw_stream_process.py diff --git a/ScopeOneCore/python/scopeone/examples/example_raw_stream_process.py b/ScopeOneCore/python/scopeone/examples/example_raw_stream_process.py new file mode 100644 index 0000000..4bd9854 --- /dev/null +++ b/ScopeOneCore/python/scopeone/examples/example_raw_stream_process.py @@ -0,0 +1,69 @@ +"""Demonstrate real-time raw frame export, Python processing, and display reinjection.""" + +import time +import numpy as np + +from scopeone import ScopeOne + + +def main() -> None: + with ScopeOne() as scopeone: + cameras = scopeone.camera_ids() + if not cameras: + raise RuntimeError( + "No cameras available. Start ScopeOne and load a configuration first." + ) + + camera_id = cameras[0] + print(f"Using camera: {camera_id}") + + # Ensure live preview is active + scopeone.start_preview(camera_id) + + # Set display to side-by-side comparison mode + scopeone.set_layer_layout("side_by_side") + + layer_id = "python_stream" + layer_name = "Python Processed" + layer_key = f"static:{layer_id}" + + last_frame_index = -1 + print("Streaming started. View ScopeOne side-by-side preview. Press Ctrl+C to stop...") + + try: + while True: + # 1. Pull latest raw frame + frame = scopeone.latest_raw_frame(camera_id) + + # Avoid redundant computation on the same frame + if frame.frame_index == last_frame_index: + time.sleep(0.005) + continue + last_frame_index = frame.frame_index + + raw_img = frame.image + + # 2. Custom processing logic (example: negative / intensity inversion) + max_val = (1 << frame.bits_per_sample) - 1 + processed_img = max_val - raw_img + + # 3. Reinject processed array into ScopeOne display + scopeone.show_image( + processed_img, + layer_id=layer_id, + name=layer_name, + camera=camera_id, + bits_per_sample=frame.bits_per_sample, + ) + except KeyboardInterrupt: + print("\nStopping stream...") + finally: + try: + scopeone.remove_static_layer(layer_key) + except Exception: + pass + print("Cleaned up Python layer.") + + +if __name__ == "__main__": + main() diff --git a/VERSION b/VERSION index 4bb0a7a..78303df 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.902 +1.5.903 From ecc609ce08aed536534181d0df6d55ed1b6fb776 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Thu, 3 Sep 2026 23:18:10 +0200 Subject: [PATCH 27/37] Restructure plugin categories and split CUDA filters --- README.md | 2 +- ScopeOneCore/src/ProcessingModuleRegistry.cpp | 15 ++- plugins/CMakeLists.txt | 20 +++- plugins/examples/CMakeLists.txt | 25 ---- plugins/examples/README.md | 31 ----- plugins/hardware/CMakeLists.txt | 3 + .../ExampleHardware}/CMakeLists.txt | 2 +- .../ExampleHardwarePlugin.cpp | 0 .../ExampleHardware}/plugin.json | 0 plugins/{ => hardware}/NIDaqmx/CMakeLists.txt | 0 .../{ => hardware}/NIDaqmx/NIDaqmxPlugin.cpp | 0 .../{ => hardware}/NIDaqmx/NIDaqmxPlugin.h | 0 plugins/{ => hardware}/NIDaqmx/plugin.json | 0 plugins/{ => hardware}/PtuFile/CMakeLists.txt | 0 .../{ => hardware}/PtuFile/PtuFilePlugin.cpp | 0 .../{ => hardware}/PtuFile/PtuFilePlugin.h | 0 plugins/{ => hardware}/PtuFile/plugin.json | 0 plugins/processing/CMakeLists.txt | 5 + .../ExampleProcessing}/CMakeLists.txt | 2 +- .../ExampleProcessingPlugin.cpp | 0 .../ExampleProcessing}/plugin.json | 0 .../processing/FrequencyFilter/CMakeLists.txt | 13 +++ .../CudaFrequencyFilterModule.cpp | 109 ++++++++++++++++++ .../CudaFrequencyFilterModule.h | 39 +++++++ .../FrequencyFilter/FrequencyFilterPlugin.cpp | 56 +++++++++ .../processing/FrequencyFilter/plugin.json | 7 ++ .../processing/GaussianBlur/CMakeLists.txt | 13 +++ .../GaussianBlur/CudaGaussianBlurModule.cpp | 56 +++++++++ .../GaussianBlur/CudaGaussianBlurModule.h | 26 +++++ .../GaussianBlur/GaussianBlurPlugin.cpp | 45 ++++++++ plugins/processing/GaussianBlur/plugin.json | 7 ++ plugins/tools/CMakeLists.txt | 3 + .../tool => tools/DhmTool}/CMakeLists.txt | 11 +- .../DhmTool}/DhmReconstruction.cpp | 0 .../DhmTool}/DhmReconstruction.h | 0 .../tool => tools/DhmTool}/DhmToolPlugin.cpp | 2 +- .../DhmTool/plugin.json} | 0 plugins/tools/ExampleTool/CMakeLists.txt | 6 + .../ExampleTool}/ExampleToolPlugin.cpp | 0 .../tool => tools/ExampleTool}/plugin.json | 0 .../ScanningDaqTool/CMakeLists.txt | 0 .../ScanningDaqTool/DaqControlWidget.cpp | 0 .../ScanningDaqTool/DaqControlWidget.h | 0 .../ScanningDaqTool/ScanningDaqToolPlugin.cpp | 0 .../ScanningDaqTool/SignalMonitorWidget.cpp | 0 .../ScanningDaqTool/SignalMonitorWidget.h | 0 .../{ => tools}/ScanningDaqTool/plugin.json | 0 scripts/build.ps1 | 3 + src/PluginManagerDialog.cpp | 7 +- 49 files changed, 432 insertions(+), 76 deletions(-) delete mode 100644 plugins/examples/CMakeLists.txt delete mode 100644 plugins/examples/README.md create mode 100644 plugins/hardware/CMakeLists.txt rename plugins/{examples/hardware => hardware/ExampleHardware}/CMakeLists.txt (75%) rename plugins/{examples/hardware => hardware/ExampleHardware}/ExampleHardwarePlugin.cpp (100%) rename plugins/{examples/hardware => hardware/ExampleHardware}/plugin.json (100%) rename plugins/{ => hardware}/NIDaqmx/CMakeLists.txt (100%) rename plugins/{ => hardware}/NIDaqmx/NIDaqmxPlugin.cpp (100%) rename plugins/{ => hardware}/NIDaqmx/NIDaqmxPlugin.h (100%) rename plugins/{ => hardware}/NIDaqmx/plugin.json (100%) rename plugins/{ => hardware}/PtuFile/CMakeLists.txt (100%) rename plugins/{ => hardware}/PtuFile/PtuFilePlugin.cpp (100%) rename plugins/{ => hardware}/PtuFile/PtuFilePlugin.h (100%) rename plugins/{ => hardware}/PtuFile/plugin.json (100%) create mode 100644 plugins/processing/CMakeLists.txt rename plugins/{examples/processing => processing/ExampleProcessing}/CMakeLists.txt (74%) rename plugins/{examples/processing => processing/ExampleProcessing}/ExampleProcessingPlugin.cpp (100%) rename plugins/{examples/processing => processing/ExampleProcessing}/plugin.json (100%) create mode 100644 plugins/processing/FrequencyFilter/CMakeLists.txt create mode 100644 plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.cpp create mode 100644 plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.h create mode 100644 plugins/processing/FrequencyFilter/FrequencyFilterPlugin.cpp create mode 100644 plugins/processing/FrequencyFilter/plugin.json create mode 100644 plugins/processing/GaussianBlur/CMakeLists.txt create mode 100644 plugins/processing/GaussianBlur/CudaGaussianBlurModule.cpp create mode 100644 plugins/processing/GaussianBlur/CudaGaussianBlurModule.h create mode 100644 plugins/processing/GaussianBlur/GaussianBlurPlugin.cpp create mode 100644 plugins/processing/GaussianBlur/plugin.json create mode 100644 plugins/tools/CMakeLists.txt rename plugins/{examples/tool => tools/DhmTool}/CMakeLists.txt (55%) rename plugins/{examples/tool => tools/DhmTool}/DhmReconstruction.cpp (100%) rename plugins/{examples/tool => tools/DhmTool}/DhmReconstruction.h (100%) rename plugins/{examples/tool => tools/DhmTool}/DhmToolPlugin.cpp (99%) rename plugins/{examples/tool/dhm_plugin.json => tools/DhmTool/plugin.json} (100%) create mode 100644 plugins/tools/ExampleTool/CMakeLists.txt rename plugins/{examples/tool => tools/ExampleTool}/ExampleToolPlugin.cpp (100%) rename plugins/{examples/tool => tools/ExampleTool}/plugin.json (100%) rename plugins/{ => tools}/ScanningDaqTool/CMakeLists.txt (100%) rename plugins/{ => tools}/ScanningDaqTool/DaqControlWidget.cpp (100%) rename plugins/{ => tools}/ScanningDaqTool/DaqControlWidget.h (100%) rename plugins/{ => tools}/ScanningDaqTool/ScanningDaqToolPlugin.cpp (100%) rename plugins/{ => tools}/ScanningDaqTool/SignalMonitorWidget.cpp (100%) rename plugins/{ => tools}/ScanningDaqTool/SignalMonitorWidget.h (100%) rename plugins/{ => tools}/ScanningDaqTool/plugin.json (100%) diff --git a/README.md b/README.md index f1b4cb1..e5cadfe 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ ScopeOne loads external plugins from these directories beside the application: - `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. Minimal standalone projects are in `plugins/examples`. +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`. diff --git a/ScopeOneCore/src/ProcessingModuleRegistry.cpp b/ScopeOneCore/src/ProcessingModuleRegistry.cpp index 801eca3..18a61bb 100644 --- a/ScopeOneCore/src/ProcessingModuleRegistry.cpp +++ b/ScopeOneCore/src/ProcessingModuleRegistry.cpp @@ -16,6 +16,10 @@ #include #include +#ifdef Q_OS_WIN +# include +#endif + namespace scopeone::core::internal { namespace @@ -252,6 +256,9 @@ namespace scopeone::core::internal { QStringList errors; const QDir directory(directoryPath); +#ifdef Q_OS_WIN + SetDllDirectoryW(reinterpret_cast(directoryPath.utf16())); +#endif for (const QFileInfo& file : directory.entryInfoList(QDir::Files, QDir::Name)) { if (!QLibrary::isLibrary(file.absoluteFilePath())) @@ -260,10 +267,16 @@ namespace scopeone::core::internal } auto loader = std::make_unique(file.absoluteFilePath()); + const QJsonObject metadata = loader->metaData(); + if (metadata.value(QStringLiteral("IID")).toString() + != QStringLiteral(ScopeOneProcessingPlugin_iid)) + { + continue; + } PluginManifest manifest; QString manifestError; if (!parsePluginManifest( - loader->metaData().value(QStringLiteral("MetaData")).toObject(), + metadata.value(QStringLiteral("MetaData")).toObject(), PluginKind::Processing, manifest, &manifestError)) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 68f767a..9c3d51b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_AUTOMOC ON) find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) find_package(ScopeOneCore CONFIG REQUIRED) +find_package(OpenCV REQUIRED COMPONENTS core imgcodecs imgproc) +find_program(SCOPEONE_CUDA_NVCC_EXECUTABLE nvcc) function(scopeone_add_plugin target kind) set_target_properties(${target} PROPERTIES @@ -19,7 +21,17 @@ function(scopeone_add_plugin target kind) ) endfunction() -add_subdirectory(NIDaqmx) -add_subdirectory(PtuFile) -add_subdirectory(ScanningDaqTool) -add_subdirectory(examples) +add_subdirectory(hardware) +add_subdirectory(tools) +if (SCOPEONE_CUDA_NVCC_EXECUTABLE) + set(CMAKE_CUDA_COMPILER "${SCOPEONE_CUDA_NVCC_EXECUTABLE}" CACHE FILEPATH + "CUDA compiler selected by the environment" FORCE) + get_filename_component(_cuda_toolkit_root "${CMAKE_CUDA_COMPILER}" DIRECTORY) + get_filename_component(_cuda_toolkit_root "${_cuda_toolkit_root}" DIRECTORY) + set(CUDAToolkit_ROOT "${_cuda_toolkit_root}" CACHE PATH + "CUDA toolkit selected by the compiler" FORCE) + enable_language(CUDA) + find_package(CUDAToolkit REQUIRED) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../ScopeOneGpuCuda ScopeOneGpuCuda) +endif () +add_subdirectory(processing) diff --git a/plugins/examples/CMakeLists.txt b/plugins/examples/CMakeLists.txt deleted file mode 100644 index e36a8a2..0000000 --- a/plugins/examples/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -cmake_minimum_required(VERSION 3.23) -project(ScopeOnePluginExamples LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_AUTOMOC ON) - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) -find_package(ScopeOneCore CONFIG REQUIRED) -find_package(OpenCV REQUIRED COMPONENTS core imgcodecs imgproc) - -function(scopeone_example_plugin target kind) - set_target_properties(${target} PROPERTIES - PREFIX "" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/${kind}" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/${kind}" - ) - install(TARGETS ${target} - RUNTIME DESTINATION "plugins/${kind}" - LIBRARY DESTINATION "plugins/${kind}" - ) -endfunction() - -add_subdirectory(processing) -add_subdirectory(tool) -add_subdirectory(hardware) diff --git a/plugins/examples/README.md b/plugins/examples/README.md deleted file mode 100644 index cab0399..0000000 --- a/plugins/examples/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# ScopeOne plugin examples - -These projects use the installed `scopeone::PluginSDK` target and do not depend on ScopeOne source files. The public headers live in `ScopeOneCore/include/scopeone`; include `scopeone/PluginSDK.h` as the SDK entry point. The same plugin build also contains the external NI-DAQmx, PTU and scanning tool plugins. - -The normal ScopeOne build script builds and installs these examples automatically during a full build: - -```powershell -.\scripts\build.ps1 -``` - -To build only the examples: - -```powershell -.\scripts\build.ps1 --target plugins -``` - -Each example plugin declares `id`, `name`, `version`, `scopeOneApi`, and `kind` in `plugin.json`. Processing plugins implement `ProcessingPlugin` and add modules to the shared pipeline. Hardware providers implement `DriverHostProviderPlugin` and run in `ScopeOne_DriverHost`. DAQ plugins implement `DaqDevicePlugin`, signal sources implement `SignalSourcePlugin`, and tool plugins implement `ScopeOneToolPlugin`. - -Install processing plugins under `plugins/processing`, tool plugins under `plugins/tools`, and hardware, DAQ and signal source plugins under `plugins/hardware`. - -Tool plugins can create an independent processing pipeline without changing the main Process panel: - -```cpp -auto pipeline = context.core().createProcessingPipeline(); -pipeline->addModule(context.core().createProcessingModule("fft")); -pipeline->addModule(context.core().createProcessingModule("mask")); -pipeline->addModule(context.core().createProcessingModule("ifft")); -auto result = pipeline->process(scopeone::core::ProcessingValue{inputFrame}); -``` - -The pipeline accepts image and complex intermediate values. Publish image results with `publishExternalFrame` and show the returned layer through the tool context. diff --git a/plugins/hardware/CMakeLists.txt b/plugins/hardware/CMakeLists.txt new file mode 100644 index 0000000..e8232b8 --- /dev/null +++ b/plugins/hardware/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(NIDaqmx) +add_subdirectory(PtuFile) +add_subdirectory(ExampleHardware) diff --git a/plugins/examples/hardware/CMakeLists.txt b/plugins/hardware/ExampleHardware/CMakeLists.txt similarity index 75% rename from plugins/examples/hardware/CMakeLists.txt rename to plugins/hardware/ExampleHardware/CMakeLists.txt index 409aaa5..7b56deb 100644 --- a/plugins/examples/hardware/CMakeLists.txt +++ b/plugins/hardware/ExampleHardware/CMakeLists.txt @@ -3,4 +3,4 @@ add_library(ScopeOneExampleHardware MODULE plugin.json ) target_link_libraries(ScopeOneExampleHardware PRIVATE scopeone::PluginSDK Qt6::Core) -scopeone_example_plugin(ScopeOneExampleHardware hardware) +scopeone_add_plugin(ScopeOneExampleHardware hardware) diff --git a/plugins/examples/hardware/ExampleHardwarePlugin.cpp b/plugins/hardware/ExampleHardware/ExampleHardwarePlugin.cpp similarity index 100% rename from plugins/examples/hardware/ExampleHardwarePlugin.cpp rename to plugins/hardware/ExampleHardware/ExampleHardwarePlugin.cpp diff --git a/plugins/examples/hardware/plugin.json b/plugins/hardware/ExampleHardware/plugin.json similarity index 100% rename from plugins/examples/hardware/plugin.json rename to plugins/hardware/ExampleHardware/plugin.json diff --git a/plugins/NIDaqmx/CMakeLists.txt b/plugins/hardware/NIDaqmx/CMakeLists.txt similarity index 100% rename from plugins/NIDaqmx/CMakeLists.txt rename to plugins/hardware/NIDaqmx/CMakeLists.txt diff --git a/plugins/NIDaqmx/NIDaqmxPlugin.cpp b/plugins/hardware/NIDaqmx/NIDaqmxPlugin.cpp similarity index 100% rename from plugins/NIDaqmx/NIDaqmxPlugin.cpp rename to plugins/hardware/NIDaqmx/NIDaqmxPlugin.cpp diff --git a/plugins/NIDaqmx/NIDaqmxPlugin.h b/plugins/hardware/NIDaqmx/NIDaqmxPlugin.h similarity index 100% rename from plugins/NIDaqmx/NIDaqmxPlugin.h rename to plugins/hardware/NIDaqmx/NIDaqmxPlugin.h diff --git a/plugins/NIDaqmx/plugin.json b/plugins/hardware/NIDaqmx/plugin.json similarity index 100% rename from plugins/NIDaqmx/plugin.json rename to plugins/hardware/NIDaqmx/plugin.json diff --git a/plugins/PtuFile/CMakeLists.txt b/plugins/hardware/PtuFile/CMakeLists.txt similarity index 100% rename from plugins/PtuFile/CMakeLists.txt rename to plugins/hardware/PtuFile/CMakeLists.txt diff --git a/plugins/PtuFile/PtuFilePlugin.cpp b/plugins/hardware/PtuFile/PtuFilePlugin.cpp similarity index 100% rename from plugins/PtuFile/PtuFilePlugin.cpp rename to plugins/hardware/PtuFile/PtuFilePlugin.cpp diff --git a/plugins/PtuFile/PtuFilePlugin.h b/plugins/hardware/PtuFile/PtuFilePlugin.h similarity index 100% rename from plugins/PtuFile/PtuFilePlugin.h rename to plugins/hardware/PtuFile/PtuFilePlugin.h diff --git a/plugins/PtuFile/plugin.json b/plugins/hardware/PtuFile/plugin.json similarity index 100% rename from plugins/PtuFile/plugin.json rename to plugins/hardware/PtuFile/plugin.json diff --git a/plugins/processing/CMakeLists.txt b/plugins/processing/CMakeLists.txt new file mode 100644 index 0000000..b042f9a --- /dev/null +++ b/plugins/processing/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(ExampleProcessing) +if (TARGET ScopeOneGpuCuda) + add_subdirectory(FrequencyFilter) + add_subdirectory(GaussianBlur) +endif () diff --git a/plugins/examples/processing/CMakeLists.txt b/plugins/processing/ExampleProcessing/CMakeLists.txt similarity index 74% rename from plugins/examples/processing/CMakeLists.txt rename to plugins/processing/ExampleProcessing/CMakeLists.txt index 50fef99..9d48301 100644 --- a/plugins/examples/processing/CMakeLists.txt +++ b/plugins/processing/ExampleProcessing/CMakeLists.txt @@ -3,4 +3,4 @@ add_library(ScopeOneExampleProcessing MODULE plugin.json ) target_link_libraries(ScopeOneExampleProcessing PRIVATE scopeone::PluginSDK Qt6::Core) -scopeone_example_plugin(ScopeOneExampleProcessing processing) +scopeone_add_plugin(ScopeOneExampleProcessing processing) diff --git a/plugins/examples/processing/ExampleProcessingPlugin.cpp b/plugins/processing/ExampleProcessing/ExampleProcessingPlugin.cpp similarity index 100% rename from plugins/examples/processing/ExampleProcessingPlugin.cpp rename to plugins/processing/ExampleProcessing/ExampleProcessingPlugin.cpp diff --git a/plugins/examples/processing/plugin.json b/plugins/processing/ExampleProcessing/plugin.json similarity index 100% rename from plugins/examples/processing/plugin.json rename to plugins/processing/ExampleProcessing/plugin.json diff --git a/plugins/processing/FrequencyFilter/CMakeLists.txt b/plugins/processing/FrequencyFilter/CMakeLists.txt new file mode 100644 index 0000000..0b28060 --- /dev/null +++ b/plugins/processing/FrequencyFilter/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(ScopeOneFrequencyFilter MODULE + CudaFrequencyFilterModule.cpp + CudaFrequencyFilterModule.h + FrequencyFilterPlugin.cpp + plugin.json +) +target_link_libraries(ScopeOneFrequencyFilter PRIVATE + scopeone::GpuCuda + scopeone::PluginSDK + Qt6::Core +) +set_target_properties(ScopeOneFrequencyFilter PROPERTIES OUTPUT_NAME "FrequencyFilterPlugin") +scopeone_add_plugin(ScopeOneFrequencyFilter processing) diff --git a/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.cpp b/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.cpp new file mode 100644 index 0000000..43a75be --- /dev/null +++ b/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.cpp @@ -0,0 +1,109 @@ +#include "CudaFrequencyFilterModule.h" + +#include "scopeone/gpu/CudaKernelLaunch.h" + +namespace scopeone::cuda_plugin +{ + CudaFrequencyFilterModule::CudaFrequencyFilterModule() + : CudaRealImageModule(scopeone::gpu::GpuMemoryLayout::Contiguous) + { + } + + CudaFrequencyFilterModule::~CudaFrequencyFilterModule() + { + destroyPlans(); + } + + QString CudaFrequencyFilterModule::id() const + { + return QStringLiteral("cuda.frequency_filter"); + } + + QString CudaFrequencyFilterModule::name() const + { + return QStringLiteral("CUDA Frequency Filter"); + } + + QVariantMap CudaFrequencyFilterModule::parameters() const + { + return {{QStringLiteral("low_cutoff"), m_lowCutoff}, + {QStringLiteral("high_cutoff"), m_highCutoff}}; + } + + void CudaFrequencyFilterModule::setParameters(const QVariantMap& parameters) + { + if (parameters.contains(QStringLiteral("low_cutoff"))) + { + m_lowCutoff = parameters.value(QStringLiteral("low_cutoff")).toFloat(); + } + if (parameters.contains(QStringLiteral("high_cutoff"))) + { + m_highCutoff = parameters.value(QStringLiteral("high_cutoff")).toFloat(); + } + } + + std::unique_ptr + CudaFrequencyFilterModule::createRuntime() const + { + auto runtime = std::make_unique(); + runtime->setParameters(parameters()); + return runtime; + } + + bool CudaFrequencyFilterModule::resetState() + { + destroyPlans(); + m_spectrum.release(); + return true; + } + + void CudaFrequencyFilterModule::destroyPlans() + { + if (m_forwardPlan != 0) + { + cufftDestroy(m_forwardPlan); + m_forwardPlan = 0; + } + if (m_inversePlan != 0) + { + cufftDestroy(m_inversePlan); + m_inversePlan = 0; + } + m_planWidth = 0; + m_planHeight = 0; + } + + bool CudaFrequencyFilterModule::processDevice( + const scopeone::gpu::GpuRealFrame& input, + scopeone::gpu::GpuRealFrame& output, + int) + { + if (m_planWidth != input.width() || m_planHeight != input.height()) + { + destroyPlans(); + if (!m_spectrum.allocateForRealImage(input.width(), input.height()) + || cufftPlan2d(&m_forwardPlan, + input.height(), + input.width(), + CUFFT_R2C) != CUFFT_SUCCESS + || cufftPlan2d(&m_inversePlan, + input.height(), + input.width(), + CUFFT_C2R) != CUFFT_SUCCESS) + { + return false; + } + m_planWidth = input.width(); + m_planHeight = input.height(); + } + return scopeone::gpu::detail::launchFrequencyFilter(input.data(), + output.data(), + m_spectrum.data(), + input.width(), + input.height(), + m_lowCutoff, + m_highCutoff, + m_forwardPlan, + m_inversePlan); + } +} diff --git a/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.h b/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.h new file mode 100644 index 0000000..0413e37 --- /dev/null +++ b/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.h @@ -0,0 +1,39 @@ +#pragma once + +#include "scopeone/gpu/CudaRealImageModule.h" +#include "scopeone/gpu/GpuComplexFrame.h" + +#include + +namespace scopeone::cuda_plugin +{ + class CudaFrequencyFilterModule final : public scopeone::gpu::CudaRealImageModule + { + public: + CudaFrequencyFilterModule(); + ~CudaFrequencyFilterModule() override; + + QString id() const override; + QString name() const override; + QVariantMap parameters() const override; + void setParameters(const QVariantMap& parameters) override; + std::unique_ptr createRuntime() const override; + bool resetState() override; + + protected: + bool processDevice(const scopeone::gpu::GpuRealFrame& input, + scopeone::gpu::GpuRealFrame& output, + int bitDepth) override; + + private: + void destroyPlans(); + + scopeone::gpu::GpuComplexFrame m_spectrum; + cufftHandle m_forwardPlan{0}; + cufftHandle m_inversePlan{0}; + int m_planWidth{0}; + int m_planHeight{0}; + float m_lowCutoff{0.01f}; + float m_highCutoff{0.25f}; + }; +} diff --git a/plugins/processing/FrequencyFilter/FrequencyFilterPlugin.cpp b/plugins/processing/FrequencyFilter/FrequencyFilterPlugin.cpp new file mode 100644 index 0000000..b231aca --- /dev/null +++ b/plugins/processing/FrequencyFilter/FrequencyFilterPlugin.cpp @@ -0,0 +1,56 @@ +#include "CudaFrequencyFilterModule.h" + +#include + +namespace +{ + class FrequencyFilterPlugin final : public QObject, + public scopeone::core::ProcessingPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneProcessingPlugin_iid FILE "plugin.json") + Q_INTERFACES(scopeone::core::ProcessingPlugin) + + public: + QList processingModules() const override + { + scopeone::core::ProcessingModuleDescriptor descriptor; + descriptor.id = QStringLiteral("cuda.frequency_filter"); + descriptor.name = QStringLiteral("CUDA Frequency Filter"); + + scopeone::core::ProcessingParameterDescriptor low; + low.key = QStringLiteral("low_cutoff"); + low.name = QStringLiteral("Low Cutoff"); + low.type = scopeone::core::ProcessingParameterType::Real; + low.defaultValue = 0.01; + low.minimum = 0.0; + low.maximum = 0.5; + low.step = 0.01; + low.decimals = 3; + descriptor.parameters.append(low); + + scopeone::core::ProcessingParameterDescriptor high; + high.key = QStringLiteral("high_cutoff"); + high.name = QStringLiteral("High Cutoff"); + high.type = scopeone::core::ProcessingParameterType::Real; + high.defaultValue = 0.25; + high.minimum = 0.0; + high.maximum = 0.5; + high.step = 0.01; + high.decimals = 3; + descriptor.parameters.append(high); + + return {descriptor}; + } + + std::unique_ptr createProcessingModule( + const QString& moduleId) override + { + return moduleId == QStringLiteral("cuda.frequency_filter") + ? std::make_unique() + : nullptr; + } + }; +} + +#include "FrequencyFilterPlugin.moc" diff --git a/plugins/processing/FrequencyFilter/plugin.json b/plugins/processing/FrequencyFilter/plugin.json new file mode 100644 index 0000000..d90a864 --- /dev/null +++ b/plugins/processing/FrequencyFilter/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "scopeone.frequency_filter", + "name": "Frequency Filter", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "processing" +} diff --git a/plugins/processing/GaussianBlur/CMakeLists.txt b/plugins/processing/GaussianBlur/CMakeLists.txt new file mode 100644 index 0000000..4cd67e7 --- /dev/null +++ b/plugins/processing/GaussianBlur/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(ScopeOneGaussianBlur MODULE + CudaGaussianBlurModule.cpp + CudaGaussianBlurModule.h + GaussianBlurPlugin.cpp + plugin.json +) +target_link_libraries(ScopeOneGaussianBlur PRIVATE + scopeone::GpuCuda + scopeone::PluginSDK + Qt6::Core +) +set_target_properties(ScopeOneGaussianBlur PROPERTIES OUTPUT_NAME "GaussianBlurPlugin") +scopeone_add_plugin(ScopeOneGaussianBlur processing) diff --git a/plugins/processing/GaussianBlur/CudaGaussianBlurModule.cpp b/plugins/processing/GaussianBlur/CudaGaussianBlurModule.cpp new file mode 100644 index 0000000..f3c8e2e --- /dev/null +++ b/plugins/processing/GaussianBlur/CudaGaussianBlurModule.cpp @@ -0,0 +1,56 @@ +#include "CudaGaussianBlurModule.h" + +#include "scopeone/gpu/CudaKernelLaunch.h" + +namespace scopeone::cuda_plugin +{ + CudaGaussianBlurModule::CudaGaussianBlurModule() + : CudaRealImageModule(scopeone::gpu::GpuMemoryLayout::Pitched2D) + { + } + + QString CudaGaussianBlurModule::id() const + { + return QStringLiteral("cuda.gaussian_blur"); + } + + QString CudaGaussianBlurModule::name() const + { + return QStringLiteral("CUDA Gaussian Blur"); + } + + QVariantMap CudaGaussianBlurModule::parameters() const + { + return {{QStringLiteral("sigma"), m_sigma}}; + } + + void CudaGaussianBlurModule::setParameters(const QVariantMap& parameters) + { + if (parameters.contains(QStringLiteral("sigma"))) + { + m_sigma = parameters.value(QStringLiteral("sigma")).toFloat(); + } + } + + std::unique_ptr + CudaGaussianBlurModule::createRuntime() const + { + auto runtime = std::make_unique(); + runtime->setParameters(parameters()); + return runtime; + } + + bool CudaGaussianBlurModule::processDevice( + const scopeone::gpu::GpuRealFrame& input, + scopeone::gpu::GpuRealFrame& output, + int) + { + return scopeone::gpu::detail::launchGaussian(input.data(), + input.pitchBytes(), + output.data(), + output.pitchBytes(), + input.width(), + input.height(), + m_sigma); + } +} diff --git a/plugins/processing/GaussianBlur/CudaGaussianBlurModule.h b/plugins/processing/GaussianBlur/CudaGaussianBlurModule.h new file mode 100644 index 0000000..5891090 --- /dev/null +++ b/plugins/processing/GaussianBlur/CudaGaussianBlurModule.h @@ -0,0 +1,26 @@ +#pragma once + +#include "scopeone/gpu/CudaRealImageModule.h" + +namespace scopeone::cuda_plugin +{ + class CudaGaussianBlurModule final : public scopeone::gpu::CudaRealImageModule + { + public: + CudaGaussianBlurModule(); + + QString id() const override; + QString name() const override; + QVariantMap parameters() const override; + void setParameters(const QVariantMap& parameters) override; + std::unique_ptr createRuntime() const override; + + protected: + bool processDevice(const scopeone::gpu::GpuRealFrame& input, + scopeone::gpu::GpuRealFrame& output, + int bitDepth) override; + + private: + float m_sigma{1.5f}; + }; +} diff --git a/plugins/processing/GaussianBlur/GaussianBlurPlugin.cpp b/plugins/processing/GaussianBlur/GaussianBlurPlugin.cpp new file mode 100644 index 0000000..af9e067 --- /dev/null +++ b/plugins/processing/GaussianBlur/GaussianBlurPlugin.cpp @@ -0,0 +1,45 @@ +#include "CudaGaussianBlurModule.h" + +#include + +namespace +{ + class GaussianBlurPlugin final : public QObject, + public scopeone::core::ProcessingPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneProcessingPlugin_iid FILE "plugin.json") + Q_INTERFACES(scopeone::core::ProcessingPlugin) + + public: + QList processingModules() const override + { + scopeone::core::ProcessingModuleDescriptor descriptor; + descriptor.id = QStringLiteral("cuda.gaussian_blur"); + descriptor.name = QStringLiteral("CUDA Gaussian Blur"); + + scopeone::core::ProcessingParameterDescriptor sigma; + sigma.key = QStringLiteral("sigma"); + sigma.name = QStringLiteral("Sigma"); + sigma.type = scopeone::core::ProcessingParameterType::Real; + sigma.defaultValue = 1.5; + sigma.minimum = 0.1; + sigma.maximum = 20.0; + sigma.step = 0.1; + sigma.decimals = 2; + descriptor.parameters.append(sigma); + + return {descriptor}; + } + + std::unique_ptr createProcessingModule( + const QString& moduleId) override + { + return moduleId == QStringLiteral("cuda.gaussian_blur") + ? std::make_unique() + : nullptr; + } + }; +} + +#include "GaussianBlurPlugin.moc" diff --git a/plugins/processing/GaussianBlur/plugin.json b/plugins/processing/GaussianBlur/plugin.json new file mode 100644 index 0000000..bf9d1e3 --- /dev/null +++ b/plugins/processing/GaussianBlur/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "scopeone.gaussian_blur", + "name": "Gaussian Blur", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "processing" +} diff --git a/plugins/tools/CMakeLists.txt b/plugins/tools/CMakeLists.txt new file mode 100644 index 0000000..17a7bc4 --- /dev/null +++ b/plugins/tools/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(DhmTool) +add_subdirectory(ScanningDaqTool) +add_subdirectory(ExampleTool) diff --git a/plugins/examples/tool/CMakeLists.txt b/plugins/tools/DhmTool/CMakeLists.txt similarity index 55% rename from plugins/examples/tool/CMakeLists.txt rename to plugins/tools/DhmTool/CMakeLists.txt index 8e33a03..bce1ad7 100644 --- a/plugins/examples/tool/CMakeLists.txt +++ b/plugins/tools/DhmTool/CMakeLists.txt @@ -1,15 +1,8 @@ -add_library(ScopeOneExampleTool MODULE - ExampleToolPlugin.cpp - plugin.json -) -target_link_libraries(ScopeOneExampleTool PRIVATE scopeone::PluginSDK Qt6::Widgets) -scopeone_example_plugin(ScopeOneExampleTool tools) - add_library(ScopeOneDhmTool MODULE DhmToolPlugin.cpp DhmReconstruction.cpp DhmReconstruction.h - dhm_plugin.json + plugin.json ) target_compile_features(ScopeOneDhmTool PRIVATE cxx_std_20) target_link_libraries(ScopeOneDhmTool PRIVATE @@ -19,4 +12,4 @@ target_link_libraries(ScopeOneDhmTool PRIVATE opencv_imgproc Qt6::Widgets ) -scopeone_example_plugin(ScopeOneDhmTool tools) +scopeone_add_plugin(ScopeOneDhmTool tools) diff --git a/plugins/examples/tool/DhmReconstruction.cpp b/plugins/tools/DhmTool/DhmReconstruction.cpp similarity index 100% rename from plugins/examples/tool/DhmReconstruction.cpp rename to plugins/tools/DhmTool/DhmReconstruction.cpp diff --git a/plugins/examples/tool/DhmReconstruction.h b/plugins/tools/DhmTool/DhmReconstruction.h similarity index 100% rename from plugins/examples/tool/DhmReconstruction.h rename to plugins/tools/DhmTool/DhmReconstruction.h diff --git a/plugins/examples/tool/DhmToolPlugin.cpp b/plugins/tools/DhmTool/DhmToolPlugin.cpp similarity index 99% rename from plugins/examples/tool/DhmToolPlugin.cpp rename to plugins/tools/DhmTool/DhmToolPlugin.cpp index 8b7041e..4ea6a02 100644 --- a/plugins/examples/tool/DhmToolPlugin.cpp +++ b/plugins/tools/DhmTool/DhmToolPlugin.cpp @@ -673,7 +673,7 @@ namespace public scopeone::ui::ScopeOneToolPlugin { Q_OBJECT - Q_PLUGIN_METADATA(IID ScopeOneToolPlugin_iid FILE "dhm_plugin.json") + Q_PLUGIN_METADATA(IID ScopeOneToolPlugin_iid FILE "plugin.json") Q_INTERFACES(scopeone::ui::ScopeOneToolPlugin) public: diff --git a/plugins/examples/tool/dhm_plugin.json b/plugins/tools/DhmTool/plugin.json similarity index 100% rename from plugins/examples/tool/dhm_plugin.json rename to plugins/tools/DhmTool/plugin.json diff --git a/plugins/tools/ExampleTool/CMakeLists.txt b/plugins/tools/ExampleTool/CMakeLists.txt new file mode 100644 index 0000000..78abc6b --- /dev/null +++ b/plugins/tools/ExampleTool/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(ScopeOneExampleTool MODULE + ExampleToolPlugin.cpp + plugin.json +) +target_link_libraries(ScopeOneExampleTool PRIVATE scopeone::PluginSDK Qt6::Widgets) +scopeone_add_plugin(ScopeOneExampleTool tools) diff --git a/plugins/examples/tool/ExampleToolPlugin.cpp b/plugins/tools/ExampleTool/ExampleToolPlugin.cpp similarity index 100% rename from plugins/examples/tool/ExampleToolPlugin.cpp rename to plugins/tools/ExampleTool/ExampleToolPlugin.cpp diff --git a/plugins/examples/tool/plugin.json b/plugins/tools/ExampleTool/plugin.json similarity index 100% rename from plugins/examples/tool/plugin.json rename to plugins/tools/ExampleTool/plugin.json diff --git a/plugins/ScanningDaqTool/CMakeLists.txt b/plugins/tools/ScanningDaqTool/CMakeLists.txt similarity index 100% rename from plugins/ScanningDaqTool/CMakeLists.txt rename to plugins/tools/ScanningDaqTool/CMakeLists.txt diff --git a/plugins/ScanningDaqTool/DaqControlWidget.cpp b/plugins/tools/ScanningDaqTool/DaqControlWidget.cpp similarity index 100% rename from plugins/ScanningDaqTool/DaqControlWidget.cpp rename to plugins/tools/ScanningDaqTool/DaqControlWidget.cpp diff --git a/plugins/ScanningDaqTool/DaqControlWidget.h b/plugins/tools/ScanningDaqTool/DaqControlWidget.h similarity index 100% rename from plugins/ScanningDaqTool/DaqControlWidget.h rename to plugins/tools/ScanningDaqTool/DaqControlWidget.h diff --git a/plugins/ScanningDaqTool/ScanningDaqToolPlugin.cpp b/plugins/tools/ScanningDaqTool/ScanningDaqToolPlugin.cpp similarity index 100% rename from plugins/ScanningDaqTool/ScanningDaqToolPlugin.cpp rename to plugins/tools/ScanningDaqTool/ScanningDaqToolPlugin.cpp diff --git a/plugins/ScanningDaqTool/SignalMonitorWidget.cpp b/plugins/tools/ScanningDaqTool/SignalMonitorWidget.cpp similarity index 100% rename from plugins/ScanningDaqTool/SignalMonitorWidget.cpp rename to plugins/tools/ScanningDaqTool/SignalMonitorWidget.cpp diff --git a/plugins/ScanningDaqTool/SignalMonitorWidget.h b/plugins/tools/ScanningDaqTool/SignalMonitorWidget.h similarity index 100% rename from plugins/ScanningDaqTool/SignalMonitorWidget.h rename to plugins/tools/ScanningDaqTool/SignalMonitorWidget.h diff --git a/plugins/ScanningDaqTool/plugin.json b/plugins/tools/ScanningDaqTool/plugin.json similarity index 100% rename from plugins/ScanningDaqTool/plugin.json rename to plugins/tools/ScanningDaqTool/plugin.json diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 97b0d97..18dabe9 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -455,6 +455,9 @@ if ($target -in @("all", "plugins")) { if ($coreOpenCvDir) { $pluginConfigureArgs += "-DOpenCV_DIR=$coreOpenCvDir" } + if ($env:CUDA_PATH) { + $pluginConfigureArgs += @("-T", "cuda=$env:CUDA_PATH") + } Invoke-Step ` -Label "Configuring ScopeOne plugins" ` -FilePath $cmake ` diff --git a/src/PluginManagerDialog.cpp b/src/PluginManagerDialog.cpp index 49b22dc..5525a3d 100644 --- a/src/PluginManagerDialog.cpp +++ b/src/PluginManagerDialog.cpp @@ -82,6 +82,10 @@ namespace scopeone::ui plugin.path = file.absoluteFilePath(); const QJsonObject loaderMetadata = loader.metaData(); plugin.interfaceId = loaderMetadata.value(QStringLiteral("IID")).toString(); + if (plugin.interfaceId.isEmpty()) + { + continue; + } plugin.metadata = loaderMetadata.value(QStringLiteral("MetaData")).toObject(); scopeone::core::parsePluginManifest( plugin.metadata, @@ -89,8 +93,7 @@ namespace scopeone::ui plugin.manifest, &plugin.error); if (plugin.error.isEmpty() - && !pluginInterfaceIds(kind).contains( - loaderMetadata.value(QStringLiteral("IID")).toString())) + && !pluginInterfaceIds(kind).contains(plugin.interfaceId)) { plugin.error = QStringLiteral("plugin interface does not match its kind"); } From eccc23a893debdde832d76e52f9920faa2759f7f Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Fri, 4 Sep 2026 01:14:23 +0200 Subject: [PATCH 28/37] Move common CUDA filters into internal runtime --- .gitignore | 2 + ScopeOneCore/include/scopeone/ScopeOneCore.h | 3 + ScopeOneCore/src/ScopeOneCore.cpp | 31 ++ ScopeOneCuda/CMakeLists.txt | 96 ++++++ .../cmake/ScopeOneCudaConfig.cmake.in | 7 + .../include/scopeone/cuda/CudaDevice.h | 12 + .../include/scopeone/cuda/CudaExport.h | 11 + .../include/scopeone/cuda/CudaKernelLaunch.h | 42 +++ .../scopeone/cuda/CudaRealImageModule.h | 29 ++ .../include/scopeone/cuda/GpuComplexFrame.h | 36 +++ .../include/scopeone/cuda/GpuRealFrame.h | 55 ++++ ScopeOneCuda/src/CudaDevice.cpp | 25 ++ .../src}/CudaFrequencyFilterModule.cpp | 10 +- .../src}/CudaFrequencyFilterModule.h | 12 +- .../src}/CudaGaussianBlurModule.cpp | 10 +- .../src}/CudaGaussianBlurModule.h | 8 +- ScopeOneCuda/src/CudaKernels.cu | 273 ++++++++++++++++++ .../src/CudaProcessingRegistration.cpp | 50 ++++ ScopeOneCuda/src/CudaRealImageModule.cpp | 74 +++++ ScopeOneCuda/src/GpuComplexFrame.cpp | 102 +++++++ ScopeOneCuda/src/GpuRealFrame.cpp | 251 ++++++++++++++++ plugins/CMakeLists.txt | 2 +- plugins/processing/CMakeLists.txt | 5 +- .../ExampleCudaProcessing/CMakeLists.txt | 16 + .../ExampleCudaKernel.cu | 52 ++++ .../ExampleCudaProcessingPlugin.cpp | 74 +++++ .../ExampleCudaProcessing/plugin.json | 7 + .../processing/FrequencyFilter/CMakeLists.txt | 13 - .../FrequencyFilter/FrequencyFilterPlugin.cpp | 56 ---- .../processing/FrequencyFilter/plugin.json | 7 - .../processing/GaussianBlur/CMakeLists.txt | 13 - .../GaussianBlur/GaussianBlurPlugin.cpp | 45 --- plugins/processing/GaussianBlur/plugin.json | 7 - 33 files changed, 1271 insertions(+), 165 deletions(-) create mode 100644 ScopeOneCuda/CMakeLists.txt create mode 100644 ScopeOneCuda/cmake/ScopeOneCudaConfig.cmake.in create mode 100644 ScopeOneCuda/include/scopeone/cuda/CudaDevice.h create mode 100644 ScopeOneCuda/include/scopeone/cuda/CudaExport.h create mode 100644 ScopeOneCuda/include/scopeone/cuda/CudaKernelLaunch.h create mode 100644 ScopeOneCuda/include/scopeone/cuda/CudaRealImageModule.h create mode 100644 ScopeOneCuda/include/scopeone/cuda/GpuComplexFrame.h create mode 100644 ScopeOneCuda/include/scopeone/cuda/GpuRealFrame.h create mode 100644 ScopeOneCuda/src/CudaDevice.cpp rename {plugins/processing/FrequencyFilter => ScopeOneCuda/src}/CudaFrequencyFilterModule.cpp (91%) rename {plugins/processing/FrequencyFilter => ScopeOneCuda/src}/CudaFrequencyFilterModule.h (69%) rename {plugins/processing/GaussianBlur => ScopeOneCuda/src}/CudaGaussianBlurModule.cpp (83%) rename {plugins/processing/GaussianBlur => ScopeOneCuda/src}/CudaGaussianBlurModule.h (65%) create mode 100644 ScopeOneCuda/src/CudaKernels.cu create mode 100644 ScopeOneCuda/src/CudaProcessingRegistration.cpp create mode 100644 ScopeOneCuda/src/CudaRealImageModule.cpp create mode 100644 ScopeOneCuda/src/GpuComplexFrame.cpp create mode 100644 ScopeOneCuda/src/GpuRealFrame.cpp create mode 100644 plugins/processing/ExampleCudaProcessing/CMakeLists.txt create mode 100644 plugins/processing/ExampleCudaProcessing/ExampleCudaKernel.cu create mode 100644 plugins/processing/ExampleCudaProcessing/ExampleCudaProcessingPlugin.cpp create mode 100644 plugins/processing/ExampleCudaProcessing/plugin.json delete mode 100644 plugins/processing/FrequencyFilter/CMakeLists.txt delete mode 100644 plugins/processing/FrequencyFilter/FrequencyFilterPlugin.cpp delete mode 100644 plugins/processing/FrequencyFilter/plugin.json delete mode 100644 plugins/processing/GaussianBlur/CMakeLists.txt delete mode 100644 plugins/processing/GaussianBlur/GaussianBlurPlugin.cpp delete mode 100644 plugins/processing/GaussianBlur/plugin.json diff --git a/.gitignore b/.gitignore index ef39800..0179a56 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ !/ScopeOneCore/** !/plugins/ !/plugins/** +!/ScopeOneCuda/ +!/ScopeOneCuda/** /ScopeOneCore/install/ /ScopeOneCore/build/ /ScopeOneCore/external/* diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 8f0c7a0..8447080 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -680,6 +680,9 @@ namespace scopeone::core 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; diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index f8500a6..c39d3a3 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -917,6 +918,7 @@ namespace scopeone::core RecordingManager* recordingManager{nullptr}; ImageProcessingManager* imageProcessingManager{nullptr}; std::unique_ptr processingModuleRegistry; + std::unique_ptr cudaLibrary; StageMosaicManager* stageMosaicManager{nullptr}; internal::DaqDeviceManager* daqDeviceManager{nullptr}; internal::SignalSourceManager* signalSourceManager{nullptr}; @@ -1175,6 +1177,27 @@ namespace scopeone::core "scopeone::core::ScanImageConfig"); m_managers->processingModuleRegistry = std::make_unique(); + const QString cudaLibraryPath = QDir(QCoreApplication::applicationDirPath()) + .filePath(QStringLiteral("ScopeOneCuda")); + { + auto cudaLibrary = std::make_unique(cudaLibraryPath); + if (cudaLibrary->load()) + { + using RegisterProcessingModules = void (*)(ScopeOneCore*); + const auto registerProcessingModules = + reinterpret_cast( + cudaLibrary->resolve("scopeone_register_processing_modules")); + if (registerProcessingModules) + { + registerProcessingModules(this); + m_managers->cudaLibrary = std::move(cudaLibrary); + } + else + { + cudaLibrary->unload(); + } + } + } const QStringList processingPluginErrors = m_managers->processingModuleRegistry->loadPlugins( QDir(QCoreApplication::applicationDirPath()).filePath(QStringLiteral("plugins/processing"))); QStringList allProcessingPluginErrors = processingPluginErrors; @@ -4305,6 +4328,14 @@ namespace scopeone::core return m_managers->processingModuleRegistry->descriptors(); } + bool ScopeOneCore::registerProcessingModule( + const ProcessingModuleDescriptor& descriptor, + std::function()> factory) + { + return m_managers->processingModuleRegistry->registerModule(descriptor, + std::move(factory)); + } + std::unique_ptr ScopeOneCore::createProcessingModule( const QString& moduleId) const { diff --git a/ScopeOneCuda/CMakeLists.txt b/ScopeOneCuda/CMakeLists.txt new file mode 100644 index 0000000..434bd2b --- /dev/null +++ b/ScopeOneCuda/CMakeLists.txt @@ -0,0 +1,96 @@ +cmake_minimum_required(VERSION 3.23) + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +set(SCOPEONE_CUDA_VERSION 1.0.0) + +add_library(ScopeOneCuda SHARED + src/CudaKernels.cu + src/CudaDevice.cpp + src/CudaFrequencyFilterModule.cpp + src/CudaFrequencyFilterModule.h + src/CudaGaussianBlurModule.cpp + src/CudaGaussianBlurModule.h + src/CudaProcessingRegistration.cpp + src/CudaRealImageModule.cpp + src/GpuComplexFrame.cpp + src/GpuRealFrame.cpp + include/scopeone/cuda/CudaDevice.h + include/scopeone/cuda/CudaRealImageModule.h + include/scopeone/cuda/CudaKernelLaunch.h + include/scopeone/cuda/GpuComplexFrame.h + include/scopeone/cuda/CudaExport.h + include/scopeone/cuda/GpuRealFrame.h +) +add_library(scopeone::Cuda ALIAS ScopeOneCuda) + +set(SCOPEONE_CUDA_ARCHITECTURES "50;75;80;86;89" CACHE STRING + "Target CUDA architectures") +set_target_properties(ScopeOneCuda PROPERTIES + EXPORT_NAME Cuda + CUDA_SEPARABLE_COMPILATION OFF + CUDA_ARCHITECTURES "${SCOPEONE_CUDA_ARCHITECTURES}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" +) + +target_compile_features(ScopeOneCuda PUBLIC cxx_std_20) +target_compile_definitions(ScopeOneCuda PRIVATE SCOPEONE_CUDA_EXPORTS) +target_include_directories(ScopeOneCuda + PUBLIC + $ + $ +) +target_link_libraries(ScopeOneCuda + PUBLIC + scopeone::ScopeOneCore + CUDA::cudart + CUDA::cufft +) + +file(GLOB SCOPEONE_CUDA_RUNTIME_DLLS + "${CUDAToolkit_BIN_DIR}/cudart64_*.dll" + "${CUDAToolkit_BIN_DIR}/cufft64_*.dll" +) +if (WIN32) + foreach (_dll IN LISTS SCOPEONE_CUDA_RUNTIME_DLLS) + add_custom_command(TARGET ScopeOneCuda POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${_dll}" + "$" + ) + endforeach () +endif () + +install(TARGETS ScopeOneCuda + EXPORT ScopeOneCudaTargets + RUNTIME DESTINATION "." + LIBRARY DESTINATION "." + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" +) +install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +if (SCOPEONE_CUDA_RUNTIME_DLLS) + install(FILES ${SCOPEONE_CUDA_RUNTIME_DLLS} DESTINATION ".") +endif () +install(EXPORT ScopeOneCudaTargets + FILE ScopeOneCudaTargets.cmake + NAMESPACE scopeone:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ScopeOneCuda" +) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ScopeOneCudaConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/ScopeOneCudaConfig.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ScopeOneCuda" +) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/ScopeOneCudaConfigVersion.cmake" + VERSION ${SCOPEONE_CUDA_VERSION} + COMPATIBILITY SameMajorVersion +) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/ScopeOneCudaConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/ScopeOneCudaConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ScopeOneCuda" +) diff --git a/ScopeOneCuda/cmake/ScopeOneCudaConfig.cmake.in b/ScopeOneCuda/cmake/ScopeOneCudaConfig.cmake.in new file mode 100644 index 0000000..dae2453 --- /dev/null +++ b/ScopeOneCuda/cmake/ScopeOneCudaConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(ScopeOneCore CONFIG REQUIRED) +find_dependency(CUDAToolkit REQUIRED) + +include("${CMAKE_CURRENT_LIST_DIR}/ScopeOneCudaTargets.cmake") diff --git a/ScopeOneCuda/include/scopeone/cuda/CudaDevice.h b/ScopeOneCuda/include/scopeone/cuda/CudaDevice.h new file mode 100644 index 0000000..932f827 --- /dev/null +++ b/ScopeOneCuda/include/scopeone/cuda/CudaDevice.h @@ -0,0 +1,12 @@ +#pragma once + +#include "scopeone/cuda/CudaExport.h" + +#include + +namespace scopeone::cuda +{ + SCOPEONE_CUDA_EXPORT bool isCudaDeviceAvailable(); + SCOPEONE_CUDA_EXPORT int cudaDeviceCount(); + SCOPEONE_CUDA_EXPORT QString cudaDeviceName(int deviceIndex = 0); +} diff --git a/ScopeOneCuda/include/scopeone/cuda/CudaExport.h b/ScopeOneCuda/include/scopeone/cuda/CudaExport.h new file mode 100644 index 0000000..68baad4 --- /dev/null +++ b/ScopeOneCuda/include/scopeone/cuda/CudaExport.h @@ -0,0 +1,11 @@ +#pragma once + +#if defined(_WIN32) +# if defined(SCOPEONE_CUDA_EXPORTS) +# define SCOPEONE_CUDA_EXPORT __declspec(dllexport) +# else +# define SCOPEONE_CUDA_EXPORT __declspec(dllimport) +# endif +#else +# define SCOPEONE_CUDA_EXPORT __attribute__((visibility("default"))) +#endif diff --git a/ScopeOneCuda/include/scopeone/cuda/CudaKernelLaunch.h b/ScopeOneCuda/include/scopeone/cuda/CudaKernelLaunch.h new file mode 100644 index 0000000..63b7d05 --- /dev/null +++ b/ScopeOneCuda/include/scopeone/cuda/CudaKernelLaunch.h @@ -0,0 +1,42 @@ +#pragma once + +#include "scopeone/cuda/CudaExport.h" + +#include + +namespace scopeone::cuda::detail +{ + SCOPEONE_CUDA_EXPORT bool convertToFloat(const void* source, + int sourceStride, + int width, + int height, + int sourceBytesPerPixel, + void* destination, + std::size_t destinationPitchBytes); + + SCOPEONE_CUDA_EXPORT bool convertFromFloat(const void* source, + std::size_t sourcePitchBytes, + void* destination, + int destinationStride, + int width, + int height, + int destinationBytesPerPixel); + + SCOPEONE_CUDA_EXPORT bool launchGaussian(const void* input, + std::size_t inputPitchBytes, + void* output, + std::size_t outputPitchBytes, + int width, + int height, + float sigma); + + SCOPEONE_CUDA_EXPORT bool launchFrequencyFilter(const void* input, + void* output, + void* spectrum, + int width, + int height, + float lowCutoff, + float highCutoff, + int forwardPlan, + int inversePlan); +} diff --git a/ScopeOneCuda/include/scopeone/cuda/CudaRealImageModule.h b/ScopeOneCuda/include/scopeone/cuda/CudaRealImageModule.h new file mode 100644 index 0000000..96840e2 --- /dev/null +++ b/ScopeOneCuda/include/scopeone/cuda/CudaRealImageModule.h @@ -0,0 +1,29 @@ +#pragma once + +#include "scopeone/ProcessingPlugin.h" +#include "scopeone/cuda/CudaExport.h" +#include "scopeone/cuda/GpuRealFrame.h" + +namespace scopeone::cuda +{ + class SCOPEONE_CUDA_EXPORT CudaRealImageModule + : public scopeone::core::ProcessingModule + { + public: + explicit CudaRealImageModule(GpuMemoryLayout layout = GpuMemoryLayout::Pitched2D); + ~CudaRealImageModule() override; + + scopeone::core::ProcessingResult process( + const scopeone::core::ImageFrame& frame, + int processingBitDepth) final override; + + protected: + virtual bool processDevice(const GpuRealFrame& input, + GpuRealFrame& output, + int bitDepth) = 0; + + GpuRealFrame m_inputBuffer; + GpuRealFrame m_outputBuffer; + GpuMemoryLayout m_layout; + }; +} diff --git a/ScopeOneCuda/include/scopeone/cuda/GpuComplexFrame.h b/ScopeOneCuda/include/scopeone/cuda/GpuComplexFrame.h new file mode 100644 index 0000000..fc1c04f --- /dev/null +++ b/ScopeOneCuda/include/scopeone/cuda/GpuComplexFrame.h @@ -0,0 +1,36 @@ +#pragma once + +#include "scopeone/cuda/CudaExport.h" + +#include + +namespace scopeone::cuda +{ + class SCOPEONE_CUDA_EXPORT GpuComplexFrame + { + public: + GpuComplexFrame() = default; + ~GpuComplexFrame(); + + GpuComplexFrame(const GpuComplexFrame&) = delete; + GpuComplexFrame& operator=(const GpuComplexFrame&) = delete; + GpuComplexFrame(GpuComplexFrame&& other) noexcept; + GpuComplexFrame& operator=(GpuComplexFrame&& other) noexcept; + + bool allocateForRealImage(int width, int height); + void release(); + bool isValid() const; + + void* data() const; + int realWidth() const; + int spectrumWidth() const; + int height() const; + std::size_t elementCount() const; + + private: + void* m_data{nullptr}; + int m_realWidth{0}; + int m_spectrumWidth{0}; + int m_height{0}; + }; +} diff --git a/ScopeOneCuda/include/scopeone/cuda/GpuRealFrame.h b/ScopeOneCuda/include/scopeone/cuda/GpuRealFrame.h new file mode 100644 index 0000000..fefc115 --- /dev/null +++ b/ScopeOneCuda/include/scopeone/cuda/GpuRealFrame.h @@ -0,0 +1,55 @@ +#pragma once + +#include "scopeone/ImageFrame.h" +#include "scopeone/cuda/CudaExport.h" + +#include + +namespace scopeone::cuda +{ + enum class GpuMemoryLayout + { + Pitched2D, + Contiguous + }; + + class SCOPEONE_CUDA_EXPORT GpuRealFrame + { + public: + GpuRealFrame() = default; + GpuRealFrame(int width, int height, GpuMemoryLayout layout); + ~GpuRealFrame(); + + GpuRealFrame(const GpuRealFrame&) = delete; + GpuRealFrame& operator=(const GpuRealFrame&) = delete; + GpuRealFrame(GpuRealFrame&& other) noexcept; + GpuRealFrame& operator=(GpuRealFrame&& other) noexcept; + + bool allocate(int width, int height, GpuMemoryLayout layout); + void release(); + bool isValid() const; + + void* data() const; + std::size_t pitchBytes() const; + int width() const; + int height() const; + GpuMemoryLayout layout() const; + + bool upload(const scopeone::core::ImageFrame& frame); + bool download(scopeone::core::ImageFrame& frame) const; + + private: + bool ensureUploadBuffer(std::size_t bytes); + bool ensureDownloadBuffer(std::size_t bytes) const; + + float* m_data{nullptr}; + std::size_t m_pitchBytes{0}; + int m_width{0}; + int m_height{0}; + GpuMemoryLayout m_layout{GpuMemoryLayout::Pitched2D}; + unsigned char* m_uploadBuffer{nullptr}; + std::size_t m_uploadCapacity{0}; + mutable unsigned char* m_downloadBuffer{nullptr}; + mutable std::size_t m_downloadCapacity{0}; + }; +} diff --git a/ScopeOneCuda/src/CudaDevice.cpp b/ScopeOneCuda/src/CudaDevice.cpp new file mode 100644 index 0000000..a8874a3 --- /dev/null +++ b/ScopeOneCuda/src/CudaDevice.cpp @@ -0,0 +1,25 @@ +#include "scopeone/cuda/CudaDevice.h" + +#include + +namespace scopeone::cuda +{ + bool isCudaDeviceAvailable() + { + return cudaDeviceCount() > 0; + } + + int cudaDeviceCount() + { + int count = 0; + return cudaGetDeviceCount(&count) == cudaSuccess ? count : 0; + } + + QString cudaDeviceName(int deviceIndex) + { + cudaDeviceProp properties{}; + return cudaGetDeviceProperties(&properties, deviceIndex) == cudaSuccess + ? QString::fromUtf8(properties.name) + : QString(); + } +} diff --git a/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.cpp b/ScopeOneCuda/src/CudaFrequencyFilterModule.cpp similarity index 91% rename from plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.cpp rename to ScopeOneCuda/src/CudaFrequencyFilterModule.cpp index 43a75be..d3ce86d 100644 --- a/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.cpp +++ b/ScopeOneCuda/src/CudaFrequencyFilterModule.cpp @@ -1,11 +1,11 @@ #include "CudaFrequencyFilterModule.h" -#include "scopeone/gpu/CudaKernelLaunch.h" +#include "scopeone/cuda/CudaKernelLaunch.h" namespace scopeone::cuda_plugin { CudaFrequencyFilterModule::CudaFrequencyFilterModule() - : CudaRealImageModule(scopeone::gpu::GpuMemoryLayout::Contiguous) + : CudaRealImageModule(scopeone::cuda::GpuMemoryLayout::Contiguous) { } @@ -74,8 +74,8 @@ namespace scopeone::cuda_plugin } bool CudaFrequencyFilterModule::processDevice( - const scopeone::gpu::GpuRealFrame& input, - scopeone::gpu::GpuRealFrame& output, + const scopeone::cuda::GpuRealFrame& input, + scopeone::cuda::GpuRealFrame& output, int) { if (m_planWidth != input.width() || m_planHeight != input.height()) @@ -96,7 +96,7 @@ namespace scopeone::cuda_plugin m_planWidth = input.width(); m_planHeight = input.height(); } - return scopeone::gpu::detail::launchFrequencyFilter(input.data(), + return scopeone::cuda::detail::launchFrequencyFilter(input.data(), output.data(), m_spectrum.data(), input.width(), diff --git a/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.h b/ScopeOneCuda/src/CudaFrequencyFilterModule.h similarity index 69% rename from plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.h rename to ScopeOneCuda/src/CudaFrequencyFilterModule.h index 0413e37..2914d3f 100644 --- a/plugins/processing/FrequencyFilter/CudaFrequencyFilterModule.h +++ b/ScopeOneCuda/src/CudaFrequencyFilterModule.h @@ -1,13 +1,13 @@ #pragma once -#include "scopeone/gpu/CudaRealImageModule.h" -#include "scopeone/gpu/GpuComplexFrame.h" +#include "scopeone/cuda/CudaRealImageModule.h" +#include "scopeone/cuda/GpuComplexFrame.h" #include namespace scopeone::cuda_plugin { - class CudaFrequencyFilterModule final : public scopeone::gpu::CudaRealImageModule + class CudaFrequencyFilterModule final : public scopeone::cuda::CudaRealImageModule { public: CudaFrequencyFilterModule(); @@ -21,14 +21,14 @@ namespace scopeone::cuda_plugin bool resetState() override; protected: - bool processDevice(const scopeone::gpu::GpuRealFrame& input, - scopeone::gpu::GpuRealFrame& output, + bool processDevice(const scopeone::cuda::GpuRealFrame& input, + scopeone::cuda::GpuRealFrame& output, int bitDepth) override; private: void destroyPlans(); - scopeone::gpu::GpuComplexFrame m_spectrum; + scopeone::cuda::GpuComplexFrame m_spectrum; cufftHandle m_forwardPlan{0}; cufftHandle m_inversePlan{0}; int m_planWidth{0}; diff --git a/plugins/processing/GaussianBlur/CudaGaussianBlurModule.cpp b/ScopeOneCuda/src/CudaGaussianBlurModule.cpp similarity index 83% rename from plugins/processing/GaussianBlur/CudaGaussianBlurModule.cpp rename to ScopeOneCuda/src/CudaGaussianBlurModule.cpp index f3c8e2e..738f06e 100644 --- a/plugins/processing/GaussianBlur/CudaGaussianBlurModule.cpp +++ b/ScopeOneCuda/src/CudaGaussianBlurModule.cpp @@ -1,11 +1,11 @@ #include "CudaGaussianBlurModule.h" -#include "scopeone/gpu/CudaKernelLaunch.h" +#include "scopeone/cuda/CudaKernelLaunch.h" namespace scopeone::cuda_plugin { CudaGaussianBlurModule::CudaGaussianBlurModule() - : CudaRealImageModule(scopeone::gpu::GpuMemoryLayout::Pitched2D) + : CudaRealImageModule(scopeone::cuda::GpuMemoryLayout::Pitched2D) { } @@ -41,11 +41,11 @@ namespace scopeone::cuda_plugin } bool CudaGaussianBlurModule::processDevice( - const scopeone::gpu::GpuRealFrame& input, - scopeone::gpu::GpuRealFrame& output, + const scopeone::cuda::GpuRealFrame& input, + scopeone::cuda::GpuRealFrame& output, int) { - return scopeone::gpu::detail::launchGaussian(input.data(), + return scopeone::cuda::detail::launchGaussian(input.data(), input.pitchBytes(), output.data(), output.pitchBytes(), diff --git a/plugins/processing/GaussianBlur/CudaGaussianBlurModule.h b/ScopeOneCuda/src/CudaGaussianBlurModule.h similarity index 65% rename from plugins/processing/GaussianBlur/CudaGaussianBlurModule.h rename to ScopeOneCuda/src/CudaGaussianBlurModule.h index 5891090..1afe5b1 100644 --- a/plugins/processing/GaussianBlur/CudaGaussianBlurModule.h +++ b/ScopeOneCuda/src/CudaGaussianBlurModule.h @@ -1,10 +1,10 @@ #pragma once -#include "scopeone/gpu/CudaRealImageModule.h" +#include "scopeone/cuda/CudaRealImageModule.h" namespace scopeone::cuda_plugin { - class CudaGaussianBlurModule final : public scopeone::gpu::CudaRealImageModule + class CudaGaussianBlurModule final : public scopeone::cuda::CudaRealImageModule { public: CudaGaussianBlurModule(); @@ -16,8 +16,8 @@ namespace scopeone::cuda_plugin std::unique_ptr createRuntime() const override; protected: - bool processDevice(const scopeone::gpu::GpuRealFrame& input, - scopeone::gpu::GpuRealFrame& output, + bool processDevice(const scopeone::cuda::GpuRealFrame& input, + scopeone::cuda::GpuRealFrame& output, int bitDepth) override; private: diff --git a/ScopeOneCuda/src/CudaKernels.cu b/ScopeOneCuda/src/CudaKernels.cu new file mode 100644 index 0000000..7a209c2 --- /dev/null +++ b/ScopeOneCuda/src/CudaKernels.cu @@ -0,0 +1,273 @@ +#include "scopeone/cuda/CudaKernelLaunch.h" + +#include +#include + +#include +#include +#include + +namespace +{ + __global__ void convertToFloatKernel(const unsigned char* source, + int sourceStride, + int width, + int height, + int sourceBytesPerPixel, + float* destination, + std::size_t destinationPitchBytes) + { + const int x = blockIdx.x * blockDim.x + threadIdx.x; + const int y = blockIdx.y * blockDim.y + threadIdx.y; + if (x >= width || y >= height) + { + return; + } + + const unsigned char* sourceRow = source + static_cast(y) * sourceStride; + float* destinationRow = reinterpret_cast( + reinterpret_cast(destination) + + static_cast(y) * destinationPitchBytes); + if (sourceBytesPerPixel == 1) + { + destinationRow[x] = static_cast(sourceRow[x]); + } + else + { + destinationRow[x] = static_cast( + reinterpret_cast(sourceRow)[x]); + } + } + + __global__ void convertFromFloatKernel(const float* source, + std::size_t sourcePitchBytes, + unsigned char* destination, + int destinationStride, + int width, + int height, + int destinationBytesPerPixel) + { + const int x = blockIdx.x * blockDim.x + threadIdx.x; + const int y = blockIdx.y * blockDim.y + threadIdx.y; + if (x >= width || y >= height) + { + return; + } + + const float* sourceRow = reinterpret_cast( + reinterpret_cast(source) + + static_cast(y) * sourcePitchBytes); + unsigned char* destinationRow = destination + + static_cast(y) * destinationStride; + const float value = fminf(fmaxf(sourceRow[x], 0.0f), 65535.0f); + if (destinationBytesPerPixel == 1) + { + destinationRow[x] = static_cast(fminf(value, 255.0f) + 0.5f); + } + else + { + reinterpret_cast(destinationRow)[x] = + static_cast(value + 0.5f); + } + } + + __global__ void gaussianKernel(const float* input, + std::size_t inputPitchBytes, + float* output, + std::size_t outputPitchBytes, + int width, + int height, + int radius, + float sigma) + { + const int x = blockIdx.x * blockDim.x + threadIdx.x; + const int y = blockIdx.y * blockDim.y + threadIdx.y; + if (x >= width || y >= height) + { + return; + } + + float sum = 0.0f; + float weightSum = 0.0f; + for (int dy = -radius; dy <= radius; ++dy) + { + const int sampleY = min(max(y + dy, 0), height - 1); + const float* inputRow = reinterpret_cast( + reinterpret_cast(input) + + static_cast(sampleY) * inputPitchBytes); + for (int dx = -radius; dx <= radius; ++dx) + { + const int sampleX = min(max(x + dx, 0), width - 1); + const float distanceSquared = static_cast(dx * dx + dy * dy); + const float weight = expf(-distanceSquared / (2.0f * sigma * sigma)); + sum += inputRow[sampleX] * weight; + weightSum += weight; + } + } + + float* outputRow = reinterpret_cast( + reinterpret_cast(output) + + static_cast(y) * outputPitchBytes); + outputRow[x] = sum / weightSum; + } + + __global__ void frequencyMaskKernel(float2* spectrum, + int spectrumWidth, + int height, + int realWidth, + float lowCutoff, + float highCutoff) + { + const int x = blockIdx.x * blockDim.x + threadIdx.x; + const int y = blockIdx.y * blockDim.y + threadIdx.y; + if (x >= spectrumWidth || y >= height) + { + return; + } + + const float fx = static_cast(x) / static_cast(realWidth); + const float fy = static_cast(y <= height / 2 ? y : y - height) + / static_cast(height); + const float radius = sqrtf(fx * fx + fy * fy); + const bool keep = radius >= lowCutoff && radius <= highCutoff; + if (!keep) + { + spectrum[y * spectrumWidth + x] = make_float2(0.0f, 0.0f); + } + } + + __global__ void normalizeKernel(float* output, int width, int height, float scale) + { + const int x = blockIdx.x * blockDim.x + threadIdx.x; + const int y = blockIdx.y * blockDim.y + threadIdx.y; + if (x < width && y < height) + { + output[y * width + x] *= scale; + } + } + + bool synchronizeKernel() + { + return cudaGetLastError() == cudaSuccess && cudaDeviceSynchronize() == cudaSuccess; + } +} + +namespace scopeone::cuda::detail +{ + bool convertToFloat(const void* source, + int sourceStride, + int width, + int height, + int sourceBytesPerPixel, + void* destination, + std::size_t destinationPitchBytes) + { + const dim3 block(16, 16); + const dim3 grid((width + block.x - 1) / block.x, + (height + block.y - 1) / block.y); + convertToFloatKernel<<>>( + static_cast(source), + sourceStride, + width, + height, + sourceBytesPerPixel, + static_cast(destination), + destinationPitchBytes); + return synchronizeKernel(); + } + + bool convertFromFloat(const void* source, + std::size_t sourcePitchBytes, + void* destination, + int destinationStride, + int width, + int height, + int destinationBytesPerPixel) + { + const dim3 block(16, 16); + const dim3 grid((width + block.x - 1) / block.x, + (height + block.y - 1) / block.y); + convertFromFloatKernel<<>>( + static_cast(source), + sourcePitchBytes, + static_cast(destination), + destinationStride, + width, + height, + destinationBytesPerPixel); + return synchronizeKernel(); + } + + bool launchGaussian(const void* input, + std::size_t inputPitchBytes, + void* output, + std::size_t outputPitchBytes, + int width, + int height, + float sigma) + { + const int radius = std::max(1, static_cast(std::ceil(3.0f * sigma))); + const dim3 block(16, 16); + const dim3 grid((width + block.x - 1) / block.x, + (height + block.y - 1) / block.y); + gaussianKernel<<>>( + static_cast(input), + inputPitchBytes, + static_cast(output), + outputPitchBytes, + width, + height, + radius, + sigma); + return synchronizeKernel(); + } + + bool launchFrequencyFilter(const void* input, + void* output, + void* spectrum, + int width, + int height, + float lowCutoff, + float highCutoff, + int forwardPlan, + int inversePlan) + { + if (cufftExecR2C(forwardPlan, + static_cast(const_cast(input)), + static_cast(spectrum)) != CUFFT_SUCCESS) + { + return false; + } + + const int spectrumWidth = width / 2 + 1; + const dim3 block(16, 16); + const dim3 grid((spectrumWidth + block.x - 1) / block.x, + (height + block.y - 1) / block.y); + frequencyMaskKernel<<>>( + static_cast(spectrum), + spectrumWidth, + height, + width, + lowCutoff, + highCutoff); + if (!synchronizeKernel()) + { + return false; + } + + if (cufftExecC2R(inversePlan, + static_cast(spectrum), + static_cast(output)) != CUFFT_SUCCESS) + { + return false; + } + const dim3 normalizeGrid((width + block.x - 1) / block.x, + (height + block.y - 1) / block.y); + normalizeKernel<<>>( + static_cast(output), + width, + height, + 1.0f / static_cast(width * height)); + return synchronizeKernel(); + } +} diff --git a/ScopeOneCuda/src/CudaProcessingRegistration.cpp b/ScopeOneCuda/src/CudaProcessingRegistration.cpp new file mode 100644 index 0000000..0b7bd93 --- /dev/null +++ b/ScopeOneCuda/src/CudaProcessingRegistration.cpp @@ -0,0 +1,50 @@ +#include "CudaFrequencyFilterModule.h" +#include "CudaGaussianBlurModule.h" + +#include "scopeone/ScopeOneCore.h" + +extern "C" SCOPEONE_CUDA_EXPORT void scopeone_register_processing_modules( + scopeone::core::ScopeOneCore* core) +{ + core->registerProcessingModule( + {QStringLiteral("cuda.gaussian_blur"), + QStringLiteral("CUDA Gaussian Blur"), + 1, + {{QStringLiteral("sigma"), + QStringLiteral("Sigma"), + scopeone::core::ProcessingParameterType::Real, + 1.5, + 0.1, + 20.0, + 0.1, + 2}}}, + []() + { + return std::make_unique(); + }); + + core->registerProcessingModule( + {QStringLiteral("cuda.frequency_filter"), + QStringLiteral("CUDA Frequency Filter"), + 1, + {{QStringLiteral("low_cutoff"), + QStringLiteral("Low Cutoff"), + scopeone::core::ProcessingParameterType::Real, + 0.01, + 0.0, + 0.5, + 0.01, + 3}, + {QStringLiteral("high_cutoff"), + QStringLiteral("High Cutoff"), + scopeone::core::ProcessingParameterType::Real, + 0.25, + 0.0, + 0.5, + 0.01, + 3}}}, + []() + { + return std::make_unique(); + }); +} diff --git a/ScopeOneCuda/src/CudaRealImageModule.cpp b/ScopeOneCuda/src/CudaRealImageModule.cpp new file mode 100644 index 0000000..cb26a0d --- /dev/null +++ b/ScopeOneCuda/src/CudaRealImageModule.cpp @@ -0,0 +1,74 @@ +#include "scopeone/cuda/CudaRealImageModule.h" + +#include "scopeone/cuda/CudaDevice.h" + +#include + +namespace scopeone::cuda +{ + CudaRealImageModule::CudaRealImageModule(GpuMemoryLayout layout) + : m_layout(layout) + { + } + + CudaRealImageModule::~CudaRealImageModule() = default; + + scopeone::core::ProcessingResult CudaRealImageModule::process( + const scopeone::core::ImageFrame& frame, + int processingBitDepth) + { + if (!frame.isValid()) + { + return {scopeone::core::ImageFrame{}, QStringLiteral("Invalid input frame")}; + } + if (!isCudaDeviceAvailable()) + { + return {scopeone::core::ImageFrame{}, QStringLiteral("CUDA device unavailable")}; + } + if (!m_inputBuffer.isValid() + || m_inputBuffer.width() != frame.width + || m_inputBuffer.height() != frame.height + || m_inputBuffer.layout() != m_layout) + { + if (!m_inputBuffer.allocate(frame.width, frame.height, m_layout) + || !m_outputBuffer.allocate(frame.width, frame.height, m_layout)) + { + return {scopeone::core::ImageFrame{}, + QStringLiteral("CUDA frame allocation failed")}; + } + } + if (!m_inputBuffer.upload(frame)) + { + return {scopeone::core::ImageFrame{}, + QStringLiteral("CUDA input upload failed")}; + } + if (!processDevice(m_inputBuffer, m_outputBuffer, processingBitDepth)) + { + return {scopeone::core::ImageFrame{}, + QStringLiteral("CUDA processing failed")}; + } + + scopeone::core::ImageFrame output; + output.cameraId = frame.cameraId; + output.width = frame.width; + output.height = frame.height; + output.pixelFormat = processingBitDepth >= 16 + ? scopeone::core::ImagePixelFormat::Mono16 + : scopeone::core::ImagePixelFormat::Mono8; + output.bitsPerSample = processingBitDepth >= 16 ? 16 : 8; + output.stride = output.width * output.bytesPerPixel(); + output.frameIndex = frame.frameIndex; + output.timestampNs = frame.timestampNs; + output.sourceRoiX = frame.sourceRoiX; + output.sourceRoiY = frame.sourceRoiY; + output.sourceRoiWidth = frame.sourceRoiWidth; + output.sourceRoiHeight = frame.sourceRoiHeight; + output.bytes.resize(static_cast(output.payloadByteCount())); + if (!m_outputBuffer.download(output)) + { + return {scopeone::core::ImageFrame{}, + QStringLiteral("CUDA output download failed")}; + } + return {std::move(output), {}}; + } +} diff --git a/ScopeOneCuda/src/GpuComplexFrame.cpp b/ScopeOneCuda/src/GpuComplexFrame.cpp new file mode 100644 index 0000000..847d3d7 --- /dev/null +++ b/ScopeOneCuda/src/GpuComplexFrame.cpp @@ -0,0 +1,102 @@ +#include "scopeone/cuda/GpuComplexFrame.h" + +#include + +#include + +namespace scopeone::cuda +{ + GpuComplexFrame::~GpuComplexFrame() + { + release(); + } + + GpuComplexFrame::GpuComplexFrame(GpuComplexFrame&& other) noexcept + : m_data(std::exchange(other.m_data, nullptr)) + , m_realWidth(std::exchange(other.m_realWidth, 0)) + , m_spectrumWidth(std::exchange(other.m_spectrumWidth, 0)) + , m_height(std::exchange(other.m_height, 0)) + { + } + + GpuComplexFrame& GpuComplexFrame::operator=(GpuComplexFrame&& other) noexcept + { + if (this != &other) + { + release(); + m_data = std::exchange(other.m_data, nullptr); + m_realWidth = std::exchange(other.m_realWidth, 0); + m_spectrumWidth = std::exchange(other.m_spectrumWidth, 0); + m_height = std::exchange(other.m_height, 0); + } + return *this; + } + + bool GpuComplexFrame::allocateForRealImage(int width, int height) + { + if (isValid() && m_realWidth == width && m_height == height) + { + return true; + } + + release(); + m_realWidth = width; + m_spectrumWidth = width / 2 + 1; + m_height = height; + const std::size_t bytes = static_cast(m_spectrumWidth) + * static_cast(m_height) + * sizeof(float2); + if (cudaMalloc(&m_data, bytes) != cudaSuccess) + { + release(); + return false; + } + return true; + } + + void GpuComplexFrame::release() + { + if (m_data) + { + cudaFree(m_data); + } + m_data = nullptr; + m_realWidth = 0; + m_spectrumWidth = 0; + m_height = 0; + } + + bool GpuComplexFrame::isValid() const + { + return m_data != nullptr + && m_realWidth > 0 + && m_spectrumWidth > 0 + && m_height > 0; + } + + void* GpuComplexFrame::data() const + { + return m_data; + } + + int GpuComplexFrame::realWidth() const + { + return m_realWidth; + } + + int GpuComplexFrame::spectrumWidth() const + { + return m_spectrumWidth; + } + + int GpuComplexFrame::height() const + { + return m_height; + } + + std::size_t GpuComplexFrame::elementCount() const + { + return static_cast(m_spectrumWidth) + * static_cast(m_height); + } +} diff --git a/ScopeOneCuda/src/GpuRealFrame.cpp b/ScopeOneCuda/src/GpuRealFrame.cpp new file mode 100644 index 0000000..6e833ec --- /dev/null +++ b/ScopeOneCuda/src/GpuRealFrame.cpp @@ -0,0 +1,251 @@ +#include "scopeone/cuda/GpuRealFrame.h" + +#include "scopeone/cuda/CudaKernelLaunch.h" + +#include + +#include + +namespace scopeone::cuda +{ + GpuRealFrame::GpuRealFrame(int width, int height, GpuMemoryLayout layout) + { + allocate(width, height, layout); + } + + GpuRealFrame::~GpuRealFrame() + { + release(); + } + + GpuRealFrame::GpuRealFrame(GpuRealFrame&& other) noexcept + : m_data(std::exchange(other.m_data, nullptr)) + , m_pitchBytes(std::exchange(other.m_pitchBytes, 0)) + , m_width(std::exchange(other.m_width, 0)) + , m_height(std::exchange(other.m_height, 0)) + , m_layout(other.m_layout) + , m_uploadBuffer(std::exchange(other.m_uploadBuffer, nullptr)) + , m_uploadCapacity(std::exchange(other.m_uploadCapacity, 0)) + , m_downloadBuffer(std::exchange(other.m_downloadBuffer, nullptr)) + , m_downloadCapacity(std::exchange(other.m_downloadCapacity, 0)) + { + } + + GpuRealFrame& GpuRealFrame::operator=(GpuRealFrame&& other) noexcept + { + if (this != &other) + { + release(); + m_data = std::exchange(other.m_data, nullptr); + m_pitchBytes = std::exchange(other.m_pitchBytes, 0); + m_width = std::exchange(other.m_width, 0); + m_height = std::exchange(other.m_height, 0); + m_layout = other.m_layout; + m_uploadBuffer = std::exchange(other.m_uploadBuffer, nullptr); + m_uploadCapacity = std::exchange(other.m_uploadCapacity, 0); + m_downloadBuffer = std::exchange(other.m_downloadBuffer, nullptr); + m_downloadCapacity = std::exchange(other.m_downloadCapacity, 0); + } + return *this; + } + + bool GpuRealFrame::allocate(int width, int height, GpuMemoryLayout layout) + { + if (isValid() && m_width == width && m_height == height && m_layout == layout) + { + return true; + } + + release(); + m_width = width; + m_height = height; + m_layout = layout; + if (layout == GpuMemoryLayout::Pitched2D) + { + if (cudaMallocPitch(reinterpret_cast(&m_data), + &m_pitchBytes, + static_cast(width) * sizeof(float), + height) != cudaSuccess) + { + release(); + return false; + } + } + else + { + m_pitchBytes = static_cast(width) * sizeof(float); + if (cudaMalloc(reinterpret_cast(&m_data), + m_pitchBytes * static_cast(height)) != cudaSuccess) + { + release(); + return false; + } + } + return true; + } + + void GpuRealFrame::release() + { + if (m_data) + { + cudaFree(m_data); + } + if (m_uploadBuffer) + { + cudaFree(m_uploadBuffer); + } + if (m_downloadBuffer) + { + cudaFree(m_downloadBuffer); + } + m_data = nullptr; + m_pitchBytes = 0; + m_width = 0; + m_height = 0; + m_uploadBuffer = nullptr; + m_uploadCapacity = 0; + m_downloadBuffer = nullptr; + m_downloadCapacity = 0; + } + + bool GpuRealFrame::isValid() const + { + return m_data != nullptr && m_width > 0 && m_height > 0 && m_pitchBytes > 0; + } + + void* GpuRealFrame::data() const + { + return m_data; + } + + std::size_t GpuRealFrame::pitchBytes() const + { + return m_pitchBytes; + } + + int GpuRealFrame::width() const + { + return m_width; + } + + int GpuRealFrame::height() const + { + return m_height; + } + + GpuMemoryLayout GpuRealFrame::layout() const + { + return m_layout; + } + + bool GpuRealFrame::ensureUploadBuffer(std::size_t bytes) + { + if (m_uploadCapacity >= bytes) + { + return true; + } + if (m_uploadBuffer) + { + cudaFree(m_uploadBuffer); + } + m_uploadBuffer = nullptr; + m_uploadCapacity = 0; + if (cudaMalloc(reinterpret_cast(&m_uploadBuffer), bytes) != cudaSuccess) + { + return false; + } + m_uploadCapacity = bytes; + return true; + } + + bool GpuRealFrame::ensureDownloadBuffer(std::size_t bytes) const + { + if (m_downloadCapacity >= bytes) + { + return true; + } + if (m_downloadBuffer) + { + cudaFree(m_downloadBuffer); + } + m_downloadBuffer = nullptr; + m_downloadCapacity = 0; + if (cudaMalloc(reinterpret_cast(&m_downloadBuffer), bytes) != cudaSuccess) + { + return false; + } + m_downloadCapacity = bytes; + return true; + } + + bool GpuRealFrame::upload(const scopeone::core::ImageFrame& frame) + { + if (!frame.isValid()) + { + return false; + } + if (!isValid() + || m_width != frame.width + || m_height != frame.height) + { + if (!allocate(frame.width, frame.height, m_layout)) + { + return false; + } + } + if (!ensureUploadBuffer(static_cast(frame.payloadByteCount()))) + { + return false; + } + if (cudaMemcpy2D(m_uploadBuffer, + static_cast(frame.stride), + frame.bytes.constData(), + static_cast(frame.stride), + static_cast(frame.stride), + static_cast(frame.height), + cudaMemcpyHostToDevice) != cudaSuccess) + { + return false; + } + return detail::convertToFloat(m_uploadBuffer, + frame.stride, + frame.width, + frame.height, + frame.bytesPerPixel(), + m_data, + m_pitchBytes); + } + + bool GpuRealFrame::download(scopeone::core::ImageFrame& frame) const + { + if (!isValid() + || frame.width != m_width + || frame.height != m_height + || (frame.pixelFormat != scopeone::core::ImagePixelFormat::Mono8 + && frame.pixelFormat != scopeone::core::ImagePixelFormat::Mono16)) + { + return false; + } + if (!ensureDownloadBuffer(static_cast(frame.payloadByteCount()))) + { + return false; + } + if (!detail::convertFromFloat(m_data, + m_pitchBytes, + m_downloadBuffer, + frame.stride, + frame.width, + frame.height, + frame.bytesPerPixel())) + { + return false; + } + return cudaMemcpy2D(frame.bytes.data(), + static_cast(frame.stride), + m_downloadBuffer, + static_cast(frame.stride), + static_cast(frame.stride), + static_cast(frame.height), + cudaMemcpyDeviceToHost) == cudaSuccess; + } +} diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9c3d51b..60f9e7e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -32,6 +32,6 @@ if (SCOPEONE_CUDA_NVCC_EXECUTABLE) "CUDA toolkit selected by the compiler" FORCE) enable_language(CUDA) find_package(CUDAToolkit REQUIRED) - add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../ScopeOneGpuCuda ScopeOneGpuCuda) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../ScopeOneCuda ScopeOneCuda) endif () add_subdirectory(processing) diff --git a/plugins/processing/CMakeLists.txt b/plugins/processing/CMakeLists.txt index b042f9a..69c0988 100644 --- a/plugins/processing/CMakeLists.txt +++ b/plugins/processing/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(ExampleProcessing) -if (TARGET ScopeOneGpuCuda) - add_subdirectory(FrequencyFilter) - add_subdirectory(GaussianBlur) +if (TARGET ScopeOneCuda) + add_subdirectory(ExampleCudaProcessing) endif () diff --git a/plugins/processing/ExampleCudaProcessing/CMakeLists.txt b/plugins/processing/ExampleCudaProcessing/CMakeLists.txt new file mode 100644 index 0000000..6b8a356 --- /dev/null +++ b/plugins/processing/ExampleCudaProcessing/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(ScopeOneExampleCudaProcessing MODULE + ExampleCudaProcessingPlugin.cpp + ExampleCudaKernel.cu + plugin.json +) +target_link_libraries(ScopeOneExampleCudaProcessing PRIVATE + scopeone::Cuda + scopeone::PluginSDK + Qt6::Core + CUDA::cudart +) +set_target_properties(ScopeOneExampleCudaProcessing PROPERTIES + CUDA_ARCHITECTURES "${SCOPEONE_CUDA_ARCHITECTURES}" +) +set_target_properties(ScopeOneExampleCudaProcessing PROPERTIES OUTPUT_NAME "ExampleCudaProcessing") +scopeone_add_plugin(ScopeOneExampleCudaProcessing processing) diff --git a/plugins/processing/ExampleCudaProcessing/ExampleCudaKernel.cu b/plugins/processing/ExampleCudaProcessing/ExampleCudaKernel.cu new file mode 100644 index 0000000..873da03 --- /dev/null +++ b/plugins/processing/ExampleCudaProcessing/ExampleCudaKernel.cu @@ -0,0 +1,52 @@ +#include + +#include + +namespace +{ + __global__ void invertKernel(const float* input, + std::size_t inputPitchBytes, + float* output, + std::size_t outputPitchBytes, + int width, + int height) + { + const int x = blockIdx.x * blockDim.x + threadIdx.x; + const int y = blockIdx.y * blockDim.y + threadIdx.y; + if (x >= width || y >= height) + { + return; + } + + const float* inputRow = reinterpret_cast( + reinterpret_cast(input) + + static_cast(y) * inputPitchBytes); + float* outputRow = reinterpret_cast( + reinterpret_cast(output) + + static_cast(y) * outputPitchBytes); + outputRow[x] = 65535.0f - inputRow[x]; + } +} + +namespace example_cuda +{ + bool launchInvert(const void* input, + std::size_t inputPitchBytes, + void* output, + std::size_t outputPitchBytes, + int width, + int height) + { + const dim3 block(16, 16); + const dim3 grid((width + block.x - 1) / block.x, + (height + block.y - 1) / block.y); + invertKernel<<>>( + static_cast(input), + inputPitchBytes, + static_cast(output), + outputPitchBytes, + width, + height); + return cudaGetLastError() == cudaSuccess && cudaDeviceSynchronize() == cudaSuccess; + } +} diff --git a/plugins/processing/ExampleCudaProcessing/ExampleCudaProcessingPlugin.cpp b/plugins/processing/ExampleCudaProcessing/ExampleCudaProcessingPlugin.cpp new file mode 100644 index 0000000..acada43 --- /dev/null +++ b/plugins/processing/ExampleCudaProcessing/ExampleCudaProcessingPlugin.cpp @@ -0,0 +1,74 @@ +#include "scopeone/ProcessingPlugin.h" +#include "scopeone/cuda/CudaRealImageModule.h" + +#include + +namespace example_cuda +{ + bool launchInvert(const void* input, + std::size_t inputPitchBytes, + void* output, + std::size_t outputPitchBytes, + int width, + int height); +} + +namespace +{ + class InvertModule final : public scopeone::cuda::CudaRealImageModule + { + public: + InvertModule() + : CudaRealImageModule(scopeone::cuda::GpuMemoryLayout::Pitched2D) + { + } + + QString id() const override { return QStringLiteral("example.cuda_invert"); } + QString name() const override { return QStringLiteral("CUDA Example Invert"); } + QVariantMap parameters() const override { return {}; } + void setParameters(const QVariantMap&) override {} + + std::unique_ptr createRuntime() const override + { + return std::make_unique(); + } + + protected: + bool processDevice(const scopeone::cuda::GpuRealFrame& input, + scopeone::cuda::GpuRealFrame& output, + int) override + { + return example_cuda::launchInvert(input.data(), + input.pitchBytes(), + output.data(), + output.pitchBytes(), + input.width(), + input.height()); + } + }; + + class ExampleCudaProcessingPlugin final : public QObject, + public scopeone::core::ProcessingPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneProcessingPlugin_iid FILE "plugin.json") + Q_INTERFACES(scopeone::core::ProcessingPlugin) + + public: + QList processingModules() const override + { + return {{QStringLiteral("example.cuda_invert"), + QStringLiteral("CUDA Example Invert")}}; + } + + std::unique_ptr createProcessingModule( + const QString& moduleId) override + { + return moduleId == QStringLiteral("example.cuda_invert") + ? std::make_unique() + : nullptr; + } + }; +} + +#include "ExampleCudaProcessingPlugin.moc" diff --git a/plugins/processing/ExampleCudaProcessing/plugin.json b/plugins/processing/ExampleCudaProcessing/plugin.json new file mode 100644 index 0000000..8322427 --- /dev/null +++ b/plugins/processing/ExampleCudaProcessing/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "example.cuda_processing", + "name": "Example CUDA Processing", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "processing" +} diff --git a/plugins/processing/FrequencyFilter/CMakeLists.txt b/plugins/processing/FrequencyFilter/CMakeLists.txt deleted file mode 100644 index 0b28060..0000000 --- a/plugins/processing/FrequencyFilter/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_library(ScopeOneFrequencyFilter MODULE - CudaFrequencyFilterModule.cpp - CudaFrequencyFilterModule.h - FrequencyFilterPlugin.cpp - plugin.json -) -target_link_libraries(ScopeOneFrequencyFilter PRIVATE - scopeone::GpuCuda - scopeone::PluginSDK - Qt6::Core -) -set_target_properties(ScopeOneFrequencyFilter PROPERTIES OUTPUT_NAME "FrequencyFilterPlugin") -scopeone_add_plugin(ScopeOneFrequencyFilter processing) diff --git a/plugins/processing/FrequencyFilter/FrequencyFilterPlugin.cpp b/plugins/processing/FrequencyFilter/FrequencyFilterPlugin.cpp deleted file mode 100644 index b231aca..0000000 --- a/plugins/processing/FrequencyFilter/FrequencyFilterPlugin.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "CudaFrequencyFilterModule.h" - -#include - -namespace -{ - class FrequencyFilterPlugin final : public QObject, - public scopeone::core::ProcessingPlugin - { - Q_OBJECT - Q_PLUGIN_METADATA(IID ScopeOneProcessingPlugin_iid FILE "plugin.json") - Q_INTERFACES(scopeone::core::ProcessingPlugin) - - public: - QList processingModules() const override - { - scopeone::core::ProcessingModuleDescriptor descriptor; - descriptor.id = QStringLiteral("cuda.frequency_filter"); - descriptor.name = QStringLiteral("CUDA Frequency Filter"); - - scopeone::core::ProcessingParameterDescriptor low; - low.key = QStringLiteral("low_cutoff"); - low.name = QStringLiteral("Low Cutoff"); - low.type = scopeone::core::ProcessingParameterType::Real; - low.defaultValue = 0.01; - low.minimum = 0.0; - low.maximum = 0.5; - low.step = 0.01; - low.decimals = 3; - descriptor.parameters.append(low); - - scopeone::core::ProcessingParameterDescriptor high; - high.key = QStringLiteral("high_cutoff"); - high.name = QStringLiteral("High Cutoff"); - high.type = scopeone::core::ProcessingParameterType::Real; - high.defaultValue = 0.25; - high.minimum = 0.0; - high.maximum = 0.5; - high.step = 0.01; - high.decimals = 3; - descriptor.parameters.append(high); - - return {descriptor}; - } - - std::unique_ptr createProcessingModule( - const QString& moduleId) override - { - return moduleId == QStringLiteral("cuda.frequency_filter") - ? std::make_unique() - : nullptr; - } - }; -} - -#include "FrequencyFilterPlugin.moc" diff --git a/plugins/processing/FrequencyFilter/plugin.json b/plugins/processing/FrequencyFilter/plugin.json deleted file mode 100644 index d90a864..0000000 --- a/plugins/processing/FrequencyFilter/plugin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "scopeone.frequency_filter", - "name": "Frequency Filter", - "version": "1.0.0", - "scopeOneApi": 1, - "kind": "processing" -} diff --git a/plugins/processing/GaussianBlur/CMakeLists.txt b/plugins/processing/GaussianBlur/CMakeLists.txt deleted file mode 100644 index 4cd67e7..0000000 --- a/plugins/processing/GaussianBlur/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_library(ScopeOneGaussianBlur MODULE - CudaGaussianBlurModule.cpp - CudaGaussianBlurModule.h - GaussianBlurPlugin.cpp - plugin.json -) -target_link_libraries(ScopeOneGaussianBlur PRIVATE - scopeone::GpuCuda - scopeone::PluginSDK - Qt6::Core -) -set_target_properties(ScopeOneGaussianBlur PROPERTIES OUTPUT_NAME "GaussianBlurPlugin") -scopeone_add_plugin(ScopeOneGaussianBlur processing) diff --git a/plugins/processing/GaussianBlur/GaussianBlurPlugin.cpp b/plugins/processing/GaussianBlur/GaussianBlurPlugin.cpp deleted file mode 100644 index af9e067..0000000 --- a/plugins/processing/GaussianBlur/GaussianBlurPlugin.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "CudaGaussianBlurModule.h" - -#include - -namespace -{ - class GaussianBlurPlugin final : public QObject, - public scopeone::core::ProcessingPlugin - { - Q_OBJECT - Q_PLUGIN_METADATA(IID ScopeOneProcessingPlugin_iid FILE "plugin.json") - Q_INTERFACES(scopeone::core::ProcessingPlugin) - - public: - QList processingModules() const override - { - scopeone::core::ProcessingModuleDescriptor descriptor; - descriptor.id = QStringLiteral("cuda.gaussian_blur"); - descriptor.name = QStringLiteral("CUDA Gaussian Blur"); - - scopeone::core::ProcessingParameterDescriptor sigma; - sigma.key = QStringLiteral("sigma"); - sigma.name = QStringLiteral("Sigma"); - sigma.type = scopeone::core::ProcessingParameterType::Real; - sigma.defaultValue = 1.5; - sigma.minimum = 0.1; - sigma.maximum = 20.0; - sigma.step = 0.1; - sigma.decimals = 2; - descriptor.parameters.append(sigma); - - return {descriptor}; - } - - std::unique_ptr createProcessingModule( - const QString& moduleId) override - { - return moduleId == QStringLiteral("cuda.gaussian_blur") - ? std::make_unique() - : nullptr; - } - }; -} - -#include "GaussianBlurPlugin.moc" diff --git a/plugins/processing/GaussianBlur/plugin.json b/plugins/processing/GaussianBlur/plugin.json deleted file mode 100644 index bf9d1e3..0000000 --- a/plugins/processing/GaussianBlur/plugin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "scopeone.gaussian_blur", - "name": "Gaussian Blur", - "version": "1.0.0", - "scopeOneApi": 1, - "kind": "processing" -} From 915b3093979e13f957f902e15aac8b2b1ac8a91e Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Sun, 6 Sep 2026 14:15:30 +0200 Subject: [PATCH 29/37] Add independent 3D layer views and layer management --- src/DeviceControlWidget.cpp | 83 ++++++++++++++- src/DeviceControlWidget.h | 5 + src/PreviewWidget.cpp | 195 +++++++++++++++++++++++++++++++----- src/PreviewWidget.h | 21 +++- 4 files changed, 271 insertions(+), 33 deletions(-) diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 706a196..ae3abf7 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -5,6 +5,7 @@ #include "PreviewWidget.h" #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -650,7 +652,9 @@ namespace scopeone::ui m_layerTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); m_layerTable->setSelectionMode(QAbstractItemView::SingleSelection); m_layerTable->setSelectionBehavior(QAbstractItemView::SelectRows); - m_layerTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_layerTable->setEditTriggers(QAbstractItemView::DoubleClicked + | QAbstractItemView::EditKeyPressed); + m_layerTable->setContextMenuPolicy(Qt::CustomContextMenu); m_layerTable->setShowGrid(false); m_layerTable->setMinimumHeight(94); m_layerTable->setMaximumHeight(150); @@ -857,6 +861,10 @@ namespace scopeone::ui connect(m_layerTable, &QTableWidget::currentCellChanged, this, &DeviceControlWidget::onPreviewLayerSelectionChanged); + connect(m_layerTable, &QTableWidget::itemChanged, + this, &DeviceControlWidget::onPreviewLayerTableItemChanged); + connect(m_layerTable, &QTableWidget::customContextMenuRequested, + this, &DeviceControlWidget::showLayerContextMenu); connect(m_layerMoveUpButton, &QPushButton::clicked, this, &DeviceControlWidget::onPreviewLayerMoveUpClicked); connect(m_layerMoveDownButton, &QPushButton::clicked, @@ -951,7 +959,7 @@ namespace scopeone::ui m_layerTable->setCellWidget(row, 0, visibleCheckBox); QTableWidgetItem* nameItem = new QTableWidgetItem(m_previewWidget->layerName(layerKey)); - nameItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + nameItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); nameItem->setData(Qt::UserRole, layerKey); m_layerTable->setItem(row, 1, nameItem); @@ -1256,6 +1264,77 @@ namespace scopeone::ui scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey())); } + void DeviceControlWidget::onPreviewLayerDuplicateClicked() + { + const QString sourceLayerKey = currentLayerKey(); + const scopeone::core::ImageFrame sourceFrame = m_scopeonecore->graphFrame(sourceLayerKey); + scopeone::core::DocumentLayer sourceLayer; + m_sceneModel->findLayer(sourceLayerKey, sourceLayer); + + const QString sourceId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(sourceLayerKey); + int copyIndex = 1; + QString duplicateSourceId; + do + { + duplicateSourceId = QStringLiteral("copy:%1_%2").arg(sourceId).arg(copyIndex++); + } + while (m_sceneModel->hasSource(duplicateSourceId)); + + scopeone::core::ImageFrame duplicateFrame = sourceFrame; + duplicateFrame.cameraId = duplicateSourceId; + const QString duplicateName = QStringLiteral("%1 (Copy)") + .arg(sourceLayer.name.isEmpty() + ? m_previewWidget->layerName(sourceLayerKey) + : sourceLayer.name); + const scopeone::core::ImageFrame published = m_scopeonecore->publishStaticFrame( + duplicateSourceId, duplicateFrame, duplicateName); + const QString duplicateLayerKey = + scopeone::core::ScopeOneCore::staticLayerKey(published.cameraId); + + m_sceneModel->setLayerVisible(duplicateLayerKey, sourceLayer.display.visible); + m_sceneModel->setLayerOpacityPercent( + duplicateLayerKey, sourceLayer.display.opacityPercent); + m_sceneModel->setLayerGamma(duplicateLayerKey, sourceLayer.display.gamma); + m_sceneModel->setLayerColormap(duplicateLayerKey, sourceLayer.display.colormap); + m_sceneModel->setLayerBlending(duplicateLayerKey, sourceLayer.display.blending); + m_sceneModel->setLayerDisplayLevels(duplicateLayerKey, + sourceLayer.display.levelMin, + sourceLayer.display.levelMax, + sourceLayer.display.levelDomainMax); + m_workspace->setActiveLayerKey(duplicateLayerKey); + } + + void DeviceControlWidget::onPreviewLayerTableItemChanged(QTableWidgetItem* item) + { + if (item->column() == 1) + { + m_sceneModel->setLayerName(item->data(Qt::UserRole).toString(), item->text()); + } + } + + void DeviceControlWidget::showLayerContextMenu(const QPoint& pos) + { + const int row = m_layerTable->rowAt(pos.y()); + if (row < 0) + { + return; + } + + m_layerTable->setCurrentCell(row, 1); + QMenu menu(this); + QAction* renameAction = menu.addAction(QStringLiteral("Rename")); + QAction* duplicateAction = menu.addAction(QStringLiteral("Duplicate Layer")); + QAction* selectedAction = menu.exec(m_layerTable->viewport()->mapToGlobal(pos)); + if (selectedAction == renameAction) + { + m_layerTable->editItem(m_layerTable->item(row, 1)); + } + else if (selectedAction == duplicateAction) + { + onPreviewLayerDuplicateClicked(); + } + } + // Open file dialog and import image files as static layers void DeviceControlWidget::onPreviewLayerImportClicked() { diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index b0853e1..6c45110 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -21,10 +21,12 @@ class QGroupBox; class QLabel; class QLineEdit; class QPushButton; +class QPoint; class QScrollArea; class QSlider; class QSpinBox; class QTableWidget; +class QTableWidgetItem; namespace scopeone::ui { @@ -110,6 +112,9 @@ namespace scopeone::ui void onPreviewLayerMoveDownClicked(); void onPreviewLayerRemoveClicked(); void onPreviewLayerImportClicked(); + void onPreviewLayerDuplicateClicked(); + void onPreviewLayerTableItemChanged(QTableWidgetItem* item); + void showLayerContextMenu(const QPoint& pos); void onLayerHistogramReady(const QString& layerKey, const scopeone::core::ScopeOneCore::HistogramStats& stats); void refreshLayerHistogram(); diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index 7b0a8af..d8cb17d 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -989,6 +989,7 @@ namespace scopeone::ui m_viewDimensionMode = mode; m_surfaceOrbiting = false; m_surfacePanning = false; + m_surfaceLayerKey.clear(); unsetCursor(); emit viewDimensionModeChanged(m_viewDimensionMode); update(); @@ -1008,13 +1009,49 @@ namespace scopeone::ui void PreviewWidget::reset3dCamera() { - m_cameraPitch = 35.0f; - m_cameraYaw = 45.0f; - m_cameraDistance = 2.8f; - m_cameraPan = QVector2D(0.0f, 0.0f); + if (m_activeLayerKey.isEmpty()) + { + return; + } + Camera3dState& camera = cameraForLayer(m_activeLayerKey); + camera = Camera3dState{}; update(); } + PreviewWidget::Camera3dState& PreviewWidget::cameraForLayer(const QString& layerKey) + { + return m_layerCameras3d[layerKey]; + } + + const PreviewWidget::Camera3dState& PreviewWidget::cameraForLayer( + const QString& layerKey) const + { + return m_layerCameras3d.constFind(layerKey).value(); + } + + QString PreviewWidget::layerKeyAt3dPosition(const QPoint& widgetPos) const + { + QMap frameSources; + std::vector frameSourceRenderInfos; + std::vector renderItems; + buildRenderSnapshot(frameSources, frameSourceRenderInfos, renderItems); + for (const RenderItem& item : renderItems) + { + if (item.layerKey == m_activeLayerKey && item.area.contains(widgetPos)) + { + return item.layerKey; + } + } + for (const RenderItem& item : renderItems) + { + if (item.area.contains(widgetPos)) + { + return item.layerKey; + } + } + return {}; + } + void PreviewWidget::set3dWireframeEnabled(bool enabled) { if (m_wireframe3d == enabled) @@ -2047,8 +2084,10 @@ namespace scopeone::ui } } - // Draws the active image layer as a GPU-displaced surface - void PreviewWidget::draw3dSurface(const RenderItem& item) + // Draws one image layer as a GPU-displaced surface + void PreviewWidget::draw3dSurface(const RenderItem& item, + const Camera3dState& camera, + const QRect& targetArea) { const FrameSourceState& frameState = *item.info->frameState; const ImageFrame& frame = item.processed ? frameState.processedFrame : frameState.rawFrame; @@ -2061,13 +2100,14 @@ namespace scopeone::ui QMatrix4x4 projection; projection.perspective(45.0f, - static_cast(width()) / static_cast(height()), + static_cast(targetArea.width()) + / static_cast(targetArea.height()), 0.1f, 100.0f); QMatrix4x4 view; - view.translate(m_cameraPan.x(), m_cameraPan.y(), -m_cameraDistance); - view.rotate(m_cameraPitch, 1.0f, 0.0f, 0.0f); - view.rotate(m_cameraYaw, 0.0f, 0.0f, 1.0f); + view.translate(camera.pan.x(), camera.pan.y(), -camera.distance); + view.rotate(camera.pitch, 1.0f, 0.0f, 0.0f); + view.rotate(camera.yaw, 0.0f, 0.0f, 1.0f); m_prog3d.bind(); glActiveTexture(GL_TEXTURE0); @@ -2518,6 +2558,7 @@ namespace scopeone::ui return; } glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glDisable(GL_SCISSOR_TEST); applyViewportForRect(rect()); glDepthMask(GL_TRUE); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -2544,6 +2585,54 @@ namespace scopeone::ui m_placeholderLabel->hide(); if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional) { + if (m_layerLayoutMode == LayerLayoutMode::SideBySide) + { + glEnable(GL_SCISSOR_TEST); + for (const RenderItem& item : renderItems) + { + applyScissorForRect(item.area); + applyViewportForRect(item.area); + glClear(GL_DEPTH_BUFFER_BIT); + draw3dSurface(item, + cameraForLayer(item.layerKey), + item.area); + } + glDisable(GL_SCISSOR_TEST); + applyViewportForRect(rect()); + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing, true); + for (const RenderItem& item : renderItems) + { + const QRect panelRect = item.area.adjusted(8, 8, -8, -8) + .intersected(QRect(item.area.x() + 8, + item.area.y() + 8, + qMax(0, item.area.width() - 16), + 42)); + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 170)); + p.drawRoundedRect(panelRect, 5, 5); + p.setPen(Qt::white); + QFont font = p.font(); + font.setBold(true); + p.setFont(font); + p.drawText(panelRect.adjusted(8, 4, -8, -4), + Qt::AlignLeft | Qt::AlignVCenter, + layerName(item.layerKey)); + p.setPen(item.layerKey == m_activeLayerKey + ? QColor(255, 220, 80) + : QColor(220, 225, 230, 190)); + p.setBrush(Qt::NoBrush); + p.setPen(QPen(p.pen().color(), item.layerKey == m_activeLayerKey ? 2 : 1)); + p.drawRect(item.area.adjusted(1, 1, -2, -2)); + } + p.setPen(QColor(220, 225, 230, 190)); + p.drawText(QRect(12, height() - 28, width() - 24, 18), + Qt::AlignRight | Qt::AlignVCenter, + tr("Left drag: orbit Right drag: pan Wheel: zoom")); + return; + } + const RenderItem* surfaceItem = &renderItems.back(); for (const RenderItem& item : renderItems) { @@ -2556,7 +2645,9 @@ namespace scopeone::ui glDisable(GL_SCISSOR_TEST); applyViewportForRect(rect()); - draw3dSurface(*surfaceItem); + draw3dSurface(*surfaceItem, + cameraForLayer(surfaceItem->layerKey), + rect()); QPainter p(this); p.setRenderHint(QPainter::Antialiasing, true); @@ -3293,13 +3384,26 @@ namespace scopeone::ui emit mousePositionChanged(event->pos()); if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional) { + const QString layerKey = layerKeyAt3dPosition(event->pos()); + if (layerKey.isEmpty()) + { + event->accept(); + return; + } + if (m_activeLayerKey != layerKey) + { + setActiveLayerKey(layerKey); + emit layerClicked(layerKey); + } + m_surfaceLayerKey = layerKey; + Camera3dState& camera = cameraForLayer(layerKey); if (event->button() == Qt::LeftButton) { m_surfaceOrbiting = true; m_surfacePanning = false; m_surfaceDragStart = event->pos(); - m_surfaceStartPitch = m_cameraPitch; - m_surfaceStartYaw = m_cameraYaw; + m_surfaceStartPitch = camera.pitch; + m_surfaceStartYaw = camera.yaw; setCursor(Qt::ClosedHandCursor); event->accept(); return; @@ -3309,7 +3413,7 @@ namespace scopeone::ui m_surfacePanning = true; m_surfaceOrbiting = false; m_surfaceDragStart = event->pos(); - m_surfaceStartPan = m_cameraPan; + m_surfaceStartPan = camera.pan; setCursor(Qt::SizeAllCursor); event->accept(); return; @@ -3461,7 +3565,16 @@ namespace scopeone::ui if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional && event->button() == Qt::LeftButton) { - reset3dCamera(); + const QString layerKey = layerKeyAt3dPosition(event->pos()); + if (!layerKey.isEmpty()) + { + if (m_activeLayerKey != layerKey) + { + setActiveLayerKey(layerKey); + emit layerClicked(layerKey); + } + reset3dCamera(); + } event->accept(); return; } @@ -3503,20 +3616,35 @@ namespace scopeone::ui { if (m_surfaceOrbiting) { + Camera3dState& camera = cameraForLayer(m_surfaceLayerKey); const QPoint delta = event->pos() - m_surfaceDragStart; - m_cameraYaw = m_surfaceStartYaw + static_cast(delta.x()) * 0.5f; - m_cameraPitch = qBound(-85.0f, - m_surfaceStartPitch + static_cast(delta.y()) * 0.5f, - 85.0f); + camera.yaw = m_surfaceStartYaw + static_cast(delta.x()) * 0.5f; + camera.pitch = qBound(-85.0f, + m_surfaceStartPitch + static_cast(delta.y()) * 0.5f, + 85.0f); update(); return; } if (m_surfacePanning) { + Camera3dState& camera = cameraForLayer(m_surfaceLayerKey); const QPoint delta = event->pos() - m_surfaceDragStart; - m_cameraPan = m_surfaceStartPan - + QVector2D(static_cast(delta.x()) / static_cast(width()), - -static_cast(delta.y()) / static_cast(height())); + QMap frameSources; + std::vector frameSourceRenderInfos; + std::vector renderItems; + buildRenderSnapshot(frameSources, frameSourceRenderInfos, renderItems); + QRect area = rect(); + for (const RenderItem& item : renderItems) + { + if (item.layerKey == m_surfaceLayerKey) + { + area = item.area; + break; + } + } + camera.pan = m_surfaceStartPan + + QVector2D(static_cast(delta.x()) / static_cast(area.width()), + -static_cast(delta.y()) / static_cast(area.height())); update(); return; } @@ -3645,6 +3773,7 @@ namespace scopeone::ui if (m_surfaceOrbiting && event->button() == Qt::LeftButton) { m_surfaceOrbiting = false; + m_surfaceLayerKey.clear(); unsetCursor(); event->accept(); return; @@ -3652,6 +3781,7 @@ namespace scopeone::ui if (m_surfacePanning && event->button() == Qt::RightButton) { m_surfacePanning = false; + m_surfaceLayerKey.clear(); unsetCursor(); event->accept(); return; @@ -3890,16 +4020,29 @@ namespace scopeone::ui { if (m_viewDimensionMode == ViewDimensionMode::ThreeDimensional) { + const QPoint widgetPos = event->position().toPoint(); + const QString layerKey = layerKeyAt3dPosition(widgetPos); + if (layerKey.isEmpty()) + { + event->accept(); + return; + } + if (m_activeLayerKey != layerKey) + { + setActiveLayerKey(layerKey); + emit layerClicked(layerKey); + } + Camera3dState& camera = cameraForLayer(layerKey); const int deltaY = event->angleDelta().y(); if (deltaY != 0) { const int steps = (deltaY / 120 != 0) ? (deltaY / 120) : ((deltaY > 0) ? 1 : -1); - m_cameraDistance = qBound(0.8f, - m_cameraDistance * std::pow(0.88f, - static_cast(steps)), - 20.0f); + camera.distance = qBound(0.8f, + camera.distance * std::pow(0.88f, + static_cast(steps)), + 20.0f); } update(); event->accept(); diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index 843abc4..33cadfc 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -234,6 +234,14 @@ namespace scopeone::ui bool firstVisibleInArea{false}; }; + struct Camera3dState + { + float pitch{35.0f}; + float yaw{45.0f}; + float distance{2.8f}; + QVector2D pan{0.0f, 0.0f}; + }; + private: QStringList m_availableCameraIds; QSet m_staticSourceIds; @@ -272,10 +280,7 @@ namespace scopeone::ui GLint m_u3dColormap{-1}, m_u3dColormapLut{-1}, m_u3dShowClipping{-1}; GLint m_u3dUvScale{-1}, m_u3dUvOffset{-1}, m_u3dLightDirection{-1}; ViewDimensionMode m_viewDimensionMode{ViewDimensionMode::TwoDimensional}; - float m_cameraPitch{35.0f}; - float m_cameraYaw{45.0f}; - float m_cameraDistance{2.8f}; - QVector2D m_cameraPan{0.0f, 0.0f}; + QMap m_layerCameras3d; float m_zScale{1.0f}; bool m_wireframe3d{false}; bool m_scaleBarVisible{true}; @@ -325,6 +330,7 @@ namespace scopeone::ui bool m_surfaceOrbiting{false}; bool m_surfacePanning{false}; QPoint m_surfaceDragStart; + QString m_surfaceLayerKey; float m_surfaceStartPitch{35.0f}; float m_surfaceStartYaw{45.0f}; QVector2D m_surfaceStartPan{0.0f, 0.0f}; @@ -398,7 +404,9 @@ namespace scopeone::ui MarkupEditMode& outEditMode) const; void clearSelectedMarkups(); void drawRenderItem(const RenderItem& item); - void draw3dSurface(const RenderItem& item); + void draw3dSurface(const RenderItem& item, + const Camera3dState& camera, + const QRect& targetArea); void ensureGlPipeline(); GLuint ensureFrameTexture(const QString& textureKey, const scopeone::core::ImageFrame& frame, @@ -430,5 +438,8 @@ namespace scopeone::ui void cancelROIDrawing(); void cancelMeasurementLineDrawing(); void cancelCrossSectionDrawing(); + Camera3dState& cameraForLayer(const QString& layerKey); + const Camera3dState& cameraForLayer(const QString& layerKey) const; + QString layerKeyAt3dPosition(const QPoint& widgetPos) const; }; } From cfd1a1ac519d66670de2625625b6a19c87ad9ece Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Mon, 7 Sep 2026 22:38:21 +0200 Subject: [PATCH 30/37] Unify gallery layers in main viewer and constrain slice slider within grid cells --- ScopeOneCore/include/scopeone/ScopeOneCore.h | 8 + ScopeOneCore/src/ScopeOneCore.cpp | 308 ++++++++++--- plugins/processing/CMakeLists.txt | 1 + .../processing/IscatProcessing/CMakeLists.txt | 18 + .../IscatProcessing/IscatProcessingModule.cpp | 406 ++++++++++++++++++ .../IscatProcessing/IscatProcessingModule.h | 65 +++ .../IscatProcessing/IscatProcessingPlugin.cpp | 124 ++++++ .../processing/IscatProcessing/plugin.json | 7 + src/DeviceControlWidget.cpp | 7 +- src/MainWindow.cpp | 102 ++++- src/MainWindow.h | 2 + src/PreviewWidget.cpp | 158 +++++++ src/PreviewWidget.h | 14 + 13 files changed, 1143 insertions(+), 77 deletions(-) create mode 100644 plugins/processing/IscatProcessing/CMakeLists.txt create mode 100644 plugins/processing/IscatProcessing/IscatProcessingModule.cpp create mode 100644 plugins/processing/IscatProcessing/IscatProcessingModule.h create mode 100644 plugins/processing/IscatProcessing/IscatProcessingPlugin.cpp create mode 100644 plugins/processing/IscatProcessing/plugin.json diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 8447080..1f8dd7b 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -584,6 +584,11 @@ namespace scopeone::core 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()); @@ -847,6 +852,8 @@ namespace scopeone::core 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); private: struct Managers; @@ -1006,6 +1013,7 @@ namespace scopeone::core QString m_realTimeProcessingSource; QHash> m_processingRequestCancelTokens; quint64 m_nextProcessingRequestId{0}; + QHash> m_layerStacks; }; } diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index c39d3a3..b620304 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,67 @@ namespace { + std::atomic s_importCounter{1}; + + struct StaticImageImportTask + { + QString filePath; + QString displayName; + QString sourceId; + std::vector slices; + QString errorMessage; + }; + + scopeone::core::ImageFrame convertImportedImage( + const cv::Mat& image, + const QString& sourceId) + { + scopeone::core::ImageFrame frame; + cv::Mat gray; + if (image.channels() == 1) + { + gray = image; + } + else if (image.channels() == 4) + { + cv::cvtColor(image, gray, cv::COLOR_BGRA2GRAY); + } + else + { + cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); + } + + if (gray.depth() == CV_8U) + { + frame.bitsPerSample = 8; + frame.pixelFormat = scopeone::core::ImagePixelFormat::Mono8; + frame.stride = static_cast(gray.step); + frame.bytes = QByteArray(reinterpret_cast(gray.data), + static_cast(gray.total() * gray.elemSize())); + } + else + { + cv::Mat gray16; + if (gray.depth() == CV_16U) + { + gray16 = gray; + } + else + { + gray.convertTo(gray16, CV_16U); + } + frame.bitsPerSample = 16; + frame.pixelFormat = scopeone::core::ImagePixelFormat::Mono16; + frame.stride = static_cast(gray16.step); + frame.bytes = QByteArray(reinterpret_cast(gray16.data), + static_cast(gray16.total() * gray16.elemSize())); + } + frame.cameraId = sourceId; + frame.width = gray.cols; + frame.height = gray.rows; + return frame; + } + // Histogram bins are fixed to keep UI cost stable constexpr int kHistogramBinCount = 256; // Auto stretch ignores a small tail on each side @@ -2856,6 +2918,24 @@ namespace scopeone::core return storedFrame; } + int ScopeOneCore::layerSliceCount(const QString& layerKey) const + { + const QString sourceId = sourceIdFromLayerKey(layerKey.trimmed()); + return static_cast(m_layerStacks.value(sourceId).size()); + } + + bool ScopeOneCore::setLayerSliceIndex(const QString& layerKey, int sliceIndex) + { + const QString sourceId = sourceIdFromLayerKey(layerKey.trimmed()); + const auto stackIt = m_layerStacks.constFind(sourceId); + const std::vector& slices = stackIt.value(); + const ImageFrame& frame = slices.at(static_cast(sliceIndex)); + DocumentLayer layer; + m_imageSceneModel->findLayer(staticLayerKey(sourceId), layer); + publishStaticFrame(sourceId, frame, layer.name); + return true; + } + // Import an external image file as a static frame layer ImageFrame ScopeOneCore::importImageAsStaticLayer(const QString& filePath, QString* outLayerKey, @@ -2871,8 +2951,8 @@ namespace scopeone::core return {}; } - const cv::Mat image = cv::imread(cleanedPath.toStdString(), cv::IMREAD_UNCHANGED); - if (image.empty()) + std::vector images; + if (!cv::imreadmulti(cleanedPath.toStdString(), images, cv::IMREAD_UNCHANGED)) { if (errorMessage) { @@ -2881,74 +2961,204 @@ namespace scopeone::core return {}; } - ImageFrame frame; - frame.width = image.cols; - frame.height = image.rows; + const QFileInfo fileInfo(cleanedPath); + const QString sourceId = QStringLiteral("imported:%1_%2") + .arg(fileInfo.completeBaseName()) + .arg(s_importCounter.fetch_add(1)); - cv::Mat gray; - if (image.channels() == 1) + std::vector slices; + slices.reserve(images.size()); + for (const cv::Mat& image : images) { - gray = image; + slices.push_back(convertImportedImage(image, sourceId)); } - else if (image.channels() == 4) + + m_layerStacks.insert(sourceId, std::move(slices)); + const ImageFrame& frame = m_layerStacks[sourceId].front(); + + const ImageFrame published = publishStaticFrame(sourceId, frame, fileInfo.fileName()); + if (published.isValid()) { - cv::cvtColor(image, gray, cv::COLOR_BGRA2GRAY); + const QString layerKey = staticLayerKey(sourceId); + if (outLayerKey) + { + *outLayerKey = layerKey; + } + if (m_imageSceneModel) + { + m_imageSceneModel->setLayerVisible(layerKey, true); + } } - else + return published; + } + + void ScopeOneCore::importImageAsStaticLayerAsync(const QString& filePath) + { + const QString cleanedPath = QDir::cleanPath(filePath.trimmed()); + const QFileInfo fileInfo(cleanedPath); + if (cleanedPath.isEmpty() || !fileInfo.isFile()) { - cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); + emit staticImageImportFinished( + filePath, + {}, + false, + QStringLiteral("File does not exist: %1").arg(filePath)); + return; } - frame.width = gray.cols; - frame.height = gray.rows; + emit staticImageImportProgress( + filePath, + 0, + QStringLiteral("Reading %1...").arg(fileInfo.fileName())); - if (gray.depth() == CV_8U) + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, + this, [this, watcher]() + { + StaticImageImportTask task = watcher->result(); + if (!task.errorMessage.isEmpty()) + { + emit staticImageImportFinished( + task.filePath, + {}, + false, + task.errorMessage); + watcher->deleteLater(); + return; + } + + m_layerStacks.insert(task.sourceId, std::move(task.slices)); + const ImageFrame& frame = m_layerStacks[task.sourceId].front(); + const ImageFrame published = publishStaticFrame( + task.sourceId, + frame, + task.displayName); + const QString layerKey = staticLayerKey(task.sourceId); + emit staticImageImportFinished( + task.filePath, + layerKey, + published.isValid(), + published.isValid() + ? QString() + : QStringLiteral("Failed to publish image layer")); + watcher->deleteLater(); + }); + + watcher->setFuture(QtConcurrent::run( + m_hardwareThreadPool.get(), + [this, cleanedPath, filePath]() + { + StaticImageImportTask task; + task.filePath = filePath; + const QFileInfo fileInfo(cleanedPath); + task.displayName = fileInfo.fileName(); + task.sourceId = QStringLiteral("imported:%1_%2") + .arg(fileInfo.completeBaseName()) + .arg(s_importCounter.fetch_add(1)); + + auto reportProgress = [this, filePath](int percent, const QString& statusText) + { + QMetaObject::invokeMethod( + this, + [this, filePath, percent, statusText]() + { + emit staticImageImportProgress(filePath, percent, statusText); + }, + Qt::QueuedConnection); + }; + + std::vector images; + if (!cv::imreadmulti(cleanedPath.toStdString(), + images, + cv::IMREAD_UNCHANGED)) + { + task.errorMessage = QStringLiteral("Failed to read image file: %1") + .arg(filePath); + return task; + } + + task.slices.reserve(images.size()); + for (size_t index = 0; index < images.size(); ++index) + { + task.slices.push_back(convertImportedImage(images[index], task.sourceId)); + const int percent = static_cast( + ((index + 1) * 100) / images.size()); + reportProgress( + percent, + QStringLiteral("Converting slice %1 / %2") + .arg(static_cast(index + 1)) + .arg(static_cast(images.size()))); + } + return task; + })); + } + + // Imports a gallery recording session as a static layer + QString ScopeOneCore::importSessionAsStaticLayer( + const std::shared_ptr& session) + { + if (!session || !session->hasRecordedOutput()) { - frame.bitsPerSample = 8; - frame.pixelFormat = ImagePixelFormat::Mono8; - frame.stride = static_cast(gray.step); - frame.bytes = QByteArray(reinterpret_cast(gray.data), - static_cast(gray.total() * gray.elemSize())); + return {}; } - else + + const QString expId = session->capturePlan().experimentId.trimmed().isEmpty() + ? QString::number(s_importCounter.fetch_add(1)) + : session->capturePlan().experimentId.trimmed(); + const QStringList cameras = session->recordedCameraIds(); + QString lastLayerKey; + + for (const QString& camera : cameras) { - cv::Mat gray16; - if (gray.depth() == CV_16U) + const qint64 count = session->recordedFrameCount(camera); + if (count <= 0) { - gray16 = gray; + continue; } - else + + const QString sourceId = QStringLiteral("gallery:%1_%2").arg(expId, camera); + const QString layerKey = staticLayerKey(sourceId); + + DocumentLayer existingLayer; + if (m_imageSceneModel->findLayer(layerKey, existingLayer)) { - gray.convertTo(gray16, CV_16U); + m_imageSceneModel->setLayerVisible(layerKey, true); + lastLayerKey = layerKey; + continue; } - frame.bitsPerSample = 16; - frame.pixelFormat = ImagePixelFormat::Mono16; - frame.stride = static_cast(gray16.step); - frame.bytes = QByteArray(reinterpret_cast(gray16.data), - static_cast(gray16.total() * gray16.elemSize())); - } - - static std::atomic s_importCounter{1}; - const QFileInfo fileInfo(cleanedPath); - const QString sourceId = QStringLiteral("imported:%1_%2") - .arg(fileInfo.completeBaseName()) - .arg(s_importCounter.fetch_add(1)); - frame.cameraId = sourceId; - const ImageFrame published = publishStaticFrame(sourceId, frame, fileInfo.fileName()); - if (published.isValid()) - { - const QString layerKey = staticLayerKey(sourceId); - if (outLayerKey) + std::vector slices; + slices.reserve(static_cast(count)); + for (int i = 0; i < count; ++i) { - *outLayerKey = layerKey; + ImageFrame frame = session->imageFrameAt(camera, i); + if (frame.isValid()) + { + frame.cameraId = sourceId; + slices.push_back(std::move(frame)); + } } - if (m_imageSceneModel) + if (slices.empty()) { - m_imageSceneModel->setLayerVisible(layerKey, true); + continue; + } + + const QString baseName = session->capturePlan().baseName.trimmed().isEmpty() + ? QStringLiteral("snapshot") + : session->capturePlan().baseName.trimmed(); + const QString displayName = cameras.size() > 1 + ? QStringLiteral("%1 - %2").arg(baseName, camera) + : baseName; + + m_layerStacks.insert(sourceId, std::move(slices)); + const ImageFrame& frame = m_layerStacks[sourceId].front(); + const ImageFrame published = publishStaticFrame(sourceId, frame, displayName); + if (published.isValid()) + { + lastLayerKey = layerKey; } } - return published; + return lastLayerKey; } // Publish the latest frame of a tool-owned realtime stream @@ -2999,6 +3209,7 @@ namespace scopeone::core const QString layerKey = staticLayerKey(trimmedSourceId); m_frameGraph.remove(FrameGraphStream::Static, trimmedSourceId); + m_layerStacks.remove(trimmedSourceId); m_imageSceneModel->removeLayer(layerKey); clearLayerAnalysis(layerKey); if (m_activeLineProfile.active @@ -3014,6 +3225,7 @@ namespace scopeone::core void ScopeOneCore::clearStaticFrames() { m_frameGraph.clear(FrameGraphStream::Static); + m_layerStacks.clear(); for (const QString& layerId : m_imageSceneModel->layerIds()) { DocumentLayer layer; diff --git a/plugins/processing/CMakeLists.txt b/plugins/processing/CMakeLists.txt index 69c0988..5767d50 100644 --- a/plugins/processing/CMakeLists.txt +++ b/plugins/processing/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(ExampleProcessing) +add_subdirectory(IscatProcessing) if (TARGET ScopeOneCuda) add_subdirectory(ExampleCudaProcessing) endif () diff --git a/plugins/processing/IscatProcessing/CMakeLists.txt b/plugins/processing/IscatProcessing/CMakeLists.txt new file mode 100644 index 0000000..1a38a9a --- /dev/null +++ b/plugins/processing/IscatProcessing/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(ScopeOneIscatProcessing MODULE + IscatProcessingPlugin.cpp + IscatProcessingModule.cpp + IscatProcessingModule.h + plugin.json +) + +target_compile_features(ScopeOneIscatProcessing PRIVATE cxx_std_20) +target_link_libraries(ScopeOneIscatProcessing PRIVATE + scopeone::PluginSDK + opencv_core + opencv_imgproc + Qt6::Core +) +set_target_properties(ScopeOneIscatProcessing PROPERTIES + OUTPUT_NAME "IscatProcessingPlugin" +) +scopeone_add_plugin(ScopeOneIscatProcessing processing) diff --git a/plugins/processing/IscatProcessing/IscatProcessingModule.cpp b/plugins/processing/IscatProcessing/IscatProcessingModule.cpp new file mode 100644 index 0000000..ac709a1 --- /dev/null +++ b/plugins/processing/IscatProcessing/IscatProcessingModule.cpp @@ -0,0 +1,406 @@ +#include "IscatProcessingModule.h" + +#include +#include +#include +#include +#include +#include + +namespace scopeone::iscat +{ + namespace + { + scopeone::core::ImageFrame makeMono16Output( + const scopeone::core::ImageFrame& reference, + const cv::Mat& values) + { + QByteArray bytes(static_cast(values.cols) + * values.rows + * static_cast(sizeof(quint16)), + Qt::Uninitialized); + cv::Mat output(values.rows, + values.cols, + CV_16UC1, + bytes.data(), + static_cast(values.cols * sizeof(quint16))); + values.convertTo(output, CV_16U); + + scopeone::core::ImageFrame frame = reference; + frame.width = values.cols; + frame.height = values.rows; + frame.stride = values.cols * static_cast(sizeof(quint16)); + frame.bitsPerSample = 16; + frame.pixelFormat = scopeone::core::ImagePixelFormat::Mono16; + frame.bytes = std::move(bytes); + return frame; + } + } + + QString IscatProcessingModule::id() const + { + return QStringLiteral("iscat.processing"); + } + + QString IscatProcessingModule::name() const + { + return QStringLiteral("iSCAT Processing"); + } + + QVariantMap IscatProcessingModule::parameters() const + { + return {{QStringLiteral("mode"), m_mode}, + {QStringLiteral("blur_sigma"), m_blurSigma}, + {QStringLiteral("median_window"), m_medianWindow}, + {QStringLiteral("ema_alpha"), m_emaAlpha}, + {QStringLiteral("capture_reference"), m_captureReference}, + {QStringLiteral("batch_size"), m_batchSize}, + {QStringLiteral("contrast_gain"), m_contrastGain}, + {QStringLiteral("enable_high_pass"), m_enableHighPass}, + {QStringLiteral("high_pass_sigma"), m_highPassSigma}, + {QStringLiteral("output_mode"), m_outputMode}}; + } + + void IscatProcessingModule::setParameters(const QVariantMap& parameters) + { + const int previousMode = m_mode; + const double previousBlurSigma = m_blurSigma; + const int previousMedianWindow = m_medianWindow; + const int previousBatchSize = m_batchSize; + + if (parameters.contains(QStringLiteral("mode"))) + { + m_mode = qBound(0, parameters.value(QStringLiteral("mode")).toInt(), 4); + } + if (parameters.contains(QStringLiteral("blur_sigma"))) + { + m_blurSigma = qBound(1.0, + parameters.value(QStringLiteral("blur_sigma")).toDouble(), + 100.0); + } + if (parameters.contains(QStringLiteral("median_window"))) + { + m_medianWindow = qBound(3, + parameters.value(QStringLiteral("median_window")).toInt(), + 101); + if ((m_medianWindow % 2) == 0) + { + ++m_medianWindow; + } + } + if (parameters.contains(QStringLiteral("ema_alpha"))) + { + m_emaAlpha = qBound(0.001, + parameters.value(QStringLiteral("ema_alpha")).toDouble(), + 0.5); + } + if (parameters.contains(QStringLiteral("capture_reference"))) + { + m_captureReference = parameters.value(QStringLiteral("capture_reference")).toBool(); + } + if (parameters.contains(QStringLiteral("batch_size"))) + { + m_batchSize = qBound(1, + parameters.value(QStringLiteral("batch_size")).toInt(), + 500); + } + if (parameters.contains(QStringLiteral("contrast_gain"))) + { + m_contrastGain = qBound(1.0, + parameters.value(QStringLiteral("contrast_gain")).toDouble(), + 500.0); + } + if (parameters.contains(QStringLiteral("enable_high_pass"))) + { + m_enableHighPass = parameters.value(QStringLiteral("enable_high_pass")).toBool(); + } + if (parameters.contains(QStringLiteral("high_pass_sigma"))) + { + m_highPassSigma = qBound(2.0, + parameters.value(QStringLiteral("high_pass_sigma")).toDouble(), + 200.0); + } + if (parameters.contains(QStringLiteral("output_mode"))) + { + m_outputMode = qBound(0, + parameters.value(QStringLiteral("output_mode")).toInt(), + 2); + } + + if (previousMode != m_mode + || previousBlurSigma != m_blurSigma + || previousMedianWindow != m_medianWindow + || previousBatchSize != m_batchSize) + { + clearState(); + } + } + + std::unique_ptr IscatProcessingModule::createRuntime() const + { + auto module = std::make_unique(); + module->setParameters(parameters()); + return module; + } + + bool IscatProcessingModule::resetState() + { + clearState(); + return true; + } + + void IscatProcessingModule::clearState() + { + m_medianBuffer.clear(); + m_lockedMedianBackground.release(); + m_emaAccumulator.release(); + m_snapshotBackground.release(); + m_draBatchA.clear(); + m_draBatchB.clear(); + m_draSumA.release(); + m_draSumB.release(); + } + + cv::Mat IscatProcessingModule::computePixelMedian(const std::deque& frames) const + { + cv::Mat median(frames.front().size(), CV_32F); + cv::parallel_for_(cv::Range(0, median.rows), + [&](const cv::Range& range) + { + std::vector values; + values.reserve(frames.size()); + for (int y = range.start; y < range.end; ++y) + { + float* outputRow = median.ptr(y); + for (int x = 0; x < median.cols; ++x) + { + values.clear(); + for (const cv::Mat& frame : frames) + { + values.push_back(frame.at(y, x)); + } + const auto middle = values.begin() + + static_cast(values.size() / 2); + std::nth_element(values.begin(), middle, values.end()); + outputRow[x] = *middle; + } + } + }); + return median; + } + + scopeone::core::ProcessingResult IscatProcessingModule::finalizeContrastOutput( + const cv::Mat& input, + const cv::Mat& background, + const scopeone::core::ImageFrame& frame) + { + if (m_outputMode == 2) + { + cv::Mat outputValues; + background.convertTo(outputValues, CV_32F); + cv::max(outputValues, 0.0f, outputValues); + cv::min(outputValues, 65535.0f, outputValues); + return {makeMono16Output(frame, outputValues), {}}; + } + + cv::Mat safeBackground; + cv::max(background, 1.0f, safeBackground); + cv::Mat contrast; + cv::subtract(input, background, contrast); + cv::divide(contrast, safeBackground, contrast); + if (m_enableHighPass) + { + cv::Mat lowFrequency; + cv::GaussianBlur(contrast, + lowFrequency, + cv::Size(0, 0), + m_highPassSigma); + contrast -= lowFrequency; + } + + cv::Mat outputValues; + if (m_outputMode == 0) + { + outputValues = contrast * (m_contrastGain * 32768.0) + 32768.0; + } + else + { + cv::absdiff(contrast, cv::Scalar::all(0.0), contrast); + outputValues = contrast * (m_contrastGain * 65535.0); + } + cv::max(outputValues, 0.0f, outputValues); + cv::min(outputValues, 65535.0f, outputValues); + return {makeMono16Output(frame, outputValues), {}}; + } + + scopeone::core::ProcessingResult IscatProcessingModule::processFlatField( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame) + { + cv::Mat background; + cv::GaussianBlur(input, + background, + cv::Size(0, 0), + m_blurSigma); + return finalizeContrastOutput(input, background, frame); + } + + scopeone::core::ProcessingResult IscatProcessingModule::processTemporalMedian( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame) + { + if (m_captureReference) + { + m_lockedMedianBackground.release(); + m_medianBuffer.clear(); + m_captureReference = false; + } + if (!m_lockedMedianBackground.empty()) + { + return finalizeContrastOutput(input, m_lockedMedianBackground, frame); + } + + m_medianBuffer.push_back(input.clone()); + if (m_medianBuffer.size() < static_cast(m_medianWindow)) + { + return {frame, {}}; + } + + cv::Mat median = computePixelMedian(m_medianBuffer); + cv::GaussianBlur(median, + m_lockedMedianBackground, + cv::Size(0, 0), + m_blurSigma); + m_medianBuffer.clear(); + return finalizeContrastOutput(input, m_lockedMedianBackground, frame); + } + + scopeone::core::ProcessingResult IscatProcessingModule::processEma( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame) + { + if (m_emaAccumulator.empty()) + { + input.copyTo(m_emaAccumulator); + } + else + { + m_emaAccumulator = (1.0 - m_emaAlpha) * m_emaAccumulator + + m_emaAlpha * input; + } + + cv::Mat background; + cv::GaussianBlur(m_emaAccumulator, + background, + cv::Size(0, 0), + m_blurSigma); + return finalizeContrastOutput(input, background, frame); + } + + scopeone::core::ProcessingResult IscatProcessingModule::processSnapshot( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame) + { + if (m_snapshotBackground.empty() || m_captureReference) + { + cv::GaussianBlur(input, + m_snapshotBackground, + cv::Size(0, 0), + m_blurSigma); + m_captureReference = false; + } + return finalizeContrastOutput(input, m_snapshotBackground, frame); + } + + scopeone::core::ProcessingResult IscatProcessingModule::processDra( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame) + { + if (m_draBatchA.size() < static_cast(m_batchSize)) + { + m_draBatchA.push_back(input.clone()); + if (m_draSumA.empty()) + { + input.copyTo(m_draSumA); + } + else + { + m_draSumA += input; + } + return {frame, {}}; + } + + if (m_draBatchB.size() < static_cast(m_batchSize)) + { + m_draBatchB.push_back(input.clone()); + if (m_draSumB.empty()) + { + input.copyTo(m_draSumB); + } + else + { + m_draSumB += input; + } + if (m_draBatchB.size() < static_cast(m_batchSize)) + { + return {frame, {}}; + } + + return finalizeContrastOutput(m_draSumB / m_batchSize, + m_draSumA / m_batchSize, + frame); + } + + const cv::Mat& oldestA = m_draBatchA.front(); + const cv::Mat& bridge = m_draBatchB.front(); + m_draSumA += bridge - oldestA; + m_draSumB += input - bridge; + m_draBatchA.pop_front(); + m_draBatchA.push_back(bridge); + m_draBatchB.pop_front(); + m_draBatchB.push_back(input.clone()); + + return finalizeContrastOutput(m_draSumB / m_batchSize, + m_draSumA / m_batchSize, + frame); + } + + scopeone::core::ProcessingResult IscatProcessingModule::process( + const scopeone::core::ImageFrame& frame, + int) + { + if (!frame.isValid() || (!frame.isMono8() && !frame.isMono16())) + { + return {scopeone::core::ImageFrame{}, QStringLiteral("Unsupported iSCAT input frame")}; + } + + const int sourceType = frame.isMono16() ? CV_16UC1 : CV_8UC1; + const cv::Mat source(frame.height, + frame.width, + sourceType, + const_cast(frame.bytes.constData()), + frame.stride); + cv::Mat input; + source.convertTo(input, CV_32F, frame.isMono8() ? 257.0 : 1.0); + if (m_stateSize != input.size()) + { + clearState(); + m_stateSize = input.size(); + } + + switch (m_mode) + { + case 0: + return processFlatField(input, frame); + case 1: + return processTemporalMedian(input, frame); + case 2: + return processEma(input, frame); + case 3: + return processSnapshot(input, frame); + case 4: + return processDra(input, frame); + } + return {}; + } +} diff --git a/plugins/processing/IscatProcessing/IscatProcessingModule.h b/plugins/processing/IscatProcessing/IscatProcessingModule.h new file mode 100644 index 0000000..9e18573 --- /dev/null +++ b/plugins/processing/IscatProcessing/IscatProcessingModule.h @@ -0,0 +1,65 @@ +#pragma once + +#include "scopeone/ProcessingPlugin.h" + +#include +#include + +namespace scopeone::iscat +{ + class IscatProcessingModule final : public scopeone::core::ProcessingModule + { + public: + QString id() const override; + QString name() const override; + QVariantMap parameters() const override; + void setParameters(const QVariantMap& parameters) override; + std::unique_ptr createRuntime() const override; + bool resetState() override; + scopeone::core::ProcessingResult process(const scopeone::core::ImageFrame& frame, + int processingBitDepth) override; + + private: + void clearState(); + cv::Mat computePixelMedian(const std::deque& frames) const; + scopeone::core::ProcessingResult processFlatField( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame); + scopeone::core::ProcessingResult processTemporalMedian( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame); + scopeone::core::ProcessingResult processEma( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame); + scopeone::core::ProcessingResult processSnapshot( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame); + scopeone::core::ProcessingResult processDra( + const cv::Mat& input, + const scopeone::core::ImageFrame& frame); + scopeone::core::ProcessingResult finalizeContrastOutput( + const cv::Mat& input, + const cv::Mat& background, + const scopeone::core::ImageFrame& frame); + + int m_mode{0}; + double m_blurSigma{15.0}; + int m_medianWindow{31}; + double m_emaAlpha{0.05}; + bool m_captureReference{false}; + int m_batchSize{16}; + double m_contrastGain{20.0}; + bool m_enableHighPass{false}; + double m_highPassSigma{30.0}; + int m_outputMode{0}; + std::deque m_medianBuffer; + cv::Mat m_lockedMedianBackground; + cv::Mat m_emaAccumulator; + cv::Mat m_snapshotBackground; + std::deque m_draBatchA; + std::deque m_draBatchB; + cv::Mat m_draSumA; + cv::Mat m_draSumB; + cv::Size m_stateSize; + }; +} diff --git a/plugins/processing/IscatProcessing/IscatProcessingPlugin.cpp b/plugins/processing/IscatProcessing/IscatProcessingPlugin.cpp new file mode 100644 index 0000000..ff75248 --- /dev/null +++ b/plugins/processing/IscatProcessing/IscatProcessingPlugin.cpp @@ -0,0 +1,124 @@ +#include "IscatProcessingModule.h" + +#include "scopeone/ProcessingPlugin.h" + +#include +#include + +namespace +{ + scopeone::core::ProcessingParameterDescriptor makeRealParameter( + const char* key, + const char* name, + double value, + double minimum, + double maximum, + double step, + int decimals) + { + scopeone::core::ProcessingParameterDescriptor descriptor{ + QString::fromLatin1(key), + QString::fromLatin1(name), + scopeone::core::ProcessingParameterType::Real, + value, + minimum, + maximum, + step}; + descriptor.decimals = decimals; + return descriptor; + } + + scopeone::core::ProcessingParameterDescriptor makeBooleanParameter( + const char* key, + const char* name, + bool value) + { + return {QString::fromLatin1(key), + QString::fromLatin1(name), + scopeone::core::ProcessingParameterType::Boolean, + value}; + } + + scopeone::core::ProcessingParameterDescriptor makeIntegerParameter( + const char* key, + const char* name, + int value, + int minimum, + int maximum, + int step) + { + return {QString::fromLatin1(key), + QString::fromLatin1(name), + scopeone::core::ProcessingParameterType::Integer, + value, + minimum, + maximum, + step}; + } + + scopeone::core::ProcessingParameterDescriptor makeChoiceParameter( + const char* key, + const char* name, + int value, + std::initializer_list choices) + { + scopeone::core::ProcessingParameterDescriptor descriptor{ + QString::fromLatin1(key), + QString::fromLatin1(name), + scopeone::core::ProcessingParameterType::Choice, + value}; + int index = 0; + for (const char* choice : choices) + { + descriptor.choices.append({QString::fromLatin1(choice), index++}); + } + return descriptor; + } + + class IscatProcessingPlugin final : public QObject, + public scopeone::core::ProcessingPlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID ScopeOneProcessingPlugin_iid FILE "plugin.json") + Q_INTERFACES(scopeone::core::ProcessingPlugin) + + public: + QList processingModules() const override + { + return {{QStringLiteral("iscat.processing"), + QStringLiteral("iSCAT Processing"), + 1, + {makeChoiceParameter("mode", + "Background mode", + 0, + {"Flat Field", + "Temporal Median", + "Dynamic EMA", + "Snapshot Reference", + "Differential Rolling (DRA)"}), + makeRealParameter("blur_sigma", "Blur sigma", 15.0, 1.0, 100.0, 0.5, 1), + makeIntegerParameter("median_window", "Median window", 31, 3, 101, 2), + makeRealParameter("ema_alpha", "Smoothing factor", 0.05, 0.001, 0.5, 0.005, 3), + makeBooleanParameter("capture_reference", "Capture Reference", false), + makeIntegerParameter("batch_size", "DRA batch size", 16, 1, 500, 1), + makeRealParameter("contrast_gain", "Contrast gain", 20.0, 1.0, 500.0, 1.0, 1), + makeBooleanParameter("enable_high_pass", "Enable High-Pass", false), + makeRealParameter("high_pass_sigma", "High-Pass sigma", 30.0, 2.0, 200.0, 1.0, 1), + makeChoiceParameter("output_mode", + "Output mode", + 0, + {"Contrast Centered", "Absolute Contrast", "Estimated Background"})}, + true}}; + } + + std::unique_ptr createProcessingModule( + const QString& moduleId) override + { + return moduleId == QStringLiteral("iscat.processing") + ? std::make_unique() + : nullptr; + } + }; +} + +#include "IscatProcessingPlugin.moc" diff --git a/plugins/processing/IscatProcessing/plugin.json b/plugins/processing/IscatProcessing/plugin.json new file mode 100644 index 0000000..6aa1c76 --- /dev/null +++ b/plugins/processing/IscatProcessing/plugin.json @@ -0,0 +1,7 @@ +{ + "id": "iscat.processing", + "name": "iSCAT Processing", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "processing" +} diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index ae3abf7..a8ce8f8 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -1343,14 +1343,9 @@ namespace scopeone::ui tr("Import Image as Layer"), QString(), tr("Images (*.tif *.tiff *.png *.jpg *.jpeg *.bmp)")); - QString lastLayerKey; for (const QString& filePath : filePaths) { - m_scopeonecore->importImageAsStaticLayer(filePath, &lastLayerKey); - } - if (!lastLayerKey.isEmpty() && m_workspace) - { - m_workspace->setActiveLayerKey(lastLayerKey); + m_scopeonecore->importImageAsStaticLayerAsync(filePath); } } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index a026778..37586a8 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -414,6 +415,46 @@ namespace scopeone::ui running ? tr("Preview is running") : tr("Preview is idle")); showStatusMessage(running ? tr("Live preview started") : tr("Live preview stopped"), 3000); }); + connect(m_scopeonecore, + &scopeone::core::ScopeOneCore::staticImageImportProgress, + this, + [this](const QString&, int percent, const QString& statusText) + { + if (percent == 0) + { + m_staticImportProgress->setRange(0, 0); + } + else + { + m_staticImportProgress->setRange(0, 100); + m_staticImportProgress->setValue(percent); + } + m_staticImportProgress->show(); + showStatusMessage(statusText); + }); + connect(m_scopeonecore, + &scopeone::core::ScopeOneCore::staticImageImportFinished, + this, + [this](const QString& filePath, + const QString& layerKey, + bool success, + const QString& errorMessage) + { + m_staticImportProgress->hide(); + if (!success) + { + showStatusMessage(errorMessage, 5000); + return; + } + + m_imageWorkspace->setActiveLayerKey(layerKey); + m_saveImageAsAction->setEnabled(true); + showStatusMessage( + tr("Imported %1 (%2 slices)") + .arg(QFileInfo(filePath).fileName()) + .arg(m_scopeonecore->layerSliceCount(layerKey)), + 5000); + }); connect(m_imageWorkspace, &ImageWorkspace::mousePositionChanged, this, &MainWindow::handlePreviewMousePosition); @@ -447,9 +488,17 @@ namespace scopeone::ui const QString&, const scopeone::core::ImageFrame& frame) { - m_previewWidget->setGraphStaticLayerFrame(sourceId, frame); + const QString layerKey = m_previewWidget->setGraphStaticLayerFrame(sourceId, frame); + m_previewWidget->setLayerSliceCount( + layerKey, + m_scopeonecore->layerSliceCount(layerKey)); schedulePreviewCursorStatusRefresh(); }); + connect(m_previewWidget, &PreviewWidget::layerSliceIndexRequested, + this, [this](const QString& layerKey, int sliceIndex) + { + m_scopeonecore->setLayerSliceIndex(layerKey, sliceIndex); + }); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::staticFrameRemoved, this, [this](const QString& sourceId) { @@ -765,14 +814,30 @@ namespace scopeone::ui showStatusMessage(tr("No gallery image available for preview"), 5000); return; } - m_imageWorkspace->openSession(session); + m_imageWorkspace->activateLiveViewer(); + const QString layerKey = m_scopeonecore->importSessionAsStaticLayer(session); + if (!layerKey.isEmpty()) + { + m_imageWorkspace->setActiveLayerKey(layerKey); + showStatusMessage( + tr("Opened gallery layer: %1").arg(m_previewWidget->layerName(layerKey)), + 3000); + } }); connect(m_imageGalleryWidget, &ImageGalleryWidget::sessionRemoved, this, [this](const std::shared_ptr& session) { - m_scopeonecore->closeRecordingSession( - session->capturePlan().experimentId); + const QString expId = session->capturePlan().experimentId.trimmed(); + if (!expId.isEmpty()) + { + for (const QString& camera : session->recordedCameraIds()) + { + const QString sourceId = QStringLiteral("gallery:%1_%2").arg(expId, camera); + m_scopeonecore->removeStaticFrame(sourceId); + } + m_scopeonecore->closeRecordingSession(expId); + } }); connect(m_imageGalleryWidget, &ImageGalleryWidget::saveSessionsRequested, this, @@ -1017,7 +1082,16 @@ namespace scopeone::ui m_statusRecordingLabel = new QLabel(tr("Recording: Idle"), this); configureStatusLabel(m_statusRecordingLabel, 120, 150, tr("Recording state")); + m_staticImportProgress = new QProgressBar(this); + m_staticImportProgress->setRange(0, 100); + m_staticImportProgress->setValue(0); + m_staticImportProgress->setTextVisible(false); + m_staticImportProgress->setFixedSize(120, 14); + m_staticImportProgress->setToolTip(tr("Image import progress")); + m_staticImportProgress->hide(); + bar->addWidget(m_statusMessageLabel, 1); + bar->addPermanentWidget(m_staticImportProgress); bar->addPermanentWidget(m_statusCursorLabel); bar->addPermanentWidget(m_statusPreviewLabel); bar->addPermanentWidget(m_statusProcessingLabel); @@ -1733,27 +1807,9 @@ namespace scopeone::ui // Import multiple image files as static layers into the workspace void MainWindow::importImages(const QStringList& filePaths) { - QString lastLayerKey; for (const QString& filePath : filePaths) { - QString layerKey; - QString error; - const scopeone::core::ImageFrame frame = - m_scopeonecore->importImageAsStaticLayer(filePath, &layerKey, &error); - if (frame.isValid()) - { - lastLayerKey = layerKey; - } - else if (!error.isEmpty()) - { - showStatusMessage(error, 5000); - } - } - if (!lastLayerKey.isEmpty()) - { - m_imageWorkspace->setActiveLayerKey(lastLayerKey); - m_saveImageAsAction->setEnabled(true); - showStatusMessage(tr("Imported %1 image layer(s)").arg(filePaths.size()), 3000); + m_scopeonecore->importImageAsStaticLayerAsync(filePath); } } diff --git a/src/MainWindow.h b/src/MainWindow.h index af3e8e5..894f7e6 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -15,6 +15,7 @@ class QCloseEvent; class QDockWidget; class QLabel; class QMenu; +class QProgressBar; class QProgressDialog; class QTimer; @@ -189,6 +190,7 @@ namespace scopeone::ui QLabel* m_statusPreviewLabel{nullptr}; QLabel* m_statusProcessingLabel{nullptr}; QLabel* m_statusRecordingLabel{nullptr}; + QProgressBar* m_staticImportProgress{nullptr}; QTimer* m_statusMessageTimer{nullptr}; QTimer* m_cursorRefreshTimer{nullptr}; scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index d8cb17d..bf03cf5 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -6,9 +6,13 @@ #include #include #include +#include #include #include #include +#include +#include +#include #include #include #include @@ -266,6 +270,56 @@ namespace scopeone::ui m_placeholderLabel->setFont(placeholderFont); m_placeholderLabel->setGeometry(rect()); + m_sliceBar = new QWidget(this); + m_sliceBar->setStyleSheet(QStringLiteral( + "QWidget { background: rgba(20, 24, 28, 220); border-radius: 5px; }" + "QLabel { color: white; }" + "QPushButton { color: white; padding: 2px 6px; }")); + auto* sliceLayout = new QHBoxLayout(m_sliceBar); + sliceLayout->setContentsMargins(6, 2, 6, 2); + sliceLayout->setSpacing(4); + m_sliceLabel = new QLabel(m_sliceBar); + m_sliceSlider = new QSlider(Qt::Horizontal, m_sliceBar); + m_sliceSlider->setMinimumWidth(30); + m_slicePlayButton = new QPushButton(QStringLiteral("Play"), m_sliceBar); + m_slicePlayButton->setCheckable(true); + sliceLayout->addWidget(m_sliceLabel); + sliceLayout->addWidget(m_sliceSlider, 1); + sliceLayout->addWidget(m_slicePlayButton); + connect(m_sliceSlider, &QSlider::valueChanged, + this, [this](int index) + { + m_layerSliceIndices[m_activeLayerKey] = index; + const int sliceCount = m_layerSliceCounts.value(m_activeLayerKey, 1); + const bool compact = m_sliceBar->width() < 260; + m_sliceLabel->setText( + compact ? tr("%1/%2").arg(index + 1).arg(sliceCount) + : tr("Slice %1 / %2").arg(index + 1).arg(sliceCount)); + emit layerSliceIndexRequested(m_activeLayerKey, index); + }); + connect(m_slicePlayButton, &QPushButton::toggled, + this, [this](bool enabled) + { + m_slicePlayButton->setText(enabled ? QStringLiteral("Stop") + : QStringLiteral("Play")); + if (enabled) + { + m_sliceTimer.start(); + } + else + { + m_sliceTimer.stop(); + } + }); + m_sliceTimer.setInterval(120); + connect(&m_sliceTimer, &QTimer::timeout, + this, [this]() + { + m_sliceSlider->setValue( + (m_sliceSlider->value() + 1) % (m_sliceSlider->maximum() + 1)); + }); + m_sliceBar->hide(); + m_fpsUpdateTimer.setInterval(3000); connect(&m_fpsUpdateTimer, &QTimer::timeout, this, &PreviewWidget::updateFrameRates); m_fpsUpdateTimer.start(); @@ -440,6 +494,7 @@ namespace scopeone::ui return; } m_layerLayoutMode = mode; + updateSliceBar(); updateImageDisplay(); emit layerLayoutModeChanged(m_layerLayoutMode); } @@ -494,6 +549,16 @@ namespace scopeone::ui return layerKey; } + void PreviewWidget::setLayerSliceCount(const QString& layerKey, int sliceCount) + { + m_layerSliceCounts.insert(layerKey, sliceCount); + m_layerSliceIndices.insert(layerKey, + qBound(0, + m_layerSliceIndices.value(layerKey), + sliceCount - 1)); + updateSliceBar(); + } + // Adds or updates one realtime tool layer QString PreviewWidget::setGraphToolLayerFrame(const QString& layerId, const ImageFrame& frame) @@ -540,6 +605,7 @@ namespace scopeone::ui updateLayerInfoDisplay(); emit visibleLayerKeysChanged(visibleLayerKeys()); emit availableLayerKeysChanged(availableLayerKeys()); + updateSliceBar(); updateImageDisplay(); return true; } @@ -566,6 +632,7 @@ namespace scopeone::ui updateLayerInfoDisplay(); emit visibleLayerKeysChanged(visibleLayerKeys()); emit availableLayerKeysChanged(availableLayerKeys()); + updateSliceBar(); updateImageDisplay(); } @@ -844,6 +911,8 @@ namespace scopeone::ui { const QString layerKey = ScopeOneCore::staticLayerKey(sourceId); m_staticSourceIds.remove(sourceId); + m_layerSliceCounts.remove(layerKey); + m_layerSliceIndices.remove(layerKey); m_layerFps.remove(layerKey); m_fpsStates.remove(layerKey); @@ -1070,6 +1139,7 @@ namespace scopeone::ui return; } m_activeLayerKey = key; + updateSliceBar(); update(); } @@ -1102,6 +1172,7 @@ namespace scopeone::ui { m_placeholderText = QStringLiteral("No layer visible"); } + updateSliceBarGeometry(); update(); } @@ -2407,6 +2478,77 @@ namespace scopeone::ui { applyViewportForRect(rect()); m_placeholderLabel->setGeometry(rect()); + updateSliceBarGeometry(); + } + + void PreviewWidget::updateSliceBar() + { + const int sliceCount = m_layerSliceCounts.value(m_activeLayerKey, 1); + if (sliceCount <= 1) + { + m_sliceBar->hide(); + m_sliceTimer.stop(); + m_slicePlayButton->setChecked(false); + return; + } + + m_sliceSlider->setRange(0, sliceCount - 1); + { + const QSignalBlocker blocker(m_sliceSlider); + m_sliceSlider->setValue(m_layerSliceIndices.value(m_activeLayerKey)); + } + updateSliceBarGeometry(); + } + + void PreviewWidget::updateSliceBarGeometry() + { + const int sliceCount = m_layerSliceCounts.value(m_activeLayerKey, 1); + if (sliceCount <= 1) + { + m_sliceBar->hide(); + return; + } + + QMap frameSources; + std::vector frameSourceRenderInfos; + std::vector renderItems; + buildRenderSnapshot(frameSources, frameSourceRenderInfos, renderItems); + + QRect cell; + for (const RenderItem& item : renderItems) + { + if (item.layerKey == m_activeLayerKey) + { + cell = item.area; + break; + } + } + + if (cell.isEmpty() || cell.width() < 80) + { + m_sliceBar->hide(); + return; + } + + const int margin = 8; + const int barHeight = 28; + const int maxBarWidth = cell.width() - 2 * margin; + + const bool compact = maxBarWidth < 260; + const bool ultraCompact = maxBarWidth < 160; + + m_slicePlayButton->setVisible(!ultraCompact); + const int currentSlice = m_sliceSlider->value() + 1; + m_sliceLabel->setText(compact ? tr("%1/%2").arg(currentSlice).arg(sliceCount) + : tr("Slice %1 / %2").arg(currentSlice).arg(sliceCount)); + + m_sliceBar->setMaximumSize(maxBarWidth, barHeight); + m_sliceBar->setGeometry(cell.x() + margin, + cell.y() + cell.height() - barHeight - margin, + maxBarWidth, + barHeight); + m_sliceBar->show(); + m_sliceBar->raise(); } // Computes tiled preview rectangles for visible layers @@ -3554,6 +3696,22 @@ namespace scopeone::ui setActiveLayerKey(target.layerKey); emit layerClicked(target.layerKey); } + else if (m_layerLayoutMode == LayerLayoutMode::SideBySide) + { + QMap frameSources; + std::vector frameSourceRenderInfos; + std::vector renderItems; + buildRenderSnapshot(frameSources, frameSourceRenderInfos, renderItems); + for (const RenderItem& item : renderItems) + { + if (item.area.contains(event->pos())) + { + setActiveLayerKey(item.layerKey); + emit layerClicked(item.layerKey); + break; + } + } + } } QOpenGLWidget::mousePressEvent(event); diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index 33cadfc..c43834f 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -30,7 +30,10 @@ class QLabel; class QMouseEvent; class QPainter; class QPointF; +class QPushButton; +class QSlider; class QWheelEvent; +class QWidget; namespace scopeone::ui { @@ -68,6 +71,7 @@ namespace scopeone::ui void setAvailableCameraIds(const QStringList& cameraIds); QString setGraphStaticLayerFrame(const QString& layerId, const scopeone::core::ImageFrame& frame); + void setLayerSliceCount(const QString& layerKey, int sliceCount); QString setGraphToolLayerFrame(const QString& layerId, const scopeone::core::ImageFrame& frame); bool removeStaticLayer(const QString& layerKey); @@ -126,6 +130,7 @@ namespace scopeone::ui void stageStepRequested(double dxScale, double dyScale, bool big); void stageZStepRequested(double dzScale, bool big); void layerClicked(const QString& layerKey); + void layerSliceIndexRequested(const QString& layerKey, int sliceIndex); void activated(); void mousePositionChanged(const QPoint& widgetPos); void roiDrawn(const QString& cameraId, @@ -251,8 +256,13 @@ namespace scopeone::ui QString m_layerInfoText{QStringLiteral("No image loaded")}; QMap m_fpsStates; QTimer m_fpsUpdateTimer; + QTimer m_sliceTimer; ImageSceneModel* m_sceneModel{nullptr}; QLabel* m_placeholderLabel{nullptr}; + QWidget* m_sliceBar{nullptr}; + QSlider* m_sliceSlider{nullptr}; + QLabel* m_sliceLabel{nullptr}; + QPushButton* m_slicePlayButton{nullptr}; mutable QMutex m_mutex; QMap m_frameSources; @@ -281,6 +291,8 @@ namespace scopeone::ui GLint m_u3dUvScale{-1}, m_u3dUvOffset{-1}, m_u3dLightDirection{-1}; ViewDimensionMode m_viewDimensionMode{ViewDimensionMode::TwoDimensional}; QMap m_layerCameras3d; + QMap m_layerSliceCounts; + QMap m_layerSliceIndices; float m_zScale{1.0f}; bool m_wireframe3d{false}; bool m_scaleBarVisible{true}; @@ -438,6 +450,8 @@ namespace scopeone::ui void cancelROIDrawing(); void cancelMeasurementLineDrawing(); void cancelCrossSectionDrawing(); + void updateSliceBar(); + void updateSliceBarGeometry(); Camera3dState& cameraForLayer(const QString& layerKey); const Camera3dState& cameraForLayer(const QString& layerKey) const; QString layerKeyAt3dPosition(const QPoint& widgetPos) const; From b920bfe4ecd316503fa1f1eb85aa8fc8815c9ebb Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:53:38 +0200 Subject: [PATCH 31/37] Add offline entire stack processing support for layer stacks --- ScopeOneCore/include/scopeone/ScopeOneCore.h | 5 + ScopeOneCore/src/ScopeOneCore.cpp | 115 +++++++++++++++++++ src/ImageProcessingWidget.cpp | 65 ++++++++--- src/MainWindow.cpp | 1 + 4 files changed, 173 insertions(+), 13 deletions(-) diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 1f8dd7b..3541a20 100644 --- a/ScopeOneCore/include/scopeone/ScopeOneCore.h +++ b/ScopeOneCore/include/scopeone/ScopeOneCore.h @@ -701,6 +701,7 @@ namespace scopeone::core const QString& sourceId = QString()); quint64 requestRecordingSessionStackProcessing(const QString& sessionId, const QString& cameraId); + quint64 requestLayerStackProcessing(const QString& layerKey); bool cancelProcessingRequest(quint64 requestId); @@ -817,6 +818,10 @@ namespace scopeone::core 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, diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index b620304..4c49efe 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -4849,6 +4849,121 @@ namespace scopeone::core return requestId; } + // Process a static layer image stack with an isolated runtime + quint64 ScopeOneCore::requestLayerStackProcessing(const QString& layerKey) + { + const QString sourceId = sourceIdFromLayerKey(layerKey); + if (sourceId.isEmpty() || !m_layerStacks.contains(sourceId) || processingModules().isEmpty()) + { + return 0; + } + + const auto inputSlices = m_layerStacks.value(sourceId); + const qsizetype frameCount = static_cast(inputSlices.size()); + if (frameCount <= 0) + { + return 0; + } + + QString cleanSourceId = sourceId; + if (cleanSourceId.startsWith(QStringLiteral("processed:"))) + { + cleanSourceId = cleanSourceId.mid(10); + } + const QString outputSourceId = QStringLiteral("processed:%1").arg(cleanSourceId); + const QString outputLayerKey = staticLayerKey(outputSourceId); + + DocumentLayer layer; + QString baseName; + if (m_imageSceneModel->findLayer(layerKey, layer) && !layer.name.isEmpty()) + { + baseName = layer.name; + } + else + { + baseName = sourceId; + } + const QString displayName = baseName.startsWith(QStringLiteral("Processed: ")) + ? baseName + : QStringLiteral("Processed: %1").arg(baseName); + + const quint64 requestId = ++m_nextProcessingRequestId; + auto cancelToken = std::make_shared(false); + m_processingRequestCancelTokens.insert(requestId, cancelToken); + auto pipeline = m_managers->imageProcessingManager->definition().createRuntime(); + const int bitDepth = static_cast(processingBitDepth()); + + struct LayerStackProcessingResult + { + std::vector frames; + QString errorMessage; + bool canceled{false}; + }; + + auto* watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, + this, [this, watcher, requestId, outputSourceId, outputLayerKey, displayName, cancelToken]() + { + LayerStackProcessingResult result = watcher->result(); + m_processingRequestCancelTokens.remove(requestId); + result.canceled = result.canceled || cancelToken->load(); + if (!result.canceled && result.errorMessage.isEmpty() && !result.frames.empty()) + { + m_layerStacks.insert(outputSourceId, std::move(result.frames)); + publishStaticFrame(outputSourceId, m_layerStacks[outputSourceId].front(), displayName); + } + if (result.canceled) + { + result.errorMessage = QStringLiteral("Processing canceled"); + } + emit layerStackProcessingFinished(requestId, outputLayerKey, result.errorMessage); + watcher->deleteLater(); + }); + + watcher->setFuture(QtConcurrent::run( + m_offlineProcessingThreadPool.get(), + [this, requestId, inputSlices, outputSourceId, frameCount, bitDepth, pipeline = std::move(pipeline), cancelToken]() + { + LayerStackProcessingResult result; + result.frames.reserve(static_cast(frameCount)); + QElapsedTimer progressTimer; + progressTimer.start(); + for (qsizetype index = 0; index < frameCount; ++index) + { + if (cancelToken->load()) + { + result.canceled = true; + break; + } + const ImageFrame& frame = inputSlices[static_cast(index)]; + if (!frame.isValid()) + { + result.errorMessage = QStringLiteral("Failed to read stack frame %1").arg(index + 1); + break; + } + ProcessingResult processed = pipeline->process(frame, bitDepth); + if (!processed.succeeded()) + { + result.errorMessage = processed.error; + break; + } + processed.frame.cameraId = outputSourceId; + result.frames.push_back(std::move(processed.frame)); + if (progressTimer.elapsed() >= 100 || index + 1 == frameCount) + { + const qint64 completed = index + 1; + QMetaObject::invokeMethod(this, [this, requestId, completed, frameCount]() + { + emit stackProcessingProgress(requestId, completed, frameCount); + }); + progressTimer.restart(); + } + } + return result; + })); + return requestId; + } + bool ScopeOneCore::cancelProcessingRequest(quint64 requestId) { const auto token = m_processingRequestCancelTokens.value(requestId); diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index 485f32c..07d6a1f 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -490,6 +490,25 @@ namespace scopeone::ui emit processedStackReady(session); refreshSources(); }); + connect(core, &scopeone::core::ScopeOneCore::layerStackProcessingFinished, + this, [this](quint64 requestId, + const QString& outputLayerKey, + const QString& error) + { + if (requestId != m_offlineProcessingRequestId + || !m_directProcessingRequest) + { + return; + } + finishOfflineProcessing(); + if (!error.isEmpty() || outputLayerKey.isEmpty()) + { + QMessageBox::warning(this, tr("Processing Failed"), error); + return; + } + emit processedLayerReady(outputLayerKey); + refreshSources(); + }); connect(workspace, &ImageWorkspace::activeDocumentChanged, this, [this]() { @@ -780,8 +799,12 @@ namespace scopeone::ui const bool canProcessImage = sourceType == QStringLiteral("layer") || sourceType == QStringLiteral("document") || sourceType == QStringLiteral("document_stack"); + const int sliceCount = (sourceType == QStringLiteral("layer")) + ? m_scopeonecore->layerSliceCount(sourceId) + : 0; const bool canProcessStack = sourceType == QStringLiteral("stack") - || sourceType == QStringLiteral("document_stack"); + || sourceType == QStringLiteral("document_stack") + || sliceCount > 1; if (!canProcessStack && m_offlineScopeCombo->currentData().toBool()) { const QSignalBlocker blocker(m_offlineScopeCombo); @@ -905,8 +928,10 @@ namespace scopeone::ui scopeone::core::DocumentLayer layer; if (m_scopeonecore->imageSceneModel()->findLayer(layerKey, layer)) { - m_sourceCombo->addItem(layer.name.isEmpty() ? layerKey : layer.name, - QStringLiteral("layer")); + const int slices = m_scopeonecore->layerSliceCount(layerKey); + const QString name = layer.name.isEmpty() ? layerKey : layer.name; + const QString title = slices > 1 ? tr("%1 (%2 slices)").arg(name).arg(slices) : name; + m_sourceCombo->addItem(title, QStringLiteral("layer")); const int index = m_sourceCombo->count() - 1; m_sourceCombo->setItemData(index, layerKey, Qt::UserRole + 1); } @@ -954,16 +979,30 @@ namespace scopeone::ui const bool entireStack = m_offlineScopeCombo->currentData().toBool(); m_directProcessingRequest = sourceType == QStringLiteral("layer") || sourceType == QStringLiteral("stack"); - m_offlineProcessingRequestId = entireStack - ? (m_directProcessingRequest - ? m_scopeonecore->requestRecordingSessionStackProcessing( - m_sourceCombo->currentData(Qt::UserRole + 1).toString(), - m_sourceCombo->currentData(Qt::UserRole + 2).toString()) - : m_workspace->processDocument(sourceId, true)) - : (m_directProcessingRequest - ? m_scopeonecore->requestImageProcessing( - m_scopeonecore->graphFrame(sourceId), sourceId) - : m_workspace->processDocument(sourceId, false)); + if (entireStack) + { + if (sourceType == QStringLiteral("layer")) + { + m_offlineProcessingRequestId = m_scopeonecore->requestLayerStackProcessing(sourceId); + } + else if (m_directProcessingRequest) + { + m_offlineProcessingRequestId = m_scopeonecore->requestRecordingSessionStackProcessing( + m_sourceCombo->currentData(Qt::UserRole + 1).toString(), + m_sourceCombo->currentData(Qt::UserRole + 2).toString()); + } + else + { + m_offlineProcessingRequestId = m_workspace->processDocument(sourceId, true); + } + } + else + { + m_offlineProcessingRequestId = m_directProcessingRequest + ? m_scopeonecore->requestImageProcessing( + m_scopeonecore->graphFrame(sourceId), sourceId) + : m_workspace->processDocument(sourceId, false); + } if (m_offlineProcessingRequestId == 0) { finishOfflineProcessing(); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 37586a8..2cccac6 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -695,6 +695,7 @@ namespace scopeone::ui connect(m_imageProcessingWidget, &ImageProcessingWidget::processedLayerReady, this, [this](const QString& layerKey) { + m_imageWorkspace->setActiveLayerKey(layerKey); showLayers({layerKey}); showStatusMessage(tr("Processed image added to preview"), 5000); }); From 9dc894b66d5b6b5d4240b02ded698419eb608787 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:41:01 +0200 Subject: [PATCH 32/37] Fix GUI startup initialization and lazy-load DAQ plugins --- ScopeOneCore/internal/DaqDeviceManager.h | 1 + ScopeOneCore/src/DaqDeviceManager.cpp | 11 +- src/AboutDialog.cpp | 12 +- src/AboutDialog.h | 4 +- src/ConfigPresetWidget.cpp | 7 +- src/ConfigPresetWidget.h | 1 - src/ConsoleWidget.cpp | 36 +-- src/ConsoleWidget.h | 7 - src/DeviceControlWidget.cpp | 172 ++++++--------- src/DeviceControlWidget.h | 15 -- src/DevicePropertyWidget.cpp | 7 +- src/DevicePropertyWidget.h | 1 - src/ImageGalleryWidget.cpp | 8 +- src/ImageProcessingWidget.cpp | 46 ++-- src/ImageProcessingWidget.h | 6 +- src/ImageToolsDialog.cpp | 8 +- src/ImageToolsDialog.h | 1 - src/ImageWorkspace.cpp | 4 + src/InspectWidget.cpp | 12 +- src/InspectWidget.h | 1 - src/MainWindow.cpp | 265 ++++++++++++++--------- src/MainWindow.h | 20 -- 22 files changed, 296 insertions(+), 349 deletions(-) diff --git a/ScopeOneCore/internal/DaqDeviceManager.h b/ScopeOneCore/internal/DaqDeviceManager.h index 4bd4a22..e8bbc99 100644 --- a/ScopeOneCore/internal/DaqDeviceManager.h +++ b/ScopeOneCore/internal/DaqDeviceManager.h @@ -41,6 +41,7 @@ namespace scopeone::core::internal QString* errorMessage); std::vector> m_loaders; + mutable bool m_pluginsLoaded{false}; QHash m_plugins; QHash m_descriptors; QHash> m_controllers; diff --git a/ScopeOneCore/src/DaqDeviceManager.cpp b/ScopeOneCore/src/DaqDeviceManager.cpp index 2ca7a2e..763173f 100644 --- a/ScopeOneCore/src/DaqDeviceManager.cpp +++ b/ScopeOneCore/src/DaqDeviceManager.cpp @@ -89,7 +89,6 @@ namespace scopeone::core::internal DaqDeviceManager::DaqDeviceManager(QObject* parent) : QObject(parent) { - loadPlugins(); } DaqDeviceManager::~DaqDeviceManager() @@ -104,6 +103,10 @@ namespace scopeone::core::internal QList DaqDeviceManager::devices() const { + if (!m_pluginsLoaded) + { + const_cast(this)->loadPlugins(); + } QList result = m_descriptors.values(); std::sort(result.begin(), result.end(), [](const DaqDeviceDescriptor& left, @@ -117,6 +120,10 @@ namespace scopeone::core::internal bool DaqDeviceManager::start(const DaqSessionConfig& config, QString* errorMessage) { + if (!m_pluginsLoaded) + { + loadPlugins(); + } const QString deviceId = config.deviceId.trimmed(); if (!m_descriptors.contains(deviceId)) { @@ -162,6 +169,7 @@ namespace scopeone::core::internal void DaqDeviceManager::loadPlugins() { + m_pluginsLoaded = true; const QStringList directories = { QDir(QCoreApplication::applicationDirPath()) .filePath(QStringLiteral("plugins/hardware")), @@ -199,7 +207,6 @@ namespace scopeone::core::internal .arg(file.fileName(), loader->errorString()); continue; } - for (const DaqDeviceDescriptor& descriptor : plugin->devices()) { const QString deviceId = descriptor.id.trimmed(); diff --git a/src/AboutDialog.cpp b/src/AboutDialog.cpp index f4d25c2..c9012c2 100644 --- a/src/AboutDialog.cpp +++ b/src/AboutDialog.cpp @@ -37,10 +37,10 @@ namespace scopeone::ui } mainLayout->addWidget(logoLabel, 0, Qt::AlignHCenter); - m_contentBrowser = new QTextBrowser(this); - m_contentBrowser->setOpenExternalLinks(true); - setContent(); - mainLayout->addWidget(m_contentBrowser, 1); + auto* contentBrowser = new QTextBrowser(this); + contentBrowser->setOpenExternalLinks(true); + setContent(contentBrowser); + mainLayout->addWidget(contentBrowser, 1); auto* okButton = new QPushButton("Close", this); connect(okButton, &QPushButton::clicked, this, &QDialog::accept); @@ -55,7 +55,7 @@ namespace scopeone::ui } // Keep app and core version text in one place - void AboutDialog::setContent() + void AboutDialog::setContent(QTextBrowser* browser) { const QString title = QStringLiteral(SCOPEONE_APP_NAME " " SCOPEONE_APP_VERSION_STRING); const QString coreVersion = scopeone::core::ScopeOneCore::getVersion(); @@ -67,7 +67,7 @@ namespace scopeone::ui const QString platformInfo = QString("%1, %2") .arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); - m_contentBrowser->setHtml(QString(R"( + browser->setHtml(QString(R"(

%1

diff --git a/src/AboutDialog.h b/src/AboutDialog.h index b25c95e..df82d2c 100644 --- a/src/AboutDialog.h +++ b/src/AboutDialog.h @@ -18,8 +18,6 @@ namespace scopeone::ui private: void setupUI(); - void setContent(); - - QTextBrowser* m_contentBrowser{nullptr}; + void setContent(QTextBrowser* browser); }; } diff --git a/src/ConfigPresetWidget.cpp b/src/ConfigPresetWidget.cpp index 969c404..3f69956 100644 --- a/src/ConfigPresetWidget.cpp +++ b/src/ConfigPresetWidget.cpp @@ -57,7 +57,7 @@ namespace scopeone::ui auto* controlLayout = new QHBoxLayout(); auto* refreshButton = new QPushButton("Refresh", this); refreshButton->setMaximumWidth(60); - connect(refreshButton, &QPushButton::clicked, this, &ConfigPresetWidget::onRefreshClicked); + connect(refreshButton, &QPushButton::clicked, this, &ConfigPresetWidget::refresh); auto* autoRefreshCheckBox = new QCheckBox("Auto Refresh", this); autoRefreshCheckBox->setChecked(m_autoRefresh); @@ -162,11 +162,6 @@ namespace scopeone::ui } } - // Refresh config presets on demand - void ConfigPresetWidget::onRefreshClicked() - { - refresh(); - } // Toggle periodic config preset refresh void ConfigPresetWidget::onAutoRefreshToggled(bool enabled) diff --git a/src/ConfigPresetWidget.h b/src/ConfigPresetWidget.h index 689586b..b30a80d 100644 --- a/src/ConfigPresetWidget.h +++ b/src/ConfigPresetWidget.h @@ -27,7 +27,6 @@ namespace scopeone::ui void errorOccurred(const QString& message); private: - void onRefreshClicked(); void onAutoRefreshToggled(bool enabled); void onAutoRefreshTimer(); diff --git a/src/ConsoleWidget.cpp b/src/ConsoleWidget.cpp index 40504aa..26f6b03 100644 --- a/src/ConsoleWidget.cpp +++ b/src/ConsoleWidget.cpp @@ -111,9 +111,8 @@ namespace scopeone::ui , m_autoScroll(true) { setupUI(); - connect(m_clearButton, &QPushButton::clicked, this, &ConsoleWidget::onClearClicked); - connect(m_showTimestampsCheckBox, &QCheckBox::toggled, this, &ConsoleWidget::onShowTimestampsToggled); - connect(m_autoScrollCheckBox, &QCheckBox::toggled, this, &ConsoleWidget::onAutoScrollToggled); + connect(m_showTimestampsCheckBox, &QCheckBox::toggled, this, &ConsoleWidget::setShowTimestamps); + connect(m_autoScrollCheckBox, &QCheckBox::toggled, this, &ConsoleWidget::setAutoScroll); connect(m_filterComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ConsoleWidget::onFilterChanged); connect(m_searchInput, &QLineEdit::textChanged, this, @@ -122,8 +121,6 @@ namespace scopeone::ui m_searchKeyword = text.trimmed(); updateDisplay(); }); - connect(m_runButton, &QPushButton::clicked, this, - [this]() { executeCommand(m_commandInput->text()); }); m_consoleTextEdit->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_consoleTextEdit, &QTextEdit::customContextMenuRequested, this, &ConsoleWidget::showContextMenu); @@ -189,12 +186,13 @@ namespace scopeone::ui QStringLiteral("ERROR")}); m_filterComboBox->setFixedWidth(80); - m_clearButton = new QPushButton(tr("Clear"), this); - m_clearButton->setFixedWidth(50); + auto* clearButton = new QPushButton(tr("Clear"), this); + clearButton->setFixedWidth(50); + connect(clearButton, &QPushButton::clicked, this, &ConsoleWidget::clearMessages); row1Layout->addWidget(m_searchInput, 1); row1Layout->addWidget(m_filterComboBox); - row1Layout->addWidget(m_clearButton); + row1Layout->addWidget(clearButton); auto* row2Layout = new QHBoxLayout(); row2Layout->setSpacing(8); @@ -231,12 +229,14 @@ namespace scopeone::ui QStringLiteral("QLineEdit { background: #1e1e1e; color: #f8f9fa; " "border: 1px solid #495057; padding: 3px 6px; }")); - m_runButton = new QPushButton(tr("Run"), this); - m_runButton->setFixedWidth(64); + auto* runButton = new QPushButton(tr("Run"), this); + runButton->setFixedWidth(64); + connect(runButton, &QPushButton::clicked, this, + [this]() { executeCommand(m_commandInput->text()); }); commandLayout->addWidget(promptLabel); commandLayout->addWidget(m_commandInput, 1); - commandLayout->addWidget(m_runButton); + commandLayout->addWidget(runButton); mainLayout->addLayout(commandLayout, 0); } @@ -418,20 +418,6 @@ namespace scopeone::ui scrollBar->setValue(scrollBar->maximum()); } - void ConsoleWidget::onClearClicked() - { - clearMessages(); - } - - void ConsoleWidget::onShowTimestampsToggled(bool show) - { - setShowTimestamps(show); - } - - void ConsoleWidget::onAutoScrollToggled(bool autoScroll) - { - setAutoScroll(autoScroll); - } // Apply the selected message type filter void ConsoleWidget::onFilterChanged() diff --git a/src/ConsoleWidget.h b/src/ConsoleWidget.h index 54590cc..cdb7f15 100644 --- a/src/ConsoleWidget.h +++ b/src/ConsoleWidget.h @@ -12,7 +12,6 @@ class QTextEdit; class QEvent; class QPoint; class QLineEdit; -class QPushButton; class QCheckBox; class QComboBox; class QLabel; @@ -54,9 +53,6 @@ namespace scopeone::ui void setApiDispatcher(ApiDispatcher dispatcher); private: - void onClearClicked(); - void onShowTimestampsToggled(bool show); - void onAutoScrollToggled(bool autoScroll); void onFilterChanged(); struct ConsoleMessage @@ -80,9 +76,6 @@ namespace scopeone::ui bool eventFilter(QObject* object, QEvent* event) override; QTextEdit* m_consoleTextEdit{nullptr}; - - QPushButton* m_clearButton{nullptr}; - QPushButton* m_runButton{nullptr}; QCheckBox* m_showTimestampsCheckBox{nullptr}; QCheckBox* m_autoScrollCheckBox{nullptr}; QComboBox* m_filterComboBox{nullptr}; diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index a8ce8f8..2e479bf 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -570,22 +570,17 @@ namespace scopeone::ui applyPreviewVisibility(m_previewWidget->visibleLayerKeys(), false); onPreviewLayerInfoTextChanged(m_previewWidget->layerInfoSummaryText()); - if (m_clippingCheckBox) - { - m_clippingCheckBox->setChecked(m_previewWidget->isClippingWarningEnabled()); - connect(m_clippingCheckBox, &QCheckBox::toggled, - m_previewWidget, &PreviewWidget::setClippingWarningEnabled); - connect(m_previewWidget, &PreviewWidget::clippingWarningChanged, - m_clippingCheckBox, &QCheckBox::setChecked); - } - if (m_scaleBarCheckBox) - { - m_scaleBarCheckBox->setChecked(m_previewWidget->isScaleBarVisible()); - connect(m_scaleBarCheckBox, &QCheckBox::toggled, - m_previewWidget, &PreviewWidget::setScaleBarVisible); - connect(m_previewWidget, &PreviewWidget::scaleBarVisibilityChanged, - m_scaleBarCheckBox, &QCheckBox::setChecked); - } + m_clippingCheckBox->setChecked(m_previewWidget->isClippingWarningEnabled()); + connect(m_clippingCheckBox, &QCheckBox::toggled, + m_previewWidget, &PreviewWidget::setClippingWarningEnabled); + connect(m_previewWidget, &PreviewWidget::clippingWarningChanged, + m_clippingCheckBox, &QCheckBox::setChecked); + + m_scaleBarCheckBox->setChecked(m_previewWidget->isScaleBarVisible()); + connect(m_scaleBarCheckBox, &QCheckBox::toggled, + m_previewWidget, &PreviewWidget::setScaleBarVisible); + connect(m_previewWidget, &PreviewWidget::scaleBarVisibilityChanged, + m_scaleBarCheckBox, &QCheckBox::setChecked); { const QSignalBlocker blocker(m_viewDimensionCombo); m_viewDimensionCombo->setCurrentIndex( @@ -633,8 +628,8 @@ namespace scopeone::ui // Builds layer and alignment controls QWidget* DeviceControlWidget::createPreviewControlsGroup() { - m_previewControlsGroup = new QGroupBox("Layers", this); - QGridLayout* controlLayout = new QGridLayout(m_previewControlsGroup); + auto* previewControlsGroup = new QGroupBox("Layers", this); + QGridLayout* controlLayout = new QGridLayout(previewControlsGroup); controlLayout->setHorizontalSpacing(6); controlLayout->setVerticalSpacing(4); controlLayout->setContentsMargins(6, 6, 6, 6); @@ -659,28 +654,28 @@ namespace scopeone::ui m_layerTable->setMinimumHeight(94); m_layerTable->setMaximumHeight(150); - m_layerHistogramGroup = new QGroupBox(QStringLiteral("Histogram"), m_previewControlsGroup); - auto* histogramLayout = new QVBoxLayout(m_layerHistogramGroup); + auto* layerHistogramGroup = new QGroupBox(QStringLiteral("Histogram"), previewControlsGroup); + auto* histogramLayout = new QVBoxLayout(layerHistogramGroup); histogramLayout->setContentsMargins(6, 6, 6, 6); - m_layerHistogramWidget = new LayerHistogramWidget(m_layerHistogramGroup); + m_layerHistogramWidget = new LayerHistogramWidget(layerHistogramGroup); histogramLayout->addWidget(m_layerHistogramWidget); auto* histogramLevelsLayout = new QHBoxLayout; - m_layerAutoButton = new QPushButton(QStringLiteral("Auto"), m_layerHistogramGroup); + m_layerAutoButton = new QPushButton(QStringLiteral("Auto"), layerHistogramGroup); m_layerAutoButton->setToolTip(QStringLiteral("Set display levels from the current image")); - m_layerFullRangeButton = new QPushButton(QStringLiteral("Full Range"), m_layerHistogramGroup); + m_layerFullRangeButton = new QPushButton(QStringLiteral("Full Range"), layerHistogramGroup); m_layerFullRangeButton->setToolTip(QStringLiteral("Show the complete intensity range")); histogramLevelsLayout->addWidget(m_layerAutoButton); histogramLevelsLayout->addWidget(m_layerFullRangeButton); histogramLayout->addLayout(histogramLevelsLayout); - m_layerAutoStretchCheckBox = new QCheckBox(QStringLiteral("Continuous Auto"), m_layerHistogramGroup); + m_layerAutoStretchCheckBox = new QCheckBox(QStringLiteral("Continuous Auto"), layerHistogramGroup); m_layerAutoStretchCheckBox->setToolTip( QStringLiteral("Update display levels continuously as images arrive")); - m_layerHistogramLogCheckBox = new QCheckBox(QStringLiteral("Log scale"), m_layerHistogramGroup); + auto* layerHistogramLogCheckBox = new QCheckBox(QStringLiteral("Log scale"), layerHistogramGroup); histogramLayout->addWidget(m_layerAutoStretchCheckBox); - histogramLayout->addWidget(m_layerHistogramLogCheckBox); - connect(m_layerHistogramLogCheckBox, &QCheckBox::toggled, + histogramLayout->addWidget(layerHistogramLogCheckBox); + connect(layerHistogramLogCheckBox, &QCheckBox::toggled, m_layerHistogramWidget, &LayerHistogramWidget::setLogScale); m_layerSettingsGroup = new QGroupBox("Layer Settings", this); @@ -696,8 +691,8 @@ namespace scopeone::ui m_layerMoveDownButton = new QPushButton(QStringLiteral("Down"), m_layerSettingsGroup); m_layerRemoveButton = new QPushButton(QStringLiteral("Remove"), m_layerSettingsGroup); m_layerRemoveButton->setMaximumWidth(68); - m_layerImportButton = new QPushButton(QStringLiteral("Import..."), m_layerSettingsGroup); - m_layerImportButton->setMaximumWidth(68); + auto* layerImportButton = new QPushButton(QStringLiteral("Import..."), m_layerSettingsGroup); + layerImportButton->setMaximumWidth(68); m_layerOpacitySpinBox = new QSpinBox(m_layerSettingsGroup); m_layerOpacitySpinBox->setRange(0, 100); @@ -725,7 +720,7 @@ namespace scopeone::ui layerSettingsLayout->addWidget(m_layerMoveUpButton, 1, 1, Qt::AlignLeft); layerSettingsLayout->addWidget(m_layerMoveDownButton, 1, 2, Qt::AlignLeft); layerSettingsLayout->addWidget(m_layerRemoveButton, 1, 3, Qt::AlignLeft); - layerSettingsLayout->addWidget(m_layerImportButton, 1, 4, Qt::AlignLeft); + layerSettingsLayout->addWidget(layerImportButton, 1, 4, Qt::AlignLeft); layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Opacity:"), m_layerSettingsGroup), 2, 0); layerSettingsLayout->addWidget(m_layerOpacitySpinBox, 2, 1, Qt::AlignLeft); layerSettingsLayout->addWidget(new QLabel(QStringLiteral("Gamma:"), m_layerSettingsGroup), 2, 2); @@ -740,21 +735,21 @@ namespace scopeone::ui auto* transformGroup = new QGroupBox(QStringLiteral("Transform"), this); auto* transformLayout = new QGridLayout(transformGroup); transformLayout->setContentsMargins(6, 6, 6, 6); - m_alignXLabel = new QLabel("X offset:", transformGroup); + auto* alignXLabel = new QLabel("X offset:", transformGroup); m_alignXSpinBox = new QSpinBox(transformGroup); m_alignXSpinBox->setRange(-1000, 1000); m_alignXSpinBox->setValue(0); m_alignXSpinBox->setFixedWidth(64); m_alignXSpinBox->setKeyboardTracking(false); - m_alignYLabel = new QLabel("Y offset:", transformGroup); + auto* alignYLabel = new QLabel("Y offset:", transformGroup); m_alignYSpinBox = new QSpinBox(transformGroup); m_alignYSpinBox->setRange(-1000, 1000); m_alignYSpinBox->setValue(0); m_alignYSpinBox->setFixedWidth(64); m_alignYSpinBox->setKeyboardTracking(false); - m_alignZoomLabel = new QLabel("Scale:", transformGroup); + auto* alignZoomLabel = new QLabel("Scale:", transformGroup); m_alignZoomSpinBox = new QSpinBox(transformGroup); m_alignZoomSpinBox->setRange(10, 500); m_alignZoomSpinBox->setValue(100); @@ -764,42 +759,42 @@ namespace scopeone::ui m_alignFlipXCheckBox = new QCheckBox("Flip X", transformGroup); m_alignFlipYCheckBox = new QCheckBox("Flip Y", transformGroup); - m_alignResetButton = new QPushButton("Reset", transformGroup); - m_alignResetButton->setMaximumWidth(50); - m_alignResetButton->setToolTip("Reset offset and flip"); + auto* alignResetButton = new QPushButton("Reset", transformGroup); + alignResetButton->setMaximumWidth(50); + alignResetButton->setToolTip("Reset offset and flip"); - transformLayout->addWidget(m_alignXLabel, 0, 0); + transformLayout->addWidget(alignXLabel, 0, 0); transformLayout->addWidget(m_alignXSpinBox, 0, 1, Qt::AlignLeft); - transformLayout->addWidget(m_alignYLabel, 0, 2); + transformLayout->addWidget(alignYLabel, 0, 2); transformLayout->addWidget(m_alignYSpinBox, 0, 3, Qt::AlignLeft); - transformLayout->addWidget(m_alignZoomLabel, 1, 0); + transformLayout->addWidget(alignZoomLabel, 1, 0); transformLayout->addWidget(m_alignZoomSpinBox, 1, 1, Qt::AlignLeft); transformLayout->addWidget(m_alignFlipXCheckBox, 1, 2, Qt::AlignLeft); transformLayout->addWidget(m_alignFlipYCheckBox, 1, 3, Qt::AlignLeft); - transformLayout->addWidget(m_alignResetButton, 1, 4, Qt::AlignLeft); + transformLayout->addWidget(alignResetButton, 1, 4, Qt::AlignLeft); transformLayout->setColumnStretch(5, 1); - m_alignXLabel->setMinimumWidth(20); - m_alignYLabel->setMinimumWidth(20); - m_alignZoomLabel->setMinimumWidth(60); + alignXLabel->setMinimumWidth(20); + alignYLabel->setMinimumWidth(20); + alignZoomLabel->setMinimumWidth(60); - m_surfaceViewGroup = new QGroupBox(QStringLiteral("Surface View"), this); - auto* surfaceViewLayout = new QGridLayout(m_surfaceViewGroup); + auto* surfaceViewGroup = new QGroupBox(QStringLiteral("Surface View"), this); + auto* surfaceViewLayout = new QGridLayout(surfaceViewGroup); surfaceViewLayout->setContentsMargins(6, 6, 6, 6); surfaceViewLayout->setHorizontalSpacing(6); surfaceViewLayout->setVerticalSpacing(4); - m_viewDimensionCombo = new QComboBox(m_surfaceViewGroup); + m_viewDimensionCombo = new QComboBox(surfaceViewGroup); m_viewDimensionCombo->addItem(QStringLiteral("2D Flat Map")); m_viewDimensionCombo->addItem(QStringLiteral("3D Surface")); m_viewDimensionCombo->setToolTip(QStringLiteral("Switch between the flat image and a displaced surface")); - m_3dZScaleSlider = new QSlider(Qt::Horizontal, m_surfaceViewGroup); + m_3dZScaleSlider = new QSlider(Qt::Horizontal, surfaceViewGroup); m_3dZScaleSlider->setRange(1, 100); m_3dZScaleSlider->setValue(10); m_3dZScaleSlider->setToolTip(QStringLiteral("Height exaggeration from 0.1x to 10.0x")); - m_3dZScaleSpinBox = new QDoubleSpinBox(m_surfaceViewGroup); + m_3dZScaleSpinBox = new QDoubleSpinBox(surfaceViewGroup); m_3dZScaleSpinBox->setRange(0.1, 10.0); m_3dZScaleSpinBox->setSingleStep(0.1); m_3dZScaleSpinBox->setDecimals(1); @@ -808,17 +803,17 @@ namespace scopeone::ui m_3dZScaleSpinBox->setFixedWidth(64); m_3dZScaleSpinBox->setKeyboardTracking(false); - m_3dWireframeCheckBox = new QCheckBox(QStringLiteral("Wireframe"), m_surfaceViewGroup); - m_3dResetButton = new QPushButton(QStringLiteral("Reset View"), m_surfaceViewGroup); - m_3dResetButton->setMaximumWidth(84); + m_3dWireframeCheckBox = new QCheckBox(QStringLiteral("Wireframe"), surfaceViewGroup); + auto* reset3dButton = new QPushButton(QStringLiteral("Reset View"), surfaceViewGroup); + reset3dButton->setMaximumWidth(84); - surfaceViewLayout->addWidget(new QLabel(QStringLiteral("Mode:"), m_surfaceViewGroup), 0, 0); + surfaceViewLayout->addWidget(new QLabel(QStringLiteral("Mode:"), surfaceViewGroup), 0, 0); surfaceViewLayout->addWidget(m_viewDimensionCombo, 0, 1, 1, 3); - surfaceViewLayout->addWidget(new QLabel(QStringLiteral("Z-Scale:"), m_surfaceViewGroup), 1, 0); + surfaceViewLayout->addWidget(new QLabel(QStringLiteral("Z-Scale:"), surfaceViewGroup), 1, 0); surfaceViewLayout->addWidget(m_3dZScaleSlider, 1, 1); surfaceViewLayout->addWidget(m_3dZScaleSpinBox, 1, 2); surfaceViewLayout->addWidget(m_3dWireframeCheckBox, 2, 0, 1, 2); - surfaceViewLayout->addWidget(m_3dResetButton, 2, 2, 1, 2, Qt::AlignLeft); + surfaceViewLayout->addWidget(reset3dButton, 2, 2, 1, 2, Qt::AlignLeft); connect(m_viewDimensionCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) @@ -848,14 +843,14 @@ namespace scopeone::ui }); connect(m_3dWireframeCheckBox, &QCheckBox::toggled, this, [this](bool enabled) { m_previewWidget->set3dWireframeEnabled(enabled); }); - connect(m_3dResetButton, &QPushButton::clicked, + connect(reset3dButton, &QPushButton::clicked, this, [this]() { m_previewWidget->reset3dCamera(); }); controlLayout->addWidget(m_layerTable, 0, 0, 1, 6); - controlLayout->addWidget(m_layerHistogramGroup, 1, 0, 1, 6); + controlLayout->addWidget(layerHistogramGroup, 1, 0, 1, 6); controlLayout->addWidget(m_layerSettingsGroup, 2, 0, 1, 6); controlLayout->addWidget(transformGroup, 3, 0, 1, 6); - controlLayout->addWidget(m_surfaceViewGroup, 4, 0, 1, 6); + controlLayout->addWidget(surfaceViewGroup, 4, 0, 1, 6); controlLayout->setColumnStretch(5, 1); @@ -866,12 +861,16 @@ namespace scopeone::ui connect(m_layerTable, &QTableWidget::customContextMenuRequested, this, &DeviceControlWidget::showLayerContextMenu); connect(m_layerMoveUpButton, &QPushButton::clicked, - this, &DeviceControlWidget::onPreviewLayerMoveUpClicked); + this, [this]() { m_sceneModel->moveLayer(currentLayerKey(), -1); }); connect(m_layerMoveDownButton, &QPushButton::clicked, - this, &DeviceControlWidget::onPreviewLayerMoveDownClicked); + this, [this]() { m_sceneModel->moveLayer(currentLayerKey(), 1); }); connect(m_layerRemoveButton, &QPushButton::clicked, - this, &DeviceControlWidget::onPreviewLayerRemoveClicked); - connect(m_layerImportButton, &QPushButton::clicked, + this, [this]() + { + m_scopeonecore->removeStaticFrame( + scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey())); + }); + connect(layerImportButton, &QPushButton::clicked, this, &DeviceControlWidget::onPreviewLayerImportClicked); connect(m_layerOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), this, &DeviceControlWidget::onPreviewLayerOpacityChanged); @@ -882,9 +881,9 @@ namespace scopeone::ui connect(m_layerBlendingComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &DeviceControlWidget::onPreviewLayerBlendingChanged); connect(m_layerAutoButton, &QPushButton::clicked, - this, &DeviceControlWidget::onPreviewLayerAutoClicked); + this, [this]() { m_workspace->autoLayerLevels(currentLayerKey()); }); connect(m_layerFullRangeButton, &QPushButton::clicked, - this, &DeviceControlWidget::onPreviewLayerFullRangeClicked); + this, [this]() { m_workspace->fullLayerLevels(currentLayerKey()); }); connect(m_layerAutoStretchCheckBox, &QCheckBox::toggled, this, &DeviceControlWidget::onPreviewLayerAutoStretchToggled); m_layerHistogramWidget->setOnLevelsChanged([this](int minLevel, int maxLevel) @@ -895,17 +894,14 @@ namespace scopeone::ui if (m_sceneModel->findLayer(layerKey, layer)) { const int domainMax = layer.display.levelDomainMax > 0 ? layer.display.levelDomainMax : 255; - if (m_workspace) - { - m_workspace->setLayerAutoStretchEnabled(layerKey, false); - } + m_workspace->setLayerAutoStretchEnabled(layerKey, false); m_layerAutoStretchCheckBox->setChecked(false); m_sceneModel->setLayerDisplayLevels(layerKey, minLevel, maxLevel, domainMax); } }); m_layerHistogramWidget->setOnAutoLevelsRequested([this]() { - onPreviewLayerAutoClicked(); + m_workspace->autoLayerLevels(currentLayerKey()); }); const auto applySourceTransform = [this]() { @@ -932,14 +928,14 @@ namespace scopeone::ui this, [applySourceTransform](bool) { applySourceTransform(); }); connect(m_alignFlipYCheckBox, &QCheckBox::toggled, this, [applySourceTransform](bool) { applySourceTransform(); }); - connect(m_alignResetButton, &QPushButton::clicked, + connect(alignResetButton, &QPushButton::clicked, this, [this]() { resetSelectedLayerTransform(); }); refreshPreviewLayerSettings(); - return m_previewControlsGroup; + return previewControlsGroup; } // Rebuilds the preview layer table @@ -1042,10 +1038,7 @@ namespace scopeone::ui { QSignalBlocker tableBlocker(m_layerTable); m_layerTable->setCurrentCell(row, 1); - if (m_workspace) - { - m_workspace->setActiveLayerKey(nextLayerKey); - } + m_workspace->setActiveLayerKey(nextLayerKey); break; } } @@ -1220,16 +1213,6 @@ namespace scopeone::ui m_layerOpacitySpinBox->setEnabled(m_layerBlendingComboBox->currentText() != QStringLiteral("Opaque")); } - void DeviceControlWidget::onPreviewLayerAutoClicked() - { - m_workspace->autoLayerLevels(currentLayerKey()); - } - - void DeviceControlWidget::onPreviewLayerFullRangeClicked() - { - m_workspace->fullLayerLevels(currentLayerKey()); - } - void DeviceControlWidget::onPreviewLayerAutoStretchToggled(bool enabled) { m_workspace->setLayerAutoStretchEnabled(currentLayerKey(), enabled); @@ -1239,31 +1222,12 @@ namespace scopeone::ui { QTableWidgetItem* item = m_layerTable->item(currentRow, 1); const QString layerKey = item ? item->data(Qt::UserRole).toString() : QString(); - if (m_workspace) - { - m_workspace->setActiveLayerKey(layerKey); - } + m_workspace->setActiveLayerKey(layerKey); refreshPreviewLayerSettings(); refreshLayerHistogram(); updateControlsState(); } - void DeviceControlWidget::onPreviewLayerMoveUpClicked() - { - m_sceneModel->moveLayer(currentLayerKey(), -1); - } - - void DeviceControlWidget::onPreviewLayerMoveDownClicked() - { - m_sceneModel->moveLayer(currentLayerKey(), 1); - } - - void DeviceControlWidget::onPreviewLayerRemoveClicked() - { - m_scopeonecore->removeStaticFrame( - scopeone::core::ScopeOneCore::sourceIdFromLayerKey(currentLayerKey())); - } - void DeviceControlWidget::onPreviewLayerDuplicateClicked() { const QString sourceLayerKey = currentLayerKey(); diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index 6c45110..942e5ba 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -101,16 +101,11 @@ namespace scopeone::ui void onPreviewLayerGammaChanged(double value); void onPreviewLayerColormapChanged(int index); void onPreviewLayerBlendingChanged(int index); - void onPreviewLayerAutoClicked(); - void onPreviewLayerFullRangeClicked(); void onPreviewLayerAutoStretchToggled(bool enabled); void onPreviewLayerSelectionChanged(int currentRow, int currentColumn, int previousRow, int previousColumn); - void onPreviewLayerMoveUpClicked(); - void onPreviewLayerMoveDownClicked(); - void onPreviewLayerRemoveClicked(); void onPreviewLayerImportClicked(); void onPreviewLayerDuplicateClicked(); void onPreviewLayerTableItemChanged(QTableWidgetItem* item); @@ -137,7 +132,6 @@ namespace scopeone::ui scopeone::core::ImageSceneModel* m_sceneModel{nullptr}; QScrollArea* m_imageControlsWidget{nullptr}; QScrollArea* m_hardwareControlsWidget{nullptr}; - QGroupBox* m_previewControlsGroup{nullptr}; QGroupBox* m_cameraControlsGroup{nullptr}; QGroupBox* m_stageControlsGroup{nullptr}; QTableWidget* m_layerTable{nullptr}; @@ -147,7 +141,6 @@ namespace scopeone::ui QPushButton* m_layerMoveUpButton{nullptr}; QPushButton* m_layerMoveDownButton{nullptr}; QPushButton* m_layerRemoveButton{nullptr}; - QPushButton* m_layerImportButton{nullptr}; QSpinBox* m_layerOpacitySpinBox{nullptr}; QDoubleSpinBox* m_layerGammaSpinBox{nullptr}; QComboBox* m_layerColormapComboBox{nullptr}; @@ -158,23 +151,15 @@ namespace scopeone::ui QCheckBox* m_clippingCheckBox{nullptr}; QCheckBox* m_scaleBarCheckBox{nullptr}; LayerHistogramWidget* m_layerHistogramWidget{nullptr}; - QCheckBox* m_layerHistogramLogCheckBox{nullptr}; - QGroupBox* m_layerHistogramGroup{nullptr}; - QGroupBox* m_surfaceViewGroup{nullptr}; QComboBox* m_viewDimensionCombo{nullptr}; QSlider* m_3dZScaleSlider{nullptr}; QDoubleSpinBox* m_3dZScaleSpinBox{nullptr}; QCheckBox* m_3dWireframeCheckBox{nullptr}; - QPushButton* m_3dResetButton{nullptr}; - QLabel* m_alignXLabel{nullptr}; QSpinBox* m_alignXSpinBox{nullptr}; - QLabel* m_alignYLabel{nullptr}; QSpinBox* m_alignYSpinBox{nullptr}; - QLabel* m_alignZoomLabel{nullptr}; QSpinBox* m_alignZoomSpinBox{nullptr}; QCheckBox* m_alignFlipXCheckBox{nullptr}; QCheckBox* m_alignFlipYCheckBox{nullptr}; - QPushButton* m_alignResetButton{nullptr}; PreviewWidget* m_previewWidget{nullptr}; QLineEdit* m_exposureLineEdit{nullptr}; diff --git a/src/DevicePropertyWidget.cpp b/src/DevicePropertyWidget.cpp index dd20726..e2d40c0 100644 --- a/src/DevicePropertyWidget.cpp +++ b/src/DevicePropertyWidget.cpp @@ -106,7 +106,7 @@ namespace scopeone::ui auto* refreshButton = new QPushButton("Refresh", this); refreshButton->setMaximumWidth(60); - connect(refreshButton, &QPushButton::clicked, this, &DevicePropertyWidget::onRefreshClicked); + connect(refreshButton, &QPushButton::clicked, this, [this]() { refresh(false); }); auto* optionsButton = new QToolButton(this); optionsButton->setText("Options"); @@ -600,11 +600,6 @@ namespace scopeone::ui } } - // Refresh all property values from hardware - void DevicePropertyWidget::onRefreshClicked() - { - refresh(false); - } // Toggle read only property visibility void DevicePropertyWidget::onShowReadOnlyToggled(bool show) diff --git a/src/DevicePropertyWidget.h b/src/DevicePropertyWidget.h index cb4c5b4..bac4634 100644 --- a/src/DevicePropertyWidget.h +++ b/src/DevicePropertyWidget.h @@ -25,7 +25,6 @@ namespace scopeone::ui void errorOccurred(const QString& message); private: - void onRefreshClicked(); void onShowReadOnlyToggled(bool show); void onShowPreInitToggled(bool show); void onAutoRefreshToggled(bool enabled); diff --git a/src/ImageGalleryWidget.cpp b/src/ImageGalleryWidget.cpp index 40ee203..9312182 100644 --- a/src/ImageGalleryWidget.cpp +++ b/src/ImageGalleryWidget.cpp @@ -90,12 +90,6 @@ namespace scopeone::ui return session.capturePlan().cameraIds.size(); } - // Count buffered frames or streamed frames written to disk - qint64 sessionFrameCount(const RecordingSessionData& session) - { - return session.recordedFrameCount(); - } - // Build a square grayscale thumbnail from a stored camera frame QIcon frameThumbnail(const scopeone::core::ImageFrame& frame) { @@ -518,7 +512,7 @@ namespace scopeone::ui { details.append(sourceName); } - details.append(QStringLiteral("%1 frame(s)").arg(sessionFrameCount(session))); + details.append(QStringLiteral("%1 frame(s)").arg(session.recordedFrameCount())); details.append(QStringLiteral("%1 camera(s)").arg(sessionCameraCount(session))); details.append(saveState); return details.join(QStringLiteral(", ")); diff --git a/src/ImageProcessingWidget.cpp b/src/ImageProcessingWidget.cpp index 07d6a1f..8ba657b 100644 --- a/src/ImageProcessingWidget.cpp +++ b/src/ImageProcessingWidget.cpp @@ -571,36 +571,36 @@ namespace scopeone::ui contentLayout->setContentsMargins(5, 5, 5, 5); QWidget* pipelineGroup = setupModuleList(); QWidget* parametersGroup = setupModuleConfig(); - setupExecutionControls(); - setupInputControls(); - contentLayout->addWidget(m_inputControlsWidget); + QWidget* executionGroup = setupExecutionControls(); + QWidget* inputGroup = setupInputControls(); + contentLayout->addWidget(inputGroup); contentLayout->addWidget(pipelineGroup); contentLayout->addWidget(parametersGroup, 1); - contentLayout->addWidget(m_executionControlsWidget); + contentLayout->addWidget(executionGroup); scrollArea->setWidget(content); mainLayout->addWidget(scrollArea); } - void ImageProcessingWidget::setupInputControls() + QWidget* ImageProcessingWidget::setupInputControls() { - m_inputControlsWidget = new QGroupBox(tr("Pipeline Input"), this); - auto* layout = new QGridLayout(m_inputControlsWidget); - m_liveModeRadio = new QRadioButton(tr("Live Stream"), m_inputControlsWidget); - m_staticModeRadio = new QRadioButton(tr("Image / Stack"), m_inputControlsWidget); + auto* inputControlsGroup = new QGroupBox(tr("Pipeline Input"), this); + auto* layout = new QGridLayout(inputControlsGroup); + m_liveModeRadio = new QRadioButton(tr("Live Stream"), inputControlsGroup); + m_staticModeRadio = new QRadioButton(tr("Image / Stack"), inputControlsGroup); m_liveModeRadio->setChecked(true); layout->addWidget(m_liveModeRadio, 0, 0); layout->addWidget(m_staticModeRadio, 0, 1); - m_sourceLabel = new QLabel(m_inputControlsWidget); + m_sourceLabel = new QLabel(inputControlsGroup); layout->addWidget(m_sourceLabel, 1, 0); - m_liveSourceCombo = new QComboBox(m_inputControlsWidget); + m_liveSourceCombo = new QComboBox(inputControlsGroup); m_liveSourceCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); layout->addWidget(m_liveSourceCombo, 1, 1, 1, 2); - m_sourceCombo = new QComboBox(m_inputControlsWidget); + m_sourceCombo = new QComboBox(inputControlsGroup); m_sourceCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); layout->addWidget(m_sourceCombo, 1, 1, 1, 2); - m_rangeLabel = new QLabel(tr("Range"), m_inputControlsWidget); + m_rangeLabel = new QLabel(tr("Range"), inputControlsGroup); layout->addWidget(m_rangeLabel, 2, 0); - m_offlineScopeCombo = new QComboBox(m_inputControlsWidget); + m_offlineScopeCombo = new QComboBox(inputControlsGroup); m_offlineScopeCombo->addItem(tr("Current frame"), false); m_offlineScopeCombo->addItem(tr("Entire stack"), true); layout->addWidget(m_offlineScopeCombo, 2, 1, 1, 2); @@ -621,14 +621,15 @@ namespace scopeone::ui connect(m_offlineScopeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { updateRunButtons(); }); onInputModeChanged(); + return inputControlsGroup; } - void ImageProcessingWidget::setupExecutionControls() + QWidget* ImageProcessingWidget::setupExecutionControls() { - m_executionControlsWidget = new QGroupBox(tr("Pipeline Output & Execution"), this); - auto* layout = new QGridLayout(m_executionControlsWidget); - layout->addWidget(new QLabel(tr("Bit depth"), m_executionControlsWidget), 0, 0); - m_processingBitDepthCombo = new QComboBox(m_executionControlsWidget); + auto* executionControlsGroup = new QGroupBox(tr("Pipeline Output & Execution"), this); + auto* layout = new QGridLayout(executionControlsGroup); + layout->addWidget(new QLabel(tr("Bit depth"), executionControlsGroup), 0, 0); + m_processingBitDepthCombo = new QComboBox(executionControlsGroup); m_processingBitDepthCombo->addItem( tr("8-bit"), static_cast(scopeone::core::ProcessingBitDepth::Bit8)); m_processingBitDepthCombo->addItem( @@ -636,8 +637,8 @@ namespace scopeone::ui connect(m_processingBitDepthCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &ImageProcessingWidget::onProcessingBitDepthChanged); layout->addWidget(m_processingBitDepthCombo, 0, 1); - m_executeButton = new QPushButton(m_executionControlsWidget); - m_cancelProcessingButton = new QPushButton(tr("Cancel"), m_executionControlsWidget); + m_executeButton = new QPushButton(executionControlsGroup); + m_cancelProcessingButton = new QPushButton(tr("Cancel"), executionControlsGroup); m_cancelProcessingButton->setEnabled(false); connect(m_executeButton, &QPushButton::clicked, this, [this]() @@ -655,9 +656,10 @@ namespace scopeone::ui this, &ImageProcessingWidget::onCancelProcessing); layout->addWidget(m_executeButton, 1, 0, 1, 2); layout->addWidget(m_cancelProcessingButton, 1, 2); - m_processingProgress = new QProgressBar(m_executionControlsWidget); + m_processingProgress = new QProgressBar(executionControlsGroup); m_processingProgress->setVisible(false); layout->addWidget(m_processingProgress, 2, 0, 1, 3); + return executionControlsGroup; } void ImageProcessingWidget::onInputModeChanged() diff --git a/src/ImageProcessingWidget.h b/src/ImageProcessingWidget.h index e903ac8..fc696f2 100644 --- a/src/ImageProcessingWidget.h +++ b/src/ImageProcessingWidget.h @@ -50,8 +50,8 @@ namespace scopeone::ui void onCancelProcessing(); void setupUI(); - void setupInputControls(); - void setupExecutionControls(); + QWidget* setupInputControls(); + QWidget* setupExecutionControls(); QWidget* setupModuleList(); QWidget* setupModuleConfig(); void updateProcessingSettings(); @@ -65,8 +65,6 @@ namespace scopeone::ui scopeone::core::ScopeOneCore* m_scopeonecore{nullptr}; ImageWorkspace* m_workspace{nullptr}; bool m_processingRunning{false}; - QWidget* m_inputControlsWidget{nullptr}; - QWidget* m_executionControlsWidget{nullptr}; QComboBox* m_sourceCombo{nullptr}; QRadioButton* m_liveModeRadio{nullptr}; QRadioButton* m_staticModeRadio{nullptr}; diff --git a/src/ImageToolsDialog.cpp b/src/ImageToolsDialog.cpp index 928271c..57b85d0 100644 --- a/src/ImageToolsDialog.cpp +++ b/src/ImageToolsDialog.cpp @@ -273,13 +273,13 @@ namespace scopeone::ui // Start a grid mosaic from the current stage position void StageMosaicDialog::startMosaic() { - m_activeCameraId = selectedCameraId(); + const QString activeCameraId = selectedCameraId(); ScopeOneCore::StageMosaicPlan plan; - plan.cameraId = m_activeCameraId; + plan.cameraId = activeCameraId; plan.xyStageId = selectedStageId(); plan.rows = m_rowsSpinBox->value(); plan.columns = m_columnsSpinBox->value(); - plan.pixelSizeUm = m_core->cameraPixelSizeUm(m_activeCameraId); + plan.pixelSizeUm = m_core->cameraPixelSizeUm(activeCameraId); plan.stepXUm = m_stepXSpinBox->value(); plan.stepYUm = m_stepYSpinBox->value(); plan.settleMs = m_settleMsSpinBox->value(); @@ -304,7 +304,7 @@ namespace scopeone::ui } setMosaicRunning(true); m_statusLabel->setText(tr("Starting mosaic capture")); - m_context.showLayers({scopeone::core::ScopeOneCore::rawLayerKey(m_activeCameraId)}); + m_context.showLayers({scopeone::core::ScopeOneCore::rawLayerKey(activeCameraId)}); } // Request cancellation after the current stage move diff --git a/src/ImageToolsDialog.h b/src/ImageToolsDialog.h index 9c8ed41..adae22e 100644 --- a/src/ImageToolsDialog.h +++ b/src/ImageToolsDialog.h @@ -67,7 +67,6 @@ namespace scopeone::ui QPushButton* m_startButton{nullptr}; QPushButton* m_stopButton{nullptr}; QLabel* m_statusLabel{nullptr}; - QString m_activeCameraId; }; class ParticleDetectionDialog : public QDialog diff --git a/src/ImageWorkspace.cpp b/src/ImageWorkspace.cpp index e442826..5c4e3e6 100644 --- a/src/ImageWorkspace.cpp +++ b/src/ImageWorkspace.cpp @@ -487,6 +487,10 @@ namespace scopeone::ui auto applyZoomText = [this]() { PreviewWidget* preview = activePreviewWidget(); + if (!preview) + { + return; + } const QString text = m_zoomCombo->currentText().trimmed(); if (text.compare(QStringLiteral("Fit"), Qt::CaseInsensitive) == 0) { diff --git a/src/InspectWidget.cpp b/src/InspectWidget.cpp index 3558876..7a9aff0 100644 --- a/src/InspectWidget.cpp +++ b/src/InspectWidget.cpp @@ -602,18 +602,18 @@ namespace scopeone::ui annotationLayout->addWidget(m_measurementInfoLabel); contentLayout->addWidget(annotationGroup); - m_crossSectionGroup = new QGroupBox(QStringLiteral("Cross Section"), contentContainer); - auto* crossSectionLayout = new QVBoxLayout(m_crossSectionGroup); + auto* crossSectionGroup = new QGroupBox(QStringLiteral("Cross Section"), contentContainer); + auto* crossSectionLayout = new QVBoxLayout(crossSectionGroup); auto* crossSectionButtons = new QHBoxLayout(); - m_drawCrossSectionButton = new QPushButton(QStringLiteral("Intensity Profile"), m_crossSectionGroup); - m_clearCrossSectionButton = new QPushButton(QStringLiteral("Clear Profile"), m_crossSectionGroup); + m_drawCrossSectionButton = new QPushButton(QStringLiteral("Intensity Profile"), crossSectionGroup); + m_clearCrossSectionButton = new QPushButton(QStringLiteral("Clear Profile"), crossSectionGroup); crossSectionButtons->addWidget(m_drawCrossSectionButton); crossSectionButtons->addWidget(m_clearCrossSectionButton); crossSectionButtons->addStretch(); crossSectionLayout->addLayout(crossSectionButtons); - m_crossSectionWidget = new InspectCrossSectionWidget(m_crossSectionGroup); + m_crossSectionWidget = new InspectCrossSectionWidget(crossSectionGroup); crossSectionLayout->addWidget(m_crossSectionWidget); - contentLayout->addWidget(m_crossSectionGroup); + contentLayout->addWidget(crossSectionGroup); contentLayout->addStretch(); diff --git a/src/InspectWidget.h b/src/InspectWidget.h index a1fc1c1..33fe1df 100644 --- a/src/InspectWidget.h +++ b/src/InspectWidget.h @@ -114,7 +114,6 @@ namespace scopeone::ui QPushButton* m_clearMeasurementLinesButton{nullptr}; QLabel* m_measurementInfoLabel{nullptr}; InspectCrossSectionWidget* m_crossSectionWidget{nullptr}; - QGroupBox* m_crossSectionGroup{nullptr}; QPushButton* m_drawCrossSectionButton{nullptr}; QPushButton* m_clearCrossSectionButton{nullptr}; bool m_cameraInitialized{false}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 2cccac6..79f3bb8 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -195,6 +195,15 @@ namespace scopeone::ui if (type == QStringLiteral("set_fit_to_window")) { PreviewWidget* preview = m_imageWorkspace->activePreviewWidget(); + if (!preview) + { + QJsonObject response; + response.insert(QStringLiteral("type"), type); + response.insert(QStringLiteral("ok"), false); + response.insert(QStringLiteral("error"), QStringLiteral("No active preview")); + callback(response); + return; + } preview->setFitToWindow( routedRequest.value(QStringLiteral("enabled")).toBool()); QJsonObject response; @@ -209,6 +218,15 @@ namespace scopeone::ui if (type == QStringLiteral("set_zoom")) { PreviewWidget* preview = m_imageWorkspace->activePreviewWidget(); + if (!preview) + { + QJsonObject response; + response.insert(QStringLiteral("type"), type); + response.insert(QStringLiteral("ok"), false); + response.insert(QStringLiteral("error"), QStringLiteral("No active preview")); + callback(response); + return; + } preview->setFitToWindow(false); preview->setZoomPercent( routedRequest.value(QStringLiteral("zoomPercent")).toInt()); @@ -241,7 +259,17 @@ namespace scopeone::ui if (type == QStringLiteral("draw_roi")) { - m_imageWorkspace->activePreviewWidget()->startROIDrawing( + PreviewWidget* preview = m_imageWorkspace->activePreviewWidget(); + if (!preview) + { + QJsonObject response; + response.insert(QStringLiteral("type"), type); + response.insert(QStringLiteral("ok"), false); + response.insert(QStringLiteral("error"), QStringLiteral("No active preview")); + callback(response); + return; + } + preview->startROIDrawing( routedRequest.value(QStringLiteral("camera")).toString()); QJsonObject response; response.insert(QStringLiteral("type"), type); @@ -617,41 +645,55 @@ namespace scopeone::ui connect(m_inspectWidget, &InspectWidget::requestDrawCrossSectionLayer, this, [this](const QString& layerKey) { - m_imageWorkspace->activePreviewWidget()->startCrossSectionDrawingForLayer(layerKey); - showStatusMessage(tr("Drag a line on the preview"), 5000); + if (auto* preview = m_imageWorkspace->activePreviewWidget()) + { + preview->startCrossSectionDrawingForLayer(layerKey); + showStatusMessage(tr("Drag a line on the preview"), 5000); + } }); connect(m_inspectWidget, &InspectWidget::requestClearCrossSection, this, [this]() { - m_imageWorkspace->activePreviewWidget()->clearCrossSection(); + if (auto* preview = m_imageWorkspace->activePreviewWidget()) + { + preview->clearCrossSection(); + } }); connect(m_inspectWidget, &InspectWidget::requestDrawMeasurementLine, this, [this](const QString& layerKey) { - m_imageWorkspace->activePreviewWidget()->startMeasurementLineDrawingForLayer(layerKey); - showStatusMessage(tr("Drag a line on the preview"), 5000); + if (auto* preview = m_imageWorkspace->activePreviewWidget()) + { + preview->startMeasurementLineDrawingForLayer(layerKey); + showStatusMessage(tr("Drag a line on the preview"), 5000); + } }); connect(m_inspectWidget, &InspectWidget::requestClearMeasurementLines, this, [this](const QString& layerKey) { - m_imageWorkspace->activeSceneModel()->clearRole( - ImageSceneModel::MarkupRole::Measurement, layerKey); + if (auto* sceneModel = m_imageWorkspace->activeSceneModel()) + { + sceneModel->clearRole( + ImageSceneModel::MarkupRole::Measurement, layerKey); + } m_inspectWidget->clearMeasurementLine(); }); connect(m_imageWorkspace, &ImageWorkspace::measurementLineDrawn, this, [this](const QString& layerKey, const QPoint& start, const QPoint& end) { - ImageSceneModel* sceneModel = m_imageWorkspace->activeSceneModel(); - const QString markupId = sceneModel->createLine( - layerKey, - start, - end, - QString(), - ImageSceneModel::MarkupRole::Measurement); - sceneModel->selectOnly(markupId); - showMeasurementLine(layerKey, start, end); + if (auto* sceneModel = m_imageWorkspace->activeSceneModel()) + { + const QString markupId = sceneModel->createLine( + layerKey, + start, + end, + QString(), + ImageSceneModel::MarkupRole::Measurement); + sceneModel->selectOnly(markupId); + showMeasurementLine(layerKey, start, end); + } }); connect(m_imageWorkspace, &ImageWorkspace::measurementLineInspected, this, [this](const QString& layerKey, @@ -730,19 +772,6 @@ namespace scopeone::ui showStatusMessage(message, success ? 5000 : 8000); }); - connect(m_exitAction, &QAction::triggered, this, &QWidget::close); - connect(m_fullScreenAction, &QAction::toggled, - this, &MainWindow::setFullScreenEnabled); - connect(m_aboutAction, &QAction::triggered, - this, [this]() { AboutDialog::showAbout(this); }); - connect(m_aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt); - connect(m_loadConfigurationAction, &QAction::triggered, - this, &MainWindow::loadConfigurationFromDialog); - connect(m_unloadConfigurationAction, &QAction::triggered, - this, &MainWindow::unloadConfigurationWithConfirmation); - connect(m_settingsAction, &QAction::triggered, - this, &MainWindow::openSettingsDialog); - connect(m_recordingWidget, &RecordingWidget::gallerySessionCaptured, this, [this](const std::shared_ptr& session) @@ -1115,79 +1144,86 @@ namespace scopeone::ui // Create application menus and persistent actions void MainWindow::setupMenuBar() { - m_fileMenu = menuBar()->addMenu(tr("&File")); - m_loadConfigurationAction = m_fileMenu->addAction(tr("&Load Configuration...")); - m_recentConfigurationsMenu = m_fileMenu->addMenu(tr("&Recent Configurations")); + auto* fileMenu = menuBar()->addMenu(tr("&File")); + m_loadConfigurationAction = fileMenu->addAction(tr("&Load Configuration...")); + connect(m_loadConfigurationAction, &QAction::triggered, + this, &MainWindow::loadConfigurationFromDialog); + m_recentConfigurationsMenu = fileMenu->addMenu(tr("&Recent Configurations")); connect(m_recentConfigurationsMenu, &QMenu::aboutToShow, this, &MainWindow::refreshRecentConfigurationsMenu); refreshRecentConfigurationsMenu(); - m_unloadConfigurationAction = m_fileMenu->addAction(tr("&Unload Configuration")); - m_fileMenu->addSeparator(); - m_importImageAction = m_fileMenu->addAction(tr("&Import Image as Layer...")); - m_importImageAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+I"))); - connect(m_importImageAction, &QAction::triggered, this, &MainWindow::openImportImageDialog); - m_saveImageAsAction = m_fileMenu->addAction(tr("Save Image &As...")); + m_unloadConfigurationAction = fileMenu->addAction(tr("&Unload Configuration")); + connect(m_unloadConfigurationAction, &QAction::triggered, + this, &MainWindow::unloadConfigurationWithConfirmation); + fileMenu->addSeparator(); + auto* importImageAction = fileMenu->addAction(tr("&Import Image as Layer...")); + importImageAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+I"))); + connect(importImageAction, &QAction::triggered, this, &MainWindow::openImportImageDialog); + m_saveImageAsAction = fileMenu->addAction(tr("Save Image &As...")); m_saveImageAsAction->setEnabled(false); connect(m_saveImageAsAction, &QAction::triggered, m_imageWorkspace, [this]() { m_imageWorkspace->saveDocumentAs(); }); - m_fileMenu->addSeparator(); - m_exitAction = m_fileMenu->addAction(tr("E&xit")); - - m_viewMenu = menuBar()->addMenu(tr("&View")); - m_fullScreenAction = m_viewMenu->addAction(tr("&Full Screen")); - m_fullScreenAction->setCheckable(true); - m_fullScreenAction->setShortcut(QKeySequence::FullScreen); + fileMenu->addSeparator(); + auto* exitAction = fileMenu->addAction(tr("E&xit")); + connect(exitAction, &QAction::triggered, this, &QWidget::close); + + auto* viewMenu = menuBar()->addMenu(tr("&View")); + auto* fullScreenAction = viewMenu->addAction(tr("&Full Screen")); + fullScreenAction->setCheckable(true); + fullScreenAction->setShortcut(QKeySequence::FullScreen); + connect(fullScreenAction, &QAction::toggled, + this, &MainWindow::setFullScreenEnabled); - m_fitToWindowAction = m_viewMenu->addAction(tr("Fit to &Window")); - m_fitToWindowAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+0"))); - connect(m_fitToWindowAction, &QAction::triggered, this, [this]() + auto* fitToWindowAction = viewMenu->addAction(tr("Fit to &Window")); + fitToWindowAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+0"))); + connect(fitToWindowAction, &QAction::triggered, this, [this]() { m_previewWidget->setFitToWindow(true); }); - m_actualSizeAction = m_viewMenu->addAction(tr("&Actual Size (100%)")); - m_actualSizeAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+1"))); - connect(m_actualSizeAction, &QAction::triggered, this, [this]() + auto* actualSizeAction = viewMenu->addAction(tr("&Actual Size (100%)")); + actualSizeAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+1"))); + connect(actualSizeAction, &QAction::triggered, this, [this]() { m_previewWidget->setFitToWindow(false); m_previewWidget->setZoomPercent(100); }); - m_zoomInAction = m_viewMenu->addAction(tr("Zoom &In")); - m_zoomInAction->setShortcuts({QKeySequence::ZoomIn, QKeySequence(QStringLiteral("Ctrl+=")), QKeySequence(QStringLiteral("Ctrl++"))}); - connect(m_zoomInAction, &QAction::triggered, this, [this]() + auto* zoomInAction = viewMenu->addAction(tr("Zoom &In")); + zoomInAction->setShortcuts({QKeySequence::ZoomIn, QKeySequence(QStringLiteral("Ctrl+=")), QKeySequence(QStringLiteral("Ctrl++"))}); + connect(zoomInAction, &QAction::triggered, this, [this]() { m_previewWidget->setFitToWindow(false); m_previewWidget->setZoomPercent(m_previewWidget->zoomPercent() + 20); }); - m_zoomOutAction = m_viewMenu->addAction(tr("Zoom &Out")); - m_zoomOutAction->setShortcut(QKeySequence::ZoomOut); - connect(m_zoomOutAction, &QAction::triggered, this, [this]() + auto* zoomOutAction = viewMenu->addAction(tr("Zoom &Out")); + zoomOutAction->setShortcut(QKeySequence::ZoomOut); + connect(zoomOutAction, &QAction::triggered, this, [this]() { m_previewWidget->setFitToWindow(false); m_previewWidget->setZoomPercent(m_previewWidget->zoomPercent() - 20); }); - m_viewMenu->addSeparator(); + viewMenu->addSeparator(); - m_scaleBarAction = m_viewMenu->addAction(tr("Show &Scale Bar")); - m_scaleBarAction->setCheckable(true); - m_scaleBarAction->setChecked(m_previewWidget->isScaleBarVisible()); - connect(m_scaleBarAction, &QAction::toggled, m_previewWidget, &PreviewWidget::setScaleBarVisible); - connect(m_previewWidget, &PreviewWidget::scaleBarVisibilityChanged, m_scaleBarAction, &QAction::setChecked); + auto* scaleBarAction = viewMenu->addAction(tr("Show &Scale Bar")); + scaleBarAction->setCheckable(true); + scaleBarAction->setChecked(m_previewWidget->isScaleBarVisible()); + connect(scaleBarAction, &QAction::toggled, m_previewWidget, &PreviewWidget::setScaleBarVisible); + connect(m_previewWidget, &PreviewWidget::scaleBarVisibilityChanged, scaleBarAction, &QAction::setChecked); - m_clippingAction = m_viewMenu->addAction(tr("Show &Saturation Warning (Hi-Lo)")); - m_clippingAction->setCheckable(true); - m_clippingAction->setShortcut(QKeySequence(Qt::Key_C)); - m_clippingAction->setChecked(m_previewWidget->isClippingWarningEnabled()); - connect(m_clippingAction, &QAction::toggled, m_previewWidget, &PreviewWidget::setClippingWarningEnabled); - connect(m_previewWidget, &PreviewWidget::clippingWarningChanged, m_clippingAction, &QAction::setChecked); + auto* clippingAction = viewMenu->addAction(tr("Show &Saturation Warning (Hi-Lo)")); + clippingAction->setCheckable(true); + clippingAction->setShortcut(QKeySequence(Qt::Key_C)); + clippingAction->setChecked(m_previewWidget->isClippingWarningEnabled()); + connect(clippingAction, &QAction::toggled, m_previewWidget, &PreviewWidget::setClippingWarningEnabled); + connect(m_previewWidget, &PreviewWidget::clippingWarningChanged, clippingAction, &QAction::setChecked); - m_toggleLayoutAction = m_viewMenu->addAction(tr("Toggle &Grid / Overlay Layout")); - m_toggleLayoutAction->setShortcut(QKeySequence(Qt::Key_G)); - m_toggleLayoutAction->setShortcutContext(Qt::ApplicationShortcut); - connect(m_toggleLayoutAction, &QAction::triggered, this, [this]() + auto* toggleLayoutAction = viewMenu->addAction(tr("Toggle &Grid / Overlay Layout")); + toggleLayoutAction->setShortcut(QKeySequence(Qt::Key_G)); + toggleLayoutAction->setShortcutContext(Qt::ApplicationShortcut); + connect(toggleLayoutAction, &QAction::triggered, this, [this]() { QWidget* focus = focusWidget(); if (focus && (qobject_cast(focus) || qobject_cast(focus) || qobject_cast(focus))) @@ -1205,33 +1241,41 @@ namespace scopeone::ui showStatusMessage(tr("Layout: Grid Split View"), 2000); } }); - addAction(m_toggleLayoutAction); + addAction(toggleLayoutAction); - m_toggleDimensionAction = m_viewMenu->addAction(tr("3D Surface View")); + m_toggleDimensionAction = viewMenu->addAction(tr("3D Surface View")); m_toggleDimensionAction->setCheckable(true); m_toggleDimensionAction->setShortcuts({QKeySequence(QStringLiteral("Ctrl+3")), QKeySequence(Qt::Key_D)}); m_toggleDimensionAction->setShortcutContext(Qt::ApplicationShortcut); connect(m_toggleDimensionAction, &QAction::toggled, this, [this](bool enabled) { - m_imageWorkspace->activePreviewWidget()->setViewDimensionMode( - enabled ? PreviewWidget::ViewDimensionMode::ThreeDimensional - : PreviewWidget::ViewDimensionMode::TwoDimensional); + if (auto* preview = m_imageWorkspace->activePreviewWidget()) + { + preview->setViewDimensionMode( + enabled ? PreviewWidget::ViewDimensionMode::ThreeDimensional + : PreviewWidget::ViewDimensionMode::TwoDimensional); + } }); addAction(m_toggleDimensionAction); - m_reset3dAction = m_viewMenu->addAction(tr("Reset 3D Camera")); + m_reset3dAction = viewMenu->addAction(tr("Reset 3D Camera")); connect(m_reset3dAction, &QAction::triggered, this, - [this]() { m_imageWorkspace->activePreviewWidget()->reset3dCamera(); }); + [this]() { + if (auto* preview = m_imageWorkspace->activePreviewWidget()) + { + preview->reset3dCamera(); + } + }); m_reset3dAction->setEnabled(false); - m_viewMenu->addSeparator(); - m_dockWidgetsMenu = m_viewMenu->addMenu(tr("&Dock Widgets")); + viewMenu->addSeparator(); + m_dockWidgetsMenu = viewMenu->addMenu(tr("&Dock Widgets")); - m_togglePreviewAction = new QAction(tr("Toggle Live Preview"), this); - m_togglePreviewAction->setShortcut(QKeySequence(Qt::Key_Space)); - m_togglePreviewAction->setShortcutContext(Qt::ApplicationShortcut); - connect(m_togglePreviewAction, &QAction::triggered, this, [this]() + auto* togglePreviewAction = new QAction(tr("Toggle Live Preview"), this); + togglePreviewAction->setShortcut(QKeySequence(Qt::Key_Space)); + togglePreviewAction->setShortcutContext(Qt::ApplicationShortcut); + connect(togglePreviewAction, &QAction::triggered, this, [this]() { QWidget* focus = focusWidget(); if (focus && (qobject_cast(focus) || qobject_cast(focus) || qobject_cast(focus))) @@ -1247,21 +1291,21 @@ namespace scopeone::ui m_scopeonecore->startPreview(m_currentControlTarget); } }); - addAction(m_togglePreviewAction); + addAction(togglePreviewAction); - m_snapAction = new QAction(tr("Snap"), this); - m_snapAction->setShortcuts({QKeySequence(Qt::CTRL | Qt::Key_Return), QKeySequence(Qt::CTRL | Qt::Key_Enter)}); - m_snapAction->setShortcutContext(Qt::ApplicationShortcut); - connect(m_snapAction, &QAction::triggered, this, [this]() + auto* snapAction = new QAction(tr("Snap"), this); + snapAction->setShortcuts({QKeySequence(Qt::CTRL | Qt::Key_Return), QKeySequence(Qt::CTRL | Qt::Key_Enter)}); + snapAction->setShortcutContext(Qt::ApplicationShortcut); + connect(snapAction, &QAction::triggered, this, [this]() { m_recordingWidget->snapToGallery(m_currentControlTarget); }); - addAction(m_snapAction); + addAction(snapAction); - m_autoContrastAction = new QAction(tr("Auto Contrast"), this); - m_autoContrastAction->setShortcut(QKeySequence(Qt::Key_A)); - m_autoContrastAction->setShortcutContext(Qt::ApplicationShortcut); - connect(m_autoContrastAction, &QAction::triggered, this, [this]() + auto* autoContrastAction = new QAction(tr("Auto Contrast"), this); + autoContrastAction->setShortcut(QKeySequence(Qt::Key_A)); + autoContrastAction->setShortcutContext(Qt::ApplicationShortcut); + connect(autoContrastAction, &QAction::triggered, this, [this]() { QWidget* focus = focusWidget(); if (focus && (qobject_cast(focus) || qobject_cast(focus) || qobject_cast(focus))) @@ -1274,27 +1318,32 @@ namespace scopeone::ui m_imageWorkspace->autoLayerLevels(activeLayer); } }); - addAction(m_autoContrastAction); + addAction(autoContrastAction); - m_toolsMenu = menuBar()->addMenu(tr("&Tools")); - m_toolRegistry->populateMenu(m_toolsMenu, this); - m_toolsMenu->addSeparator(); - auto* pluginManagerAction = m_toolsMenu->addAction(tr("Plugin &Manager...")); + auto* toolsMenu = menuBar()->addMenu(tr("&Tools")); + m_toolRegistry->populateMenu(toolsMenu, this); + toolsMenu->addSeparator(); + auto* pluginManagerAction = toolsMenu->addAction(tr("Plugin &Manager...")); connect(pluginManagerAction, &QAction::triggered, this, [this]() { PluginManagerDialog(this).exec(); }); - m_settingsAction = m_toolsMenu->addAction(tr("&Settings...")); + auto* settingsAction = toolsMenu->addAction(tr("&Settings...")); + connect(settingsAction, &QAction::triggered, + this, &MainWindow::openSettingsDialog); - m_helpMenu = menuBar()->addMenu(tr("&Help")); - auto* reportProblemAction = m_helpMenu->addAction(tr("Report a &Problem...")); + auto* helpMenu = menuBar()->addMenu(tr("&Help")); + auto* reportProblemAction = helpMenu->addAction(tr("Report a &Problem...")); connect(reportProblemAction, &QAction::triggered, this, []() { QDesktopServices::openUrl(QUrl(QStringLiteral( "https://github.com/Experimental-Microscopy-Lab/ScopeOne/issues"))); }); - m_helpMenu->addSeparator(); - m_aboutQtAction = m_helpMenu->addAction(tr("About &Qt")); - m_aboutAction = m_helpMenu->addAction(tr("&About ScopeOne")); + helpMenu->addSeparator(); + auto* aboutQtAction = helpMenu->addAction(tr("About &Qt")); + connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt); + auto* aboutAction = helpMenu->addAction(tr("&About ScopeOne")); + connect(aboutAction, &QAction::triggered, + this, [this]() { AboutDialog::showAbout(this); }); } // Create the camera control dock diff --git a/src/MainWindow.h b/src/MainWindow.h index 894f7e6..c78e163 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -153,34 +153,14 @@ namespace scopeone::ui QDockWidget* m_consoleDockWidget{nullptr}; InspectWidget* m_inspectWidget{nullptr}; - QMenu* m_fileMenu{nullptr}; QMenu* m_recentConfigurationsMenu{nullptr}; - QMenu* m_viewMenu{nullptr}; - QMenu* m_toolsMenu{nullptr}; - QMenu* m_helpMenu{nullptr}; QMenu* m_dockWidgetsMenu{nullptr}; - QAction* m_exitAction{nullptr}; - QAction* m_fullScreenAction{nullptr}; - QAction* m_fitToWindowAction{nullptr}; - QAction* m_actualSizeAction{nullptr}; - QAction* m_zoomInAction{nullptr}; - QAction* m_zoomOutAction{nullptr}; - QAction* m_scaleBarAction{nullptr}; - QAction* m_clippingAction{nullptr}; - QAction* m_toggleLayoutAction{nullptr}; QAction* m_toggleDimensionAction{nullptr}; QAction* m_reset3dAction{nullptr}; - QAction* m_togglePreviewAction{nullptr}; - QAction* m_snapAction{nullptr}; - QAction* m_autoContrastAction{nullptr}; QAction* m_loadConfigurationAction{nullptr}; QAction* m_unloadConfigurationAction{nullptr}; QAction* m_saveImageAsAction{nullptr}; - QAction* m_importImageAction{nullptr}; - QAction* m_settingsAction{nullptr}; - QAction* m_aboutAction{nullptr}; - QAction* m_aboutQtAction{nullptr}; QPointer m_loadConfigProgress; QPointer m_closeSaveProgress; From 5ff23286224e5ecfa36c33023ea4876a584689e4 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:28:41 +0200 Subject: [PATCH 33/37] Refine Python examples and fix DHM input preview --- .../examples/example_config_stage_record.py | 33 +++++++++++ .../examples/example_raw_stream_process.py | 59 +++++-------------- plugins/tools/DhmTool/DhmToolPlugin.cpp | 18 ++++-- 3 files changed, 61 insertions(+), 49 deletions(-) create mode 100644 ScopeOneCore/python/scopeone/examples/example_config_stage_record.py diff --git a/ScopeOneCore/python/scopeone/examples/example_config_stage_record.py b/ScopeOneCore/python/scopeone/examples/example_config_stage_record.py new file mode 100644 index 0000000..d033329 --- /dev/null +++ b/ScopeOneCore/python/scopeone/examples/example_config_stage_record.py @@ -0,0 +1,33 @@ +from scopeone import ScopeOne + + +CONFIG_PATH = r"C:\Users\T7910\Documents\Source\cpp\ScopeOne\config\MMConfig_demo.cfg" +SAVE_DIR = r"C:\Users\T7910\Downloads" + + +def main() -> None: + with ScopeOne() as scope: + scope.load_config(CONFIG_PATH) + + camera = scope.camera_ids()[0] + scope.set_exposure(10.0, camera) + + scope.set_property(camera, "Mode", "Fluorescent Beads") + + stage = scope.current_xy_stage_device() + print("Position:", scope.read_xy_position(stage)) + + with scope.record(frames=10, camera=camera) as first: + first.save(SAVE_DIR, "before_move", format="ome-tiff") + + scope.move_xy_relative(10.0, 0.0, stage) + print("Position:", scope.read_xy_position(stage)) + + with scope.record(frames=10, camera=camera) as second: + second.save(SAVE_DIR, "after_move", format="ome-tiff") + + scope.unload_config() + + +if __name__ == "__main__": + main() diff --git a/ScopeOneCore/python/scopeone/examples/example_raw_stream_process.py b/ScopeOneCore/python/scopeone/examples/example_raw_stream_process.py index 4bd9854..22b0315 100644 --- a/ScopeOneCore/python/scopeone/examples/example_raw_stream_process.py +++ b/ScopeOneCore/python/scopeone/examples/example_raw_stream_process.py @@ -1,68 +1,41 @@ -"""Demonstrate real-time raw frame export, Python processing, and display reinjection.""" +"""Invert a live camera stream in Python and show it beside the raw image.""" import time -import numpy as np from scopeone import ScopeOne def main() -> None: - with ScopeOne() as scopeone: - cameras = scopeone.camera_ids() - if not cameras: - raise RuntimeError( - "No cameras available. Start ScopeOne and load a configuration first." - ) - - camera_id = cameras[0] - print(f"Using camera: {camera_id}") - - # Ensure live preview is active - scopeone.start_preview(camera_id) - - # Set display to side-by-side comparison mode - scopeone.set_layer_layout("side_by_side") - + with ScopeOne() as scope: + camera = scope.camera_ids()[0] layer_id = "python_stream" - layer_name = "Python Processed" layer_key = f"static:{layer_id}" - last_frame_index = -1 - print("Streaming started. View ScopeOne side-by-side preview. Press Ctrl+C to stop...") + scope.start_preview(camera) + scope.set_layer_layout("side_by_side") + print(f"Streaming {camera}. Press Ctrl+C to stop.") + last_frame = -1 try: while True: - # 1. Pull latest raw frame - frame = scopeone.latest_raw_frame(camera_id) - - # Avoid redundant computation on the same frame - if frame.frame_index == last_frame_index: + frame = scope.latest_raw_frame(camera) + if frame.frame_index == last_frame: time.sleep(0.005) continue - last_frame_index = frame.frame_index - - raw_img = frame.image + last_frame = frame.frame_index - # 2. Custom processing logic (example: negative / intensity inversion) max_val = (1 << frame.bits_per_sample) - 1 - processed_img = max_val - raw_img - - # 3. Reinject processed array into ScopeOne display - scopeone.show_image( - processed_img, + scope.show_image( + max_val - frame.image, layer_id=layer_id, - name=layer_name, - camera=camera_id, + name="Python Processed", + camera=camera, bits_per_sample=frame.bits_per_sample, ) except KeyboardInterrupt: - print("\nStopping stream...") + print("\nStopped.") finally: - try: - scopeone.remove_static_layer(layer_key) - except Exception: - pass - print("Cleaned up Python layer.") + scope.remove_static_layer(layer_key) if __name__ == "__main__": diff --git a/plugins/tools/DhmTool/DhmToolPlugin.cpp b/plugins/tools/DhmTool/DhmToolPlugin.cpp index 4ea6a02..c3b0052 100644 --- a/plugins/tools/DhmTool/DhmToolPlugin.cpp +++ b/plugins/tools/DhmTool/DhmToolPlugin.cpp @@ -248,17 +248,22 @@ namespace connect(m_context.core().imageSceneModel(), &scopeone::core::ImageSceneModel::layersChanged, this, - [this]() { refreshLayerComboBox(); }); + [this]() + { + const QString previousSelection = m_layerComboBox->currentData().toString(); + refreshLayerComboBox(); + if (previousSelection != m_layerComboBox->currentData().toString()) + { + startReconstruction(); + } + }); connect(m_layerComboBox, &QComboBox::currentIndexChanged, this, [this]() { const QString layerKey = m_layerComboBox->currentData().toString(); m_sourceId = scopeone::core::ScopeOneCore::sourceIdFromLayerKey(layerKey); m_stream->setSourceId(m_sourceId); - if (!m_liveCheckBox->isChecked()) - { - startReconstruction(); - } + startReconstruction(); }); m_spectrumView->setSelectionChanged([this](const QPoint& offset) @@ -442,7 +447,8 @@ namespace frame.width, frame.height, frame.stride, - QImage::Format_Grayscale16); + frame.isMono16() ? QImage::Format_Grayscale16 + : QImage::Format_Grayscale8); preview->setPixmap(QPixmap::fromImage(image).scaled( preview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } From fc675538ef826f724f605e7de9c98d766959cbc4 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 8 Sep 2026 20:24:49 +0200 Subject: [PATCH 34/37] Add simulated PMT scan source --- plugins/hardware/CMakeLists.txt | 1 + plugins/hardware/SimulatedPmt/CMakeLists.txt | 14 + .../SimulatedPmt/SimulatedPmtPlugin.cpp | 141 ++++++ .../SimulatedPmt/SimulatedPmtPlugin.h | 22 + .../SimulatedPmt/SimulatedPmtSource.cpp | 467 ++++++++++++++++++ .../SimulatedPmt/SimulatedPmtSource.h | 56 +++ plugins/hardware/SimulatedPmt/plugin.json | 8 + .../ScanningDaqTool/SignalMonitorWidget.cpp | 17 + .../ScanningDaqTool/SignalMonitorWidget.h | 1 + 9 files changed, 727 insertions(+) create mode 100644 plugins/hardware/SimulatedPmt/CMakeLists.txt create mode 100644 plugins/hardware/SimulatedPmt/SimulatedPmtPlugin.cpp create mode 100644 plugins/hardware/SimulatedPmt/SimulatedPmtPlugin.h create mode 100644 plugins/hardware/SimulatedPmt/SimulatedPmtSource.cpp create mode 100644 plugins/hardware/SimulatedPmt/SimulatedPmtSource.h create mode 100644 plugins/hardware/SimulatedPmt/plugin.json diff --git a/plugins/hardware/CMakeLists.txt b/plugins/hardware/CMakeLists.txt index e8232b8..b050fd7 100644 --- a/plugins/hardware/CMakeLists.txt +++ b/plugins/hardware/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(NIDaqmx) add_subdirectory(PtuFile) add_subdirectory(ExampleHardware) +add_subdirectory(SimulatedPmt) diff --git a/plugins/hardware/SimulatedPmt/CMakeLists.txt b/plugins/hardware/SimulatedPmt/CMakeLists.txt new file mode 100644 index 0000000..850510a --- /dev/null +++ b/plugins/hardware/SimulatedPmt/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(ScopeOneSimulatedPmt MODULE + SimulatedPmtPlugin.cpp + SimulatedPmtPlugin.h + SimulatedPmtSource.cpp + SimulatedPmtSource.h + plugin.json +) + +target_link_libraries(ScopeOneSimulatedPmt PRIVATE + scopeone::PluginSDK + Qt6::Core +) + +scopeone_add_plugin(ScopeOneSimulatedPmt hardware) diff --git a/plugins/hardware/SimulatedPmt/SimulatedPmtPlugin.cpp b/plugins/hardware/SimulatedPmt/SimulatedPmtPlugin.cpp new file mode 100644 index 0000000..83d2423 --- /dev/null +++ b/plugins/hardware/SimulatedPmt/SimulatedPmtPlugin.cpp @@ -0,0 +1,141 @@ +#include "SimulatedPmtPlugin.h" + +#include "SimulatedPmtSource.h" + +namespace scopeone::plugins +{ + namespace + { + constexpr auto kSourceId = "pmt:simulator"; + + scopeone::core::SignalParameterDescriptor realParameter( + const QString& key, + const QString& name, + double value, + double minimum, + double maximum, + const QString& suffix) + { + scopeone::core::SignalParameterDescriptor parameter; + parameter.key = key; + parameter.name = name; + parameter.type = scopeone::core::SignalParameterType::Real; + parameter.defaultValue = value; + parameter.hasRange = true; + parameter.minimum = minimum; + parameter.maximum = maximum; + parameter.suffix = suffix; + return parameter; + } + + scopeone::core::SignalParameterDescriptor choiceParameter( + const QString& key, + const QString& name, + const QStringList& values, + const QStringList& names, + const QString& value) + { + scopeone::core::SignalParameterDescriptor parameter; + parameter.key = key; + parameter.name = name; + parameter.type = scopeone::core::SignalParameterType::Choice; + parameter.defaultValue = value; + for (int index = 0; index < values.size(); ++index) + { + parameter.choices.append(values[index]); + parameter.choiceNames.append(names[index]); + } + return parameter; + } + + scopeone::core::SignalSourceDescriptor sourceDescriptor() + { + using namespace scopeone::core; + + SignalSourceDescriptor descriptor; + descriptor.id = QString::fromLatin1(kSourceId); + descriptor.name = QStringLiteral("Simulated PMT"); + descriptor.provider = QStringLiteral("ScopeOne"); + descriptor.quantity = QStringLiteral("Photon count rate"); + descriptor.unit = QStringLiteral("counts/s"); + descriptor.streamType = SignalStreamType::TimestampedEvents; + descriptor.parameters.append(realParameter( + QStringLiteral("voltage"), + QStringLiteral("Control voltage"), + 1.0, + 0.0, + 1.25, + QStringLiteral(" V"))); + descriptor.parameters.append(realParameter( + QStringLiteral("gain"), + QStringLiteral("Gain"), + 1.0, + 1.0, + 100.0, + QString())); + descriptor.parameters.append(realParameter( + QStringLiteral("baseRate"), + QStringLiteral("Base rate"), + 1000000.0, + 10000.0, + 1000000.0, + QStringLiteral(" counts/s"))); + descriptor.parameters.append(realParameter( + QStringLiteral("darkCountRate"), + QStringLiteral("Dark count rate"), + 500.0, + 0.0, + 5000.0, + QStringLiteral(" counts/s"))); + descriptor.parameters.append(realParameter( + QStringLiteral("modulationFrequency"), + QStringLiteral("Modulation frequency"), + 2.0, + 0.0, + 1000.0, + QStringLiteral(" Hz"))); + descriptor.parameters.append(realParameter( + QStringLiteral("scanFrameRate"), + QStringLiteral("Scan frame rate"), + 2.0, + 0.5, + 10.0, + QStringLiteral(" Hz"))); + descriptor.parameters.append(choiceParameter( + QStringLiteral("waveform"), + QStringLiteral("Waveform"), + {QStringLiteral("constant"), QStringLiteral("sine"), QStringLiteral("square")}, + {QStringLiteral("Constant"), QStringLiteral("Sine"), QStringLiteral("Square")}, + QStringLiteral("constant"))); + descriptor.parameters.append(choiceParameter( + QStringLiteral("noiseMode"), + QStringLiteral("Noise"), + {QStringLiteral("poisson"), QStringLiteral("gaussian"), QStringLiteral("none")}, + {QStringLiteral("Poisson"), QStringLiteral("Gaussian"), QStringLiteral("None")}, + QStringLiteral("poisson"))); + descriptor.parameters.append(choiceParameter( + QStringLiteral("pattern"), + QStringLiteral("Pattern"), + {QStringLiteral("beads"), QStringLiteral("usaf"), + QStringLiteral("grid"), QStringLiteral("cells")}, + {QStringLiteral("Fluorescent Beads"), QStringLiteral("USAF Target"), + QStringLiteral("Concentric Grid"), QStringLiteral("Cell Structure")}, + QStringLiteral("beads"))); + return descriptor; + } + } + + QList SimulatedPmtPlugin::signalSources() const + { + return {sourceDescriptor()}; + } + + scopeone::core::SignalSource* SimulatedPmtPlugin::createSignalSource( + const QString& sourceId, + QObject* parent) + { + return sourceId.trimmed() == QString::fromLatin1(kSourceId) + ? new SimulatedPmtSource(parent) + : nullptr; + } +} diff --git a/plugins/hardware/SimulatedPmt/SimulatedPmtPlugin.h b/plugins/hardware/SimulatedPmt/SimulatedPmtPlugin.h new file mode 100644 index 0000000..f4c8954 --- /dev/null +++ b/plugins/hardware/SimulatedPmt/SimulatedPmtPlugin.h @@ -0,0 +1,22 @@ +#pragma once + +#include "scopeone/SignalSource.h" + +#include + +namespace scopeone::plugins +{ + class SimulatedPmtPlugin final : public QObject, + public scopeone::core::SignalSourcePlugin + { + Q_OBJECT + Q_PLUGIN_METADATA(IID SCOPEONE_SIGNAL_SOURCE_PLUGIN_IID FILE "plugin.json") + Q_INTERFACES(scopeone::core::SignalSourcePlugin) + + public: + QList signalSources() const override; + scopeone::core::SignalSource* createSignalSource( + const QString& sourceId, + QObject* parent = nullptr) override; + }; +} diff --git a/plugins/hardware/SimulatedPmt/SimulatedPmtSource.cpp b/plugins/hardware/SimulatedPmt/SimulatedPmtSource.cpp new file mode 100644 index 0000000..a3c8dec --- /dev/null +++ b/plugins/hardware/SimulatedPmt/SimulatedPmtSource.cpp @@ -0,0 +1,467 @@ +#include "SimulatedPmtSource.h" + +#include +#include +#include +#include +#include +#include + +namespace scopeone::plugins +{ + using namespace scopeone::core; + + namespace + { + constexpr auto kSourceId = "pmt:simulator"; + constexpr double kTickPeriodSeconds = 1.0e-9; + constexpr double Pi = 3.14159265358979323846; + constexpr quint32 kPhotonCode = 1; + + int photonCount(double mean, + const QString& noiseMode, + std::mt19937_64& random) + { + if (noiseMode == QStringLiteral("none")) + { + return std::max(0, static_cast(std::llround(mean))); + } + if (noiseMode == QStringLiteral("gaussian")) + { + const double sigma = std::sqrt(std::max(0.0, mean)); + return std::max(0, static_cast(std::llround( + std::normal_distribution(mean, sigma)(random)))); + } + return std::poisson_distribution(mean)(random); + } + + double gaussianSpot(double x, + double y, + double centerX, + double centerY, + double sigma) + { + const double dx = x - centerX; + const double dy = y - centerY; + return std::exp(-(dx * dx + dy * dy) / (2.0 * sigma * sigma)); + } + + struct Bead + { + double x; + double y; + double brightness; + }; + + constexpr std::array Beads{{ + {0.08, 0.10, 0.91}, {0.19, 0.08, 0.84}, {0.32, 0.12, 0.98}, + {0.46, 0.07, 0.88}, {0.62, 0.11, 0.94}, {0.77, 0.08, 0.82}, + {0.91, 0.13, 0.96}, {0.13, 0.25, 0.87}, {0.29, 0.29, 1.00}, + {0.48, 0.23, 0.83}, {0.69, 0.27, 0.92}, {0.87, 0.31, 0.86}, + {0.08, 0.43, 0.95}, {0.24, 0.47, 0.81}, {0.42, 0.39, 0.90}, + {0.61, 0.45, 0.97}, {0.80, 0.42, 0.85}, {0.94, 0.50, 0.93}, + {0.14, 0.63, 0.89}, {0.34, 0.58, 0.99}, {0.53, 0.66, 0.84}, + {0.72, 0.61, 0.96}, {0.89, 0.68, 0.80}, {0.07, 0.82, 0.92}, + {0.22, 0.88, 0.86}, {0.39, 0.79, 0.95}, {0.58, 0.86, 0.82}, + {0.76, 0.81, 0.98}, {0.92, 0.89, 0.88}, {0.47, 0.94, 0.91}, + {0.30, 0.70, 0.85}, {0.67, 0.75, 0.94} + }}; + + struct BeadDoublet + { + double x; + double y; + double firstBrightness; + double secondBrightness; + }; + + constexpr std::array BeadDoublets{{ + {0.183, 0.176, 1.00, 0.92}, + {0.543, 0.335, 0.88, 0.96}, + {0.373, 0.515, 0.98, 0.86}, + {0.713, 0.925, 0.91, 1.00} + }}; + + double patternIntensity(const QString& pattern, + int column, + int line, + int width, + int height) + { + const double x = (column + 0.5) / width; + const double y = (line + 0.5) / height; + if (pattern == QStringLiteral("beads")) + { + const double resolutionScale = std::min(width, height) / 256.0; + const double fwhmPixels = 4.0 * resolutionScale; + const double sigmaPixels = fwhmPixels / 2.354820045; + double value = 0.002; + for (const Bead& bead : Beads) + { + value += bead.brightness + * gaussianSpot(column + 0.5, + line + 0.5, + bead.x * width, + bead.y * height, + sigmaPixels); + } + const double doubletSpacingPixels = 4.0 * resolutionScale; + for (const BeadDoublet& doublet : BeadDoublets) + { + const double centerX = doublet.x * width; + const double centerY = doublet.y * height; + value += doublet.firstBrightness + * gaussianSpot(column + 0.5, + line + 0.5, + centerX - doubletSpacingPixels * 0.5, + centerY, + sigmaPixels); + value += doublet.secondBrightness + * gaussianSpot(column + 0.5, + line + 0.5, + centerX + doubletSpacingPixels * 0.5, + centerY, + sigmaPixels); + } + return std::min(value, 1.0); + } + if (pattern == QStringLiteral("usaf")) + { + const double barX = std::abs(std::sin(2.0 * Pi * 18.0 * x)); + const double barY = std::abs(std::sin(2.0 * Pi * 12.0 * y)); + return (x > 0.15 && x < 0.85 && y > 0.2 && y < 0.8) + ? std::max(barX, barY) + : 0.02; + } + if (pattern == QStringLiteral("grid")) + { + const double dx = x - 0.5; + const double dy = y - 0.5; + const double radius = std::sqrt(dx * dx + dy * dy); + const double rings = 0.5 + 0.5 * std::cos(2.0 * Pi * 18.0 * radius); + const double lines = 0.5 + 0.5 * std::cos(2.0 * Pi * 8.0 * x) + * std::cos(2.0 * Pi * 8.0 * y); + return std::max(rings, lines); + } + + double value = 0.02; + value = std::max(value, gaussianSpot(x, y, 0.28, 0.35, 0.07)); + value = std::max(value, gaussianSpot(x, y, 0.62, 0.32, 0.09)); + value = std::max(value, gaussianSpot(x, y, 0.45, 0.65, 0.10)); + value = std::max(value, + 0.5 + 0.5 * std::sin(2.0 * Pi * (4.0 * x + 2.0 * y))); + return value; + } + } + + SimulatedPmtSource::SimulatedPmtSource(QObject* parent) + : SignalSource(parent) + { + } + + SimulatedPmtSource::~SimulatedPmtSource() + { + std::lock_guard lock(m_threadMutex); + if (m_worker.joinable()) + { + m_worker.request_stop(); + m_worker.join(); + } + } + + bool SimulatedPmtSource::start(const SignalAcquisitionConfig& config, + QString* errorMessage) + { + if (config.sourceId.trimmed() != QString::fromLatin1(kSourceId)) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Unknown simulated PMT source"); + } + return false; + } + + std::lock_guard lock(m_threadMutex); + if (m_state == SignalSourceState::Starting + || m_state == SignalSourceState::Running + || m_state == SignalSourceState::Stopping) + { + if (errorMessage) + { + *errorMessage = QStringLiteral("Simulated PMT is already active"); + } + return false; + } + if (m_worker.joinable()) + { + m_worker.join(); + } + + Settings settings; + settings.voltage = config.sourceSettings + .value(QStringLiteral("voltage"), settings.voltage).toDouble(); + settings.gain = config.sourceSettings + .value(QStringLiteral("gain"), settings.gain).toDouble(); + settings.baseRate = config.sourceSettings + .value(QStringLiteral("baseRate"), settings.baseRate).toDouble(); + settings.darkCountRate = config.sourceSettings + .value(QStringLiteral("darkCountRate"), settings.darkCountRate).toDouble(); + settings.modulationFrequency = config.sourceSettings + .value(QStringLiteral("modulationFrequency"), settings.modulationFrequency).toDouble(); + settings.scanFrameRate = config.sourceSettings + .value(QStringLiteral("scanFrameRate"), settings.scanFrameRate).toDouble(); + settings.waveform = config.sourceSettings + .value(QStringLiteral("waveform"), settings.waveform).toString(); + settings.noiseMode = config.sourceSettings + .value(QStringLiteral("noiseMode"), settings.noiseMode).toString(); + settings.pattern = config.sourceSettings + .value(QStringLiteral("pattern"), settings.pattern).toString(); + + setState(SignalSourceState::Starting, QStringLiteral("Starting simulated PMT")); + m_worker = std::jthread([this, config, settings](std::stop_token stopToken) + { + run(stopToken, config, settings); + }); + return true; + } + + void SimulatedPmtSource::stop() + { + std::lock_guard lock(m_threadMutex); + if (m_state == SignalSourceState::Starting + || m_state == SignalSourceState::Running) + { + setState(SignalSourceState::Stopping, QStringLiteral("Stopping simulated PMT")); + m_worker.request_stop(); + } + } + + SignalSourceState SimulatedPmtSource::state() const + { + return m_state.load(); + } + + QString SimulatedPmtSource::stateMessage() const + { + std::lock_guard lock(m_stateMutex); + return m_stateMessage; + } + + void SimulatedPmtSource::setState(SignalSourceState state, const QString& message) + { + { + std::lock_guard lock(m_stateMutex); + m_stateMessage = message; + m_state.store(state); + } + emit stateChanged(state, message); + } + + double SimulatedPmtSource::rateAt(double timeSeconds, const Settings& settings) const + { + const double gain = settings.gain * std::pow(settings.voltage, 3.0); + const double signalRate = settings.baseRate * gain + settings.darkCountRate; + const double phase = 2.0 * Pi * settings.modulationFrequency * timeSeconds; + + if (settings.waveform == QStringLiteral("sine")) + { + return signalRate * (0.15 + 0.85 * (0.5 + 0.5 * std::sin(phase))); + } + if (settings.waveform == QStringLiteral("square")) + { + return signalRate * (std::sin(phase) >= 0.0 ? 1.0 : 0.15); + } + return signalRate; + } + + void SimulatedPmtSource::run(std::stop_token stopToken, + const SignalAcquisitionConfig& config, + const Settings& settings) + { + setState(SignalSourceState::Running, QStringLiteral("Simulated PMT is running")); + if (config.scanImage.enabled) + { + runScan(stopToken, config, settings); + } + else + { + runStream(stopToken, config, settings); + } + + if (stopToken.stop_requested()) + { + setState(SignalSourceState::Idle, QStringLiteral("Simulated PMT stopped")); + } + else + { + setState(SignalSourceState::Idle, QStringLiteral("Simulated PMT finished")); + } + } + + void SimulatedPmtSource::runStream(std::stop_token stopToken, + const SignalAcquisitionConfig& config, + const Settings& settings) + { + constexpr int chunkSize = 64; + const double sampleInterval = config.sampleIntervalSeconds; + const auto startTime = std::chrono::steady_clock::now(); + std::mt19937_64 random(std::random_device{}()); + quint64 sampleIndex = 0; + quint64 totalEvents = 0; + + while (!stopToken.stop_requested() + && sampleIndex * sampleInterval * 1000.0 < config.durationMs) + { + TimeSeriesChunk chunk; + chunk.sourceId = QString::fromLatin1(kSourceId); + chunk.quantity = QStringLiteral("Photon count rate"); + chunk.unit = QStringLiteral("counts/s"); + chunk.startTimeSeconds = sampleIndex * sampleInterval; + chunk.sampleIntervalSeconds = sampleInterval; + chunk.values.reserve(chunkSize); + + TimestampedEventChunk events; + if (config.publishTimestampedEvents) + { + events.sourceId = QString::fromLatin1(kSourceId); + events.tickPeriodSeconds = sampleInterval; + } + + for (int index = 0; index < chunkSize; ++index) + { + const double timeSeconds = sampleIndex * sampleInterval; + const double rate = rateAt(timeSeconds, settings); + const int count = photonCount(rate * sampleInterval, + settings.noiseMode, + random); + const double value = settings.noiseMode == QStringLiteral("none") + ? rate + : count / sampleInterval; + totalEvents += static_cast(count); + if (config.publishTimestampedEvents) + { + for (int event = 0; event < count; ++event) + { + events.eventTicks.append(sampleIndex); + events.eventCodes.append(kPhotonCode); + } + } + chunk.values.append(value); + ++sampleIndex; + } + chunk.totalInputEvents = totalEvents; + chunk.totalMarkers = 0; + emit timeSeriesReady(chunk); + if (events.isValid()) + { + emit timestampedEventsReady(events); + } + std::this_thread::sleep_until( + startTime + std::chrono::duration_cast( + std::chrono::duration(sampleIndex * sampleInterval))); + } + } + + void SimulatedPmtSource::runScan(std::stop_token stopToken, + const SignalAcquisitionConfig& config, + const Settings& settings) + { + const ScanImageConfig& scan = config.scanImage; + const double frameDuration = 1.0 / settings.scanFrameRate; + const double lineDuration = frameDuration / scan.height; + const int samplesPerLine = scan.width * 2; + const double sampleDuration = lineDuration / samplesPerLine; + const quint64 lineTicks = std::max( + 1, + static_cast(std::llround(lineDuration / kTickPeriodSeconds))); + const quint64 frameTicks = lineTicks * static_cast(scan.height); + const double actualLineDuration = lineTicks * kTickPeriodSeconds; + const double actualFrameDuration = frameTicks * kTickPeriodSeconds; + const auto startTime = std::chrono::steady_clock::now(); + std::mt19937_64 random(std::random_device{}()); + EventCountBinner binner(QString::fromLatin1(kSourceId), + QStringLiteral("Photon count rate"), + QStringLiteral("counts/s"), + kTickPeriodSeconds, + config.sampleIntervalSeconds); + quint64 frameIndex = 0; + + while (!stopToken.stop_requested() + && frameIndex * actualFrameDuration * 1000.0 < config.durationMs) + { + const quint64 frameStart = frameIndex * frameTicks; + TimestampedEventChunk events; + events.sourceId = QString::fromLatin1(kSourceId); + events.tickPeriodSeconds = kTickPeriodSeconds; + events.markerTicks.append(frameStart); + events.markerCodes.append(scan.frameStartMarker); + binner.addMarker(frameStart, scan.frameStartMarker); + + const double gain = settings.gain * std::pow(settings.voltage, 3.0); + for (int line = 0; line < scan.height; ++line) + { + const quint64 lineStart = frameStart + lineTicks * static_cast(line); + for (int sample = 0; sample < samplesPerLine; ++sample) + { + const int column = sample < scan.width + ? sample + : samplesPerLine - 1 - sample; + const double rate = patternIntensity(settings.pattern, + column, + line, + scan.width, + scan.height) + * settings.baseRate * gain + + settings.darkCountRate; + const int count = photonCount(rate * sampleDuration, + settings.noiseMode, + random); + const quint64 pixelStart = lineStart + + static_cast( + static_cast(sample) * lineTicks / samplesPerLine); + const quint64 pixelEnd = lineStart + + static_cast( + static_cast(sample + 1) * lineTicks / samplesPerLine); + const quint64 pixelWidth = std::max( + 1, + pixelEnd > pixelStart ? pixelEnd - pixelStart : 1); + std::uniform_int_distribution offset(0, pixelWidth - 1); + for (int event = 0; event < count; ++event) + { + const quint64 tick = pixelStart + offset(random); + events.eventTicks.append(tick); + events.eventCodes.append(kPhotonCode); + binner.addEvent(tick); + } + } + + const quint64 lineEnd = lineStart + lineTicks; + events.markerTicks.append(lineEnd); + events.markerCodes.append(scan.lineMarker); + binner.addMarker(lineEnd, scan.lineMarker); + for (const TimeSeriesChunk& chunk : binner.takeReadyChunks()) + { + emit timeSeriesReady(chunk); + } + } + + if (scan.frameEndMarker != 0) + { + events.markerTicks.append(frameStart + frameTicks); + events.markerCodes.append(scan.frameEndMarker); + binner.addMarker(frameStart + frameTicks, scan.frameEndMarker); + } + binner.advanceToElapsedSeconds((frameIndex + 1) * actualFrameDuration); + for (const TimeSeriesChunk& chunk : binner.takeCompletedChunks()) + { + emit timeSeriesReady(chunk); + } + emit timestampedEventsReady(events); + ++frameIndex; + + std::this_thread::sleep_until( + startTime + std::chrono::duration_cast( + std::chrono::duration(frameIndex * actualFrameDuration))); + } + } +} diff --git a/plugins/hardware/SimulatedPmt/SimulatedPmtSource.h b/plugins/hardware/SimulatedPmt/SimulatedPmtSource.h new file mode 100644 index 0000000..9971363 --- /dev/null +++ b/plugins/hardware/SimulatedPmt/SimulatedPmtSource.h @@ -0,0 +1,56 @@ +#pragma once + +#include "scopeone/SignalSource.h" + +#include +#include +#include + +namespace scopeone::plugins +{ + class SimulatedPmtSource final : public scopeone::core::SignalSource + { + public: + explicit SimulatedPmtSource(QObject* parent = nullptr); + ~SimulatedPmtSource() override; + + bool start(const scopeone::core::SignalAcquisitionConfig& config, + QString* errorMessage = nullptr) override; + void stop() override; + scopeone::core::SignalSourceState state() const override; + QString stateMessage() const override; + + private: + struct Settings + { + double voltage{1.0}; + double gain{1.0}; + double baseRate{1000000.0}; + double darkCountRate{500.0}; + double modulationFrequency{2.0}; + double scanFrameRate{2.0}; + QString waveform{QStringLiteral("constant")}; + QString noiseMode{QStringLiteral("poisson")}; + QString pattern{QStringLiteral("beads")}; + }; + + void run(std::stop_token stopToken, + const scopeone::core::SignalAcquisitionConfig& config, + const Settings& settings); + void runStream(std::stop_token stopToken, + const scopeone::core::SignalAcquisitionConfig& config, + const Settings& settings); + void runScan(std::stop_token stopToken, + const scopeone::core::SignalAcquisitionConfig& config, + const Settings& settings); + double rateAt(double timeSeconds, const Settings& settings) const; + void setState(scopeone::core::SignalSourceState state, const QString& message); + + mutable std::mutex m_stateMutex; + std::mutex m_threadMutex; + std::jthread m_worker; + std::atomic m_state{ + scopeone::core::SignalSourceState::Idle}; + QString m_stateMessage{QStringLiteral("Simulated PMT is idle")}; + }; +} diff --git a/plugins/hardware/SimulatedPmt/plugin.json b/plugins/hardware/SimulatedPmt/plugin.json new file mode 100644 index 0000000..546f876 --- /dev/null +++ b/plugins/hardware/SimulatedPmt/plugin.json @@ -0,0 +1,8 @@ +{ + "id": "pmt.simulator", + "name": "Simulated PMT", + "version": "1.0.0", + "scopeOneApi": 1, + "kind": "hardware", + "providerId": "pmt.simulator" +} diff --git a/plugins/tools/ScanningDaqTool/SignalMonitorWidget.cpp b/plugins/tools/ScanningDaqTool/SignalMonitorWidget.cpp index fb5a10a..dbea648 100644 --- a/plugins/tools/ScanningDaqTool/SignalMonitorWidget.cpp +++ b/plugins/tools/ScanningDaqTool/SignalMonitorWidget.cpp @@ -368,6 +368,22 @@ namespace scopeone::plugins { qCritical().noquote() << message; }); + connect(m_core, &scopeone::core::ScopeOneCore::staticFramePublished, + this, + [this](const QString& sourceId, + const QString&, + const scopeone::core::ImageFrame&) + { + if (!m_scanImageCheck->isChecked() + || m_scanLayerShown + || sourceId != QStringLiteral("scan:%1").arg(m_activeSourceId)) + { + return; + } + m_scanLayerShown = true; + m_context.showLayers({ + scopeone::core::ScopeOneCore::staticLayerKey(sourceId)}); + }); connect(m_core, &scopeone::core::ScopeOneCore::scanImageSessionReady, this, [this](const std::shared_ptr& session) { @@ -569,6 +585,7 @@ namespace scopeone::plugins return; } m_activeSourceId = descriptor.id; + m_scanLayerShown = false; } void SignalMonitorWidget::handleTimeSeries( diff --git a/plugins/tools/ScanningDaqTool/SignalMonitorWidget.h b/plugins/tools/ScanningDaqTool/SignalMonitorWidget.h index dfcc7c7..a2d26d1 100644 --- a/plugins/tools/ScanningDaqTool/SignalMonitorWidget.h +++ b/plugins/tools/ScanningDaqTool/SignalMonitorWidget.h @@ -67,5 +67,6 @@ namespace scopeone::plugins QLabel* m_statusLabel{nullptr}; SignalTracePlot* m_plot{nullptr}; QString m_activeSourceId; + bool m_scanLayerShown{false}; }; } From 85067b1c5d592183f7003fb1e6c0bf5a1e04a436 Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:32:57 +0200 Subject: [PATCH 35/37] Align CUDA processing modules with CPU --- ScopeOneCore/src/ExperimentDocument.cpp | 6 +- .../include/scopeone/cuda/CudaKernelLaunch.h | 11 +- .../src/CudaFrequencyFilterModule.cpp | 67 ++++-- ScopeOneCuda/src/CudaFrequencyFilterModule.h | 8 +- ScopeOneCuda/src/CudaGaussianBlurModule.cpp | 14 +- ScopeOneCuda/src/CudaGaussianBlurModule.h | 3 +- ScopeOneCuda/src/CudaKernels.cu | 218 +++++++++++++++--- .../src/CudaProcessingRegistration.cpp | 60 +++-- 8 files changed, 317 insertions(+), 70 deletions(-) diff --git a/ScopeOneCore/src/ExperimentDocument.cpp b/ScopeOneCore/src/ExperimentDocument.cpp index 3588ac5..24272c5 100644 --- a/ScopeOneCore/src/ExperimentDocument.cpp +++ b/ScopeOneCore/src/ExperimentDocument.cpp @@ -589,7 +589,8 @@ namespace scopeone::core } }; - if (moduleId == QStringLiteral("frequency_domain_filter")) + if (moduleId == QStringLiteral("frequency_domain_filter") + || moduleId == QStringLiteral("cuda.frequency_filter")) { normalizeDouble(QStringLiteral("min_feature_size")); normalizeDouble(QStringLiteral("max_feature_size")); @@ -611,7 +612,8 @@ namespace scopeone::core normalizeInt(QStringLiteral("spatial_mode")); normalizeInt(QStringLiteral("temporal_mode")); } - else if (moduleId == QStringLiteral("gaussian_blur")) + else if (moduleId == QStringLiteral("gaussian_blur") + || moduleId == QStringLiteral("cuda.gaussian_blur")) { normalizeInt(QStringLiteral("kernel_size")); normalizeDouble(QStringLiteral("sigma")); diff --git a/ScopeOneCuda/include/scopeone/cuda/CudaKernelLaunch.h b/ScopeOneCuda/include/scopeone/cuda/CudaKernelLaunch.h index 63b7d05..069aae9 100644 --- a/ScopeOneCuda/include/scopeone/cuda/CudaKernelLaunch.h +++ b/ScopeOneCuda/include/scopeone/cuda/CudaKernelLaunch.h @@ -28,6 +28,7 @@ namespace scopeone::cuda::detail std::size_t outputPitchBytes, int width, int height, + int kernelSize, float sigma); SCOPEONE_CUDA_EXPORT bool launchFrequencyFilter(const void* input, @@ -35,8 +36,12 @@ namespace scopeone::cuda::detail void* spectrum, int width, int height, - float lowCutoff, - float highCutoff, + float minFeatureSize, + float maxFeatureSize, + int filterKind, + int outputMode, int forwardPlan, - int inversePlan); + int inversePlan, + void* minMaxScratch, + float maxValue); } diff --git a/ScopeOneCuda/src/CudaFrequencyFilterModule.cpp b/ScopeOneCuda/src/CudaFrequencyFilterModule.cpp index d3ce86d..585995a 100644 --- a/ScopeOneCuda/src/CudaFrequencyFilterModule.cpp +++ b/ScopeOneCuda/src/CudaFrequencyFilterModule.cpp @@ -2,6 +2,9 @@ #include "scopeone/cuda/CudaKernelLaunch.h" +#include +#include + namespace scopeone::cuda_plugin { CudaFrequencyFilterModule::CudaFrequencyFilterModule() @@ -12,6 +15,7 @@ namespace scopeone::cuda_plugin CudaFrequencyFilterModule::~CudaFrequencyFilterModule() { destroyPlans(); + releaseScratch(); } QString CudaFrequencyFilterModule::id() const @@ -26,20 +30,30 @@ namespace scopeone::cuda_plugin QVariantMap CudaFrequencyFilterModule::parameters() const { - return {{QStringLiteral("low_cutoff"), m_lowCutoff}, - {QStringLiteral("high_cutoff"), m_highCutoff}}; + return {{QStringLiteral("min_feature_size"), m_minFeatureSize}, + {QStringLiteral("max_feature_size"), m_maxFeatureSize}, + {QStringLiteral("filter_kind"), m_filterKind}, + {QStringLiteral("output_mode"), m_outputMode}}; } void CudaFrequencyFilterModule::setParameters(const QVariantMap& parameters) { - if (parameters.contains(QStringLiteral("low_cutoff"))) + if (parameters.contains(QStringLiteral("min_feature_size"))) + { + m_minFeatureSize = qMax(0.0f, + parameters.value(QStringLiteral("min_feature_size")).toFloat()); + } + if (parameters.contains(QStringLiteral("max_feature_size"))) { - m_lowCutoff = parameters.value(QStringLiteral("low_cutoff")).toFloat(); + m_maxFeatureSize = qMax(0.0f, + parameters.value(QStringLiteral("max_feature_size")).toFloat()); } - if (parameters.contains(QStringLiteral("high_cutoff"))) + if (m_minFeatureSize > m_maxFeatureSize) { - m_highCutoff = parameters.value(QStringLiteral("high_cutoff")).toFloat(); + std::swap(m_minFeatureSize, m_maxFeatureSize); } + m_filterKind = qBound(0, parameters.value(QStringLiteral("filter_kind"), m_filterKind).toInt(), 1); + m_outputMode = qBound(0, parameters.value(QStringLiteral("output_mode"), m_outputMode).toInt(), 2); } std::unique_ptr @@ -54,6 +68,7 @@ namespace scopeone::cuda_plugin { destroyPlans(); m_spectrum.release(); + releaseScratch(); return true; } @@ -73,10 +88,19 @@ namespace scopeone::cuda_plugin m_planHeight = 0; } + void CudaFrequencyFilterModule::releaseScratch() + { + if (m_minMaxScratch) + { + cudaFree(m_minMaxScratch); + m_minMaxScratch = nullptr; + } + } + bool CudaFrequencyFilterModule::processDevice( const scopeone::cuda::GpuRealFrame& input, scopeone::cuda::GpuRealFrame& output, - int) + int bitDepth) { if (m_planWidth != input.width() || m_planHeight != input.height()) { @@ -85,25 +109,38 @@ namespace scopeone::cuda_plugin || cufftPlan2d(&m_forwardPlan, input.height(), input.width(), - CUFFT_R2C) != CUFFT_SUCCESS - || cufftPlan2d(&m_inversePlan, - input.height(), - input.width(), - CUFFT_C2R) != CUFFT_SUCCESS) + CUFFT_R2C) != CUFFT_SUCCESS) { return false; } m_planWidth = input.width(); m_planHeight = input.height(); } + if (m_outputMode == 2 && m_inversePlan == 0 + && cufftPlan2d(&m_inversePlan, + input.height(), + input.width(), + CUFFT_C2R) != CUFFT_SUCCESS) + { + return false; + } + if (!m_minMaxScratch + && cudaMalloc(&m_minMaxScratch, 2 * sizeof(unsigned int)) != cudaSuccess) + { + return false; + } return scopeone::cuda::detail::launchFrequencyFilter(input.data(), output.data(), m_spectrum.data(), input.width(), input.height(), - m_lowCutoff, - m_highCutoff, + m_minFeatureSize, + m_maxFeatureSize, + m_filterKind, + m_outputMode, m_forwardPlan, - m_inversePlan); + m_inversePlan, + m_minMaxScratch, + bitDepth >= 16 ? 65535.0f : 255.0f); } } diff --git a/ScopeOneCuda/src/CudaFrequencyFilterModule.h b/ScopeOneCuda/src/CudaFrequencyFilterModule.h index 2914d3f..a04c419 100644 --- a/ScopeOneCuda/src/CudaFrequencyFilterModule.h +++ b/ScopeOneCuda/src/CudaFrequencyFilterModule.h @@ -27,13 +27,17 @@ namespace scopeone::cuda_plugin private: void destroyPlans(); + void releaseScratch(); scopeone::cuda::GpuComplexFrame m_spectrum; cufftHandle m_forwardPlan{0}; cufftHandle m_inversePlan{0}; int m_planWidth{0}; int m_planHeight{0}; - float m_lowCutoff{0.01f}; - float m_highCutoff{0.25f}; + void* m_minMaxScratch{nullptr}; + float m_minFeatureSize{2.0f}; + float m_maxFeatureSize{10.0f}; + int m_filterKind{0}; + int m_outputMode{2}; }; } diff --git a/ScopeOneCuda/src/CudaGaussianBlurModule.cpp b/ScopeOneCuda/src/CudaGaussianBlurModule.cpp index 738f06e..f6dd972 100644 --- a/ScopeOneCuda/src/CudaGaussianBlurModule.cpp +++ b/ScopeOneCuda/src/CudaGaussianBlurModule.cpp @@ -21,14 +21,23 @@ namespace scopeone::cuda_plugin QVariantMap CudaGaussianBlurModule::parameters() const { - return {{QStringLiteral("sigma"), m_sigma}}; + return {{QStringLiteral("kernel_size"), m_kernelSize}, + {QStringLiteral("sigma"), m_sigma}}; } void CudaGaussianBlurModule::setParameters(const QVariantMap& parameters) { + if (parameters.contains(QStringLiteral("kernel_size"))) + { + m_kernelSize = qMax(1, parameters.value(QStringLiteral("kernel_size")).toInt()); + if ((m_kernelSize % 2) == 0) + { + ++m_kernelSize; + } + } if (parameters.contains(QStringLiteral("sigma"))) { - m_sigma = parameters.value(QStringLiteral("sigma")).toFloat(); + m_sigma = qMax(0.0f, parameters.value(QStringLiteral("sigma")).toFloat()); } } @@ -51,6 +60,7 @@ namespace scopeone::cuda_plugin output.pitchBytes(), input.width(), input.height(), + m_kernelSize, m_sigma); } } diff --git a/ScopeOneCuda/src/CudaGaussianBlurModule.h b/ScopeOneCuda/src/CudaGaussianBlurModule.h index 1afe5b1..d016df0 100644 --- a/ScopeOneCuda/src/CudaGaussianBlurModule.h +++ b/ScopeOneCuda/src/CudaGaussianBlurModule.h @@ -21,6 +21,7 @@ namespace scopeone::cuda_plugin int bitDepth) override; private: - float m_sigma{1.5f}; + int m_kernelSize{3}; + float m_sigma{0.0f}; }; } diff --git a/ScopeOneCuda/src/CudaKernels.cu b/ScopeOneCuda/src/CudaKernels.cu index 7a209c2..7709208 100644 --- a/ScopeOneCuda/src/CudaKernels.cu +++ b/ScopeOneCuda/src/CudaKernels.cu @@ -71,6 +71,15 @@ namespace } } + __device__ int reflect101(int value, int size) + { + while (value < 0 || value >= size) + { + value = value < 0 ? -value : 2 * size - value - 2; + } + return value; + } + __global__ void gaussianKernel(const float* input, std::size_t inputPitchBytes, float* output, @@ -91,13 +100,13 @@ namespace float weightSum = 0.0f; for (int dy = -radius; dy <= radius; ++dy) { - const int sampleY = min(max(y + dy, 0), height - 1); + const int sampleY = reflect101(y + dy, height); const float* inputRow = reinterpret_cast( reinterpret_cast(input) + static_cast(sampleY) * inputPitchBytes); for (int dx = -radius; dx <= radius; ++dx) { - const int sampleX = min(max(x + dx, 0), width - 1); + const int sampleX = reflect101(x + dx, width); const float distanceSquared = static_cast(dx * dx + dy * dy); const float weight = expf(-distanceSquared / (2.0f * sigma * sigma)); sum += inputRow[sampleX] * weight; @@ -115,8 +124,9 @@ namespace int spectrumWidth, int height, int realWidth, - float lowCutoff, - float highCutoff) + float minFeatureSize, + float maxFeatureSize, + int filterKind) { const int x = blockIdx.x * blockDim.x + threadIdx.x; const int y = blockIdx.y * blockDim.y + threadIdx.y; @@ -125,24 +135,109 @@ namespace return; } - const float fx = static_cast(x) / static_cast(realWidth); + constexpr float twoPi = 6.28318530717958647692f; + const float fx = twoPi * static_cast(x) / static_cast(realWidth); const float fy = static_cast(y <= height / 2 ? y : y - height) - / static_cast(height); - const float radius = sqrtf(fx * fx + fy * fy); - const bool keep = radius >= lowCutoff && radius <= highCutoff; - if (!keep) - { - spectrum[y * spectrumWidth + x] = make_float2(0.0f, 0.0f); - } + * twoPi / static_cast(height); + const float radiusSquared = fx * fx + fy * fy; + const float mask = filterKind == 1 + ? (radiusSquared * maxFeatureSize * maxFeatureSize > 1.0f + && radiusSquared * minFeatureSize * minFeatureSize < 1.0f + ? 1.0f + : 0.0f) + : expf(-radiusSquared * minFeatureSize * minFeatureSize * 0.5f) + - expf(-radiusSquared * maxFeatureSize * maxFeatureSize * 0.5f); + spectrum[y * spectrumWidth + x].x *= mask; + spectrum[y * spectrumWidth + x].y *= mask; } - __global__ void normalizeKernel(float* output, int width, int height, float scale) + __global__ void renderSpectrumKernel(const float2* spectrum, + int spectrumWidth, + int width, + int height, + float* output) { const int x = blockIdx.x * blockDim.x + threadIdx.x; const int y = blockIdx.y * blockDim.y + threadIdx.y; - if (x < width && y < height) + if (x >= width || y >= height) + { + return; + } + const int sourceX = (x + width / 2) % width; + const int sourceY = (y + height / 2) % height; + float2 value; + if (sourceX <= width / 2) + { + value = spectrum[sourceY * spectrumWidth + sourceX]; + } + else + { + const float2 conjugate = spectrum[((height - sourceY) % height) * spectrumWidth + + width - sourceX]; + value = make_float2(conjugate.x, -conjugate.y); + } + output[y * width + x] = log1pf(hypotf(value.x, value.y)); + } + + __global__ void scaleKernel(float* output, int count, float scale) + { + const int index = blockIdx.x * blockDim.x + threadIdx.x; + if (index < count) + { + output[index] *= scale; + } + } + + __device__ unsigned int orderedFloat(float value) + { + const unsigned int bits = __float_as_uint(value); + return (bits & 0x80000000U) == 0 ? bits | 0x80000000U : ~bits; + } + + __device__ float floatFromOrdered(unsigned int value) + { + const unsigned int bits = (value & 0x80000000U) == 0 ? ~value : value & ~0x80000000U; + return __uint_as_float(bits); + } + + __global__ void reduceMinMaxKernel(const float* input, int count, unsigned int* limits) + { + __shared__ unsigned int blockMinimum; + __shared__ unsigned int blockMaximum; + if (threadIdx.x == 0) + { + blockMinimum = 0xFF800000U; + blockMaximum = 0x007FFFFFU; + } + __syncthreads(); + const int index = blockIdx.x * blockDim.x + threadIdx.x; + if (index < count) + { + const unsigned int value = orderedFloat(input[index]); + atomicMin(&blockMinimum, value); + atomicMax(&blockMaximum, value); + } + __syncthreads(); + if (threadIdx.x == 0) { - output[y * width + x] *= scale; + atomicMin(&limits[0], blockMinimum); + atomicMax(&limits[1], blockMaximum); + } + } + + __global__ void minMaxNormalizeKernel(float* output, + int count, + const unsigned int* limits, + float maxValue) + { + const int index = blockIdx.x * blockDim.x + threadIdx.x; + if (index < count) + { + const float minimum = floatFromOrdered(limits[0]); + const float maximum = floatFromOrdered(limits[1]); + output[index] = maximum == minimum + ? 0.0f + : (output[index] - minimum) * maxValue / (maximum - minimum); } } @@ -150,6 +245,26 @@ namespace { return cudaGetLastError() == cudaSuccess && cudaDeviceSynchronize() == cudaSuccess; } + + bool normalizeMinMax(float* output, int count, void* scratch, float maxValue) + { + const unsigned int limits[] = {0xFF800000U, 0x007FFFFFU}; + if (cudaMemcpy(scratch, limits, sizeof(limits), cudaMemcpyHostToDevice) != cudaSuccess) + { + return false; + } + constexpr int blockSize = 256; + reduceMinMaxKernel<<<(count + blockSize - 1) / blockSize, blockSize>>>( + output, + count, + static_cast(scratch)); + minMaxNormalizeKernel<<<(count + blockSize - 1) / blockSize, blockSize>>>( + output, + count, + static_cast(scratch), + maxValue); + return synchronizeKernel(); + } } namespace scopeone::cuda::detail @@ -204,9 +319,18 @@ namespace scopeone::cuda::detail std::size_t outputPitchBytes, int width, int height, + int kernelSize, float sigma) { - const int radius = std::max(1, static_cast(std::ceil(3.0f * sigma))); + kernelSize = std::max(1, kernelSize); + if ((kernelSize % 2) == 0) + { + ++kernelSize; + } + const float effectiveSigma = sigma > 0.0f + ? sigma + : 0.3f * ((kernelSize - 1) * 0.5f - 1.0f) + 0.8f; + const int radius = kernelSize / 2; const dim3 block(16, 16); const dim3 grid((width + block.x - 1) / block.x, (height + block.y - 1) / block.y); @@ -218,7 +342,7 @@ namespace scopeone::cuda::detail width, height, radius, - sigma); + effectiveSigma); return synchronizeKernel(); } @@ -227,10 +351,14 @@ namespace scopeone::cuda::detail void* spectrum, int width, int height, - float lowCutoff, - float highCutoff, + float minFeatureSize, + float maxFeatureSize, + int filterKind, + int outputMode, int forwardPlan, - int inversePlan) + int inversePlan, + void* minMaxScratch, + float maxValue) { if (cufftExecR2C(forwardPlan, static_cast(const_cast(input)), @@ -239,35 +367,67 @@ namespace scopeone::cuda::detail return false; } - const int spectrumWidth = width / 2 + 1; const dim3 block(16, 16); + const dim3 imageGrid((width + block.x - 1) / block.x, + (height + block.y - 1) / block.y); + const int spectrumWidth = width / 2 + 1; const dim3 grid((spectrumWidth + block.x - 1) / block.x, (height + block.y - 1) / block.y); + if (outputMode == 0) + { + renderSpectrumKernel<<>>(static_cast(spectrum), + spectrumWidth, + width, + height, + static_cast(output)); + return synchronizeKernel() + && normalizeMinMax(static_cast(output), + width * height, + minMaxScratch, + maxValue); + } frequencyMaskKernel<<>>( static_cast(spectrum), spectrumWidth, height, width, - lowCutoff, - highCutoff); + minFeatureSize, + maxFeatureSize, + filterKind); if (!synchronizeKernel()) { return false; } + if (outputMode == 1) + { + renderSpectrumKernel<<>>(static_cast(spectrum), + spectrumWidth, + width, + height, + static_cast(output)); + return synchronizeKernel() + && normalizeMinMax(static_cast(output), + width * height, + minMaxScratch, + maxValue); + } + if (cufftExecC2R(inversePlan, static_cast(spectrum), static_cast(output)) != CUFFT_SUCCESS) { return false; } - const dim3 normalizeGrid((width + block.x - 1) / block.x, - (height + block.y - 1) / block.y); - normalizeKernel<<>>( + constexpr int blockSize = 256; + scaleKernel<<<(width * height + blockSize - 1) / blockSize, blockSize>>>( static_cast(output), - width, - height, + width * height, 1.0f / static_cast(width * height)); - return synchronizeKernel(); + return synchronizeKernel() + && normalizeMinMax(static_cast(output), + width * height, + minMaxScratch, + maxValue); } } diff --git a/ScopeOneCuda/src/CudaProcessingRegistration.cpp b/ScopeOneCuda/src/CudaProcessingRegistration.cpp index 0b7bd93..1c4a242 100644 --- a/ScopeOneCuda/src/CudaProcessingRegistration.cpp +++ b/ScopeOneCuda/src/CudaProcessingRegistration.cpp @@ -10,12 +10,20 @@ extern "C" SCOPEONE_CUDA_EXPORT void scopeone_register_processing_modules( {QStringLiteral("cuda.gaussian_blur"), QStringLiteral("CUDA Gaussian Blur"), 1, - {{QStringLiteral("sigma"), + {{QStringLiteral("kernel_size"), + QStringLiteral("Kernel size"), + scopeone::core::ProcessingParameterType::Integer, + 3, + 1, + 99, + 2, + 0}, + {QStringLiteral("sigma"), QStringLiteral("Sigma"), scopeone::core::ProcessingParameterType::Real, - 1.5, - 0.1, - 20.0, + 0.0, + 0.0, + 100.0, 0.1, 2}}}, []() @@ -27,22 +35,42 @@ extern "C" SCOPEONE_CUDA_EXPORT void scopeone_register_processing_modules( {QStringLiteral("cuda.frequency_filter"), QStringLiteral("CUDA Frequency Filter"), 1, - {{QStringLiteral("low_cutoff"), - QStringLiteral("Low Cutoff"), + {{QStringLiteral("output_mode"), + QStringLiteral("Output"), + scopeone::core::ProcessingParameterType::Choice, + 2, + 0, + 2, + 1, + 0, + {{QStringLiteral("Spectrum"), 0}, + {QStringLiteral("Filtered spectrum"), 1}, + {QStringLiteral("Filtered image"), 2}}}, + {QStringLiteral("min_feature_size"), + QStringLiteral("Min feature size"), scopeone::core::ProcessingParameterType::Real, - 0.01, + 2.0, 0.0, - 0.5, - 0.01, - 3}, - {QStringLiteral("high_cutoff"), - QStringLiteral("High Cutoff"), + 1000.0, + 0.1, + 2}, + {QStringLiteral("max_feature_size"), + QStringLiteral("Max feature size"), scopeone::core::ProcessingParameterType::Real, - 0.25, + 10.0, 0.0, - 0.5, - 0.01, - 3}}}, + 1000.0, + 0.1, + 2}, + {QStringLiteral("filter_kind"), + QStringLiteral("Filter kind"), + scopeone::core::ProcessingParameterType::Choice, + 0, + 0, + 1, + 1, + 0, + {{QStringLiteral("Smooth"), 0}, {QStringLiteral("Hard"), 1}}}}}, []() { return std::make_unique(); From d59acfa8f55833e781b634bf1234db91dc953d6d Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:08:33 +0200 Subject: [PATCH 36/37] Document Python package installation --- ScopeOneCore/python/scopeone/README.md | 30 +++++++++++++++++++++ ScopeOneCore/python/scopeone/pyproject.toml | 4 +-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ScopeOneCore/python/scopeone/README.md b/ScopeOneCore/python/scopeone/README.md index 60f731a..330b669 100644 --- a/ScopeOneCore/python/scopeone/README.md +++ b/ScopeOneCore/python/scopeone/README.md @@ -2,6 +2,36 @@ Python client for ScopeOne's language-neutral Local API. It controls a running ScopeOne app and can be used by scripts, notebooks, or Python-based agent tool adapters. +## Installation + +Requires Python 3.10 or later and a running ScopeOne desktop application. + +Create and activate an isolated Conda environment: + +```powershell +conda create -n scopeone python=3.13 +conda activate scopeone +``` + +Install from this source checkout: + +```powershell +cd ScopeOneCore\python\scopeone +python -m pip install -e . +``` + +For a regular, non-editable installation, use: + +```powershell +python -m pip install . +``` + +`pip` installs the required `numpy` and `pywin32` dependencies automatically on Windows. Verify the connection after ScopeOne is running: + +```powershell +python -c "from scopeone import ScopeOne; print(ScopeOne().version())" +``` + ## Project layout - Python package project root: `ScopeOneCore/python/scopeone` diff --git a/ScopeOneCore/python/scopeone/pyproject.toml b/ScopeOneCore/python/scopeone/pyproject.toml index a038da9..833e9cd 100644 --- a/ScopeOneCore/python/scopeone/pyproject.toml +++ b/ScopeOneCore/python/scopeone/pyproject.toml @@ -28,8 +28,8 @@ classifiers = [ ] dependencies = [ - "numpy>=1.21.2", - "pywin32>=300; platform_system == 'Windows'", + "numpy==2.4.6", + "pywin32==311; platform_system == 'Windows'", ] [tool.setuptools] From f2088246a4c9280fef6e06191c0671274828a89e Mon Sep 17 00:00:00 2001 From: tz <185176969+tzhaoo@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:52:37 +0200 Subject: [PATCH 37/37] Move layer FPS tracking into ScopeOneCore --- ScopeOneCore/include/scopeone/ScopeOneCore.h | 13 ++ ScopeOneCore/include/scopeone/ToolPlugin.h | 3 + ScopeOneCore/src/ScopeOneCore.cpp | 53 +++++ VERSION | 2 +- .../ScanningDaqTool/DaqControlWidget.cpp | 29 +-- .../tools/ScanningDaqTool/DaqControlWidget.h | 2 + src/DeviceControlWidget.cpp | 14 +- src/DeviceControlWidget.h | 1 + src/MainWindow.cpp | 31 ++- src/MainWindow.h | 3 + src/PreviewWidget.cpp | 183 ++++++++++-------- src/PreviewWidget.h | 22 +-- 12 files changed, 247 insertions(+), 109 deletions(-) diff --git a/ScopeOneCore/include/scopeone/ScopeOneCore.h b/ScopeOneCore/include/scopeone/ScopeOneCore.h index 3541a20..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 @@ -574,6 +576,8 @@ namespace scopeone::core 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, @@ -859,6 +863,8 @@ namespace scopeone::core 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; @@ -933,6 +939,8 @@ namespace scopeone::core 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(); @@ -997,6 +1005,11 @@ namespace scopeone::core 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 diff --git a/ScopeOneCore/include/scopeone/ToolPlugin.h b/ScopeOneCore/include/scopeone/ToolPlugin.h index 1c286e9..71dd013 100644 --- a/ScopeOneCore/include/scopeone/ToolPlugin.h +++ b/ScopeOneCore/include/scopeone/ToolPlugin.h @@ -3,6 +3,7 @@ #include "scopeone/ScopeOneCore.h" #include +#include #include #include #include @@ -35,6 +36,8 @@ namespace scopeone::ui 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, diff --git a/ScopeOneCore/src/ScopeOneCore.cpp b/ScopeOneCore/src/ScopeOneCore.cpp index 4c49efe..833921c 100644 --- a/ScopeOneCore/src/ScopeOneCore.cpp +++ b/ScopeOneCore/src/ScopeOneCore.cpp @@ -1285,6 +1285,12 @@ namespace scopeone::core m_previewFlushTimer->setTimerType(Qt::PreciseTimer); connect(m_previewFlushTimer, &QTimer::timeout, this, &ScopeOneCore::flushPreviewFrames); + m_layerFrameRateTimer = new QTimer(this); + m_layerFrameRateTimer->setInterval(3000); + connect(m_layerFrameRateTimer, &QTimer::timeout, + this, &ScopeOneCore::updateLayerFrameRates); + m_layerFrameRateElapsed.start(); + m_layerFrameRateTimer->start(); if constexpr (kFrameRateDiagnosticsEnabled) { auto* frameRateDiagnosticTimer = new QTimer(this); @@ -2637,6 +2643,7 @@ namespace scopeone::core ImageFrame normalizedFrame(frame); normalizedFrame.cameraId = cameraId; const QString layerKey = rawLayerKey(cameraId); + recordLayerFrame(layerKey); m_imageSceneModel->updateLayerFrame(layerKey, normalizedFrame); m_frameGraph.publishLatest(FrameGraphStream::Raw, normalizedFrame); emit newRawFrameReady(normalizedFrame); @@ -2686,6 +2693,7 @@ namespace scopeone::core } bool queueFlush = false; + recordLayerFrame(processedLayerKey(frame.cameraId)); { QMutexLocker locker(&m_managers->processedDeliveryMutex); if (!m_managers->imageProcessingManager->isRealTimeProcessingEnabled()) @@ -2832,6 +2840,18 @@ namespace scopeone::core return frames; } + double ScopeOneCore::layerFrameRate(const QString& layerKey) const + { + QMutexLocker locker(&m_layerFrameRateMutex); + return m_layerFrameRates.value(layerKey.trimmed(), 0.0); + } + + QMap ScopeOneCore::layerFrameRates() const + { + QMutexLocker locker(&m_layerFrameRateMutex); + return m_layerFrameRates; + } + // Read one pixel from a named frame graph layer bool ScopeOneCore::graphPixelValue(const QString& layerKey, const QPoint& imagePos, int& value) const { @@ -2924,6 +2944,37 @@ namespace scopeone::core return static_cast(m_layerStacks.value(sourceId).size()); } + void ScopeOneCore::recordLayerFrame(const QString& layerKey, quint64 count) + { + QMutexLocker locker(&m_layerFrameRateMutex); + m_layerFrameCounts[layerKey] += count; + } + + void ScopeOneCore::updateLayerFrameRates() + { + const double elapsedSeconds = static_cast(m_layerFrameRateElapsed.restart()) / 1000.0; + QMap frameRates; + { + QMutexLocker locker(&m_layerFrameRateMutex); + frameRates = m_layerFrameRates; + for (auto it = frameRates.begin(); it != frameRates.end(); ++it) + { + it.value() = 0.0; + } + for (auto it = m_layerFrameCounts.cbegin(); it != m_layerFrameCounts.cend(); ++it) + { + frameRates[it.key()] = static_cast(it.value()) / elapsedSeconds; + } + m_layerFrameCounts.clear(); + m_layerFrameRates = frameRates; + } + for (auto it = frameRates.cbegin(); it != frameRates.cend(); ++it) + { + emit layerFrameRateChanged(it.key(), it.value()); + } + emit layerFrameRatesUpdated(frameRates); + } + bool ScopeOneCore::setLayerSliceIndex(const QString& layerKey, int sliceIndex) { const QString sourceId = sourceIdFromLayerKey(layerKey.trimmed()); @@ -3170,6 +3221,7 @@ namespace scopeone::core { return {}; } + recordLayerFrame(toolLayerKey(sourceId)); const ImageFrame storedFrame = graphFrame(toolLayerKey(sourceId)); const QString layerKey = toolLayerKey(storedFrame.cameraId); ensureSceneLayer(layerKey, @@ -3195,6 +3247,7 @@ namespace scopeone::core { return {}; } + recordLayerFrame(QStringLiteral("external:%1").arg(sourceId.trimmed())); return graphFrame(QStringLiteral("external:%1").arg(sourceId.trimmed())); } diff --git a/VERSION b/VERSION index 78303df..5487dbb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.903 +1.5.915 diff --git a/plugins/tools/ScanningDaqTool/DaqControlWidget.cpp b/plugins/tools/ScanningDaqTool/DaqControlWidget.cpp index 2bb0521..7addc78 100644 --- a/plugins/tools/ScanningDaqTool/DaqControlWidget.cpp +++ b/plugins/tools/ScanningDaqTool/DaqControlWidget.cpp @@ -90,34 +90,39 @@ namespace scopeone::plugins m_rasterGroup = new QGroupBox(tr("Raster scan timing"), this); m_rasterGroup->setCheckable(true); m_rasterGroup->setChecked(false); - auto* rasterForm = new QFormLayout(m_rasterGroup); - m_lineClockCombo = editableCombo({}, m_rasterGroup); + m_rasterContents = new QWidget(m_rasterGroup); + auto* rasterLayout = new QVBoxLayout(m_rasterGroup); + rasterLayout->setContentsMargins(0, 0, 0, 0); + rasterLayout->addWidget(m_rasterContents); + auto* rasterForm = new QFormLayout(m_rasterContents); + m_lineClockCombo = editableCombo({}, m_rasterContents); rasterForm->addRow(tr("Line clock input"), m_lineClockCombo); m_lineRateSpin = realSpin(0.001, 100000000.0, 1000.0, 3, - tr(" Hz"), m_rasterGroup); + tr(" Hz"), m_rasterContents); rasterForm->addRow(tr("Nominal line rate"), m_lineRateSpin); - m_activeLinesSpin = new QSpinBox(m_rasterGroup); + m_activeLinesSpin = new QSpinBox(m_rasterContents); m_activeLinesSpin->setRange(2, 1000000); m_activeLinesSpin->setValue(512); rasterForm->addRow(tr("Active lines"), m_activeLinesSpin); - m_flybackLinesSpin = new QSpinBox(m_rasterGroup); + m_flybackLinesSpin = new QSpinBox(m_rasterContents); m_flybackLinesSpin->setRange(2, 1000000); m_flybackLinesSpin->setValue(16); rasterForm->addRow(tr("Flyback lines"), m_flybackLinesSpin); - m_yChannelCombo = new QComboBox(m_rasterGroup); + m_yChannelCombo = new QComboBox(m_rasterContents); rasterForm->addRow(tr("Y analog output"), m_yChannelCombo); m_yStartSpin = realSpin(-1000.0, 1000.0, -1.0, 6, - tr(" V"), m_rasterGroup); + tr(" V"), m_rasterContents); rasterForm->addRow(tr("Y start"), m_yStartSpin); m_yEndSpin = realSpin(-1000.0, 1000.0, 1.0, 6, - tr(" V"), m_rasterGroup); + tr(" V"), m_rasterContents); rasterForm->addRow(tr("Y end"), m_yEndSpin); - m_frameCounterCombo = new QComboBox(m_rasterGroup); + m_frameCounterCombo = new QComboBox(m_rasterContents); rasterForm->addRow(tr("Frame counter"), m_frameCounterCombo); - m_lineOutputCombo = editableCombo({}, m_rasterGroup); + m_lineOutputCombo = editableCombo({}, m_rasterContents); rasterForm->addRow(tr("Line output"), m_lineOutputCombo); - m_frameOutputCombo = editableCombo({}, m_rasterGroup); + m_frameOutputCombo = editableCombo({}, m_rasterContents); rasterForm->addRow(tr("Frame output"), m_frameOutputCombo); + m_rasterContents->setVisible(false); layout->addWidget(m_rasterGroup); layout->addWidget(new QLabel(tr("Counter pulse tasks"), this)); @@ -204,6 +209,8 @@ namespace scopeone::plugins connect(m_deviceCombo, &QComboBox::currentIndexChanged, this, &DaqControlWidget::refreshDevice); + connect(m_rasterGroup, &QGroupBox::toggled, + m_rasterContents, &QWidget::setVisible); connect(m_addPulseButton, &QPushButton::clicked, this, &DaqControlWidget::addPulseRow); connect(m_removePulseButton, &QPushButton::clicked, this, [this]() diff --git a/plugins/tools/ScanningDaqTool/DaqControlWidget.h b/plugins/tools/ScanningDaqTool/DaqControlWidget.h index 0978edc..246f879 100644 --- a/plugins/tools/ScanningDaqTool/DaqControlWidget.h +++ b/plugins/tools/ScanningDaqTool/DaqControlWidget.h @@ -13,6 +13,7 @@ class QLineEdit; class QPushButton; class QSpinBox; class QTableWidget; +class QWidget; namespace scopeone::core { @@ -49,6 +50,7 @@ namespace scopeone::plugins QLabel* m_productLabel{nullptr}; QLabel* m_resourcesLabel{nullptr}; QGroupBox* m_rasterGroup{nullptr}; + QWidget* m_rasterContents{nullptr}; QComboBox* m_lineClockCombo{nullptr}; QDoubleSpinBox* m_lineRateSpin{nullptr}; QSpinBox* m_activeLinesSpin{nullptr}; diff --git a/src/DeviceControlWidget.cpp b/src/DeviceControlWidget.cpp index 2e479bf..a7ec9f7 100644 --- a/src/DeviceControlWidget.cpp +++ b/src/DeviceControlWidget.cpp @@ -623,6 +623,15 @@ namespace scopeone::ui const QSignalBlocker blocker(m_3dWireframeCheckBox); m_3dWireframeCheckBox->setChecked(enabled); }); + m_3dColorbarCheckBox->setChecked(m_previewWidget->isThreeDimensionalColorbarVisible()); + connect(m_3dColorbarCheckBox, &QCheckBox::toggled, + m_previewWidget, &PreviewWidget::setThreeDimensionalColorbarVisible); + connect(m_previewWidget, &PreviewWidget::threeDimensionalColorbarVisibilityChanged, + this, [this](bool visible) + { + const QSignalBlocker blocker(m_3dColorbarCheckBox); + m_3dColorbarCheckBox->setChecked(visible); + }); } // Builds layer and alignment controls @@ -804,6 +813,8 @@ namespace scopeone::ui m_3dZScaleSpinBox->setKeyboardTracking(false); m_3dWireframeCheckBox = new QCheckBox(QStringLiteral("Wireframe"), surfaceViewGroup); + m_3dColorbarCheckBox = new QCheckBox(QStringLiteral("Colorbar"), surfaceViewGroup); + m_3dColorbarCheckBox->setChecked(true); auto* reset3dButton = new QPushButton(QStringLiteral("Reset View"), surfaceViewGroup); reset3dButton->setMaximumWidth(84); @@ -813,7 +824,8 @@ namespace scopeone::ui surfaceViewLayout->addWidget(m_3dZScaleSlider, 1, 1); surfaceViewLayout->addWidget(m_3dZScaleSpinBox, 1, 2); surfaceViewLayout->addWidget(m_3dWireframeCheckBox, 2, 0, 1, 2); - surfaceViewLayout->addWidget(reset3dButton, 2, 2, 1, 2, Qt::AlignLeft); + surfaceViewLayout->addWidget(m_3dColorbarCheckBox, 2, 2); + surfaceViewLayout->addWidget(reset3dButton, 2, 3, 1, 1, Qt::AlignLeft); connect(m_viewDimensionCombo, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) diff --git a/src/DeviceControlWidget.h b/src/DeviceControlWidget.h index 942e5ba..bf8ee16 100644 --- a/src/DeviceControlWidget.h +++ b/src/DeviceControlWidget.h @@ -155,6 +155,7 @@ namespace scopeone::ui QSlider* m_3dZScaleSlider{nullptr}; QDoubleSpinBox* m_3dZScaleSpinBox{nullptr}; QCheckBox* m_3dWireframeCheckBox{nullptr}; + QCheckBox* m_3dColorbarCheckBox{nullptr}; QSpinBox* m_alignXSpinBox{nullptr}; QSpinBox* m_alignYSpinBox{nullptr}; QSpinBox* m_alignZoomSpinBox{nullptr}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 79f3bb8..ed9f131 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -490,10 +490,8 @@ namespace scopeone::ui this, &MainWindow::handleRoiDrawn); connect(m_previewWidget, &PreviewWidget::imageFilesDropped, this, &MainWindow::importImages); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::rawFramesAcquired, - m_previewWidget, &PreviewWidget::trackRawFrameRate); - connect(m_scopeonecore, &scopeone::core::ScopeOneCore::processedFramesCompleted, - m_previewWidget, &PreviewWidget::trackProcessedFrameRate); + connect(m_scopeonecore, &scopeone::core::ScopeOneCore::layerFrameRatesUpdated, + m_previewWidget, &PreviewWidget::setLayerFrameRates); connect(m_scopeonecore, &scopeone::core::ScopeOneCore::previewRawFrameReady, this, [this](const scopeone::core::ImageFrame& frame) { @@ -1050,6 +1048,16 @@ namespace scopeone::ui return m_scopeonecore->graphFrame(currentLayerKey()); } + double MainWindow::layerFrameRate(const QString& layerKey) const + { + return m_scopeonecore->layerFrameRate(layerKey); + } + + QMap MainWindow::layerFrameRates() const + { + return m_scopeonecore->layerFrameRates(); + } + scopeone::core::ImageFrame MainWindow::publishToolStreamFrame( const QString& sourceId, const scopeone::core::ImageFrame& frame, @@ -1269,6 +1277,20 @@ namespace scopeone::ui }); m_reset3dAction->setEnabled(false); + m_toggle3dColorbarAction = viewMenu->addAction(tr("3D Colorbar")); + m_toggle3dColorbarAction->setCheckable(true); + m_toggle3dColorbarAction->setChecked(m_previewWidget->isThreeDimensionalColorbarVisible()); + connect(m_toggle3dColorbarAction, &QAction::toggled, + this, [this](bool visible) + { + if (auto* preview = m_imageWorkspace->activePreviewWidget()) + { + preview->setThreeDimensionalColorbarVisible(visible); + } + }); + connect(m_previewWidget, &PreviewWidget::threeDimensionalColorbarVisibilityChanged, + m_toggle3dColorbarAction, &QAction::setChecked); + viewMenu->addSeparator(); m_dockWidgetsMenu = viewMenu->addMenu(tr("&Dock Widgets")); @@ -1677,6 +1699,7 @@ namespace scopeone::ui } m_toggleDimensionAction->setEnabled(true); m_reset3dAction->setEnabled(threeDimensional); + m_toggle3dColorbarAction->setEnabled(threeDimensional); } // Show one transient status message without disturbing persistent fields diff --git a/src/MainWindow.h b/src/MainWindow.h index c78e163..f7086fa 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -49,6 +49,8 @@ namespace scopeone::ui scopeone::core::ScopeOneCore& core() const override; QString currentLayerKey() const override; scopeone::core::ImageFrame currentFrame() const override; + double layerFrameRate(const QString& layerKey) const override; + QMap layerFrameRates() const override; scopeone::core::ImageFrame publishToolStreamFrame( const QString& sourceId, const scopeone::core::ImageFrame& frame, @@ -157,6 +159,7 @@ namespace scopeone::ui QMenu* m_dockWidgetsMenu{nullptr}; QAction* m_toggleDimensionAction{nullptr}; + QAction* m_toggle3dColorbarAction{nullptr}; QAction* m_reset3dAction{nullptr}; QAction* m_loadConfigurationAction{nullptr}; QAction* m_unloadConfigurationAction{nullptr}; diff --git a/src/PreviewWidget.cpp b/src/PreviewWidget.cpp index bf03cf5..c47cee7 100644 --- a/src/PreviewWidget.cpp +++ b/src/PreviewWidget.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -320,9 +321,6 @@ namespace scopeone::ui }); m_sliceBar->hide(); - m_fpsUpdateTimer.setInterval(3000); - connect(&m_fpsUpdateTimer, &QTimer::timeout, this, &PreviewWidget::updateFrameRates); - m_fpsUpdateTimer.start(); } // Releases cached OpenGL textures @@ -390,17 +388,6 @@ namespace scopeone::ui } } - // Counts one frame in a live layer throughput window - void PreviewWidget::updateLayerFps(const QString& layerKey, quint64 frameCount) - { - FpsState& state = m_fpsStates[layerKey]; - if (!state.intervalTimer.isValid()) - { - state.intervalTimer.start(); - } - state.framesSinceUpdate += frameCount; - } - // Stores one graph processed frame and updates layer statistics void PreviewWidget::setGraphProcessedFrame(const ImageFrame& frame) { @@ -446,44 +433,23 @@ namespace scopeone::ui update(); } - // Tracks completed processing throughput from aggregated frame counts - void PreviewWidget::trackProcessedFrameRate(const QString& cameraId, quint64 frameCount) - { - const QString sourceId = normalizedSourceId(cameraId); - if (!sourceId.isEmpty() && frameCount > 0) - { - updateLayerFps(previewLayerKey(sourceId, true), frameCount); - } - } - - // Tracks acquired raw throughput from backend frame counts - void PreviewWidget::trackRawFrameRate(const QString& cameraId, quint64 frameCount) - { - const QString sourceId = normalizedSourceId(cameraId); - if (!sourceId.isEmpty() && frameCount > 0) - { - updateLayerFps(previewLayerKey(sourceId, false), frameCount); - } - } - // Clears live throughput windows when preview stops void PreviewWidget::resetLiveFrameRates() { - bool changed = false; for (auto it = m_layerFps.begin(); it != m_layerFps.end(); ++it) { - if ((ScopeOneCore::isRawLayerKey(it.key()) || ScopeOneCore::isProcessedLayerKey(it.key())) - && !qFuzzyIsNull(it.value())) + if (ScopeOneCore::isRawLayerKey(it.key()) || ScopeOneCore::isProcessedLayerKey(it.key())) { it.value() = 0.0; - changed = true; } } - m_fpsStates.clear(); - if (changed) - { - updateLayerInfoDisplay(); - } + updateLayerInfoDisplay(); + } + + void PreviewWidget::setLayerFrameRates(const QMap& frameRates) + { + m_layerFps = frameRates; + updateLayerInfoDisplay(); } // Changes how multiple preview layers are laid out @@ -581,7 +547,6 @@ namespace scopeone::ui return {}; } initializeLayerInfo(layerKey); - updateLayerFps(layerKey, 1); updateLayerInfoDisplay(); updateImageDisplay(); if (newLayer) @@ -782,8 +747,6 @@ namespace scopeone::ui const QString normalizedId = normalizedSourceId(sourceId); m_layerFps.remove(previewLayerKey(normalizedId, false)); m_layerFps.remove(previewLayerKey(normalizedId, true)); - m_fpsStates.remove(previewLayerKey(normalizedId, false)); - m_fpsStates.remove(previewLayerKey(normalizedId, true)); updateLayerInfoDisplay(); QMutexLocker lock(&m_mutex); @@ -817,7 +780,6 @@ namespace scopeone::ui for (const QString& cameraId : m_availableCameraIds) { m_layerFps.remove(previewLayerKey(cameraId, true)); - m_fpsStates.remove(previewLayerKey(cameraId, true)); } updateLayerInfoDisplay(); @@ -914,7 +876,6 @@ namespace scopeone::ui m_layerSliceCounts.remove(layerKey); m_layerSliceIndices.remove(layerKey); m_layerFps.remove(layerKey); - m_fpsStates.remove(layerKey); { QMutexLocker lock(&m_mutex); @@ -1132,6 +1093,17 @@ namespace scopeone::ui update(); } + void PreviewWidget::setThreeDimensionalColorbarVisible(bool visible) + { + if (m_threeDimensionalColorbarVisible == visible) + { + return; + } + m_threeDimensionalColorbarVisible = visible; + emit threeDimensionalColorbarVisibilityChanged(visible); + update(); + } + void PreviewWidget::setActiveLayerKey(const QString& key) { if (m_activeLayerKey == key) @@ -1176,38 +1148,6 @@ namespace scopeone::ui update(); } - // Publishes raw and processed throughput over one shared time window - void PreviewWidget::updateFrameRates() - { - if (m_fpsStates.isEmpty()) - { - return; - } - - bool changed = false; - for (auto it = m_fpsStates.begin(); it != m_fpsStates.end(); ++it) - { - FpsState& state = it.value(); - const qint64 elapsedNs = state.intervalTimer.nsecsElapsed(); - const double fps = elapsedNs > 0 - ? (static_cast(state.framesSinceUpdate) * 1000000000.0) - / static_cast(elapsedNs) - : 0.0; - state.framesSinceUpdate = 0; - state.intervalTimer.restart(); - double& currentFps = m_layerFps[it.key()]; - if (!qFuzzyCompare(currentFps + 1.0, fps + 1.0)) - { - currentFps = fps; - changed = true; - } - } - if (changed) - { - updateLayerInfoDisplay(); - } - } - // Checks whether a frame source has raw data bool PreviewWidget::hasRawFrame(const FrameSourceState& frameState) const { @@ -2227,6 +2167,72 @@ namespace scopeone::ui m_prog3d.release(); } + QImage PreviewWidget::colormapStripImage(int colormapIndex, int height) const + { + const QStringList names = ImageSceneModel::supportedColormaps(); + const QByteArray lut = loadImageJLut(names[colormapIndex]); + QImage strip(16, height, QImage::Format_RGB888); + for (int y = 0; y < height; ++y) + { + const int lutIndex = 255 - (y * 255 / (height - 1)); + uchar* line = strip.scanLine(y); + for (int x = 0; x < strip.width(); ++x) + { + line[3 * x] = static_cast(lut[3 * lutIndex]); + line[3 * x + 1] = static_cast(lut[3 * lutIndex + 1]); + line[3 * x + 2] = static_cast(lut[3 * lutIndex + 2]); + } + } + return strip; + } + + void PreviewWidget::draw3dColorbar(QPainter& painter, + const RenderItem& item, + const QRect& viewportRect) const + { + const int panelWidth = 100; + const int panelHeight = 220; + const QRect panel(viewportRect.right() - panelWidth - 16, + viewportRect.center().y() - panelHeight / 2, + panelWidth, + panelHeight); + const QRect strip(panel.x() + 10, panel.y() + 32, 16, 174); + const QStringList names = ImageSceneModel::supportedColormaps(); + + painter.save(); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setPen(Qt::NoPen); + painter.setBrush(QColor(0, 0, 0, 160)); + painter.drawRoundedRect(panel, 5, 5); + painter.drawImage(strip, colormapStripImage(item.display.colormapIndex, strip.height())); + + QFont font = painter.font(); + font.setPointSize(8); + painter.setFont(font); + painter.setPen(Qt::white); + painter.drawText(QRect(panel.x() + 4, panel.y() + 7, panel.width() - 8, 18), + Qt::AlignCenter, + names[item.display.colormapIndex]); + + const QFontMetrics metrics(font); + for (int tick = 0; tick < 5; ++tick) + { + const double fraction = static_cast(tick) / 4.0; + const int y = strip.top() + qRound(fraction * (strip.height() - 1)); + const double value = item.display.levelMax + + (item.display.levelMin - item.display.levelMax) * fraction; + painter.setPen(QPen(Qt::white, 1)); + painter.drawLine(strip.right() + 2, y, strip.right() + 7, y); + painter.drawText(QRect(strip.right() + 10, + y - metrics.height() / 2, + panel.right() - strip.right() - 14, + metrics.height()), + Qt::AlignLeft | Qt::AlignVCenter, + QString::number(value, 'f', 0)); + } + painter.restore(); + } + // Updates the layer info summary text void PreviewWidget::updateLayerInfoDisplay() { @@ -2768,6 +2774,19 @@ namespace scopeone::ui p.setPen(QPen(p.pen().color(), item.layerKey == m_activeLayerKey ? 2 : 1)); p.drawRect(item.area.adjusted(1, 1, -2, -2)); } + if (m_threeDimensionalColorbarVisible) + { + const RenderItem* activeItem = &renderItems.front(); + for (const RenderItem& item : renderItems) + { + if (item.layerKey == m_activeLayerKey) + { + activeItem = &item; + break; + } + } + draw3dColorbar(p, *activeItem, activeItem->area); + } p.setPen(QColor(220, 225, 230, 190)); p.drawText(QRect(12, height() - 28, width() - 24, 18), Qt::AlignRight | Qt::AlignVCenter, @@ -2812,6 +2831,10 @@ namespace scopeone::ui p.drawText(panelRect.adjusted(10, 25, -10, -7), Qt::AlignLeft | Qt::AlignVCenter, detail); + if (m_threeDimensionalColorbarVisible) + { + draw3dColorbar(p, *surfaceItem, rect()); + } p.setPen(QColor(220, 225, 230, 190)); p.drawText(QRect(12, height() - 28, width() - 24, 18), Qt::AlignRight | Qt::AlignVCenter, diff --git a/src/PreviewWidget.h b/src/PreviewWidget.h index c43834f..088cdb4 100644 --- a/src/PreviewWidget.h +++ b/src/PreviewWidget.h @@ -25,6 +25,7 @@ class QDragEnterEvent; class QDropEvent; class QEvent; +class QImage; class QKeyEvent; class QLabel; class QMouseEvent; @@ -63,9 +64,8 @@ namespace scopeone::ui void setGraphProcessedFrame(const scopeone::core::ImageFrame& frame); void setGraphRawFrame(const scopeone::core::ImageFrame& frame); - void trackProcessedFrameRate(const QString& cameraId, quint64 frameCount); - void trackRawFrameRate(const QString& cameraId, quint64 frameCount); void resetLiveFrameRates(); + void setLayerFrameRates(const QMap& frameRates); void setLayerLayoutMode(LayerLayoutMode mode); LayerLayoutMode layerLayoutMode() const; void setAvailableCameraIds(const QStringList& cameraIds); @@ -101,6 +101,8 @@ namespace scopeone::ui void reset3dCamera(); void set3dWireframeEnabled(bool enabled); bool is3dWireframeEnabled() const { return m_wireframe3d; } + void setThreeDimensionalColorbarVisible(bool visible); + bool isThreeDimensionalColorbarVisible() const { return m_threeDimensionalColorbarVisible; } void setActiveLayerKey(const QString& key); QString activeLayerKey() const { return m_activeLayerKey; } void setPixelSizeCallback(std::function callback); @@ -127,6 +129,7 @@ namespace scopeone::ui void viewDimensionModeChanged(ViewDimensionMode mode); void threeDimensionalZScaleChanged(float scale); void threeDimensionalWireframeChanged(bool enabled); + void threeDimensionalColorbarVisibilityChanged(bool visible); void stageStepRequested(double dxScale, double dyScale, bool big); void stageZStepRequested(double dzScale, bool big); void layerClicked(const QString& layerKey); @@ -162,12 +165,6 @@ namespace scopeone::ui void dropEvent(QDropEvent* event) override; private: - struct FpsState - { - QElapsedTimer intervalTimer; - quint64 framesSinceUpdate{0}; - }; - enum class Blending { Translucent = 0, Additive, Minimum, Opaque, Multiplicative }; enum class FrameRole { Raw, Processed }; enum class MarkupEditMode @@ -254,8 +251,6 @@ namespace scopeone::ui LayerLayoutMode m_layerLayoutMode{LayerLayoutMode::SideBySide}; QMap m_layerFps; QString m_layerInfoText{QStringLiteral("No image loaded")}; - QMap m_fpsStates; - QTimer m_fpsUpdateTimer; QTimer m_sliceTimer; ImageSceneModel* m_sceneModel{nullptr}; QLabel* m_placeholderLabel{nullptr}; @@ -295,6 +290,7 @@ namespace scopeone::ui QMap m_layerSliceIndices; float m_zScale{1.0f}; bool m_wireframe3d{false}; + bool m_threeDimensionalColorbarVisible{true}; bool m_scaleBarVisible{true}; bool m_clippingWarning{false}; QString m_activeLayerKey; @@ -347,8 +343,6 @@ namespace scopeone::ui float m_surfaceStartYaw{45.0f}; QVector2D m_surfaceStartPan{0.0f, 0.0f}; void updateImageDisplay(); - void updateLayerFps(const QString& layerKey, quint64 frameCount = 1); - void updateFrameRates(); bool storeSourceFrame(const QString& sourceId, FrameRole role, const scopeone::core::ImageFrame& frame, @@ -419,6 +413,10 @@ namespace scopeone::ui void draw3dSurface(const RenderItem& item, const Camera3dState& camera, const QRect& targetArea); + void draw3dColorbar(QPainter& painter, + const RenderItem& item, + const QRect& viewportRect) const; + QImage colormapStripImage(int colormapIndex, int height) const; void ensureGlPipeline(); GLuint ensureFrameTexture(const QString& textureKey, const scopeone::core::ImageFrame& frame,