From 6adb4bf8528a5f66de0bcd6657caf00d5ab986cb Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:48:29 -0800 Subject: [PATCH 1/7] allow snap to call insertImage instead of keeping its own buffer --- MMCore/CoreCallback.cpp | 24 +++++++++ MMCore/Devices/CameraInstance.cpp | 84 +++++++++++++++++++++++++++++-- MMCore/Devices/CameraInstance.h | 23 +++++++++ 3 files changed, 127 insertions(+), 4 deletions(-) diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 572f9a0ce..4dd831693 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -251,6 +251,18 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, const Metadata* pMd, bool doProcess) { + // Check if the camera is currently snapping an image, if its calling InsertImage, + // then its circumventing having its own buffer. This allows camera device adapters to + // not have to implement their own buffers, and can instead just copy data in directly. + { + std::shared_ptr camera = std::static_pointer_cast(core_->deviceManager_->GetDevice(caller)); + if (camera->IsSnapping()) { + // Don't do any metadata or image processing, for consistency with the existing SnapImage()/GetImage() methods + camera->StoreSnappedImage(buf, width, height, byteDepth, 1); + return DEVICE_OK; + } + } + try { Metadata md = AddCameraMetadata(caller, pMd); @@ -283,6 +295,18 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, const Metadata* pMd, bool doProcess) { + // Check if the camera is currently snapping an image, if its calling InsertImage, + // then its circumventing having its own buffer. This allows camera device adapters to + // not have to implement their own buffers, and can instead just copy data in directly. + { + std::shared_ptr camera = std::static_pointer_cast(core_->deviceManager_->GetDevice(caller)); + if (camera->IsSnapping()) { + // Don't do any metadata or image processing, for consistency with the existing SnapImage()/GetImage() methods + camera->StoreSnappedImage(buf, width, height, byteDepth, 1); + return DEVICE_OK; + } + } + try { Metadata md = AddCameraMetadata(caller, pMd); diff --git a/MMCore/Devices/CameraInstance.cpp b/MMCore/Devices/CameraInstance.cpp index 8633d1b5d..9bf048f7e 100644 --- a/MMCore/Devices/CameraInstance.cpp +++ b/MMCore/Devices/CameraInstance.cpp @@ -22,10 +22,57 @@ #include "CameraInstance.h" -int CameraInstance::SnapImage() { RequireInitialized(__func__); return GetImpl()->SnapImage(); } -const unsigned char* CameraInstance::GetImageBuffer() { RequireInitialized(__func__); return GetImpl()->GetImageBuffer(); } -const unsigned char* CameraInstance::GetImageBuffer(unsigned channelNr) { RequireInitialized(__func__); return GetImpl()->GetImageBuffer(channelNr); } -const unsigned int* CameraInstance::GetImageBufferAsRGB32() { RequireInitialized(__func__); return GetImpl()->GetImageBufferAsRGB32(); } +int CameraInstance::SnapImage() { + RequireInitialized(__func__); + isSnapping_.store(true); + int ret = DEVICE_OK; + if (!GetImpl()->IsNewAPIImplemented()) { + ret = GetImpl()->SnapImage(); + } else { + ret = GetImpl()->AcquisitionArm(1); + if (ret != DEVICE_OK) { + isSnapping_.store(false); + return ret; + } + ret = GetImpl()->AcquisitionStart(); + + // Use condition variable to wait for image capture + std::unique_lock lock(imageMutex_); + imageAvailable_.wait(lock, [this]() { + return !GetImpl()->IsCapturing() || !isSnapping_.load(); + }); + } + isSnapping_.store(false); + return ret; +} + +const unsigned char* CameraInstance::GetImageBuffer() { + RequireInitialized(__func__); + const unsigned char* snappedPixels = snappedImage_.GetPixels(0); + if (snappedPixels != nullptr) { + return snappedPixels; + } + return GetImpl()->GetImageBuffer(); +} + +const unsigned char* CameraInstance::GetImageBuffer(unsigned channelNr) { + RequireInitialized(__func__); + const unsigned char* snappedPixels = snappedImage_.GetPixels(channelNr); + if (snappedPixels != nullptr) { + return snappedPixels; + } + return GetImpl()->GetImageBuffer(channelNr); +} + +const unsigned int* CameraInstance::GetImageBufferAsRGB32() { + RequireInitialized(__func__); + const unsigned int* snappedPixels = reinterpret_cast(snappedImage_.GetPixels(0)); + if (snappedPixels != nullptr) { + return snappedPixels; + } + return GetImpl()->GetImageBufferAsRGB32(); +} + unsigned CameraInstance::GetNumberOfComponents() const { RequireInitialized(__func__); return GetImpl()->GetNumberOfComponents(); } std::string CameraInstance::GetComponentName(unsigned component) @@ -154,3 +201,32 @@ int CameraInstance::StopExposureSequence() { RequireInitialized(__func__); retur int CameraInstance::ClearExposureSequence() { RequireInitialized(__func__); return GetImpl()->ClearExposureSequence(); } int CameraInstance::AddToExposureSequence(double exposureTime_ms) { RequireInitialized(__func__); return GetImpl()->AddToExposureSequence(exposureTime_ms); } int CameraInstance::SendExposureSequence() const { RequireInitialized(__func__); return GetImpl()->SendExposureSequence(); } + +bool CameraInstance::IsSnapping() const { + return isSnapping_.load(); +} + +void CameraInstance::StoreSnappedImage(const unsigned char* buf, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents) { + // Calculate total buffer size needed + size_t totalBytes = width * height * byteDepth * nComponents; + + // If the buffer doesn't exist or has wrong dimensions, create/resize it + if (snappedImage_.Width() != width || + snappedImage_.Height() != height || + snappedImage_.Depth() != byteDepth) { + snappedImage_.Resize(width, height, byteDepth); + } + + // Assume channel 0 + snappedImage_.SetPixels(0, buf); + + // now ready for GetImage to be called + isSnapping_.store(false); + + // Notify any waiting threads that the image is available + { + std::lock_guard lock(imageMutex_); + imageAvailable_.notify_all(); + } +} diff --git a/MMCore/Devices/CameraInstance.h b/MMCore/Devices/CameraInstance.h index f228a37e6..2cd47f539 100644 --- a/MMCore/Devices/CameraInstance.h +++ b/MMCore/Devices/CameraInstance.h @@ -20,6 +20,10 @@ #pragma once #include "DeviceInstanceBase.h" +#include +#include +#include +#include "../FrameBuffer.h" class CameraInstance : public DeviceInstanceBase @@ -82,4 +86,23 @@ class CameraInstance : public DeviceInstanceBase int ClearExposureSequence(); int AddToExposureSequence(double exposureTime_ms); int SendExposureSequence() const; + + /** + * Checks if the camera is currently snapping an image. + * Thread-safe method that can be called from any thread. + * @return true if the camera is currently snapping an image, false otherwise + */ + bool IsSnapping() const; + void StoreSnappedImage(const unsigned char* buf, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents); + +private: + // Atomic flag to track if the camera is currently snapping an image + std::atomic isSnapping_{false}; + + // Frame buffer to store captured images + mm::FrameBuffer snappedImage_; + + std::mutex imageMutex_; + std::condition_variable imageAvailable_; }; From 8a0dd5de8c02ca93cbb6ee04377bec4b1a299c91 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 8 Mar 2025 15:29:57 -0800 Subject: [PATCH 2/7] remove future new camera api changes --- MMCore/Devices/CameraInstance.cpp | 23 +---------------------- MMCore/Devices/CameraInstance.h | 5 ----- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/MMCore/Devices/CameraInstance.cpp b/MMCore/Devices/CameraInstance.cpp index 9bf048f7e..791518525 100644 --- a/MMCore/Devices/CameraInstance.cpp +++ b/MMCore/Devices/CameraInstance.cpp @@ -25,23 +25,7 @@ int CameraInstance::SnapImage() { RequireInitialized(__func__); isSnapping_.store(true); - int ret = DEVICE_OK; - if (!GetImpl()->IsNewAPIImplemented()) { - ret = GetImpl()->SnapImage(); - } else { - ret = GetImpl()->AcquisitionArm(1); - if (ret != DEVICE_OK) { - isSnapping_.store(false); - return ret; - } - ret = GetImpl()->AcquisitionStart(); - - // Use condition variable to wait for image capture - std::unique_lock lock(imageMutex_); - imageAvailable_.wait(lock, [this]() { - return !GetImpl()->IsCapturing() || !isSnapping_.load(); - }); - } + int ret = GetImpl()->SnapImage(); isSnapping_.store(false); return ret; } @@ -224,9 +208,4 @@ void CameraInstance::StoreSnappedImage(const unsigned char* buf, unsigned width, // now ready for GetImage to be called isSnapping_.store(false); - // Notify any waiting threads that the image is available - { - std::lock_guard lock(imageMutex_); - imageAvailable_.notify_all(); - } } diff --git a/MMCore/Devices/CameraInstance.h b/MMCore/Devices/CameraInstance.h index 2cd47f539..6e16383c1 100644 --- a/MMCore/Devices/CameraInstance.h +++ b/MMCore/Devices/CameraInstance.h @@ -21,8 +21,6 @@ #include "DeviceInstanceBase.h" #include -#include -#include #include "../FrameBuffer.h" @@ -102,7 +100,4 @@ class CameraInstance : public DeviceInstanceBase // Frame buffer to store captured images mm::FrameBuffer snappedImage_; - - std::mutex imageMutex_; - std::condition_variable imageAvailable_; }; From 6183045a54a0bb8b9ac019ac0f680eb067d33fd4 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 8 Mar 2025 15:53:29 -0800 Subject: [PATCH 3/7] fix ncomponents --- MMCore/CoreCallback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 4dd831693..465b375aa 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -302,7 +302,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf std::shared_ptr camera = std::static_pointer_cast(core_->deviceManager_->GetDevice(caller)); if (camera->IsSnapping()) { // Don't do any metadata or image processing, for consistency with the existing SnapImage()/GetImage() methods - camera->StoreSnappedImage(buf, width, height, byteDepth, 1); + camera->StoreSnappedImage(buf, width, height, byteDepth, nComponents); return DEVICE_OK; } } From aed4e97c62b4ac51541d172ecb072534f4c7e1bb Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 8 Mar 2025 15:57:42 -0800 Subject: [PATCH 4/7] changes for multi-channel cameras --- MMCore/Devices/CameraInstance.cpp | 9 ++++----- MMCore/Devices/CameraInstance.h | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MMCore/Devices/CameraInstance.cpp b/MMCore/Devices/CameraInstance.cpp index 791518525..11ba9869f 100644 --- a/MMCore/Devices/CameraInstance.cpp +++ b/MMCore/Devices/CameraInstance.cpp @@ -25,6 +25,7 @@ int CameraInstance::SnapImage() { RequireInitialized(__func__); isSnapping_.store(true); + insertImageCounter_.store(0); int ret = GetImpl()->SnapImage(); isSnapping_.store(false); return ret; @@ -202,10 +203,8 @@ void CameraInstance::StoreSnappedImage(const unsigned char* buf, unsigned width, snappedImage_.Resize(width, height, byteDepth); } - // Assume channel 0 - snappedImage_.SetPixels(0, buf); + // For multi-channel cameras, insertImage will be called once for each channel + snappedImage_.SetPixels(insertImageCounter_.load(), buf); + insertImageCounter_.fetch_add(1); - // now ready for GetImage to be called - isSnapping_.store(false); - } diff --git a/MMCore/Devices/CameraInstance.h b/MMCore/Devices/CameraInstance.h index 6e16383c1..514ae4f91 100644 --- a/MMCore/Devices/CameraInstance.h +++ b/MMCore/Devices/CameraInstance.h @@ -100,4 +100,5 @@ class CameraInstance : public DeviceInstanceBase // Frame buffer to store captured images mm::FrameBuffer snappedImage_; + std::atomic insertImageCounter_{0}; }; From b385397ecdd0623eb2ff88dd9c1e22e78a800ca8 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 8 Mar 2025 16:04:07 -0800 Subject: [PATCH 5/7] change variable name --- MMCore/Devices/CameraInstance.cpp | 6 +++--- MMCore/Devices/CameraInstance.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MMCore/Devices/CameraInstance.cpp b/MMCore/Devices/CameraInstance.cpp index 11ba9869f..a24d26c43 100644 --- a/MMCore/Devices/CameraInstance.cpp +++ b/MMCore/Devices/CameraInstance.cpp @@ -25,7 +25,7 @@ int CameraInstance::SnapImage() { RequireInitialized(__func__); isSnapping_.store(true); - insertImageCounter_.store(0); + multiChannelImageCounter_.store(0); int ret = GetImpl()->SnapImage(); isSnapping_.store(false); return ret; @@ -204,7 +204,7 @@ void CameraInstance::StoreSnappedImage(const unsigned char* buf, unsigned width, } // For multi-channel cameras, insertImage will be called once for each channel - snappedImage_.SetPixels(insertImageCounter_.load(), buf); - insertImageCounter_.fetch_add(1); + snappedImage_.SetPixels(multiChannelImageCounter_.load(), buf); + multiChannelImageCounter_.fetch_add(1); } diff --git a/MMCore/Devices/CameraInstance.h b/MMCore/Devices/CameraInstance.h index 514ae4f91..b76f1268d 100644 --- a/MMCore/Devices/CameraInstance.h +++ b/MMCore/Devices/CameraInstance.h @@ -100,5 +100,5 @@ class CameraInstance : public DeviceInstanceBase // Frame buffer to store captured images mm::FrameBuffer snappedImage_; - std::atomic insertImageCounter_{0}; + std::atomic multiChannelImageCounter_{0}; }; From 75764f1ad33ddf1db9c6f7bd209c14dca924b5f1 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 8 Mar 2025 16:14:44 -0800 Subject: [PATCH 6/7] delete unused --- MMCore/Devices/CameraInstance.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/MMCore/Devices/CameraInstance.cpp b/MMCore/Devices/CameraInstance.cpp index a24d26c43..bf3ba7b0b 100644 --- a/MMCore/Devices/CameraInstance.cpp +++ b/MMCore/Devices/CameraInstance.cpp @@ -193,9 +193,6 @@ bool CameraInstance::IsSnapping() const { void CameraInstance::StoreSnappedImage(const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents) { - // Calculate total buffer size needed - size_t totalBytes = width * height * byteDepth * nComponents; - // If the buffer doesn't exist or has wrong dimensions, create/resize it if (snappedImage_.Width() != width || snappedImage_.Height() != height || From 224e5e1bd5988aad181f1a2a7fd7d8ea1ef25c7a Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 8 Mar 2025 16:17:58 -0800 Subject: [PATCH 7/7] fix compiler warnings --- MMCore/CoreCallback.cpp | 4 ++-- MMCore/Devices/CameraInstance.cpp | 2 +- MMCore/Devices/CameraInstance.h | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 465b375aa..15c9ab225 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -258,7 +258,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf std::shared_ptr camera = std::static_pointer_cast(core_->deviceManager_->GetDevice(caller)); if (camera->IsSnapping()) { // Don't do any metadata or image processing, for consistency with the existing SnapImage()/GetImage() methods - camera->StoreSnappedImage(buf, width, height, byteDepth, 1); + camera->StoreSnappedImage(buf, width, height, byteDepth); return DEVICE_OK; } } @@ -302,7 +302,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf std::shared_ptr camera = std::static_pointer_cast(core_->deviceManager_->GetDevice(caller)); if (camera->IsSnapping()) { // Don't do any metadata or image processing, for consistency with the existing SnapImage()/GetImage() methods - camera->StoreSnappedImage(buf, width, height, byteDepth, nComponents); + camera->StoreSnappedImage(buf, width, height, byteDepth); return DEVICE_OK; } } diff --git a/MMCore/Devices/CameraInstance.cpp b/MMCore/Devices/CameraInstance.cpp index bf3ba7b0b..b35b89bf3 100644 --- a/MMCore/Devices/CameraInstance.cpp +++ b/MMCore/Devices/CameraInstance.cpp @@ -192,7 +192,7 @@ bool CameraInstance::IsSnapping() const { } void CameraInstance::StoreSnappedImage(const unsigned char* buf, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents) { + unsigned byteDepth) { // If the buffer doesn't exist or has wrong dimensions, create/resize it if (snappedImage_.Width() != width || snappedImage_.Height() != height || diff --git a/MMCore/Devices/CameraInstance.h b/MMCore/Devices/CameraInstance.h index b76f1268d..5ce2a9aad 100644 --- a/MMCore/Devices/CameraInstance.h +++ b/MMCore/Devices/CameraInstance.h @@ -91,8 +91,7 @@ class CameraInstance : public DeviceInstanceBase * @return true if the camera is currently snapping an image, false otherwise */ bool IsSnapping() const; - void StoreSnappedImage(const unsigned char* buf, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents); + void StoreSnappedImage(const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth); private: // Atomic flag to track if the camera is currently snapping an image