From 27e4c8ce0b85b1c6a6af8170db662fa4a209f787 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:06:00 -0800 Subject: [PATCH 01/46] initial data buffer commit --- MMCore/BufferAdapter.cpp | 128 ++++++++++++++++++++++++ MMCore/BufferAdapter.h | 93 ++++++++++++++++++ MMCore/Buffer_v2.cpp | 206 +++++++++++++++++++++++++++++++++++++++ MMCore/Buffer_v2.h | 187 +++++++++++++++++++++++++++++++++++ MMCore/MMCore.cpp | 69 ++++++------- MMCore/MMCore.h | 4 +- 6 files changed, 652 insertions(+), 35 deletions(-) create mode 100644 MMCore/BufferAdapter.cpp create mode 100644 MMCore/BufferAdapter.h create mode 100644 MMCore/Buffer_v2.cpp create mode 100644 MMCore/Buffer_v2.h diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp new file mode 100644 index 000000000..60d1d3bea --- /dev/null +++ b/MMCore/BufferAdapter.cpp @@ -0,0 +1,128 @@ +#include "BufferAdapter.h" + +// For demonstration, we assume DEVICE_OK and DEVICE_ERR macros are defined in MMCore.h or an included error header. +#ifndef DEVICE_OK + #define DEVICE_OK 0 +#endif +#ifndef DEVICE_ERR + #define DEVICE_ERR -1 +#endif + +BufferAdapter::BufferAdapter(bool useV2Buffer, unsigned int memorySizeMB) + : useV2_(useV2Buffer), circBuffer_(nullptr), v2Buffer_(nullptr) +{ + if (useV2_) { + // Create a new v2 buffer with a total size of memorySizeMB megabytes. + // Multiply by (1 << 20) to convert megabytes to bytes. + size_t bytes = memorySizeMB * (1 << 20); + v2Buffer_ = new DataBuffer(bytes, "DEFAULT"); + } else { + circBuffer_ = new CircularBuffer(memorySizeMB); + // Optionally, perform any necessary initialization for the circular buffer. + } +} + +BufferAdapter::~BufferAdapter() +{ + if (useV2_) { + if (v2Buffer_) { + delete v2Buffer_; + } + } else { + if (circBuffer_) { + delete circBuffer_; + } + } +} + +bool BufferAdapter::InsertImage(const MM::Device *caller, const unsigned char* buf, + unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, const char* serializedMetadata) +{ + if (useV2_) { + // For demonstration, assume that for a simple image the number of bytes is width * height * byteDepth * nComponents. + size_t dataSize = width * height * byteDepth * nComponents; + int res = v2Buffer_->InsertData(caller, buf, dataSize, serializedMetadata); + return (res == 0); + } else { + // For now, pass a null metadata pointer. + return circBuffer_->InsertImage(buf, width, height, byteDepth, nComponents, nullptr); + } +} + +const unsigned char* BufferAdapter::GetTopImage() const +{ + if (useV2_) { + // Minimal support: the v2Buffer currently does not expose GetTopImage. + return nullptr; + } else { + return circBuffer_->GetTopImage(); + } +} + +const unsigned char* BufferAdapter::GetNextImage() +{ + if (useV2_) { + // Minimal support: return nullptr since v2Buffer does not provide next image retrieval. + return nullptr; + } else { + return circBuffer_->GetNextImage(); + } +} + +const mm::ImgBuffer* BufferAdapter::GetNthFromTopImageBuffer(unsigned long n) const +{ + if (useV2_) { + // Implement logic for v2Buffer if available + return nullptr; // Placeholder + } else { + return circBuffer_->GetNthFromTopImageBuffer(n); + } +} + +const mm::ImgBuffer* BufferAdapter::GetNextImageBuffer(unsigned channel) +{ + if (useV2_) { + // Implement logic for v2Buffer if available + return nullptr; // Placeholder + } else { + return circBuffer_->GetNextImageBuffer(channel); + } +} + +bool BufferAdapter::Initialize(unsigned numChannels, unsigned width, unsigned height, unsigned bytesPerPixel) +{ + if (useV2_) { + // Implement initialization logic for v2Buffer + return true; // Placeholder + } else { + return circBuffer_->Initialize(numChannels, width, height, bytesPerPixel); + } +} + +unsigned BufferAdapter::GetMemorySizeMB() const +{ + if (useV2_) { + return 0; // TODO: need to implement this + } else { + return circBuffer_->GetMemorySizeMB(); + } +} + +long BufferAdapter::GetRemainingImageCount() const +{ + if (useV2_) { + return 0; // TODO: need to implement this + } else { + return circBuffer_->GetRemainingImageCount(); + } +} + +void BufferAdapter::Clear() +{ + if (useV2_) { + // In this basic implementation, we call ReleaseBuffer with the known buffer name. + v2Buffer_->ReleaseBuffer("DEFAULT"); + } else { + circBuffer_->Clear(); + } +} \ No newline at end of file diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h new file mode 100644 index 000000000..e618aea30 --- /dev/null +++ b/MMCore/BufferAdapter.h @@ -0,0 +1,93 @@ +#ifndef BUFFERADAPTER_H +#define BUFFERADAPTER_H + +#include "CircularBuffer.h" +#include "Buffer_v2.h" +#include "../MMDevice/MMDevice.h" + +// BufferAdapter provides a common interface for buffer operations +// used by MMCore. It currently supports only a minimal set of functions. +class BufferAdapter { +public: + /** + * Constructor. + * @param useV2Buffer Set to true to use the new DataBuffer (v2); false to use CircularBuffer. + * @param memorySizeMB Memory size for the buffer (in megabytes). + */ + BufferAdapter(bool useV2Buffer, unsigned int memorySizeMB); + ~BufferAdapter(); + + /** + * Insert an image into the buffer. + * @param caller The device inserting the image. + * @param buf The image data. + * @param width Image width. + * @param height Image height. + * @param byteDepth Bytes per pixel. + * @param nComponents Number of components in the image. + * @param pMd Metadata associated with the image. + * @return true on success, false on error. + */ + bool InsertImage(const MM::Device *caller, const unsigned char* buf, + unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, Metadata* pMd); + + /** + * Get a pointer to the top (most recent) image. + * @return Pointer to image data, or nullptr if unavailable. + */ + const unsigned char* GetTopImage() const; + + /** + * Get a pointer to the next image from the buffer. + * @return Pointer to image data, or nullptr if unavailable. + */ + const unsigned char* GetNextImage(); + + /** + * Get a pointer to the nth image from the top of the buffer. + * @param n The index from the top. + * @return Pointer to image data, or nullptr if unavailable. + */ + const mm::ImgBuffer* GetNthFromTopImageBuffer(unsigned long n) const; + + /** + * Get a pointer to the next image buffer for a specific channel. + * @param channel The channel number. + * @return Pointer to image data, or nullptr if unavailable. + */ + const mm::ImgBuffer* GetNextImageBuffer(unsigned channel); + + /** + * Initialize the buffer with the given parameters. + * @param numChannels Number of channels. + * @param width Image width. + * @param height Image height. + * @param bytesPerPixel Bytes per pixel. + * @return true on success, false on error. + */ + bool Initialize(unsigned numChannels, unsigned width, unsigned height, unsigned bytesPerPixel); + + /** + * Get the memory size of the buffer in megabytes. + * @return Memory size in MB. + */ + unsigned GetMemorySizeMB() const; + + /** + * Get the remaining image count in the buffer. + * @return Number of remaining images. + */ + long GetRemainingImageCount() const; + + /** + * Clear the entire image buffer. + */ + void Clear(); + +private: + bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. + CircularBuffer* circBuffer_; + DataBuffer* v2Buffer_; +}; + +#endif // BUFFERADAPTER_H \ No newline at end of file diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp new file mode 100644 index 000000000..e4ef33750 --- /dev/null +++ b/MMCore/Buffer_v2.cpp @@ -0,0 +1,206 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: Buffer_v2.cpp +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +//----------------------------------------------------------------------------- +// DESCRIPTION: Generic implementation of a buffer for storing image data and +// metadata. Provides thread-safe access for reading and writing +// with configurable overflow behavior. +// +// COPYRIGHT: Henry Pinkard, 2025 +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Henry Pinkard, 01/31/2025 + + +/* +Design Overview: + +The buffer is designed as a flexible data structure for storing image data and metadata: + +Buffer Structure: +- A large block of contiguous memory divided into slots + +Slots: +- Contiguous sections within the buffer that can vary in size +- Support exclusive write access with shared read access +- Memory management through reference counting: + - Writers get exclusive ownership during writes + - Readers can get shared read-only access + - Slots are recycled when all references are released + +Data Access: +- Two access patterns supported: + 1. Copy-based access + 2. Direct pointer access with explicit release +- Reference counting ensures safe memory management +- Slots become available for recycling when: + - Writing is complete (via Insert or GetWritingSlot+Release) + - All readers have released their references + +Metadata Handling: +- Devices must specify PixelType when adding data +- Device-specific metadata requirements (e.g. image dimensions) are handled at the + device API level rather than in the buffer API to maintain clean separation +*/ + + +#include "Buffer_v2.h" +#include + +DataBuffer::DataBuffer(size_t numBytes, const char* name) { + // Initialize basic buffer with given size + AllocateBuffer(numBytes, name); +} + +DataBuffer::~DataBuffer() { + // Cleanup + delete[] buffer_; +} + +/** + * Allocate a character buffer + * @param numBytes The size of the buffer to allocate. + * @param name The name of the buffer. + * @return Error code (0 on success). + */ +int DataBuffer::AllocateBuffer(size_t numBytes, const char* name) { + buffer_ = new char[numBytes]; + bufferSize_ = numBytes; + bufferName_ = name; + // TODO: Store the buffer in a member variable for later use + return DEVICE_OK; +} + +/** + * Release the buffer if it matches the given name. + * @param name The name of the buffer to release. + * @return Error code (0 on success, error if buffer not found or already released). + */ +int DataBuffer::ReleaseBuffer(const char* name) { + if (buffer_ != nullptr && bufferName_ == name) { + delete[] buffer_; + buffer_ = nullptr; + bufferSize_ = 0; + bufferName_ = nullptr; + return DEVICE_OK; + } + // TODO: throw errors if other code holds pointers on stuff + return DEVICE_ERR; // Return an error if the buffer is not found or already released +} + +/** + * @brief Copy data into the next available slot in the buffer. + * + * Returns the size of the copied data through dataSize. + * Implementing code should check the device type of the caller, and ensure that + * all required metadata for interpreting its image data is there. + * Note: this can be implemented in terms of Get/Release slot + memcopy. + * + * @param caller The device calling this function. + * @param data The data to be copied into the buffer. + * @param dataSize The size of the data to be copied. + * @param serializedMetadata The serialized metadata associated with the data. + * @return Error code (0 on success). + */ +int DataBuffer::InsertData(const MM::Device *caller, const void* data, size_t dataSize, const char* serializedMetadata) { + // Basic implementation - just copy data + // TODO: Add proper buffer management and metadata handling + return 0; +} + +/** + * Check if a new slot has been fully written in this buffer + * @return true if new data is ready, false otherwise + */ +bool DataBuffer::IsNewDataReady() { + // Basic implementation + return false; +} + +/** + * Copy the next available data and metadata from the buffer + * @param dataDestination Destination buffer to copy data into + * @param dataSize Returns the size of the copied data, or 0 if no data available + * @param md Metadata object to populate + * @param waitForData If true, block until data becomes available + * @return Error code (0 on success) + */ +int DataBuffer::CopyNextDataAndMetadata(void* dataDestination, + size_t* dataSize, Metadata &md, bool waitForData) { + // Basic implementation + // TODO: Add proper data copying and metadata handling + return 0; +} + +/** + * Configure whether to overwrite old data when buffer is full. + * + * If true, when there are no more slots available for writing because + * images haven't been read fast enough, then automatically recycle the + * oldest slot(s) in the buffer as needed in order to make space for new images. + * This is suitable for situations when its okay to drop frames, like live + * view when data is not being saved. + * + * If false, then throw an exception if the buffer becomes full. + * + * @param overwrite Whether to enable overwriting of old data + * @return Error code (0 on success) + */ +int DataBuffer::SetOverwriteData(bool overwrite) { + // Basic implementation + return 0; +} + +/** + * @brief Get a pointer to the next available data slot in the buffer for writing. + * + * The caller must release the slot using ReleaseDataSlot after writing is complete. + * Internally this will use a std::unique_ptr. + * + * @param caller The device calling this function. + * @param slot Pointer to the slot where data will be written. + * @param slotSize The size of the slot. + * @param serializedMetadata The serialized metadata associated with the data. + * @return Error code (0 on success). + */ +int DataBuffer::GetWritingSlot(const MM::Device *caller, void** slot, size_t slotSize, const char* serializedMetadata) { + // Basic implementation + return 0; +} + +/** + * @brief Release a data slot after writing is complete. + * + * @param caller The device calling this function. + * @param buffer The buffer to be released. + * @return Error code (0 on success). + */ +int DataBuffer::ReleaseWritingSlot(const MM::Device *caller, void* buffer) { + // Basic implementation + return 0; +} + +/** + * Get a pointer to a heap allocated Metadata object with the required fields filled in + * @param md Pointer to the Metadata object to be created. + * @param width The width of the image. + * @param height The height of the image. + * @param bitDepth The bit depth of the image. + * @return Error code (0 on success). + */ +int DataBuffer::CreateCameraRequiredMetadata(Metadata** md, int width, int height, int bitDepth) { + // Basic implementation + // TODO: Create metadata with required camera fields + return 0; +} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h new file mode 100644 index 000000000..7cdb02523 --- /dev/null +++ b/MMCore/Buffer_v2.h @@ -0,0 +1,187 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: Buffer_v2.h +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +//----------------------------------------------------------------------------- +// DESCRIPTION: Generic implementation of a buffer for storing image data and +// metadata. Provides thread-safe access for reading and writing +// with configurable overflow behavior. +// +// COPYRIGHT: Henry Pinkard, 2025 +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Henry Pinkard, henry.pinkard@gmail.com, 01/31/2025 +// + +#pragma once + +#include "Metadata.h" +#include "../MMDevice/MMDevice.h" +#include +#include + +class DataBuffer { + public: + DataBuffer(size_t numBytes, const char* name); + ~DataBuffer(); + + + /////// Buffer Allocation and Destruction + + // C version + int AllocateBuffer(size_t numBytes, const char* name); + //// Is there a need for a name? + //// Maybe makes sense for Core to assing a unique integer + int ReleaseBuffer(const char* name); + + // TODO: Other versions for allocating buffers Java, Python + + + /////// Monitoring the Buffer /////// + int GetAvailableBytes(void* buffer, size_t* availableBytes); + int GetNumSlotsUsed(void* buffer, size_t* numSlotsUsed); + + + + //// Configuration options + /** + * Configure whether to overwrite old data when buffer is full. + * + * If true, when there are no more slots available for writing because + * images haven't been read fast enough, then automatically recycle the + * oldest slot(s) in the buffer as needed in order to make space for new images. + * This is suitable for situations when its okay to drop frames, like live + * view when data is not being saved. + * + * If false, then throw an exception if the buffer becomes full. + * + * @param overwrite Whether to enable overwriting of old data + * @return Error code (0 on success) + */ + int SetOverwriteData(bool overwrite); + + + + /////// Getting Data Out /////// + + /** + * Check if a new slot has been fully written in this buffer + * @return true if new data is ready, false otherwise + */ + bool IsNewDataReady(); + + /** + * Copy the next available data and metadata from the buffer + * @param dataDestination Destination buffer to copy data into + * @param dataSize Returns the size of the copied data, or 0 if no data available + * @param md Metadata object to populate + * @param waitForData If true, block until data becomes available + * @return Error code (0 on success) + */ + int CopyNextDataAndMetadata(void* dataDestination, + size_t* dataSize, Metadata &md, bool waitForData); + + + /** + * Copy the next available metadata from the buffer + * Returns the size of the copied metadata through metadataSize, + * or 0 if no metadata is available + */ + int CopyNextMetadata(void* buffer, Metadata &md); + + /** + * Get a pointer to the next available data slot in the buffer + * The caller must release the slot using ReleaseNextDataAndMetadata + * If awaitReady is false and the data was inserted using GetWritingSlot, it + * is possible to read the data as it is being written (e.g. to monitor progress) + * of large or slow image being written + * + * Internally this will use a std::shared_ptr + */ + int GetNextSlotPointer(void** slotPointer, size_t* dataSize, + Metadata &md, bool awaitReady=true); + + /** + * Release the next data slot and its associated metadata + */ + int ReleaseNextSlot(void** slotPointer); + + + ////// Writing Data into buffer ////// + + /** + * @brief Copy data into the next available slot in the buffer. + * + * Returns the size of the copied data through dataSize. + * Implementing code should check the device type of the caller, and ensure that + * all required metadata for interpreting its image data is there. + * Note: this can be implemented in terms of Get/Release slot + memcopy. + * + * @param caller The device calling this function. + * @param data The data to be copied into the buffer. + * @param dataSize The size of the data to be copied. + * @param serializedMetadata The serialized metadata associated with the data. + * @return Error code (0 on success). + */ + int InsertData(const MM::Device *caller, const void* data, size_t dataSize, const char* serializedMetadata); + + /** + * @brief Get a pointer to the next available data slot in the buffer for writing. + * + * The caller must release the slot using ReleaseDataSlot after writing is complete. + * Internally this will use a std::unique_ptr. + * + * @param caller The device calling this function. + * @param slot Pointer to the slot where data will be written. + * @param slotSize The size of the slot. + * @param serializedMetadata The serialized metadata associated with the data. + * @return Error code (0 on success). + */ + int GetWritingSlot(const MM::Device *caller, void** slot, size_t slotSize, const char* serializedMetadata); + + /** + * @brief Release a data slot after writing is complete. + * + * @param caller The device calling this function. + * @param buffer The buffer to be released. + * @return Error code (0 on success). + */ + int ReleaseWritingSlot(const MM::Device *caller, void* buffer); + + + + + ////// Camera API ////// + + // Set the buffer for a camera to write into + int SetCameraBuffer(const char* camera, void* buffer); + + // Get a pointer to a heap allocated Metadata object with the required fields filled in + int CreateCameraRequiredMetadata(Metadata**, int width, int height, int bitDepth); + + private: + // Basic buffer management + void* buffer_; + size_t bufferSize_; + std::string bufferName_; + + // Read/write positions + size_t writePos_; + size_t readPos_; + + // Configuration + bool overwriteWhenFull_; + + // Mutex for thread safety + std::mutex mutex_; +}; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 19dd2b0cb..5164dfea2 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -137,7 +137,7 @@ CMMCore::CMMCore() : properties_(0), externalCallback_(0), pixelSizeGroup_(0), - cbuf_(0), + bufferAdapter_(nullptr), pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), pPostedErrorsLock_(NULL) @@ -151,7 +151,7 @@ CMMCore::CMMCore() : callback_ = new CoreCallback(this); const unsigned seqBufMegabytes = (sizeof(void*) > 4) ? 250 : 25; - cbuf_ = new CircularBuffer(seqBufMegabytes); + bufferAdapter_ = new BufferAdapter(false, seqBufMegabytes); nullAffine_ = new std::vector(6); for (int i = 0; i < 6; i++) { @@ -181,7 +181,7 @@ CMMCore::~CMMCore() delete callback_; delete configGroups_; delete properties_; - delete cbuf_; + delete bufferAdapter_; delete pixelSizeGroup_; delete pPostedErrorsLock_; @@ -2830,12 +2830,12 @@ void CMMCore::startSequenceAcquisition(long numImages, double intervalMs, bool s try { - if (!cbuf_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - cbuf_->Clear(); + bufferAdapter_->Clear(); mm::DeviceModuleLockGuard guard(camera); LOG_DEBUG(coreLogger_) << "Will start sequence acquisition from default camera"; @@ -2874,12 +2874,12 @@ void CMMCore::startSequenceAcquisition(const char* label, long numImages, double throw CMMError(getCoreErrorText(MMERR_NotAllowedDuringSequenceAcquisition).c_str(), MMERR_NotAllowedDuringSequenceAcquisition); - if (!cbuf_->Initialize(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), pCam->GetImageHeight(), pCam->GetImageBytesPerPixel())) + if (!bufferAdapter_->Initialize(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), pCam->GetImageHeight(), pCam->GetImageBytesPerPixel())) { logError(getDeviceName(pCam).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - cbuf_->Clear(); + bufferAdapter_->Clear(); LOG_DEBUG(coreLogger_) << "Will start sequence acquisition from camera " << label; @@ -2925,12 +2925,12 @@ void CMMCore::initializeCircularBuffer() throw (CMMError) if (camera) { mm::DeviceModuleLockGuard guard(camera); - if (!cbuf_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - cbuf_->Clear(); + bufferAdapter_->Clear(); } else { @@ -2977,12 +2977,12 @@ void CMMCore::startContinuousSequenceAcquisition(double intervalMs) throw (CMMEr ,MMERR_NotAllowedDuringSequenceAcquisition); } - if (!cbuf_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - cbuf_->Clear(); + bufferAdapter_->Clear(); LOG_DEBUG(coreLogger_) << "Will start continuous sequence acquisition from current camera"; int nRet = camera->StartSequenceAcquisition(intervalMs); if (nRet != DEVICE_OK) @@ -3078,7 +3078,7 @@ void* CMMCore::getLastImage() throw (CMMError) } } - unsigned char* pBuf = const_cast(cbuf_->GetTopImage()); + unsigned char* pBuf = const_cast(bufferAdapter_->GetTopImage()); if (pBuf != 0) return pBuf; else @@ -3094,7 +3094,7 @@ void* CMMCore::getLastImageMD(unsigned channel, unsigned slice, Metadata& md) co if (slice != 0) throw CMMError("Slice must be 0"); - const mm::ImgBuffer* pBuf = cbuf_->GetTopImageBuffer(channel); + const mm::ImgBuffer* pBuf = bufferAdapter_->GetTopImageBuffer(channel); if (pBuf != 0) { md = pBuf->GetMetadata(); @@ -3135,7 +3135,7 @@ void* CMMCore::getLastImageMD(Metadata& md) const throw (CMMError) */ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw (CMMError) { - const mm::ImgBuffer* pBuf = cbuf_->GetNthFromTopImageBuffer(n); + const mm::ImgBuffer* pBuf = bufferAdapter_->GetNthFromTopImageBuffer(n); if (pBuf != 0) { md = pBuf->GetMetadata(); @@ -3159,7 +3159,7 @@ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw */ void* CMMCore::popNextImage() throw (CMMError) { - unsigned char* pBuf = const_cast(cbuf_->GetNextImage()); + unsigned char* pBuf = const_cast(bufferAdapter_->GetNextImage()); if (pBuf != 0) return pBuf; else @@ -3177,7 +3177,7 @@ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) th if (slice != 0) throw CMMError("Slice must be 0"); - const mm::ImgBuffer* pBuf = cbuf_->GetNextImageBuffer(channel); + const mm::ImgBuffer* pBuf = bufferAdapter_->GetNextImageBuffer(channel); if (pBuf != 0) { md = pBuf->GetMetadata(); @@ -3203,7 +3203,7 @@ void* CMMCore::popNextImageMD(Metadata& md) throw (CMMError) */ void CMMCore::clearCircularBuffer() throw (CMMError) { - cbuf_->Clear(); + bufferAdapter_->Clear(); } /** @@ -3212,12 +3212,13 @@ void CMMCore::clearCircularBuffer() throw (CMMError) void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes ) throw (CMMError) { - delete cbuf_; // discard old buffer + delete bufferAdapter_; // discard old buffer LOG_DEBUG(coreLogger_) << "Will set circular buffer size to " << sizeMB << " MB"; try { - cbuf_ = new CircularBuffer(sizeMB); + // TODO: need to store a flag about which buffer to use + bufferAdapter_ = new BufferAdapter(false, sizeMB); } catch (std::bad_alloc& ex) { @@ -3226,7 +3227,7 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes messs << getCoreErrorText(MMERR_OutOfMemory).c_str() << " " << ex.what() << '\n'; throw CMMError(messs.str().c_str() , MMERR_OutOfMemory); } - if (NULL == cbuf_) throw CMMError(getCoreErrorText(MMERR_OutOfMemory).c_str(), MMERR_OutOfMemory); + if (NULL == bufferAdapter_) throw CMMError(getCoreErrorText(MMERR_OutOfMemory).c_str(), MMERR_OutOfMemory); try @@ -3237,7 +3238,7 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes if (camera) { mm::DeviceModuleLockGuard guard(camera); - if (!cbuf_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } @@ -3250,7 +3251,7 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes messs << getCoreErrorText(MMERR_OutOfMemory).c_str() << " " << ex.what() << '\n'; throw CMMError(messs.str().c_str() , MMERR_OutOfMemory); } - if (NULL == cbuf_) + if (NULL == bufferAdapter_) throw CMMError(getCoreErrorText(MMERR_OutOfMemory).c_str(), MMERR_OutOfMemory); } @@ -3259,9 +3260,9 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes */ unsigned CMMCore::getCircularBufferMemoryFootprint() { - if (cbuf_) + if (bufferAdapter_) { - return cbuf_->GetMemorySizeMB(); + return bufferAdapter_->GetMemorySizeMB(); } return 0; } @@ -3271,9 +3272,9 @@ unsigned CMMCore::getCircularBufferMemoryFootprint() */ long CMMCore::getRemainingImageCount() { - if (cbuf_) + if (bufferAdapter_) { - return cbuf_->GetRemainingImageCount(); + return bufferAdapter_->GetRemainingImageCount(); } return 0; } @@ -3283,9 +3284,9 @@ long CMMCore::getRemainingImageCount() */ long CMMCore::getBufferTotalCapacity() { - if (cbuf_) + if (bufferAdapter_) { - return cbuf_->GetSize(); + return bufferAdapter_->GetSize(); } return 0; } @@ -3297,9 +3298,9 @@ long CMMCore::getBufferTotalCapacity() */ long CMMCore::getBufferFreeCapacity() { - if (cbuf_) + if (bufferAdapter_) { - return cbuf_->GetFreeSize(); + return bufferAdapter_->GetFreeSize(); } return 0; } @@ -3309,7 +3310,7 @@ long CMMCore::getBufferFreeCapacity() */ bool CMMCore::isBufferOverflowed() const { - return cbuf_->Overflow(); + return bufferAdapter_->Overflow(); } /** @@ -4412,7 +4413,7 @@ void CMMCore::setROI(int x, int y, int xSize, int ySize) throw (CMMError) // inconsistent with the current image size. There is no way to "fix" // popNextImage() to handle this correctly, so we need to make sure we // discard such images. - cbuf_->Clear(); + bufferAdapter_->Clear(); } else throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); @@ -4491,7 +4492,7 @@ void CMMCore::setROI(const char* label, int x, int y, int xSize, int ySize) thro // inconsistent with the current image size. There is no way to "fix" // popNextImage() to handle this correctly, so we need to make sure we // discard such images. - cbuf_->Clear(); + bufferAdapter_->Clear(); } else throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); @@ -4550,7 +4551,7 @@ void CMMCore::clearROI() throw (CMMError) // inconsistent with the current image size. There is no way to "fix" // popNextImage() to handle this correctly, so we need to make sure we // discard such images. - cbuf_->Clear(); + bufferAdapter_->Clear(); } } diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 83d67b0b2..f31b8b412 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -65,6 +65,7 @@ #include "Error.h" #include "ErrorCodes.h" #include "Logging/Logger.h" +#include "BufferAdapter.h" #include #include @@ -658,7 +659,8 @@ class CMMCore CorePropertyCollection* properties_; MMEventCallback* externalCallback_; // notification hook to the higher layer (e.g. GUI) PixelSizeConfigGroup* pixelSizeGroup_; - CircularBuffer* cbuf_; + // New adapter to wrap either the circular buffer or the DataBuffer (v2) + BufferAdapter* bufferAdapter_; std::shared_ptr pluginManager_; std::shared_ptr deviceManager_; From f886b12d971687e2e4040913dd831d35895425d9 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 31 Jan 2025 16:08:13 -0800 Subject: [PATCH 02/46] Factored out circularBuffer behind new API --- MMCore/BufferAdapter.cpp | 84 ++++++++++++++++++++++++++++---- MMCore/BufferAdapter.h | 91 +++++++++++++++++++++++++++++------ MMCore/Buffer_v2.h | 15 ++---- MMCore/CoreCallback.cpp | 10 ++-- MMCore/MMCore.cpp | 7 +-- MMCore/MMCore.h | 2 +- MMCore/MMCore.vcxproj | 6 ++- MMCore/MMCore.vcxproj.filters | 14 +++++- 8 files changed, 185 insertions(+), 44 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index 60d1d3bea..185c04036 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -35,17 +35,23 @@ BufferAdapter::~BufferAdapter() } } -bool BufferAdapter::InsertImage(const MM::Device *caller, const unsigned char* buf, - unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, const char* serializedMetadata) -{ +bool BufferAdapter::InsertImage(const unsigned char* buf, + unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd) { if (useV2_) { - // For demonstration, assume that for a simple image the number of bytes is width * height * byteDepth * nComponents. - size_t dataSize = width * height * byteDepth * nComponents; - int res = v2Buffer_->InsertData(caller, buf, dataSize, serializedMetadata); - return (res == 0); + // Implement logic for v2Buffer if available + return false; // Placeholder } else { - // For now, pass a null metadata pointer. - return circBuffer_->InsertImage(buf, width, height, byteDepth, nComponents, nullptr); + return circBuffer_->InsertImage(buf, width, height, byteDepth, pMd); + } +} + +bool BufferAdapter::InsertImage(const unsigned char *buf, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, Metadata *pMd) { + if (useV2_) { + // Implement logic for v2Buffer if available + return false; // Placeholder + } else { + return circBuffer_->InsertImage(buf, width, height, byteDepth, nComponents, pMd); } } @@ -125,4 +131,64 @@ void BufferAdapter::Clear() } else { circBuffer_->Clear(); } +} + +bool BufferAdapter::InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, + unsigned height, unsigned byteDepth, Metadata *pMd) { + if (useV2_) { + // Implement logic for v2Buffer if available + return false; // Placeholder + } else { + return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, pMd); + } +} + +bool BufferAdapter::InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, + unsigned height, unsigned byteDepth, unsigned nComponents, Metadata *pMd) { + if (useV2_) { + // Implement logic for v2Buffer if available + return false; // Placeholder + } else { + return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, nComponents, pMd); + } +} + +long BufferAdapter::GetSize() const +{ + if (useV2_) { + // Implement logic for v2Buffer if available + return 0; // Placeholder + } else { + return circBuffer_->GetSize(); + } +} + +long BufferAdapter::GetFreeSize() const +{ + if (useV2_) { + // Implement logic for v2Buffer if available + return 0; // Placeholder + } else { + return circBuffer_->GetFreeSize(); + } +} + +bool BufferAdapter::Overflow() const +{ + if (useV2_) { + // Implement logic for v2Buffer if available + return false; // Placeholder + } else { + return circBuffer_->Overflow(); + } +} + +const mm::ImgBuffer* BufferAdapter::GetTopImageBuffer(unsigned channel) const +{ + if (useV2_) { + // Implement logic for v2Buffer if available + return nullptr; // Placeholder + } else { + return circBuffer_->GetTopImageBuffer(channel); + } } \ No newline at end of file diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index e618aea30..f768e5b8f 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -17,20 +17,6 @@ class BufferAdapter { BufferAdapter(bool useV2Buffer, unsigned int memorySizeMB); ~BufferAdapter(); - /** - * Insert an image into the buffer. - * @param caller The device inserting the image. - * @param buf The image data. - * @param width Image width. - * @param height Image height. - * @param byteDepth Bytes per pixel. - * @param nComponents Number of components in the image. - * @param pMd Metadata associated with the image. - * @return true on success, false on error. - */ - bool InsertImage(const MM::Device *caller, const unsigned char* buf, - unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, Metadata* pMd); - /** * Get a pointer to the top (most recent) image. * @return Pointer to image data, or nullptr if unavailable. @@ -84,6 +70,83 @@ class BufferAdapter { */ void Clear(); + /** + * Insert an image into the buffer. + * @param buf The image data. + * @param width Image width. + * @param height Image height. + * @param byteDepth Bytes per pixel. + * @param pMd Metadata associated with the image. + * @return true on success, false on error. + */ + bool InsertImage(const unsigned char *buf, unsigned width, unsigned height, + unsigned byteDepth, Metadata *pMd); + + /** + * Insert an image into the buffer with specified number of components. + * @param buf The image data. + * @param width Image width. + * @param height Image height. + * @param byteDepth Bytes per pixel. + * @param nComponents Number of components in the image. + * @param pMd Metadata associated with the image. + * @return true on success, false on error. + */ + bool InsertImage(const unsigned char *buf, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, Metadata *pMd); + + /** + * Insert a multi-channel image into the buffer. + * @param buf The image data. + * @param numChannels Number of channels in the image. + * @param width Image width. + * @param height Image height. + * @param byteDepth Bytes per pixel. + * @param pMd Metadata associated with the image. + * @return true on success, false on error. + */ + bool InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, + unsigned height, unsigned byteDepth, Metadata *pMd); + + /** + * Insert a multi-channel image into the buffer with specified number of components. + * @param buf The image data. + * @param numChannels Number of channels in the image. + * @param width Image width. + * @param height Image height. + * @param byteDepth Bytes per pixel. + * @param nComponents Number of components in the image. + * @param pMd Metadata associated with the image. + * @return true on success, false on error. + */ + bool InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, + unsigned height, unsigned byteDepth, unsigned nComponents, Metadata *pMd); + + /** + * Get the total capacity of the buffer. + * @return Total capacity of the buffer. + */ + long GetSize() const; + + /** + * Get the free capacity of the buffer. + * @return Free capacity of the buffer. + */ + long GetFreeSize() const; + + /** + * Check if the buffer is overflowed. + * @return True if overflowed, false otherwise. + */ + bool Overflow() const; + + /** + * Get a pointer to the top image buffer for a specific channel. + * @param channel The channel number. + * @return Pointer to image data, or nullptr if unavailable. + */ + const mm::ImgBuffer* GetTopImageBuffer(unsigned channel) const; + private: bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. CircularBuffer* circBuffer_; diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 7cdb02523..cca62f1e2 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -20,12 +20,12 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // -// AUTHOR: Henry Pinkard, henry.pinkard@gmail.com, 01/31/2025 -// +// AUTHOR: Henry Pinkard, 01/31/2025 + #pragma once -#include "Metadata.h" +#include "../MMDevice/ImageMetadata.h" #include "../MMDevice/MMDevice.h" #include #include @@ -171,17 +171,12 @@ class DataBuffer { private: // Basic buffer management - void* buffer_; + char* buffer_; size_t bufferSize_; std::string bufferName_; - // Read/write positions - size_t writePos_; - size_t readPos_; - // Configuration bool overwriteWhenFull_; - // Mutex for thread safety - std::mutex mutex_; + }; diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 572f9a0ce..5dff1539c 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -263,7 +263,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf ip->Process(const_cast(buf), width, height, byteDepth); } } - if (core_->cbuf_->InsertImage(buf, width, height, byteDepth, &md)) + if (core_->bufferAdapter_->InsertImage(buf, width, height, byteDepth, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; @@ -295,7 +295,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf ip->Process(const_cast(buf), width, height, byteDepth); } } - if (core_->cbuf_->InsertImage(buf, width, height, byteDepth, nComponents, &md)) + if (core_->bufferAdapter_->InsertImage(buf, width, height, byteDepth, nComponents, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; @@ -322,7 +322,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const ImgBuffer & imgBuf void CoreCallback::ClearImageBuffer(const MM::Device* /*caller*/) { - core_->cbuf_->Clear(); + core_->bufferAdapter_->Clear(); } bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, @@ -332,7 +332,7 @@ bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, if (slices != 1) return false; - return core_->cbuf_->Initialize(channels, w, h, pixDepth); + return core_->bufferAdapter_->Initialize(channels, w, h, pixDepth); } int CoreCallback::InsertMultiChannel(const MM::Device* caller, @@ -352,7 +352,7 @@ int CoreCallback::InsertMultiChannel(const MM::Device* caller, { ip->Process( const_cast(buf), width, height, byteDepth); } - if (core_->cbuf_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md)) + if (core_->bufferAdapter_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 5164dfea2..b52e4d234 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -140,7 +140,8 @@ CMMCore::CMMCore() : bufferAdapter_(nullptr), pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), - pPostedErrorsLock_(NULL) + pPostedErrorsLock_(NULL), + useV2Buffer_(false) { configGroups_ = new ConfigGroupCollection(); pixelSizeGroup_ = new PixelSizeConfigGroup(); @@ -151,7 +152,7 @@ CMMCore::CMMCore() : callback_ = new CoreCallback(this); const unsigned seqBufMegabytes = (sizeof(void*) > 4) ? 250 : 25; - bufferAdapter_ = new BufferAdapter(false, seqBufMegabytes); + bufferAdapter_ = new BufferAdapter(useV2Buffer_, seqBufMegabytes); nullAffine_ = new std::vector(6); for (int i = 0; i < 6; i++) { @@ -3218,7 +3219,7 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes try { // TODO: need to store a flag about which buffer to use - bufferAdapter_ = new BufferAdapter(false, sizeMB); + bufferAdapter_ = new BufferAdapter(useV2Buffer_, sizeMB); } catch (std::bad_alloc& ex) { diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index f31b8b412..9e1f63394 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -89,7 +89,6 @@ class CPluginManager; -class CircularBuffer; class ConfigGroupCollection; class CoreCallback; class CorePropertyCollection; @@ -673,6 +672,7 @@ class CMMCore MMThreadLock* pPostedErrorsLock_; mutable std::deque > postedErrors_; + bool useV2Buffer_; // Whether to use the V2 buffer implementation private: void InitializeErrorMessages(); diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index ebfdcb9ba..cefe0ccd2 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -75,6 +75,8 @@ + + @@ -113,6 +115,8 @@ + + @@ -181,4 +185,4 @@ - + \ No newline at end of file diff --git a/MMCore/MMCore.vcxproj.filters b/MMCore/MMCore.vcxproj.filters index 1920583b6..77ca63b99 100644 --- a/MMCore/MMCore.vcxproj.filters +++ b/MMCore/MMCore.vcxproj.filters @@ -141,6 +141,12 @@ Source Files + + Source Files + + + Source Files + @@ -305,5 +311,11 @@ Header Files + + Header Files + + + Header Files + - + \ No newline at end of file From ebbc1bda43bc92028252912f91af12a0d330e928 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 1 Feb 2025 14:02:10 -0800 Subject: [PATCH 03/46] finished concurrency for buffer slots --- MMCore/BufferAdapter.cpp | 14 +-- MMCore/BufferAdapter.h | 4 + MMCore/Buffer_v2.cpp | 230 +++++++++++++++++++++++++++++++++------ MMCore/Buffer_v2.h | 161 +++++++++++++++++++++------ MMCore/MMCore.cpp | 2 +- 5 files changed, 335 insertions(+), 76 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index 185c04036..aec92a2d8 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -8,17 +8,16 @@ #define DEVICE_ERR -1 #endif +const char* const BufferAdapter::DEFAULT_V2_BUFFER_NAME = "DEFAULT_BUFFER"; + BufferAdapter::BufferAdapter(bool useV2Buffer, unsigned int memorySizeMB) : useV2_(useV2Buffer), circBuffer_(nullptr), v2Buffer_(nullptr) { if (useV2_) { - // Create a new v2 buffer with a total size of memorySizeMB megabytes. - // Multiply by (1 << 20) to convert megabytes to bytes. - size_t bytes = memorySizeMB * (1 << 20); - v2Buffer_ = new DataBuffer(bytes, "DEFAULT"); + // Create a new v2 buffer directly with MB + v2Buffer_ = new DataBuffer(memorySizeMB, DEFAULT_V2_BUFFER_NAME); } else { circBuffer_ = new CircularBuffer(memorySizeMB); - // Optionally, perform any necessary initialization for the circular buffer. } } @@ -108,7 +107,7 @@ bool BufferAdapter::Initialize(unsigned numChannels, unsigned width, unsigned he unsigned BufferAdapter::GetMemorySizeMB() const { if (useV2_) { - return 0; // TODO: need to implement this + return v2Buffer_->GetMemorySizeMB(); } else { return circBuffer_->GetMemorySizeMB(); } @@ -126,8 +125,7 @@ long BufferAdapter::GetRemainingImageCount() const void BufferAdapter::Clear() { if (useV2_) { - // In this basic implementation, we call ReleaseBuffer with the known buffer name. - v2Buffer_->ReleaseBuffer("DEFAULT"); + v2Buffer_->ReleaseBuffer(DEFAULT_V2_BUFFER_NAME); } else { circBuffer_->Clear(); } diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index f768e5b8f..e0359dedd 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -9,6 +9,8 @@ // used by MMCore. It currently supports only a minimal set of functions. class BufferAdapter { public: + static const char* const DEFAULT_V2_BUFFER_NAME; + /** * Constructor. * @param useV2Buffer Set to true to use the new DataBuffer (v2); false to use CircularBuffer. @@ -151,6 +153,8 @@ class BufferAdapter { bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. CircularBuffer* circBuffer_; DataBuffer* v2Buffer_; + + }; #endif // BUFFERADAPTER_H \ No newline at end of file diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index e4ef33750..afe938ad9 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -6,7 +6,7 @@ // DESCRIPTION: Generic implementation of a buffer for storing image data and // metadata. Provides thread-safe access for reading and writing // with configurable overflow behavior. -// +//// // COPYRIGHT: Henry Pinkard, 2025 // // LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. @@ -57,10 +57,15 @@ Metadata Handling: #include "Buffer_v2.h" #include +#include // for std::this_thread::yield if needed -DataBuffer::DataBuffer(size_t numBytes, const char* name) { - // Initialize basic buffer with given size - AllocateBuffer(numBytes, name); +/////////////////////////////////////////////////////////////////////////////// +// DataBuffer Implementation +/////////////////////////////////////////////////////////////////////////////// + +DataBuffer::DataBuffer(unsigned int memorySizeMB, const std::string& name) { + // Convert MB to bytes for internal allocation + AllocateBuffer(memorySizeMB, name); } DataBuffer::~DataBuffer() { @@ -70,15 +75,16 @@ DataBuffer::~DataBuffer() { /** * Allocate a character buffer - * @param numBytes The size of the buffer to allocate. + * @param memorySizeMB The size (in MB) of the buffer to allocate. * @param name The name of the buffer. * @return Error code (0 on success). */ -int DataBuffer::AllocateBuffer(size_t numBytes, const char* name) { +int DataBuffer::AllocateBuffer(unsigned int memorySizeMB, const std::string& name) { + // Convert MB to bytes (1 MB = 1048576 bytes) + size_t numBytes = static_cast(memorySizeMB) * (1ULL << 20); buffer_ = new char[numBytes]; bufferSize_ = numBytes; bufferName_ = name; - // TODO: Store the buffer in a member variable for later use return DEVICE_OK; } @@ -87,44 +93,46 @@ int DataBuffer::AllocateBuffer(size_t numBytes, const char* name) { * @param name The name of the buffer to release. * @return Error code (0 on success, error if buffer not found or already released). */ -int DataBuffer::ReleaseBuffer(const char* name) { +int DataBuffer::ReleaseBuffer(const std::string& name) { if (buffer_ != nullptr && bufferName_ == name) { delete[] buffer_; buffer_ = nullptr; bufferSize_ = 0; - bufferName_ = nullptr; + bufferName_.clear(); return DEVICE_OK; } - // TODO: throw errors if other code holds pointers on stuff - return DEVICE_ERR; // Return an error if the buffer is not found or already released + // TODO: Handle errors if other parts of the system still hold pointers. + return DEVICE_ERR; } /** * @brief Copy data into the next available slot in the buffer. * * Returns the size of the copied data through dataSize. - * Implementing code should check the device type of the caller, and ensure that + * TODO: Implementing code should check the device type of the caller, and ensure that * all required metadata for interpreting its image data is there. * Note: this can be implemented in terms of Get/Release slot + memcopy. * * @param caller The device calling this function. - * @param data The data to be copied into the buffer. - * @param dataSize The size of the data to be copied. - * @param serializedMetadata The serialized metadata associated with the data. + * @param data The data to be copied. + * @param dataSize Size of the data to copy. + * @param serializedMetadata The associated metadata. * @return Error code (0 on success). */ -int DataBuffer::InsertData(const MM::Device *caller, const void* data, size_t dataSize, const char* serializedMetadata) { - // Basic implementation - just copy data - // TODO: Add proper buffer management and metadata handling - return 0; +int DataBuffer::InsertData(const MM::Device *caller, const void* data, + size_t dataSize, const std::string& serializedMetadata) { + // TODO: Create a slot, copy the data into it, then release write access on the slot. + // Also, ensure that a slot is not garbage-collected while data remains available. + return DEVICE_OK; } + /** * Check if a new slot has been fully written in this buffer * @return true if new data is ready, false otherwise */ bool DataBuffer::IsNewDataReady() { - // Basic implementation + // TODO: Implement checking logic based on the slot state. return false; } @@ -138,9 +146,9 @@ bool DataBuffer::IsNewDataReady() { */ int DataBuffer::CopyNextDataAndMetadata(void* dataDestination, size_t* dataSize, Metadata &md, bool waitForData) { - // Basic implementation - // TODO: Add proper data copying and metadata handling - return 0; + // Basic implementation: + // TODO: Use slot management to return data from the next available slot. + return DEVICE_OK; } /** @@ -158,8 +166,8 @@ int DataBuffer::CopyNextDataAndMetadata(void* dataDestination, * @return Error code (0 on success) */ int DataBuffer::SetOverwriteData(bool overwrite) { - // Basic implementation - return 0; + overwriteWhenFull_ = overwrite; + return DEVICE_OK; } /** @@ -174,9 +182,15 @@ int DataBuffer::SetOverwriteData(bool overwrite) { * @param serializedMetadata The serialized metadata associated with the data. * @return Error code (0 on success). */ -int DataBuffer::GetWritingSlot(const MM::Device *caller, void** slot, size_t slotSize, const char* serializedMetadata) { - // Basic implementation - return 0; +int DataBuffer::GetWritingSlot(const MM::Device *caller, void** slot, + size_t slotSize, const std::string& serializedMetadata) { + // TODO: For now, we simply return a pointer into the buffer; a full implementation + // would create and manage a BufferSlot and assign it with exclusive write access. + if (slot == nullptr || slotSize > bufferSize_) + return DEVICE_ERR; + // TODO: understnad this pointer stuff + *slot = static_cast(buffer_); + return DEVICE_OK; } /** @@ -187,8 +201,8 @@ int DataBuffer::GetWritingSlot(const MM::Device *caller, void** slot, size_t slo * @return Error code (0 on success). */ int DataBuffer::ReleaseWritingSlot(const MM::Device *caller, void* buffer) { - // Basic implementation - return 0; + // TODO + return DEVICE_OK; } /** @@ -200,7 +214,157 @@ int DataBuffer::ReleaseWritingSlot(const MM::Device *caller, void* buffer) { * @return Error code (0 on success). */ int DataBuffer::CreateCameraRequiredMetadata(Metadata** md, int width, int height, int bitDepth) { - // Basic implementation - // TODO: Create metadata with required camera fields - return 0; + // TODO: Implement camera-specific metadata creation. + return DEVICE_OK; +} + +unsigned int DataBuffer::GetMemorySizeMB() const { + // Convert bytes to MB (1 MB = 1048576 bytes) + return static_cast(bufferSize_ >> 20); +} + +/////////////////////////////////////////////////////////////////////////////// +// BufferSlot Implementation +/////////////////////////////////////////////////////////////////////////////// + +/** + * Constructor. + * Initializes the slot with the specified starting byte offset and length. + * Also initializes atomic variables that track reader and writer access. + */ +BufferSlot::BufferSlot(std::size_t start, std::size_t length) + : start_(start), length_(length), + readAccessCountAtomicInt_(0), + writeAtomicBool_(false) +{ + // No readers are active and the write lock is free upon construction. +} + +/** + * Destructor. + * Currently no dynamic memory is used inside BufferSlot, so nothing needs to be cleaned up. + */ +BufferSlot::~BufferSlot() { + // No explicit cleanup required here. +} + +/** + * Returns the start offset (in bytes) of the slot within the main buffer. + */ +std::size_t BufferSlot::GetStart() const { + return start_; +} + +/** + * Returns the length (in bytes) of the slot. + */ +std::size_t BufferSlot::GetLength() const { + return length_; +} + +/** + * Sets a detail for this slot using the provided key and value. + * Typically used to store metadata information (e.g. width, height). + */ +void BufferSlot::SetDetail(const std::string &key, std::size_t value) { + details_[key] = value; +} + +/** + * Retrieves a previously set detail. + * Returns 0 if the key is not found. + */ +std::size_t BufferSlot::GetDetail(const std::string &key) const { + auto it = details_.find(key); + return (it != details_.end()) ? it->second : 0; +} + +/** + * Clears all additional details associated with this slot. + */ +void BufferSlot::ClearDetails() { + details_.clear(); +} + +/** + * Attempts to acquire exclusive write access. + * This method first attempts to set the write flag atomically. + * If it fails, that indicates another writer holds the lock. + * Next, it attempts to confirm that no readers are active. + * If there are active readers, it reverts the write flag and returns false. + */ +bool BufferSlot::AcquireWriteAccess() { + bool expected = false; + // Attempt to atomically set the write flag. + if (!writeAtomicBool_.compare_exchange_strong(expected, true, std::memory_order_acquire)) { + // A writer is already active. + return false; + } + // Ensure no readers are active by checking the read counter. + int expectedReaders = 0; + if (!readAccessCountAtomicInt_.compare_exchange_strong(expectedReaders, 0, std::memory_order_acquire)) { + // Active readers are present; revert the write lock. + writeAtomicBool_.store(false, std::memory_order_release); + return false; + } + // Exclusive write access has been acquired. + return true; +} + +/** + * Releases exclusive write access. + * The writer flag is cleared, and waiting readers are notified so that + * they may acquire shared read access once the write is complete. + */ +void BufferSlot::ReleaseWriteAccess() { + // Publish all writes by releasing the writer flag. + writeAtomicBool_.store(false, std::memory_order_release); + // Notify waiting readers (using the condition variable) + // that the slot is now available for read access. + std::lock_guard lock(writeCompleteConditionMutex_); + writeCompleteCondition_.notify_all(); +} + +/** + * Acquires shared read access. + * This is a blocking operation – if a writer is active, + * the calling thread will wait until the writer releases its lock. + * Once unlocked, the method increments the reader count. + */ +bool BufferSlot::AcquireReadAccess() { + // Acquire the mutex associated with the condition variable. + std::unique_lock lock(writeCompleteConditionMutex_); + // Block until no writer is active. + writeCompleteCondition_.wait(lock, [this]() { + return !writeAtomicBool_.load(std::memory_order_acquire); + }); + // Now that there is no writer, increment the reader counter. + readAccessCountAtomicInt_.fetch_add(1, std::memory_order_acquire); + return true; +} + +/** + * Releases shared read access. + * The reader count is decremented using release semantics to ensure that all + * prior read operations complete before the decrement is visible to other threads. + */ +void BufferSlot::ReleaseReadAccess() { + readAccessCountAtomicInt_.fetch_sub(1, std::memory_order_release); +} + +/** + * Checks if the slot is available for acquiring write access. + * A slot is available for writing if there are no active readers and no writer. + */ +bool BufferSlot::IsAvailableForWriting() const { + return (readAccessCountAtomicInt_.load(std::memory_order_acquire) == 0) && + (!writeAtomicBool_.load(std::memory_order_acquire)); +} + +/** + * Checks if the slot is available for acquiring read access. + * A slot is available for reading if no writer currently holds the lock. + */ +bool BufferSlot::IsAvailableForReading() const { + return !writeAtomicBool_.load(std::memory_order_acquire); } diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index cca62f1e2..69684f3c4 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -6,7 +6,13 @@ // DESCRIPTION: Generic implementation of a buffer for storing image data and // metadata. Provides thread-safe access for reading and writing // with configurable overflow behavior. -// +// +// The buffer is organized into slots (BufferSlot objects), each of which +// supports exclusive write access and shared read access. Read access is +// delivered using const pointers and is counted via an atomic counter, while +// write access requires acquiring an exclusive lock. This ensures that once a +// read pointer is given out it cannot be misused for writing. +// // COPYRIGHT: Henry Pinkard, 2025 // // LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. @@ -21,7 +27,7 @@ // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // // AUTHOR: Henry Pinkard, 01/31/2025 - +/////////////////////////////////////////////////////////////////////////////// #pragma once @@ -29,23 +35,108 @@ #include "../MMDevice/MMDevice.h" #include #include +#include +#include +#include +#include +#include + +/** + * BufferSlot represents a contiguous slot in the DataBuffer that holds image + * data and metadata. It manages exclusive (write) and shared (read) access + * using atomics, a mutex, and a condition variable. + */ +class BufferSlot { +public: + // Constructor: Initializes the slot with the given start offset and length. + BufferSlot(std::size_t start, std::size_t length); + // Destructor. + ~BufferSlot(); + + // Returns the start offset (in bytes) of the slot. + std::size_t GetStart() const; + // Returns the length (in bytes) of the slot. + std::size_t GetLength() const; + + // Stores a detail (e.g., width, height) associated with the slot. + void SetDetail(const std::string &key, std::size_t value); + // Retrieves a stored detail; returns 0 if the key is not found. + std::size_t GetDetail(const std::string &key) const; + // Clears all stored details. + void ClearDetails(); + + // --- Methods for synchronizing access --- + + /** + * Try to acquire exclusive write access. + * Returns true on success, false if the slot is already locked for writing + * or if active readers exist. + */ + bool AcquireWriteAccess(); + /** + * Release exclusive write access. + * Clears the write flag and notifies waiting readers. + */ + void ReleaseWriteAccess(); + /** + * Acquire shared read access by blocking until no writer is active. + * Once the waiting condition is met, the reader count is incremented. + * Returns true when read access has been acquired. + */ + bool AcquireReadAccess(); + /** + * Release shared read access. + * Decrements the reader count using release semantics. + */ + void ReleaseReadAccess(); + + /** + * Return true if the slot is available for acquiring write access (i.e., + * no active writer or reader). + */ + bool IsAvailableForWriting() const; + /** + * Return true if the slot is available for acquiring read access + * (no active writer). + */ + bool IsAvailableForReading() const; + +private: + // Basic slot information. + std::size_t start_; // Byte offset within the buffer. + std::size_t length_; // Length of the slot in bytes. + std::map details_; // Additional details (e.g., image dimensions). + + // Synchronization primitives. + std::atomic readAccessCountAtomicInt_; // Count of active readers. + std::atomic writeAtomicBool_; // True if the slot is locked for writing. + mutable std::mutex writeCompleteConditionMutex_; // Mutex for condition variable. + mutable std::condition_variable writeCompleteCondition_; // Condition variable for blocking readers. +}; -class DataBuffer { - public: - DataBuffer(size_t numBytes, const char* name); - ~DataBuffer(); - - /////// Buffer Allocation and Destruction +/** + * DataBuffer manages a large contiguous memory area, divided into BufferSlot + * objects for storing image data and metadata. It supports two data access + * patterns: copy-based access and direct pointer access via retrieval of slots. + */ +class DataBuffer { +public: + DataBuffer(unsigned int memorySizeMB, const std::string& name); + ~DataBuffer(); - // C version - int AllocateBuffer(size_t numBytes, const char* name); - //// Is there a need for a name? - //// Maybe makes sense for Core to assing a unique integer - int ReleaseBuffer(const char* name); + // Buffer Allocation and Destruction + int AllocateBuffer(unsigned int memorySizeMB, const std::string& name); + int ReleaseBuffer(const std::string& name); // TODO: Other versions for allocating buffers Java, Python + + /** + * Get the total memory size of the buffer in megabytes + * @return Size of the buffer in MB + */ + unsigned int GetMemorySizeMB() const; /////// Monitoring the Buffer /////// int GetAvailableBytes(void* buffer, size_t* availableBytes); @@ -120,7 +211,7 @@ class DataBuffer { ////// Writing Data into buffer ////// /** - * @brief Copy data into the next available slot in the buffer. + * Copy data into the next available slot in the buffer. * * Returns the size of the copied data through dataSize. * Implementing code should check the device type of the caller, and ensure that @@ -133,10 +224,11 @@ class DataBuffer { * @param serializedMetadata The serialized metadata associated with the data. * @return Error code (0 on success). */ - int InsertData(const MM::Device *caller, const void* data, size_t dataSize, const char* serializedMetadata); + int InsertData(const MM::Device *caller, const void* data, size_t dataSize, + const std::string& serializedMetadata); /** - * @brief Get a pointer to the next available data slot in the buffer for writing. + * Get a pointer to the next available data slot in the buffer for writing. * * The caller must release the slot using ReleaseDataSlot after writing is complete. * Internally this will use a std::unique_ptr. @@ -147,16 +239,16 @@ class DataBuffer { * @param serializedMetadata The serialized metadata associated with the data. * @return Error code (0 on success). */ - int GetWritingSlot(const MM::Device *caller, void** slot, size_t slotSize, const char* serializedMetadata); + int GetWritingSlot(const MM::Device *caller, void** slot, size_t slotSize, + const std::string& serializedMetadata); - /** - * @brief Release a data slot after writing is complete. - * - * @param caller The device calling this function. - * @param buffer The buffer to be released. - * @return Error code (0 on success). - */ - int ReleaseWritingSlot(const MM::Device *caller, void* buffer); + /** + * Release a data slot after writing is complete. + * @param caller The device calling this function. + * @param buffer The slot to be released. + * @return Error code (0 on success). + */ + int ReleaseWritingSlot(const MM::Device *caller, void* buffer); @@ -164,19 +256,20 @@ class DataBuffer { ////// Camera API ////// // Set the buffer for a camera to write into - int SetCameraBuffer(const char* camera, void* buffer); + int SetCameraBuffer(const std::string& camera, void* buffer); // Get a pointer to a heap allocated Metadata object with the required fields filled in int CreateCameraRequiredMetadata(Metadata**, int width, int height, int bitDepth); - private: - // Basic buffer management - char* buffer_; - size_t bufferSize_; - std::string bufferName_; - - // Configuration - bool overwriteWhenFull_; +private: + // Basic buffer management. + char* buffer_; + size_t bufferSize_; + std::string bufferName_; + // Configuration. + bool overwriteWhenFull_; + // List of active buffer slots. Each slot manages its own read/write access. + std::vector activeSlots_; }; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index b52e4d234..e3867acf5 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -141,7 +141,7 @@ CMMCore::CMMCore() : pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), pPostedErrorsLock_(NULL), - useV2Buffer_(false) + useV2Buffer_(true) { configGroups_ = new ConfigGroupCollection(); pixelSizeGroup_ = new PixelSizeConfigGroup(); From 94125ee9e4d7dbd201ec3b7ae344ef601050a37d Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 4 Feb 2025 13:29:28 -0800 Subject: [PATCH 04/46] most functionality in v2 buffer implemented --- MMCore/BufferAdapter.cpp | 284 ++++++++++----- MMCore/BufferAdapter.h | 25 +- MMCore/Buffer_v2.cpp | 709 +++++++++++++++++++++++++++++--------- MMCore/Buffer_v2.h | 462 ++++++++++++++++--------- MMCore/CircularBuffer.cpp | 114 +----- MMCore/CircularBuffer.h | 6 +- MMCore/MMCore.cpp | 40 +-- 7 files changed, 1072 insertions(+), 568 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index aec92a2d8..2bbcdb306 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -1,4 +1,5 @@ #include "BufferAdapter.h" +#include // For demonstration, we assume DEVICE_OK and DEVICE_ERR macros are defined in MMCore.h or an included error header. #ifndef DEVICE_OK @@ -8,14 +9,40 @@ #define DEVICE_ERR -1 #endif -const char* const BufferAdapter::DEFAULT_V2_BUFFER_NAME = "DEFAULT_BUFFER"; + +static std::string FormatLocalTime(std::chrono::time_point tp) { + using namespace std::chrono; + auto us = duration_cast(tp.time_since_epoch()); + auto secs = duration_cast(us); + auto whole = duration_cast(secs); + auto frac = static_cast((us - whole).count()); + + // As of C++14/17, it is simpler (and probably faster) to use C functions for + // date-time formatting + + std::time_t t(secs.count()); // time_t is seconds on platforms we support + std::tm *ptm; +#ifdef _WIN32 // Windows localtime() is documented thread-safe + ptm = std::localtime(&t); +#else // POSIX has localtime_r() + std::tm tmstruct; + ptm = localtime_r(&t, &tmstruct); +#endif + + // Format as "yyyy-mm-dd hh:mm:ss.uuuuuu" (26 chars) + const char *timeFmt = "%Y-%m-%d %H:%M:%S"; + char buf[32]; + std::size_t len = std::strftime(buf, sizeof(buf), timeFmt, ptm); + std::snprintf(buf + len, sizeof(buf) - len, ".%06d", frac); + return buf; +} + BufferAdapter::BufferAdapter(bool useV2Buffer, unsigned int memorySizeMB) : useV2_(useV2Buffer), circBuffer_(nullptr), v2Buffer_(nullptr) { if (useV2_) { - // Create a new v2 buffer directly with MB - v2Buffer_ = new DataBuffer(memorySizeMB, DEFAULT_V2_BUFFER_NAME); + v2Buffer_ = new DataBuffer(memorySizeMB); } else { circBuffer_ = new CircularBuffer(memorySizeMB); } @@ -34,71 +61,44 @@ BufferAdapter::~BufferAdapter() } } -bool BufferAdapter::InsertImage(const unsigned char* buf, - unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd) { - if (useV2_) { - // Implement logic for v2Buffer if available - return false; // Placeholder - } else { - return circBuffer_->InsertImage(buf, width, height, byteDepth, pMd); - } -} - -bool BufferAdapter::InsertImage(const unsigned char *buf, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, Metadata *pMd) { - if (useV2_) { - // Implement logic for v2Buffer if available - return false; // Placeholder - } else { - return circBuffer_->InsertImage(buf, width, height, byteDepth, nComponents, pMd); - } -} - -const unsigned char* BufferAdapter::GetTopImage() const +const unsigned char* BufferAdapter::GetLastImage() const { if (useV2_) { - // Minimal support: the v2Buffer currently does not expose GetTopImage. - return nullptr; + Metadata dummyMetadata; + return v2Buffer_->PeekDataReadPointerAtIndex(0, nullptr, dummyMetadata); + // TODO: ensure calling code releases the slot after use } else { return circBuffer_->GetTopImage(); } -} -const unsigned char* BufferAdapter::GetNextImage() -{ - if (useV2_) { - // Minimal support: return nullptr since v2Buffer does not provide next image retrieval. - return nullptr; - } else { - return circBuffer_->GetNextImage(); - } } -const mm::ImgBuffer* BufferAdapter::GetNthFromTopImageBuffer(unsigned long n) const +const unsigned char* BufferAdapter::PopNextImage() { if (useV2_) { - // Implement logic for v2Buffer if available - return nullptr; // Placeholder + Metadata dummyMetadata; + return v2Buffer_->PopNextDataReadPointer(nullptr, dummyMetadata, false); + // TODO: ensure calling code releases the slot after use } else { - return circBuffer_->GetNthFromTopImageBuffer(n); - } -} - -const mm::ImgBuffer* BufferAdapter::GetNextImageBuffer(unsigned channel) -{ - if (useV2_) { - // Implement logic for v2Buffer if available - return nullptr; // Placeholder - } else { - return circBuffer_->GetNextImageBuffer(channel); + return circBuffer_->PopNextImage(); } } bool BufferAdapter::Initialize(unsigned numChannels, unsigned width, unsigned height, unsigned bytesPerPixel) { + startTime_ = std::chrono::steady_clock::now(); // Initialize start time + imageNumbers_.clear(); if (useV2_) { - // Implement initialization logic for v2Buffer - return true; // Placeholder + try { + // Reinitialize the v2Buffer using its current allocated memory size. + int ret = v2Buffer_->ReinitializeBuffer(v2Buffer_->GetMemorySizeMB()); + if (ret != DEVICE_OK) + return false; + } catch (const std::exception& ex) { + // Optionally log the exception + return false; + } + return true; } else { return circBuffer_->Initialize(numChannels, width, height, bytesPerPixel); } @@ -116,7 +116,7 @@ unsigned BufferAdapter::GetMemorySizeMB() const long BufferAdapter::GetRemainingImageCount() const { if (useV2_) { - return 0; // TODO: need to implement this + return v2Buffer_->GetRemainingImageCount(); } else { return circBuffer_->GetRemainingImageCount(); } @@ -125,68 +125,192 @@ long BufferAdapter::GetRemainingImageCount() const void BufferAdapter::Clear() { if (useV2_) { - v2Buffer_->ReleaseBuffer(DEFAULT_V2_BUFFER_NAME); + v2Buffer_->ReleaseBuffer(); } else { circBuffer_->Clear(); } + // Reset image counters when buffer is cleared + imageNumbers_.clear(); } -bool BufferAdapter::InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, - unsigned height, unsigned byteDepth, Metadata *pMd) { +long BufferAdapter::GetSize(long imageSize) const +{ if (useV2_) { - // Implement logic for v2Buffer if available - return false; // Placeholder + return v2Buffer_->GetMemorySizeMB() * 1024 * 1024 / imageSize; } else { - return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, pMd); + return circBuffer_->GetSize(); } + } -bool BufferAdapter::InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, - unsigned height, unsigned byteDepth, unsigned nComponents, Metadata *pMd) { +long BufferAdapter::GetFreeSize(long imageSize) const +{ if (useV2_) { - // Implement logic for v2Buffer if available - return false; // Placeholder + return v2Buffer_->GetFreeMemory() / imageSize; } else { - return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, nComponents, pMd); + return circBuffer_->GetFreeSize(); } } -long BufferAdapter::GetSize() const +bool BufferAdapter::Overflow() const { if (useV2_) { - // Implement logic for v2Buffer if available - return 0; // Placeholder + return v2Buffer_->Overflow(); } else { - return circBuffer_->GetSize(); + return circBuffer_->Overflow(); } } -long BufferAdapter::GetFreeSize() const +void BufferAdapter::ProcessMetadata(Metadata& md, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents) { + // Track image numbers per camera + { + std::lock_guard lock(imageNumbersMutex_); + std::string cameraName = md.GetSingleTag(MM::g_Keyword_Metadata_CameraLabel).GetValue(); + if (imageNumbers_.end() == imageNumbers_.find(cameraName)) + { + imageNumbers_[cameraName] = 0; + } + + // insert image number + md.put(MM::g_Keyword_Metadata_ImageNumber, CDeviceUtils::ConvertToString(imageNumbers_[cameraName])); + ++imageNumbers_[cameraName]; + } + + if (!md.HasTag(MM::g_Keyword_Elapsed_Time_ms)) + { + // if time tag was not supplied by the camera insert current timestamp + using namespace std::chrono; + auto elapsed = steady_clock::now() - startTime_; + md.PutImageTag(MM::g_Keyword_Elapsed_Time_ms, + std::to_string(duration_cast(elapsed).count())); + } + + // Note: It is not ideal to use local time. I think this tag is rarely + // used. Consider replacing with UTC (micro)seconds-since-epoch (with + // different tag key) after addressing current usage. + auto now = std::chrono::system_clock::now(); + md.PutImageTag(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); + + md.PutImageTag(MM::g_Keyword_Metadata_Width, width); + md.PutImageTag(MM::g_Keyword_Metadata_Height, height); + if (byteDepth == 1) + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY8); + else if (byteDepth == 2) + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY16); + else if (byteDepth == 4) + { + if (nComponents == 1) + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY32); + else + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB32); + } + else if (byteDepth == 8) + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB64); + else + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_Unknown); +} + +bool BufferAdapter::InsertImage(const unsigned char* buf, + unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd) { + return InsertMultiChannel(buf, 1, width, height, byteDepth, 1, pMd); +} + +bool BufferAdapter::InsertImage(const unsigned char *buf, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, Metadata *pMd) { + return InsertMultiChannel(buf, 1, width, height, byteDepth, nComponents, pMd); +} + + +bool BufferAdapter::InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, + unsigned height, unsigned byteDepth, Metadata *pMd) { + return InsertMultiChannel(buf, numChannels, width, height, byteDepth, 1, pMd); +} + +bool BufferAdapter::InsertMultiChannel(const unsigned char* buf, unsigned numChannels, + unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, Metadata* pMd) { + + // Initialize metadata with either provided metadata or create empty + Metadata md = (pMd != nullptr) ? *pMd : Metadata(); + + // Process common metadata + ProcessMetadata(md, width, height, byteDepth, nComponents); + + if (useV2_) { + // All the data needed to interpret the image is in the metadata + // This function will copy data and metadata into the buffer + v2Buffer_->InsertData(buf, width * height * byteDepth *numChannels, &md); + } else { + return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, + byteDepth, &md); + } +} + +void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) { if (useV2_) { - // Implement logic for v2Buffer if available - return 0; // Placeholder + // In v2, we use PeekNextDataReadPointer (which does not advance the internal pointer) + // Note: the v2 buffer is not channel aware, so the 'channel' parameter is ignored. + // TODO implement the channel aware version + void* slotPtr = nullptr; + size_t dataSize = 0; + int ret = v2Buffer_->PeekNextDataReadPointer(&slotPtr, &dataSize, md); + if (ret != DEVICE_OK || slotPtr == nullptr) + throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); + return slotPtr; + // TODO: make sure calling code releases the slot after use } else { - return circBuffer_->GetFreeSize(); + const mm::ImgBuffer* pBuf = circBuffer_->GetTopImageBuffer(channel); + if (pBuf != nullptr) { + md = pBuf->GetMetadata(); + return const_cast(pBuf->GetPixels()); + } else { + throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty); + } } } -bool BufferAdapter::Overflow() const +void* BufferAdapter::GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError) { if (useV2_) { - // Implement logic for v2Buffer if available - return false; // Placeholder + size_t dataSize = 0; + const unsigned char* slotPtr = v2Buffer_->PeekDataReadPointerAtIndex(n, &dataSize, md); + if (slotPtr == nullptr) + throw CMMError("V2 buffer does not contain enough data.", MMERR_CircularBufferEmpty); + // Return a non-const pointer (caller must be careful with the const_cast) + return const_cast(slotPtr); + // TODO: make sure calling code releases the slot after use } else { - return circBuffer_->Overflow(); + const mm::ImgBuffer* pBuf = circBuffer_->GetNthFromTopImageBuffer(n); + if (pBuf != nullptr) { + md = pBuf->GetMetadata(); + return const_cast(pBuf->GetPixels()); + } else { + throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty); + } } } -const mm::ImgBuffer* BufferAdapter::GetTopImageBuffer(unsigned channel) const +void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError) { if (useV2_) { - // Implement logic for v2Buffer if available - return nullptr; // Placeholder + // For v2, consume the data by calling PopNextDataReadPointer, + // which returns a const unsigned char* or throws an exception on error. + // The caller is expected to call ReleaseDataReadPointer on the returned pointer once done. + // TODO: make channel aware + size_t dataSize = 0; + const unsigned char* slotPtr = v2Buffer_->PopNextDataReadPointer(&dataSize, md, false); + if (slotPtr == nullptr) + throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); + return const_cast(slotPtr); + // TODO: ensure that calling code releases the read pointer after use. } else { - return circBuffer_->GetTopImageBuffer(channel); + const mm::ImgBuffer* pBuf = circBuffer_->GetNextImageBuffer(channel); + if (pBuf != nullptr) { + md = pBuf->GetMetadata(); + return const_cast(pBuf->GetPixels()); + } else { + throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty); + } } -} \ No newline at end of file +} diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index e0359dedd..ca3627430 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -4,6 +4,9 @@ #include "CircularBuffer.h" #include "Buffer_v2.h" #include "../MMDevice/MMDevice.h" +#include +#include +#include // BufferAdapter provides a common interface for buffer operations // used by MMCore. It currently supports only a minimal set of functions. @@ -23,13 +26,13 @@ class BufferAdapter { * Get a pointer to the top (most recent) image. * @return Pointer to image data, or nullptr if unavailable. */ - const unsigned char* GetTopImage() const; + const unsigned char* GetLastImage() const; /** * Get a pointer to the next image from the buffer. * @return Pointer to image data, or nullptr if unavailable. */ - const unsigned char* GetNextImage(); + const unsigned char* PopNextImage(); /** * Get a pointer to the nth image from the top of the buffer. @@ -128,13 +131,14 @@ class BufferAdapter { * Get the total capacity of the buffer. * @return Total capacity of the buffer. */ - long GetSize() const; + long GetSize(long imageSize) const; /** * Get the free capacity of the buffer. - * @return Free capacity of the buffer. + * @param imageSize Size of a single image in bytes. + * @return Number of images that can be added without overflowing. */ - long GetFreeSize() const; + long GetFreeSize(long imageSize) const; /** * Check if the buffer is overflowed. @@ -149,12 +153,21 @@ class BufferAdapter { */ const mm::ImgBuffer* GetTopImageBuffer(unsigned channel) const; + void* GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError); + void* GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError); + void* PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError); + private: bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. CircularBuffer* circBuffer_; DataBuffer* v2Buffer_; - + std::chrono::steady_clock::time_point startTime_; + std::map imageNumbers_; // Track image numbers per camera + std::mutex imageNumbersMutex_; // Mutex to protect access to imageNumbers_ + + void ProcessMetadata(Metadata& md, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents); }; #endif // BUFFERADAPTER_H \ No newline at end of file diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index afe938ad9..69cf38cef 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -45,7 +45,7 @@ Data Access: 2. Direct pointer access with explicit release - Reference counting ensures safe memory management - Slots become available for recycling when: - - Writing is complete (via Insert or GetWritingSlot+Release) + - Writing is complete (via Insert or GetDataWriteSlot+Release) - All readers have released their references Metadata Handling: @@ -58,170 +58,14 @@ Metadata Handling: #include "Buffer_v2.h" #include #include // for std::this_thread::yield if needed +#include +#include +#include +#include +#include -/////////////////////////////////////////////////////////////////////////////// -// DataBuffer Implementation -/////////////////////////////////////////////////////////////////////////////// - -DataBuffer::DataBuffer(unsigned int memorySizeMB, const std::string& name) { - // Convert MB to bytes for internal allocation - AllocateBuffer(memorySizeMB, name); -} - -DataBuffer::~DataBuffer() { - // Cleanup - delete[] buffer_; -} - -/** - * Allocate a character buffer - * @param memorySizeMB The size (in MB) of the buffer to allocate. - * @param name The name of the buffer. - * @return Error code (0 on success). - */ -int DataBuffer::AllocateBuffer(unsigned int memorySizeMB, const std::string& name) { - // Convert MB to bytes (1 MB = 1048576 bytes) - size_t numBytes = static_cast(memorySizeMB) * (1ULL << 20); - buffer_ = new char[numBytes]; - bufferSize_ = numBytes; - bufferName_ = name; - return DEVICE_OK; -} - -/** - * Release the buffer if it matches the given name. - * @param name The name of the buffer to release. - * @return Error code (0 on success, error if buffer not found or already released). - */ -int DataBuffer::ReleaseBuffer(const std::string& name) { - if (buffer_ != nullptr && bufferName_ == name) { - delete[] buffer_; - buffer_ = nullptr; - bufferSize_ = 0; - bufferName_.clear(); - return DEVICE_OK; - } - // TODO: Handle errors if other parts of the system still hold pointers. - return DEVICE_ERR; -} - -/** - * @brief Copy data into the next available slot in the buffer. - * - * Returns the size of the copied data through dataSize. - * TODO: Implementing code should check the device type of the caller, and ensure that - * all required metadata for interpreting its image data is there. - * Note: this can be implemented in terms of Get/Release slot + memcopy. - * - * @param caller The device calling this function. - * @param data The data to be copied. - * @param dataSize Size of the data to copy. - * @param serializedMetadata The associated metadata. - * @return Error code (0 on success). - */ -int DataBuffer::InsertData(const MM::Device *caller, const void* data, - size_t dataSize, const std::string& serializedMetadata) { - // TODO: Create a slot, copy the data into it, then release write access on the slot. - // Also, ensure that a slot is not garbage-collected while data remains available. - return DEVICE_OK; -} -/** - * Check if a new slot has been fully written in this buffer - * @return true if new data is ready, false otherwise - */ -bool DataBuffer::IsNewDataReady() { - // TODO: Implement checking logic based on the slot state. - return false; -} - -/** - * Copy the next available data and metadata from the buffer - * @param dataDestination Destination buffer to copy data into - * @param dataSize Returns the size of the copied data, or 0 if no data available - * @param md Metadata object to populate - * @param waitForData If true, block until data becomes available - * @return Error code (0 on success) - */ -int DataBuffer::CopyNextDataAndMetadata(void* dataDestination, - size_t* dataSize, Metadata &md, bool waitForData) { - // Basic implementation: - // TODO: Use slot management to return data from the next available slot. - return DEVICE_OK; -} - -/** - * Configure whether to overwrite old data when buffer is full. - * - * If true, when there are no more slots available for writing because - * images haven't been read fast enough, then automatically recycle the - * oldest slot(s) in the buffer as needed in order to make space for new images. - * This is suitable for situations when its okay to drop frames, like live - * view when data is not being saved. - * - * If false, then throw an exception if the buffer becomes full. - * - * @param overwrite Whether to enable overwriting of old data - * @return Error code (0 on success) - */ -int DataBuffer::SetOverwriteData(bool overwrite) { - overwriteWhenFull_ = overwrite; - return DEVICE_OK; -} - -/** - * @brief Get a pointer to the next available data slot in the buffer for writing. - * - * The caller must release the slot using ReleaseDataSlot after writing is complete. - * Internally this will use a std::unique_ptr. - * - * @param caller The device calling this function. - * @param slot Pointer to the slot where data will be written. - * @param slotSize The size of the slot. - * @param serializedMetadata The serialized metadata associated with the data. - * @return Error code (0 on success). - */ -int DataBuffer::GetWritingSlot(const MM::Device *caller, void** slot, - size_t slotSize, const std::string& serializedMetadata) { - // TODO: For now, we simply return a pointer into the buffer; a full implementation - // would create and manage a BufferSlot and assign it with exclusive write access. - if (slot == nullptr || slotSize > bufferSize_) - return DEVICE_ERR; - // TODO: understnad this pointer stuff - *slot = static_cast(buffer_); - return DEVICE_OK; -} - -/** - * @brief Release a data slot after writing is complete. - * - * @param caller The device calling this function. - * @param buffer The buffer to be released. - * @return Error code (0 on success). - */ -int DataBuffer::ReleaseWritingSlot(const MM::Device *caller, void* buffer) { - // TODO - return DEVICE_OK; -} - -/** - * Get a pointer to a heap allocated Metadata object with the required fields filled in - * @param md Pointer to the Metadata object to be created. - * @param width The width of the image. - * @param height The height of the image. - * @param bitDepth The bit depth of the image. - * @return Error code (0 on success). - */ -int DataBuffer::CreateCameraRequiredMetadata(Metadata** md, int width, int height, int bitDepth) { - // TODO: Implement camera-specific metadata creation. - return DEVICE_OK; -} - -unsigned int DataBuffer::GetMemorySizeMB() const { - // Convert bytes to MB (1 MB = 1048576 bytes) - return static_cast(bufferSize_ >> 20); -} /////////////////////////////////////////////////////////////////////////////// // BufferSlot Implementation @@ -235,21 +79,17 @@ unsigned int DataBuffer::GetMemorySizeMB() const { BufferSlot::BufferSlot(std::size_t start, std::size_t length) : start_(start), length_(length), readAccessCountAtomicInt_(0), - writeAtomicBool_(false) + writeAtomicBool_(true) // The slot is created with write access held by default. { - // No readers are active and the write lock is free upon construction. + // No readers are active and the slot starts with write access. } -/** - * Destructor. - * Currently no dynamic memory is used inside BufferSlot, so nothing needs to be cleaned up. - */ BufferSlot::~BufferSlot() { // No explicit cleanup required here. } /** - * Returns the start offset (in bytes) of the slot within the main buffer. + * Returns the start offset (in bytes) of the slot from the start of the buffer. */ std::size_t BufferSlot::GetStart() const { return start_; @@ -348,8 +188,11 @@ bool BufferSlot::AcquireReadAccess() { * The reader count is decremented using release semantics to ensure that all * prior read operations complete before the decrement is visible to other threads. */ -void BufferSlot::ReleaseReadAccess() { - readAccessCountAtomicInt_.fetch_sub(1, std::memory_order_release); +bool BufferSlot::ReleaseReadAccess() { + // fetch_sub returns the previous value. If that value was 1, + // then this call decrements the active reader count to zero. + int prevCount = readAccessCountAtomicInt_.fetch_sub(1, std::memory_order_release); + return (prevCount == 1); } /** @@ -368,3 +211,527 @@ bool BufferSlot::IsAvailableForWriting() const { bool BufferSlot::IsAvailableForReading() const { return !writeAtomicBool_.load(std::memory_order_acquire); } + + + +/////////////////////////////////////////////////////////////////////////////// +// DataBuffer Implementation +/////////////////////////////////////////////////////////////////////////////// + +DataBuffer::DataBuffer(unsigned int memorySizeMB) + : buffer_(nullptr), + bufferSize_(0), + overwriteWhenFull_(false), + nextAllocOffset_(0), + currentSlotIndex_(0), + overflow_(false) +{ + AllocateBuffer(memorySizeMB); +} + +DataBuffer::~DataBuffer() { + delete[] buffer_; +} + +/** + * Allocate a character buffer + * @param memorySizeMB The size (in MB) of the buffer to allocate. + * @return Error code (0 on success). + */ +int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { + // Convert MB to bytes (1 MB = 1048576 bytes) + size_t numBytes = static_cast(memorySizeMB) * (1ULL << 20); + buffer_ = new char[numBytes]; + bufferSize_ = numBytes; + overflow_ = false; + return DEVICE_OK; +} + +/** + * Release the buffer. + * @return Error code (0 on success, error if buffer not found or already released). + */ +int DataBuffer::ReleaseBuffer() { + if (buffer_ != nullptr) { + delete[] buffer_; + buffer_ = nullptr; + bufferSize_ = 0; + return DEVICE_OK; + } + // TODO: Handle errors if other parts of the system still hold pointers. + return DEVICE_ERR; +} + +/** + * Copy data into the next available slot in the buffer. + * + * Returns the size of the copied data through dataSize. + * all required metadata for interpreting its image data is there. + * Note: this can be implemented in terms of Get/Release slot + memcopy. + * + * @param caller The device calling this function. + * @param data The data to be copied. + * @param dataSize Size of the data to copy. + * @param serializedMetadata The associated metadata. + * @return Error code (0 on success). + */ +int DataBuffer::InsertData(const void* data, + size_t dataSize, const Metadata* pMd) { + // Get a write slot of the required size + void* slotPointer = nullptr; + int result = GetDataWriteSlot(dataSize, &slotPointer); + if (result != DEVICE_OK) + return result; + + // Copy the data into the slot + std::memcpy(slotPointer, data, dataSize); + if (pMd) { + // md.Serialize().c_str() + // TODO: Need a metadata lock and perhaps a map from buffer offset to metadata? + } + + // Release the write slot + return ReleaseDataWriteSlot(&slotPointer); +} + +/** + * Copy the next available data and metadata from the buffer + * @param dataDestination Destination buffer to copy data into + * @param dataSize Returns the size of the copied data, or 0 if no data available + * @param md Metadata object to populate + * @param waitForData If true, block until data becomes available + * @return Error code (0 on success) + */ +int DataBuffer::CopyNextDataAndMetadata(void* dataDestination, + size_t* dataSize, Metadata &md, bool waitForData) { + void* sourcePtr = nullptr; + int result = PopNextDataReadPointer(&sourcePtr, dataSize, md, waitForData); + if (result != DEVICE_OK) + return result; + + // Copy the data from the slot into the user's destination buffer + std::memcpy(dataDestination, sourcePtr, *dataSize); + + // Release the read pointer (this will handle cleanup and index management) + return ReleaseDataReadPointer(&sourcePtr); +} + +/** + * Configure whether to overwrite old data when buffer is full. + * + * If true, when there are no more slots available for writing because + * images haven't been read fast enough, then automatically recycle the + * oldest slot(s) in the buffer as needed in order to make space for new images. + * This is suitable for situations when its okay to drop frames, like live + * view when data is not being saved. + * + * If false, then throw an exception if the buffer becomes full. + * + * @param overwrite Whether to enable overwriting of old data + * @return Error code (0 on success) + */ +int DataBuffer::SetOverwriteData(bool overwrite) { + overwriteWhenFull_ = overwrite; + return DEVICE_OK; +} + + +/** + * Get a pointer to the next available data slot in the buffer for writing. + * + * The caller must release the slot using ReleaseDataSlot after writing is complete. + */ +int DataBuffer::GetDataWriteSlot(size_t slotSize, void** slotPointer) { + // AllocateNextSlot allocates a slot for writing new data of variable size. + // + // First, it checks if there is a recently released slot start (from releasedSlots_). + // For each candidate, it uses the activeSlotMap_ mechanism to + // verify that the candidate start yields a gap large enough to allocate slotSize bytes. + // This is done so to prefer recently released slots, in order to get performance + // boosts from reusing recently freed memory. + // + // If no released slot fits, then it falls back to using nextAllocOffset_ and similar + // collision checks. In overwrite mode, wrap-around is supported. + + // Ensure that only one thread can allocate a slot at a time. + std::lock_guard lock(slotManagementMutex_); + + // First, try using a released slot candidate (FILO order) + for (int i = static_cast(releasedSlots_.size()) - 1; i >= 0; i--) { + size_t candidateStart = releasedSlots_[i]; + size_t localCandidate = candidateStart; + + // Find first slot at or after our candidate position + auto nextIt = activeSlotsByStart_.lower_bound(localCandidate); + + // If there's a previous slot, ensure we don't overlap with it + if (nextIt != activeSlotsByStart_.begin()) { + size_t prevSlotEnd = std::prev(nextIt)->first + std::prev(nextIt)->second->GetLength(); + // If our candidate region [candidateStart, candidateStart+slotSize) overlaps the previous slot, + // bump candidateStart to the end of the conflicting slot and try again. + if (prevSlotEnd > localCandidate) { + localCandidate = prevSlotEnd; + } + } + + // Check if there's space before the next active slot + nextIt = activeSlotsByStart_.lower_bound(localCandidate); + bool candidateValid = true; + + if (nextIt != activeSlotsByStart_.end()) { + // Case 1: There is a next slot + // Check if our proposed slot would overlap with the next slot + candidateValid = (localCandidate + slotSize <= nextIt->first); + } else if (localCandidate + slotSize > bufferSize_) { + // Case 2: No next slot, but we'd exceed buffer size + if (!overwriteWhenFull_) { + candidateValid = false; + } else { + // Try wrapping around to start of buffer + localCandidate = 0; + nextIt = activeSlotsByStart_.lower_bound(localCandidate); + + // If there are any slots, ensure we don't overlap with the last one + if (nextIt != activeSlotsByStart_.begin()) { + auto prevIt = std::prev(nextIt); + size_t prevSlotEnd = prevIt->first + prevIt->second->GetLength(); + if (prevSlotEnd > localCandidate) { + localCandidate = prevSlotEnd; + } + } + + // Check if wrapped position would overlap with first slot + if (nextIt != activeSlotsByStart_.end()) { + candidateValid = (localCandidate + slotSize <= nextIt->first); + } + } + } + + if (candidateValid) { + // Remove the candidate from releasedSlots_ (it was taken from the "back" if available). + releasedSlots_.erase(releasedSlots_.begin() + i); + activeSlotsVector_.push_back(BufferSlot(localCandidate, slotSize)); + BufferSlot* slot = &activeSlotsVector_.back(); + activeSlotsByStart_[localCandidate] = slot; + *slotPointer = buffer_ + slot->GetStart(); + return DEVICE_OK; + } + } + // If no released candidate fits, use nextAllocOffset_ fallback. + size_t candidateStart = nextAllocOffset_; + if (candidateStart + slotSize > bufferSize_) { + // Not enough space in the buffer: if we are not allowed to overwrite then set our overflow flag. + if (!overwriteWhenFull_) { + overflow_ = true; // <-- mark that overflow has happened + *slotPointer = nullptr; + return DEVICE_ERR; + } + candidateStart = 0; // Reset to start of buffer + + // Since we're starting at position 0, remove any slots that start before our requested size + auto it = activeSlotsByStart_.begin(); + while (it != activeSlotsByStart_.end() && it->first < slotSize) { + BufferSlot* slot = it->second; + if (!slot->IsAvailableForWriting() || !slot->IsAvailableForReading()) { + throw std::runtime_error("Cannot overwrite slot that is currently being accessed (has active readers or writers)"); + } + + // Remove from both tracking structures + activeSlotsVector_.erase( + std::remove_if(activeSlotsVector_.begin(), activeSlotsVector_.end(), + [targetStart = it->first](const BufferSlot& slot) { + return slot.GetStart() == targetStart; + }), + activeSlotsVector_.end()); + it = activeSlotsByStart_.erase(it); + } + } + + // Create and track the new slot + activeSlotsVector_.push_back(BufferSlot(candidateStart, slotSize)); + BufferSlot* newSlot = &activeSlotsVector_.back(); + activeSlotsByStart_[candidateStart] = newSlot; + + // Update nextAllocOffset_ for next allocation + nextAllocOffset_ = candidateStart + slotSize; + if (nextAllocOffset_ >= bufferSize_) { + nextAllocOffset_ = 0; + } + + *slotPointer = buffer_ + newSlot->GetStart(); + return DEVICE_OK; +} + +/** + * @brief Release a data slot after writing is complete. + * + * @param caller The device calling this function. + * @param buffer The buffer to be released. + * @return Error code (0 on success). + */ +int DataBuffer::ReleaseDataWriteSlot(void** slotPointer) { + if (slotPointer == nullptr || *slotPointer == nullptr) + return DEVICE_ERR; + + std::lock_guard lock(slotManagementMutex_); + + // Calculate the offset from the buffer start to find the corresponding slot + char* ptr = static_cast(*slotPointer); + size_t offset = ptr - buffer_; + + // Find the slot in activeSlotsByStart_ + auto it = activeSlotsByStart_.find(offset); + if (it == activeSlotsByStart_.end()) { + return DEVICE_ERR; // Slot not found + } + + // Release the write access + BufferSlot* slot = it->second; + slot->ReleaseWriteAccess(); + + // Clear the pointer + *slotPointer = nullptr; + + // Notify any waiting readers that new data is available + dataCV_.notify_all(); + + return DEVICE_OK; +} + + +/** + * ReleaseSlot is called after a slot's content has been fully read. + * It assumes the caller has already released its read access (the slot is free). + * + * This implementation pushes only the start of the released slot onto the FILO + * (releasedSlots_) and removes the slot from the active slot map and activeSlots_. + */ +int DataBuffer::ReleaseDataReadPointer(void** slotPointer) { + if (slotPointer == nullptr || *slotPointer == nullptr) + return DEVICE_ERR; + + std::unique_lock lock(slotManagementMutex_); + + // Compute the slot's start offset. + char* ptr = static_cast(*slotPointer); + size_t offset = ptr - buffer_; + + // Find the slot in activeSlotMap_. + auto it = activeSlotsByStart_.find(offset); + if (it != activeSlotsByStart_.end()) { + BufferSlot* slot = it->second; + // Check if the slot is being accessed by any readers or writers. + if (!slot->IsAvailableForWriting() || !slot->IsAvailableForReading()) { + // TODO: right way to handle exceptions? + throw std::runtime_error("Cannot release slot that is currently being accessed"); + } + + // If we've reached max size, remove the oldest element (front of vector). + if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) { + releasedSlots_.erase(releasedSlots_.begin()); + } + releasedSlots_.push_back(offset); + + // Remove slot from active structures. + activeSlotsByStart_.erase(it); + for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { + if (vecIt->GetStart() == offset) { + // Determine the index being removed. + size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); + activeSlotsVector_.erase(vecIt); + // Adjust currentSlotIndex_: + // If the deleted slot was before the current index, decrement it. + if (currentSlotIndex_ > indexDeleted) + currentSlotIndex_--; + + break; + } + } + } + *slotPointer = nullptr; + return DEVICE_OK; +} + +const unsigned char* DataBuffer::PopNextDataReadPointer(size_t* dataSize, Metadata &md, bool waitForData) { + while (true) { + std::unique_lock lock(slotManagementMutex_); + + // If data is available, process it. + if (!activeSlotsVector_.empty() && currentSlotIndex_ < activeSlotsVector_.size()) { + BufferSlot& currentSlot = activeSlotsVector_[currentSlotIndex_]; + + if (!currentSlot.AcquireReadAccess()) { + throw std::runtime_error("Failed to acquire read access for the current slot."); + } + + const unsigned char* slotPointer = reinterpret_cast(buffer_ + currentSlot.GetStart()); + if (dataSize != nullptr) { + *dataSize = currentSlot.GetLength(); + } + + currentSlotIndex_++; + return slotPointer; + } + + // No data available. + if (!waitForData) { + throw std::runtime_error("No data available to read."); + } + + // Wait for notification of new data. + dataCV_.wait(lock); + // When we wake up, the while loop will check again if data is available + } +} + +unsigned int DataBuffer::GetMemorySizeMB() const { + // Convert bytes to MB (1 MB = 1048576 bytes) + return static_cast(bufferSize_ >> 20); +} + +int DataBuffer::PeekNextDataReadPointer(void** slotPointer, size_t* dataSize, + Metadata &md) { + // Immediately check if there is an unread slot without waiting. + std::unique_lock lock(slotManagementMutex_); + if (activeSlotsVector_.empty() || currentSlotIndex_ >= activeSlotsVector_.size()) { + return DEVICE_ERR; // No unread data available. + } + + // Obtain the next available slot *without* advancing currentSlotIndex_. + BufferSlot& currentSlot = activeSlotsVector_[currentSlotIndex_]; + if (!currentSlot.AcquireReadAccess()) + return DEVICE_ERR; + + *slotPointer = buffer_ + currentSlot.GetStart(); + *dataSize = currentSlot.GetLength(); + // (If metadata is stored per slot, populate md here.) + return DEVICE_OK; +} + +const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* dataSize, Metadata &md) { + + std::unique_lock lock(slotManagementMutex_); + if (activeSlotsVector_.empty() || (currentSlotIndex_ + n) >= activeSlotsVector_.size()) { + throw std::runtime_error("Not enough unread data available."); + } + + BufferSlot& slot = activeSlotsVector_[currentSlotIndex_ + n]; + if (!slot.AcquireReadAccess()) + throw std::runtime_error("Failed to acquire read access for the selected slot."); + + // Assign the size from the slot. + if (dataSize != nullptr) { + *dataSize = slot.GetLength(); + } + + // Return a constant pointer to the data. + return reinterpret_cast(buffer_ + slot.GetStart()); +} + +/** + * Releases the read access that was acquired by a peek. + * This is similar to ReleaseDataReadPointer except that it does not + * remove the slot from the active list. This should be used when the + * overwriteWhenFull_ flag is true and the caller wants to release the + * peeked slot for reuse. + */ + +int DataBuffer::ReleasePeekDataReadPointer(void** slotPointer) { + if (slotPointer == nullptr || *slotPointer == nullptr) + return DEVICE_ERR; + + + std::lock_guard lock(slotManagementMutex_); + char* ptr = static_cast(*slotPointer); + size_t offset = ptr - buffer_; + + // Look up the corresponding slot by its buffer offset. + auto it = activeSlotsByStart_.find(offset); + if (it == activeSlotsByStart_.end()) + return DEVICE_ERR; // Slot not found + + BufferSlot* slot = it->second; + // Release the read access (this does NOT remove the slot from the active list) + slot->ReleaseReadAccess(); + + *slotPointer = nullptr; + return DEVICE_OK; +} + +size_t DataBuffer::GetOccupiedSlotCount() const { + std::lock_guard lock(slotManagementMutex_); + return activeSlotsVector_.size(); +} + +size_t DataBuffer::GetOccupiedMemory() const { + std::lock_guard lock(slotManagementMutex_); + size_t usedMemory = 0; + for (const auto& slot : activeSlotsVector_) { + usedMemory += slot.GetLength(); + } + return usedMemory; +} + +size_t DataBuffer::GetFreeMemory() const { + std::lock_guard lock(slotManagementMutex_); + // Free memory is the total buffer size minus the sum of all occupied memory. + size_t usedMemory = 0; + for (const auto& slot : activeSlotsVector_) { + usedMemory += slot.GetLength(); + } + return (bufferSize_ > usedMemory) ? (bufferSize_ - usedMemory) : 0; +} + +bool DataBuffer::Overflow() const { + std::lock_guard lock(slotManagementMutex_); + return overflow_; +} + +/** + * Reinitialize the DataBuffer by clearing all internal data structures, + * releasing the current buffer, and reallocating a new one. + * This method uses the existing slotManagementMutex_ to ensure thread-safety. + * + * @param memorySizeMB New size (in MB) for the buffer. + * @return DEVICE_OK on success. + * @throws std::runtime_error if any slot is still actively being read or written. + */ +int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { + std::lock_guard lock(slotManagementMutex_); + + // Check that there are no outstanding readers or writers. + for (const BufferSlot &slot : activeSlotsVector_) { + if (!slot.IsAvailableForReading() || !slot.IsAvailableForWriting()) { + throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); + } + } + + // Clear internal data structures. + activeSlotsVector_.clear(); + activeSlotsByStart_.clear(); + releasedSlots_.clear(); + currentSlotIndex_ = 0; + nextAllocOffset_ = 0; + overflow_ = false; + + // Release the old buffer. + if (buffer_ != nullptr) { + delete[] buffer_; + buffer_ = nullptr; + bufferSize_ = 0; + } + + // Allocate a new buffer using the provided memory size. + AllocateBuffer(memorySizeMB); + + return DEVICE_OK; +} + +long DataBuffer::GetRemainingImageCount() const { + std::lock_guard lock(slotManagementMutex_); + // Return the number of unread slots. + // currentSlotIndex_ tracks the next slot to read, + // so unread count is the total slots minus this index. + return (activeSlotsVector_.size() > currentSlotIndex_) ? + static_cast(activeSlotsVector_.size() - currentSlotIndex_) : 0; +} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 69684f3c4..b24bea5ef 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -40,236 +40,354 @@ #include #include #include +#include /** * BufferSlot represents a contiguous slot in the DataBuffer that holds image - * data and metadata. It manages exclusive (write) and shared (read) access + * data and metadata. It supports exclusive write access with shared read access, * using atomics, a mutex, and a condition variable. */ class BufferSlot { public: - // Constructor: Initializes the slot with the given start offset and length. + /** + * Constructor. + * Initializes the slot with the specified starting byte offset and length. + * Also initializes atomic variables that track reader and writer access. + * + * @param start The starting offset (in bytes) within the buffer. + * @param length The length (in bytes) of the slot. + */ BufferSlot(std::size_t start, std::size_t length); - // Destructor. + + /** + * Destructor. + */ ~BufferSlot(); - // Returns the start offset (in bytes) of the slot. + /** + * Returns the starting offset (in bytes) of the slot in the buffer. + * + * @return The slot's start offset. + */ std::size_t GetStart() const; - // Returns the length (in bytes) of the slot. + + /** + * Returns the length (in bytes) of the slot. + * + * @return The slot's length. + */ std::size_t GetLength() const; - // Stores a detail (e.g., width, height) associated with the slot. + /** + * Stores a detail (for example, image width or height) associated with the slot. + * + * @param key The name of the detail. + * @param value The value of the detail. + */ void SetDetail(const std::string &key, std::size_t value); - // Retrieves a stored detail; returns 0 if the key is not found. + + /** + * Retrieves a previously stored detail. + * Returns 0 if the key is not found. + * + * @param key The detail key. + * @return The stored value or 0 if not found. + */ std::size_t GetDetail(const std::string &key) const; - // Clears all stored details. - void ClearDetails(); - // --- Methods for synchronizing access --- + /** + * Clears all stored details from the slot. + */ + void ClearDetails(); /** - * Try to acquire exclusive write access. - * Returns true on success, false if the slot is already locked for writing - * or if active readers exist. + * Attempts to acquire exclusive write access. + * It first tries to atomically set the write flag. + * If another writer is active or if there are active readers, + * the write lock is not acquired. + * + * @return True if the slot is locked for writing; false otherwise. */ bool AcquireWriteAccess(); + /** - * Release exclusive write access. + * Releases exclusive write access. * Clears the write flag and notifies waiting readers. */ void ReleaseWriteAccess(); + /** - * Acquire shared read access by blocking until no writer is active. - * Once the waiting condition is met, the reader count is incremented. - * Returns true when read access has been acquired. + * Acquires shared read access. + * This is a blocking operation — if a writer is active, the caller waits + * until the writer releases its lock. Once available, the reader count is incremented. + * + * @return True when read access has been successfully acquired. */ bool AcquireReadAccess(); + /** - * Release shared read access. - * Decrements the reader count using release semantics. + * Releases shared read access. + * Decrements the reader count. + * + * @return True if this call released the last active reader; false otherwise. */ - void ReleaseReadAccess(); + bool ReleaseReadAccess(); /** - * Return true if the slot is available for acquiring write access (i.e., - * no active writer or reader). + * Checks if the slot is currently available for writing. + * A slot is available if there are no active readers and no active writer. + * + * @return True if available for writing, false otherwise. */ bool IsAvailableForWriting() const; + /** - * Return true if the slot is available for acquiring read access - * (no active writer). + * Checks if the slot is available for acquiring read access. + * A slot is available for reading if no writer presently holds the lock. + * + * @return True if available for reading. */ bool IsAvailableForReading() const; private: - // Basic slot information. - std::size_t start_; // Byte offset within the buffer. - std::size_t length_; // Length of the slot in bytes. - std::map details_; // Additional details (e.g., image dimensions). - - // Synchronization primitives. - std::atomic readAccessCountAtomicInt_; // Count of active readers. - std::atomic writeAtomicBool_; // True if the slot is locked for writing. - mutable std::mutex writeCompleteConditionMutex_; // Mutex for condition variable. - mutable std::condition_variable writeCompleteCondition_; // Condition variable for blocking readers. + std::size_t start_; + std::size_t length_; + std::map details_; + std::atomic readAccessCountAtomicInt_; + std::atomic writeAtomicBool_; + mutable std::mutex writeCompleteConditionMutex_; + mutable std::condition_variable writeCompleteCondition_; }; /** - * DataBuffer manages a large contiguous memory area, divided into BufferSlot - * objects for storing image data and metadata. It supports two data access - * patterns: copy-based access and direct pointer access via retrieval of slots. + * DataBuffer manages a contiguous block of memory divided into BufferSlot objects + * for storing image data and metadata. It provides thread-safe access for both + * reading and writing operations and supports configurable overflow behavior. + * + * Two data access patterns are provided: + * 1. Copy-based access. + * 2. Direct pointer access with an explicit release. + * + * Reference counting is used to ensure that memory is managed safely. A slot + * is recycled when all references (readers and writers) have been released. */ class DataBuffer { public: - DataBuffer(unsigned int memorySizeMB, const std::string& name); + /** + * Maximum number of released slots to track. + */ + static const size_t MAX_RELEASED_SLOTS = 50; + + /** + * Constructor. + * Initializes the DataBuffer with a specified memory size in MB. + * + * @param memorySizeMB The size (in megabytes) of the buffer. + */ + DataBuffer(unsigned int memorySizeMB); + + /** + * Destructor. + */ ~DataBuffer(); - // Buffer Allocation and Destruction - int AllocateBuffer(unsigned int memorySizeMB, const std::string& name); - int ReleaseBuffer(const std::string& name); + /** + * Allocates a contiguous block of memory for the buffer. + * + * @param memorySizeMB The amount of memory (in MB) to allocate. + * @return DEVICE_OK on success. + */ + int AllocateBuffer(unsigned int memorySizeMB); + + /** + * Releases the allocated buffer. + * + * @return DEVICE_OK on success, or an error if the buffer is already released. + */ + int ReleaseBuffer(); + + /** + * Copies data into the next available slot in the buffer along with its metadata. + * The copy-based approach is implemented using a slot acquisition, memory copy, and then + * slot release. + * + * @param data Pointer to the data to be inserted. + * @param dataSize The size of data (in bytes) being inserted. + * @param pMd Pointer to the metadata associated with the data. + * @return DEVICE_OK on success. + */ + int InsertData(const void* data, size_t dataSize, const Metadata* pMd); + + /** + * Copies data and metadata from the next available slot in the buffer into the provided destination. + * + * @param dataDestination Destination buffer into which data will be copied. + * @param dataSize On success, returns the size of the copied data. + * @param md Metadata object to be populated with the data's metadata. + * @param waitForData If true, block until data becomes available. + * @return DEVICE_OK on success. + */ + int CopyNextDataAndMetadata(void* dataDestination, size_t* dataSize, Metadata &md, bool waitForData); + + /** + * Sets whether the buffer should overwrite old data when it is full. + * If true, the buffer will recycle the oldest slot when no free slot is available; + * if false, an error is returned when writing new data fails due to a full buffer. + * + * @param overwrite True to enable overwriting, false to disable. + * @return DEVICE_OK on success. + */ + int SetOverwriteData(bool overwrite); + + /** + * Acquires a pointer to a free slot in the buffer for writing purposes. + * The caller must later call ReleaseDataWriteSlot after finishing writing. + * + * @param slotSize The required size of the write slot. + * @param slotPointer On success, receives a pointer within the buffer where data can be written. + * @return DEVICE_OK on success. + */ + int GetDataWriteSlot(size_t slotSize, void** slotPointer); + + /** + * Releases the write slot after data writing is complete. + * This clears the write lock and notifies any waiting reader threads. + * + * @param slotPointer Pointer previously obtained from GetDataWriteSlot. + * @return DEVICE_OK on success. + */ + int ReleaseDataWriteSlot(void** slotPointer); + + /** + * Releases read access on a data slot after its contents have been completely read. + * This makes the slot available for recycling. + * + * @param slotPointer Pointer previously obtained from GetNextDataReadPointer. + * @return DEVICE_OK on success. + */ + int ReleaseDataReadPointer(void** slotPointer); + + /** + * Retrieves and removes (consumes) the next available data slot for reading. + * This method advances the internal reading index. + * + * @param dataSize On success, returns the size of the data. + * @param md Associated metadata for the data. + * @param waitForData If true, block until data becomes available. + * @return Pointer to the next available data in the buffer. + */ + const unsigned char* PopNextDataReadPointer(size_t* dataSize, Metadata &md, bool waitForData); + + + /** + * Peeks at the next unread data slot without consuming it. + * The slot remains available for subsequent acquisitions. + * + * @param slotPointer On success, receives the pointer to the data. + * @param dataSize On success, returns the size of the data. + * @param md Associated metadata for the data. + * @return DEVICE_OK on success, or an error code if no data is available. + */ + int PeekNextDataReadPointer(void** slotPointer, size_t* dataSize, Metadata &md); - // TODO: Other versions for allocating buffers Java, Python + /** + * Peeks at the nth unread data slot without consuming it. + * (n = 0 is equivalent to PeekNextDataReadPointer.) + * + * @param n The index of the unread slot. + * @param dataSize On success, returns the size of the data. + * @param md Associated metadata for the data. + * @return const pointer to the data. + */ + const unsigned char* PeekDataReadPointerAtIndex(size_t n, size_t* dataSize, Metadata &md); + + + /** + * Releases the read access that was acquired by a peek operation. + * This method releases the temporary read access without consuming the slot. + * + * @param slotPointer Pointer previously obtained from a peek method. + * @return DEVICE_OK on success. + */ + int ReleasePeekDataReadPointer(void** slotPointer); - /** - * Get the total memory size of the buffer in megabytes - * @return Size of the buffer in MB + * Returns the total memory size of the buffer in megabytes. + * + * @return The buffer size in MB. */ unsigned int GetMemorySizeMB() const; - /////// Monitoring the Buffer /////// - int GetAvailableBytes(void* buffer, size_t* availableBytes); - int GetNumSlotsUsed(void* buffer, size_t* numSlotsUsed); - - - - //// Configuration options - /** - * Configure whether to overwrite old data when buffer is full. - * - * If true, when there are no more slots available for writing because - * images haven't been read fast enough, then automatically recycle the - * oldest slot(s) in the buffer as needed in order to make space for new images. - * This is suitable for situations when its okay to drop frames, like live - * view when data is not being saved. - * - * If false, then throw an exception if the buffer becomes full. - * - * @param overwrite Whether to enable overwriting of old data - * @return Error code (0 on success) - */ - int SetOverwriteData(bool overwrite); - - - - /////// Getting Data Out /////// - - /** - * Check if a new slot has been fully written in this buffer - * @return true if new data is ready, false otherwise - */ - bool IsNewDataReady(); - - /** - * Copy the next available data and metadata from the buffer - * @param dataDestination Destination buffer to copy data into - * @param dataSize Returns the size of the copied data, or 0 if no data available - * @param md Metadata object to populate - * @param waitForData If true, block until data becomes available - * @return Error code (0 on success) - */ - int CopyNextDataAndMetadata(void* dataDestination, - size_t* dataSize, Metadata &md, bool waitForData); - - - /** - * Copy the next available metadata from the buffer - * Returns the size of the copied metadata through metadataSize, - * or 0 if no metadata is available - */ - int CopyNextMetadata(void* buffer, Metadata &md); - - /** - * Get a pointer to the next available data slot in the buffer - * The caller must release the slot using ReleaseNextDataAndMetadata - * If awaitReady is false and the data was inserted using GetWritingSlot, it - * is possible to read the data as it is being written (e.g. to monitor progress) - * of large or slow image being written - * - * Internally this will use a std::shared_ptr - */ - int GetNextSlotPointer(void** slotPointer, size_t* dataSize, - Metadata &md, bool awaitReady=true); - - /** - * Release the next data slot and its associated metadata - */ - int ReleaseNextSlot(void** slotPointer); - - - ////// Writing Data into buffer ////// - - /** - * Copy data into the next available slot in the buffer. - * - * Returns the size of the copied data through dataSize. - * Implementing code should check the device type of the caller, and ensure that - * all required metadata for interpreting its image data is there. - * Note: this can be implemented in terms of Get/Release slot + memcopy. - * - * @param caller The device calling this function. - * @param data The data to be copied into the buffer. - * @param dataSize The size of the data to be copied. - * @param serializedMetadata The serialized metadata associated with the data. - * @return Error code (0 on success). - */ - int InsertData(const MM::Device *caller, const void* data, size_t dataSize, - const std::string& serializedMetadata); - - /** - * Get a pointer to the next available data slot in the buffer for writing. - * - * The caller must release the slot using ReleaseDataSlot after writing is complete. - * Internally this will use a std::unique_ptr. - * - * @param caller The device calling this function. - * @param slot Pointer to the slot where data will be written. - * @param slotSize The size of the slot. - * @param serializedMetadata The serialized metadata associated with the data. - * @return Error code (0 on success). - */ - int GetWritingSlot(const MM::Device *caller, void** slot, size_t slotSize, - const std::string& serializedMetadata); - - /** - * Release a data slot after writing is complete. - * @param caller The device calling this function. - * @param buffer The slot to be released. - * @return Error code (0 on success). - */ - int ReleaseWritingSlot(const MM::Device *caller, void* buffer); - - - - - ////// Camera API ////// - - // Set the buffer for a camera to write into - int SetCameraBuffer(const std::string& camera, void* buffer); - - // Get a pointer to a heap allocated Metadata object with the required fields filled in - int CreateCameraRequiredMetadata(Metadata**, int width, int height, int bitDepth); + /** + * Returns the number of currently occupied slots in the buffer. + * + * @return The number of occupied slots. + */ + size_t GetOccupiedSlotCount() const; + + /** + * Returns the total occupied memory (in bytes) within the buffer. + * + * @return The sum of the lengths of all active slots. + */ + size_t GetOccupiedMemory() const; + + /** + * Returns the amount of free memory (in bytes) remaining in the buffer. + * + * @return The number of free bytes available for new data. + */ + size_t GetFreeMemory() const; + + /** + * Returns whether the buffer has been overflowed (i.e. an attempt to + * allocate a write slot failed because there was no available space). + */ + bool Overflow() const; + + /** + * Returns the number of unread data slots in the buffer. + * + * @return The number of unread data slots. + */ + long GetRemainingImageCount() const; + + /** + * Reinitialize the DataBuffer by clearing all internal data structures, + * releasing the current buffer, and reallocating a new one. + * This method uses the existing slotManagementMutex_ to ensure thread safety. + * + * @param memorySizeMB New size (in MB) for the buffer. + * @return DEVICE_OK on success. + * @throws std::runtime_error if any slot is still actively being read or written. + */ + int ReinitializeBuffer(unsigned int memorySizeMB); private: - // Basic buffer management. + // Pointer to the allocated buffer memory. char* buffer_; + // Total size (in bytes) of the allocated buffer. size_t bufferSize_; - std::string bufferName_; - // Configuration. + // Whether the buffer should overwrite older data when full. bool overwriteWhenFull_; - // List of active buffer slots. Each slot manages its own read/write access. - std::vector activeSlots_; + // New: overflow indicator (set to true if an insert fails because of buffer full) + bool overflow_; + + // Data structures for tracking active slot usage. + std::vector activeSlotsVector_; + std::map activeSlotsByStart_; + std::vector releasedSlots_; + + // The next available offset for a new data slot. + size_t nextAllocOffset_; + + // Tracks the current slot index for read operations. + size_t currentSlotIndex_; + + // Synchronization primitives for managing slot access. + std::condition_variable dataCV_; + mutable std::mutex slotManagementMutex_; }; diff --git a/MMCore/CircularBuffer.cpp b/MMCore/CircularBuffer.cpp index 2453bda49..c30a1b58e 100644 --- a/MMCore/CircularBuffer.cpp +++ b/MMCore/CircularBuffer.cpp @@ -72,7 +72,6 @@ CircularBuffer::~CircularBuffer() {} bool CircularBuffer::Initialize(unsigned channels, unsigned int w, unsigned int h, unsigned int pixDepth) { MMThreadGuard guard(g_bufferLock); - imageNumbers_.clear(); startTime_ = std::chrono::steady_clock::now(); bool ret = true; @@ -139,7 +138,6 @@ void CircularBuffer::Clear() saveIndex_=0; overflow_ = false; startTime_ = std::chrono::steady_clock::now(); - imageNumbers_.clear(); } unsigned long CircularBuffer::GetSize() const @@ -164,61 +162,12 @@ unsigned long CircularBuffer::GetRemainingImageCount() const return (unsigned long)(insertIndex_ - saveIndex_); } -static std::string FormatLocalTime(std::chrono::time_point tp) { - using namespace std::chrono; - auto us = duration_cast(tp.time_since_epoch()); - auto secs = duration_cast(us); - auto whole = duration_cast(secs); - auto frac = static_cast((us - whole).count()); - - // As of C++14/17, it is simpler (and probably faster) to use C functions for - // date-time formatting - - std::time_t t(secs.count()); // time_t is seconds on platforms we support - std::tm *ptm; -#ifdef _WIN32 // Windows localtime() is documented thread-safe - ptm = std::localtime(&t); -#else // POSIX has localtime_r() - std::tm tmstruct; - ptm = localtime_r(&t, &tmstruct); -#endif - - // Format as "yyyy-mm-dd hh:mm:ss.uuuuuu" (26 chars) - const char *timeFmt = "%Y-%m-%d %H:%M:%S"; - char buf[32]; - std::size_t len = std::strftime(buf, sizeof(buf), timeFmt, ptm); - std::snprintf(buf + len, sizeof(buf) - len, ".%06d", frac); - return buf; -} - -/** -* Inserts a single image in the buffer. -*/ -bool CircularBuffer::InsertImage(const unsigned char* pixArray, unsigned int width, unsigned int height, unsigned int byteDepth, const Metadata* pMd) throw (CMMError) -{ - return InsertMultiChannel(pixArray, 1, width, height, byteDepth, pMd); -} - -/** -* Inserts a single image, possibly with multiple channels, but with 1 component, in the buffer. -*/ -bool CircularBuffer::InsertMultiChannel(const unsigned char* pixArray, unsigned int numChannels, unsigned int width, unsigned int height, unsigned int byteDepth, const Metadata* pMd) throw (CMMError) -{ - return InsertMultiChannel(pixArray, numChannels, width, height, byteDepth, 1, pMd); -} - -/** -* Inserts a single image, possibly with multiple components, in the buffer. -*/ -bool CircularBuffer::InsertImage(const unsigned char* pixArray, unsigned int width, unsigned int height, unsigned int byteDepth, unsigned int nComponents, const Metadata* pMd) throw (CMMError) -{ - return InsertMultiChannel(pixArray, 1, width, height, byteDepth, nComponents, pMd); -} /** * Inserts a multi-channel frame in the buffer. */ -bool CircularBuffer::InsertMultiChannel(const unsigned char* pixArray, unsigned int numChannels, unsigned int width, unsigned int height, unsigned int byteDepth, unsigned int nComponents, const Metadata* pMd) throw (CMMError) +bool CircularBuffer::InsertMultiChannel(const unsigned char* pixArray, unsigned int numChannels, unsigned int width, unsigned int height, + unsigned int byteDepth, const Metadata* pMd) throw (CMMError) { MMThreadGuard insertGuard(g_insertLock); @@ -241,7 +190,6 @@ bool CircularBuffer::InsertMultiChannel(const unsigned char* pixArray, unsigned for (unsigned i=0; iSetMetadata(*pMd); } - - std::string cameraName = md.GetSingleTag(MM::g_Keyword_Metadata_CameraLabel).GetValue(); - if (imageNumbers_.end() == imageNumbers_.find(cameraName)) - { - imageNumbers_[cameraName] = 0; - } - - // insert image number. - md.put(MM::g_Keyword_Metadata_ImageNumber, CDeviceUtils::ConvertToString(imageNumbers_[cameraName])); - ++imageNumbers_[cameraName]; - } - - if (!md.HasTag(MM::g_Keyword_Elapsed_Time_ms)) - { - // if time tag was not supplied by the camera insert current timestamp - using namespace std::chrono; - auto elapsed = steady_clock::now() - startTime_; - md.PutImageTag(MM::g_Keyword_Elapsed_Time_ms, - std::to_string(duration_cast(elapsed).count())); - } - - // Note: It is not ideal to use local time. I think this tag is rarely - // used. Consider replacing with UTC (micro)seconds-since-epoch (with - // different tag key) after addressing current usage. - auto now = std::chrono::system_clock::now(); - md.PutImageTag(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); - - md.PutImageTag(MM::g_Keyword_Metadata_Width, width); - md.PutImageTag(MM::g_Keyword_Metadata_Height, height); - if (byteDepth == 1) - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY8); - else if (byteDepth == 2) - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY16); - else if (byteDepth == 4) - { - if (nComponents == 1) - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY32); - else - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB32); - } - else if (byteDepth == 8) - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB64); - else - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_Unknown); - - pImg->SetMetadata(md); - //pImg->SetPixels(pixArray + i * singleChannelSize); + } + //pImg->SetPixels(pixArray + i * singleChannelSize); // TODO: In MMCore the ImgBuffer::GetPixels() returns const pointer. // It would be better to have something like ImgBuffer::GetPixelsRW() in MMDevice. // Or even better - pass tasksMemCopy_ to ImgBuffer constructor // and utilize parallel copy also in single snap acquisitions. - tasksMemCopy_->MemCopy((void*)pImg->GetPixels(), + tasksMemCopy_->MemCopy((void*)pImg->GetPixels(), pixArray + i * singleChannelSize, singleChannelSize); } @@ -362,7 +264,7 @@ const mm::ImgBuffer* CircularBuffer::GetNthFromTopImageBuffer(long n, return frameArray_[targetIndex].FindImage(channel); } -const unsigned char* CircularBuffer::GetNextImage() +const unsigned char* CircularBuffer::PopNextImage() { const mm::ImgBuffer* img = GetNextImageBuffer(0); if (!img) diff --git a/MMCore/CircularBuffer.h b/MMCore/CircularBuffer.h index 636c02ceb..c3d7b86b0 100644 --- a/MMCore/CircularBuffer.h +++ b/MMCore/CircularBuffer.h @@ -66,12 +66,9 @@ class CircularBuffer unsigned int Height() const {MMThreadGuard guard(g_bufferLock); return height_;} unsigned int Depth() const {MMThreadGuard guard(g_bufferLock); return pixDepth_;} - bool InsertImage(const unsigned char* pixArray, unsigned int width, unsigned int height, unsigned int byteDepth, const Metadata* pMd) throw (CMMError); bool InsertMultiChannel(const unsigned char* pixArray, unsigned int numChannels, unsigned int width, unsigned int height, unsigned int byteDepth, const Metadata* pMd) throw (CMMError); - bool InsertImage(const unsigned char* pixArray, unsigned int width, unsigned int height, unsigned int byteDepth, unsigned int nComponents, const Metadata* pMd) throw (CMMError); - bool InsertMultiChannel(const unsigned char* pixArray, unsigned int numChannels, unsigned int width, unsigned int height, unsigned int byteDepth, unsigned int nComponents, const Metadata* pMd) throw (CMMError); const unsigned char* GetTopImage() const; - const unsigned char* GetNextImage(); + const unsigned char* PopNextImage(); const mm::ImgBuffer* GetTopImageBuffer(unsigned channel) const; const mm::ImgBuffer* GetNthFromTopImageBuffer(unsigned long n) const; const mm::ImgBuffer* GetNthFromTopImageBuffer(long n, unsigned channel) const; @@ -89,7 +86,6 @@ class CircularBuffer unsigned int pixDepth_; long imageCounter_; std::chrono::time_point startTime_; - std::map imageNumbers_; // Invariants: // 0 <= saveIndex_ <= insertIndex_ diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index e3867acf5..150316540 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -3079,7 +3079,7 @@ void* CMMCore::getLastImage() throw (CMMError) } } - unsigned char* pBuf = const_cast(bufferAdapter_->GetTopImage()); + unsigned char* pBuf = const_cast(bufferAdapter_->GetLastImage()); if (pBuf != 0) return pBuf; else @@ -3095,14 +3095,7 @@ void* CMMCore::getLastImageMD(unsigned channel, unsigned slice, Metadata& md) co if (slice != 0) throw CMMError("Slice must be 0"); - const mm::ImgBuffer* pBuf = bufferAdapter_->GetTopImageBuffer(channel); - if (pBuf != 0) - { - md = pBuf->GetMetadata(); - return const_cast(pBuf->GetPixels()); - } - else - throw CMMError(getCoreErrorText(MMERR_CircularBufferEmpty).c_str(), MMERR_CircularBufferEmpty); + return bufferAdapter_->GetLastImageMD(channel, md); } /** @@ -3136,14 +3129,7 @@ void* CMMCore::getLastImageMD(Metadata& md) const throw (CMMError) */ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw (CMMError) { - const mm::ImgBuffer* pBuf = bufferAdapter_->GetNthFromTopImageBuffer(n); - if (pBuf != 0) - { - md = pBuf->GetMetadata(); - return const_cast(pBuf->GetPixels()); - } - else - throw CMMError(getCoreErrorText(MMERR_CircularBufferEmpty).c_str(), MMERR_CircularBufferEmpty); + return bufferAdapter_->GetNthImageMD(n, md); } /** @@ -3160,7 +3146,7 @@ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw */ void* CMMCore::popNextImage() throw (CMMError) { - unsigned char* pBuf = const_cast(bufferAdapter_->GetNextImage()); + unsigned char* pBuf = const_cast(bufferAdapter_->PopNextImage()); if (pBuf != 0) return pBuf; else @@ -3178,14 +3164,7 @@ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) th if (slice != 0) throw CMMError("Slice must be 0"); - const mm::ImgBuffer* pBuf = bufferAdapter_->GetNextImageBuffer(channel); - if (pBuf != 0) - { - md = pBuf->GetMetadata(); - return const_cast(pBuf->GetPixels()); - } - else - throw CMMError(getCoreErrorText(MMERR_CircularBufferEmpty).c_str(), MMERR_CircularBufferEmpty); + return bufferAdapter_->PopNextImageMD(channel, md); } /** @@ -3287,7 +3266,10 @@ long CMMCore::getBufferTotalCapacity() { if (bufferAdapter_) { - return bufferAdapter_->GetSize(); + // Compute image size from the current camera parameters. + long imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel(); + // Pass the computed image size as an argument to the adapter. + return bufferAdapter_->GetSize(imageSize); } return 0; } @@ -3301,7 +3283,9 @@ long CMMCore::getBufferFreeCapacity() { if (bufferAdapter_) { - return bufferAdapter_->GetFreeSize(); + // Compute image size from the current camera parameters. + long imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel(); + return bufferAdapter_->GetFreeSize(imageSize); } return 0; } From 8efc90d60d23d25c7874442f2219d623345a65a1 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:51:33 -0800 Subject: [PATCH 05/46] added metadata to buffer --- MMCore/BufferAdapter.cpp | 6 +- MMCore/Buffer_v2.cpp | 410 ++++++++++++++++++++++----------------- MMCore/Buffer_v2.h | 184 ++++++++++-------- 3 files changed, 336 insertions(+), 264 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index 2bbcdb306..ac12beb0a 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -77,7 +77,7 @@ const unsigned char* BufferAdapter::PopNextImage() { if (useV2_) { Metadata dummyMetadata; - return v2Buffer_->PopNextDataReadPointer(nullptr, dummyMetadata, false); + return v2Buffer_->PopNextDataReadPointer(dummyMetadata, nullptr, false); // TODO: ensure calling code releases the slot after use } else { return circBuffer_->PopNextImage(); @@ -252,7 +252,7 @@ void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw // In v2, we use PeekNextDataReadPointer (which does not advance the internal pointer) // Note: the v2 buffer is not channel aware, so the 'channel' parameter is ignored. // TODO implement the channel aware version - void* slotPtr = nullptr; + unsigned char* slotPtr = nullptr; size_t dataSize = 0; int ret = v2Buffer_->PeekNextDataReadPointer(&slotPtr, &dataSize, md); if (ret != DEVICE_OK || slotPtr == nullptr) @@ -299,7 +299,7 @@ void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMEr // The caller is expected to call ReleaseDataReadPointer on the returned pointer once done. // TODO: make channel aware size_t dataSize = 0; - const unsigned char* slotPtr = v2Buffer_->PopNextDataReadPointer(&dataSize, md, false); + const unsigned char* slotPtr = v2Buffer_->PopNextDataReadPointer(md, &dataSize, false); if (slotPtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); return const_cast(slotPtr); diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 69cf38cef..0c61ba83f 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -63,9 +63,13 @@ Metadata Handling: #include #include #include +#include - - +// New internal header that precedes every slot's data. +struct BufferSlotRecord { + size_t imageSize; + size_t metadataSize; +}; /////////////////////////////////////////////////////////////////////////////// // BufferSlot Implementation @@ -241,7 +245,7 @@ DataBuffer::~DataBuffer() { int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { // Convert MB to bytes (1 MB = 1048576 bytes) size_t numBytes = static_cast(memorySizeMB) * (1ULL << 20); - buffer_ = new char[numBytes]; + buffer_ = new unsigned char[numBytes]; bufferSize_ = numBytes; overflow_ = false; return DEVICE_OK; @@ -263,57 +267,67 @@ int DataBuffer::ReleaseBuffer() { } /** - * Copy data into the next available slot in the buffer. - * - * Returns the size of the copied data through dataSize. - * all required metadata for interpreting its image data is there. - * Note: this can be implemented in terms of Get/Release slot + memcopy. - * - * @param caller The device calling this function. - * @param data The data to be copied. - * @param dataSize Size of the data to copy. - * @param serializedMetadata The associated metadata. - * @return Error code (0 on success). + * Pack the data as [BufferSlotRecord][image data][serialized metadata] */ -int DataBuffer::InsertData(const void* data, - size_t dataSize, const Metadata* pMd) { - // Get a write slot of the required size - void* slotPointer = nullptr; - int result = GetDataWriteSlot(dataSize, &slotPointer); +int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Metadata* pMd) { + size_t metaSize = 0; + std::string metaStr; + if (pMd) { + metaStr = pMd->Serialize(); + metaSize = metaStr.size(); + } + // Total size is header + image data + metadata + size_t totalSize = sizeof(BufferSlotRecord) + dataSize + metaSize; + unsigned char* imageDataPointer = nullptr; + // TOFO: handle metadata pointer + int result = GetDataWriteSlot(totalSize, metaSize, &imageDataPointer, nullptr); if (result != DEVICE_OK) return result; - // Copy the data into the slot - std::memcpy(slotPointer, data, dataSize); - if (pMd) { - // md.Serialize().c_str() - // TODO: Need a metadata lock and perhaps a map from buffer offset to metadata? + // The externally returned imageDataPointer points to the image data. + // Write out the header by subtracting the header size. + BufferSlotRecord* headerPointer = reinterpret_cast(imageDataPointer - sizeof(BufferSlotRecord)); + headerPointer->imageSize = dataSize; + headerPointer->metadataSize = metaSize; + + // Copy the image data into the allocated slot (imageDataPointer is already at the image data). + std::memcpy(imageDataPointer, data, dataSize); + + // If metadata is available, copy it right after the image data. + if (metaSize > 0) { + unsigned char* metaPtr = imageDataPointer + dataSize; + std::memcpy(metaPtr, metaStr.data(), metaSize); } // Release the write slot - return ReleaseDataWriteSlot(&slotPointer); + return ReleaseDataWriteSlot(&imageDataPointer, metaSize > 0 ? static_cast(metaSize) : -1); } /** - * Copy the next available data and metadata from the buffer - * @param dataDestination Destination buffer to copy data into - * @param dataSize Returns the size of the copied data, or 0 if no data available - * @param md Metadata object to populate - * @param waitForData If true, block until data becomes available - * @return Error code (0 on success) + * Reads the header from the slot, then copies the image data into the destination and + * uses the metadata blob (if any) to populate 'md'. */ -int DataBuffer::CopyNextDataAndMetadata(void* dataDestination, - size_t* dataSize, Metadata &md, bool waitForData) { - void* sourcePtr = nullptr; - int result = PopNextDataReadPointer(&sourcePtr, dataSize, md, waitForData); - if (result != DEVICE_OK) - return result; - - // Copy the data from the slot into the user's destination buffer - std::memcpy(dataDestination, sourcePtr, *dataSize); - - // Release the read pointer (this will handle cleanup and index management) - return ReleaseDataReadPointer(&sourcePtr); +int DataBuffer::CopyNextDataAndMetadata(unsigned char* dataDestination, size_t* imageDataSize, Metadata &md, bool waitForData) { + const unsigned char* imageDataPointer = PopNextDataReadPointer(md, imageDataSize, waitForData); + if (imageDataPointer == nullptr) + return DEVICE_ERR; + + const BufferSlotRecord* headerPointer = reinterpret_cast(imageDataPointer - sizeof(BufferSlotRecord)); + *imageDataSize = headerPointer->imageSize; + // imageDataPointer already points to the image data. + std::memcpy(dataDestination, imageDataPointer, headerPointer->imageSize); + + // Extract the metadata (if any) following the image data. + std::string metaStr; + if (headerPointer->metadataSize > 0) { + const char* metaDataStart = reinterpret_cast(imageDataPointer + headerPointer->imageSize); + metaStr.assign(metaDataStart, headerPointer->metadataSize); + } + // Restore the metadata + // This is analogous to what is done in FrameBuffer.cpp: + md.Restore(metaStr.c_str()); + + return ReleaseDataReadPointer(&imageDataPointer); } /** @@ -341,8 +355,8 @@ int DataBuffer::SetOverwriteData(bool overwrite) { * * The caller must release the slot using ReleaseDataSlot after writing is complete. */ -int DataBuffer::GetDataWriteSlot(size_t slotSize, void** slotPointer) { - // AllocateNextSlot allocates a slot for writing new data of variable size. +int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, const unsigned char** imageDataPointer, const unsigned char** metadataPointer) { + // AllocateNextSlot allocates a slot for writing new data of variable size. // // First, it checks if there is a recently released slot start (from releasedSlots_). // For each candidate, it uses the activeSlotMap_ mechanism to @@ -352,9 +366,11 @@ int DataBuffer::GetDataWriteSlot(size_t slotSize, void** slotPointer) { // // If no released slot fits, then it falls back to using nextAllocOffset_ and similar // collision checks. In overwrite mode, wrap-around is supported. + // Lock to ensure exclusive allocation. + std::lock_guard lock(slotManagementMutex_); - // Ensure that only one thread can allocate a slot at a time. - std::lock_guard lock(slotManagementMutex_); + // Total slot size is the header plus the image and metadata lengths. + size_t totalSlotSize = sizeof(BufferSlotRecord) + imageSize + metadataSize; // First, try using a released slot candidate (FILO order) for (int i = static_cast(releasedSlots_.size()) - 1; i >= 0; i--) { @@ -364,26 +380,25 @@ int DataBuffer::GetDataWriteSlot(size_t slotSize, void** slotPointer) { // Find first slot at or after our candidate position auto nextIt = activeSlotsByStart_.lower_bound(localCandidate); - // If there's a previous slot, ensure we don't overlap with it + // If a previous slot exists, adjust to avoid overlap. if (nextIt != activeSlotsByStart_.begin()) { size_t prevSlotEnd = std::prev(nextIt)->first + std::prev(nextIt)->second->GetLength(); // If our candidate region [candidateStart, candidateStart+slotSize) overlaps the previous slot, // bump candidateStart to the end of the conflicting slot and try again. if (prevSlotEnd > localCandidate) { localCandidate = prevSlotEnd; - } + } } // Check if there's space before the next active slot nextIt = activeSlotsByStart_.lower_bound(localCandidate); bool candidateValid = true; - if (nextIt != activeSlotsByStart_.end()) { // Case 1: There is a next slot // Check if our proposed slot would overlap with the next slot - candidateValid = (localCandidate + slotSize <= nextIt->first); - } else if (localCandidate + slotSize > bufferSize_) { - // Case 2: No next slot, but we'd exceed buffer size + candidateValid = (localCandidate + totalSlotSize <= nextIt->first); + } else if (localCandidate + totalSlotSize > bufferSize_) { + // Case 2: No next slot, but we'd exceed buffer size if (!overwriteWhenFull_) { candidateValid = false; } else { @@ -402,7 +417,7 @@ int DataBuffer::GetDataWriteSlot(size_t slotSize, void** slotPointer) { // Check if wrapped position would overlap with first slot if (nextIt != activeSlotsByStart_.end()) { - candidateValid = (localCandidate + slotSize <= nextIt->first); + candidateValid = (localCandidate + totalSlotSize <= nextIt->first); } } } @@ -410,27 +425,30 @@ int DataBuffer::GetDataWriteSlot(size_t slotSize, void** slotPointer) { if (candidateValid) { // Remove the candidate from releasedSlots_ (it was taken from the "back" if available). releasedSlots_.erase(releasedSlots_.begin() + i); - activeSlotsVector_.push_back(BufferSlot(localCandidate, slotSize)); - BufferSlot* slot = &activeSlotsVector_.back(); + activeSlotsVector_.push_back(std::make_unique(localCandidate, totalSlotSize)); + BufferSlot* slot = activeSlotsVector_.back().get(); activeSlotsByStart_[localCandidate] = slot; - *slotPointer = buffer_ + slot->GetStart(); + *imageDataPointer = buffer_ + slot->GetStart() + sizeof(BufferSlotRecord); + *metadataPointer = *imageDataPointer + imageSize; return DEVICE_OK; } } - // If no released candidate fits, use nextAllocOffset_ fallback. + + // If no released candidate fits, fall back to nextAllocOffset_. size_t candidateStart = nextAllocOffset_; - if (candidateStart + slotSize > bufferSize_) { + if (candidateStart + totalSlotSize > bufferSize_) { // Not enough space in the buffer: if we are not allowed to overwrite then set our overflow flag. if (!overwriteWhenFull_) { - overflow_ = true; // <-- mark that overflow has happened - *slotPointer = nullptr; + overflow_ = true; + *imageDataPointer = nullptr; + *metadataPointer = nullptr; return DEVICE_ERR; } candidateStart = 0; // Reset to start of buffer // Since we're starting at position 0, remove any slots that start before our requested size auto it = activeSlotsByStart_.begin(); - while (it != activeSlotsByStart_.end() && it->first < slotSize) { + while (it != activeSlotsByStart_.end() && it->first < totalSlotSize) { BufferSlot* slot = it->second; if (!slot->IsAvailableForWriting() || !slot->IsAvailableForReading()) { throw std::runtime_error("Cannot overwrite slot that is currently being accessed (has active readers or writers)"); @@ -439,26 +457,23 @@ int DataBuffer::GetDataWriteSlot(size_t slotSize, void** slotPointer) { // Remove from both tracking structures activeSlotsVector_.erase( std::remove_if(activeSlotsVector_.begin(), activeSlotsVector_.end(), - [targetStart = it->first](const BufferSlot& slot) { - return slot.GetStart() == targetStart; - }), + [targetStart = it->first](const std::unique_ptr& slot) { + return slot->GetStart() == targetStart; + }), activeSlotsVector_.end()); it = activeSlotsByStart_.erase(it); } } - // Create and track the new slot - activeSlotsVector_.push_back(BufferSlot(candidateStart, slotSize)); - BufferSlot* newSlot = &activeSlotsVector_.back(); + activeSlotsVector_.push_back(std::make_unique(candidateStart, totalSlotSize)); + BufferSlot* newSlot = activeSlotsVector_.back().get(); activeSlotsByStart_[candidateStart] = newSlot; - - // Update nextAllocOffset_ for next allocation - nextAllocOffset_ = candidateStart + slotSize; + nextAllocOffset_ = candidateStart + totalSlotSize; if (nextAllocOffset_ >= bufferSize_) { nextAllocOffset_ = 0; } - - *slotPointer = buffer_ + newSlot->GetStart(); + *imageDataPointer = buffer_ + newSlot->GetStart() + sizeof(BufferSlotRecord); + *metadataPointer = *imageDataPointer + imageSize; return DEVICE_OK; } @@ -469,30 +484,39 @@ int DataBuffer::GetDataWriteSlot(size_t slotSize, void** slotPointer) { * @param buffer The buffer to be released. * @return Error code (0 on success). */ -int DataBuffer::ReleaseDataWriteSlot(void** slotPointer) { - if (slotPointer == nullptr || *slotPointer == nullptr) +int DataBuffer::ReleaseDataWriteSlot(unsigned char** imageDataPointer, int actualMetadataBytes) { + if (imageDataPointer == nullptr || *imageDataPointer == nullptr) return DEVICE_ERR; std::lock_guard lock(slotManagementMutex_); - // Calculate the offset from the buffer start to find the corresponding slot - char* ptr = static_cast(*slotPointer); - size_t offset = ptr - buffer_; + // Convert the externally provided imageDataPointer (which points to the image data) + // to the true slot start (header) by subtracting sizeof(BufferSlotRecord). + unsigned char* headerPointer = *imageDataPointer - sizeof(BufferSlotRecord); + size_t offset = headerPointer - buffer_; - // Find the slot in activeSlotsByStart_ + // Locate the slot using the true header offset. auto it = activeSlotsByStart_.find(offset); - if (it == activeSlotsByStart_.end()) { + if (it == activeSlotsByStart_.end()) return DEVICE_ERR; // Slot not found - } // Release the write access BufferSlot* slot = it->second; slot->ReleaseWriteAccess(); - // Clear the pointer - *slotPointer = nullptr; + // If a valid actual metadata byte count is provided (i.e. not -1), + // update the header->metadataSize to the actual metadata length if it is less. + if (actualMetadataBytes != -1) { + BufferSlotRecord* hdr = reinterpret_cast(headerPointer); + if (static_cast(actualMetadataBytes) < hdr->metadataSize) { + hdr->metadataSize = actualMetadataBytes; + } + } - // Notify any waiting readers that new data is available + // Clear the externally provided image data pointer. + *imageDataPointer = nullptr; + + // Notify any waiting threads that new data is available. dataCV_.notify_all(); return DEVICE_OK; @@ -501,87 +525,101 @@ int DataBuffer::ReleaseDataWriteSlot(void** slotPointer) { /** * ReleaseSlot is called after a slot's content has been fully read. - * It assumes the caller has already released its read access (the slot is free). * * This implementation pushes only the start of the released slot onto the FILO * (releasedSlots_) and removes the slot from the active slot map and activeSlots_. */ -int DataBuffer::ReleaseDataReadPointer(void** slotPointer) { - if (slotPointer == nullptr || *slotPointer == nullptr) +int DataBuffer::ReleaseDataReadPointer(const unsigned char** imageDataPointer) { + if (imageDataPointer == nullptr || *imageDataPointer == nullptr) return DEVICE_ERR; std::unique_lock lock(slotManagementMutex_); - - // Compute the slot's start offset. - char* ptr = static_cast(*slotPointer); - size_t offset = ptr - buffer_; - // Find the slot in activeSlotMap_. + // Compute the header pointer by subtracting the header size. + const unsigned char* headerPointer = *imageDataPointer - sizeof(BufferSlotRecord); + size_t offset = headerPointer - buffer_; + + // Find the slot in activeSlotsByStart_ auto it = activeSlotsByStart_.find(offset); if (it != activeSlotsByStart_.end()) { BufferSlot* slot = it->second; - // Check if the slot is being accessed by any readers or writers. - if (!slot->IsAvailableForWriting() || !slot->IsAvailableForReading()) { - // TODO: right way to handle exceptions? - throw std::runtime_error("Cannot release slot that is currently being accessed"); - } - - // If we've reached max size, remove the oldest element (front of vector). - if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) { - releasedSlots_.erase(releasedSlots_.begin()); - } - releasedSlots_.push_back(offset); + // Release the previously acquired read access. + slot->ReleaseReadAccess(); - // Remove slot from active structures. - activeSlotsByStart_.erase(it); - for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { - if (vecIt->GetStart() == offset) { - // Determine the index being removed. - size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); - activeSlotsVector_.erase(vecIt); - // Adjust currentSlotIndex_: - // If the deleted slot was before the current index, decrement it. - if (currentSlotIndex_ > indexDeleted) - currentSlotIndex_--; - - break; + // Now check if the slot is not being accessed + // (i.e. this was the last/readers and no writer holds it) + if (slot->IsAvailableForWriting() && slot->IsAvailableForReading()) { + // Ensure we do not exceed the maximum number of released slots. + if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) + releasedSlots_.erase(releasedSlots_.begin()); + releasedSlots_.push_back(offset); + + // Remove slot from the active tracking structures. + activeSlotsByStart_.erase(it); + for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { + if (vecIt->get()->GetStart() == offset) { + // Determine the index being removed. + size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); + activeSlotsVector_.erase(vecIt); + // Adjust currentSlotIndex_: + // If the deleted slot was before the current index, decrement it. + if (currentSlotIndex_ > indexDeleted) + currentSlotIndex_--; + break; + } } } + } else { + throw std::runtime_error("Cannot release slot that is not in the buffer."); } - *slotPointer = nullptr; + *imageDataPointer = nullptr; return DEVICE_OK; } -const unsigned char* DataBuffer::PopNextDataReadPointer(size_t* dataSize, Metadata &md, bool waitForData) { - while (true) { - std::unique_lock lock(slotManagementMutex_); - - // If data is available, process it. - if (!activeSlotsVector_.empty() && currentSlotIndex_ < activeSlotsVector_.size()) { - BufferSlot& currentSlot = activeSlotsVector_[currentSlotIndex_]; - - if (!currentSlot.AcquireReadAccess()) { - throw std::runtime_error("Failed to acquire read access for the current slot."); - } - - const unsigned char* slotPointer = reinterpret_cast(buffer_ + currentSlot.GetStart()); - if (dataSize != nullptr) { - *dataSize = currentSlot.GetLength(); - } - - currentSlotIndex_++; - return slotPointer; - } - - // No data available. - if (!waitForData) { - throw std::runtime_error("No data available to read."); - } - - // Wait for notification of new data. +const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData) +{ + std::unique_lock lock(slotManagementMutex_); + + // Wait until there is data available if requested. + // (Here we check whether activeSlotsVector_ has an unread slot. + // Adjust the condition as appropriate for your implementation.) + while (activeSlotsVector_.empty()) { + if (!waitForData) + return nullptr; dataCV_.wait(lock); - // When we wake up, the while loop will check again if data is available } + + // Assume that the next unread slot is at index currentSlotIndex_. + // (Depending on your data structure you might pop from a deque or update an iterator.) + BufferSlot* slot = activeSlotsVector_[currentSlotIndex_].get(); + // Get the starting offset for this slot. + size_t slotStart = slot->GetStart(); + + // The header is stored at the beginning of the slot. + const BufferSlotRecord* header = reinterpret_cast(buffer_ + slotStart); + + // The image data region starts right after the header. + const unsigned char* imageDataPointer = buffer_ + slotStart + sizeof(BufferSlotRecord); + + // Set the output image data size from the header. + *imageDataSize = header->imageSize; + + // Populate the metadata. + if (header->metadataSize > 0) { + const char* metaDataStart = reinterpret_cast(imageDataPointer + header->imageSize); + // Assuming Metadata::Restore takes a null-terminated string or similar. + md.Restore(metaDataStart); + } else { + // If no metadata is available, clear the metadata object. + md.Clear(); + } + + // Mark that this slot has been consumed. + // For this example, we simply move to the next slot. + currentSlotIndex_ = (currentSlotIndex_ + 1) % activeSlotsVector_.size(); + + // Unlock and return the pointer to the image data region. + return imageDataPointer; } unsigned int DataBuffer::GetMemorySizeMB() const { @@ -589,7 +627,7 @@ unsigned int DataBuffer::GetMemorySizeMB() const { return static_cast(bufferSize_ >> 20); } -int DataBuffer::PeekNextDataReadPointer(void** slotPointer, size_t* dataSize, +int DataBuffer::PeekNextDataReadPointer(const unsigned char** imageDataPointer, size_t* imageDataSize, Metadata &md) { // Immediately check if there is an unread slot without waiting. std::unique_lock lock(slotManagementMutex_); @@ -598,34 +636,61 @@ int DataBuffer::PeekNextDataReadPointer(void** slotPointer, size_t* dataSize, } // Obtain the next available slot *without* advancing currentSlotIndex_. - BufferSlot& currentSlot = activeSlotsVector_[currentSlotIndex_]; + BufferSlot& currentSlot = *activeSlotsVector_[currentSlotIndex_]; if (!currentSlot.AcquireReadAccess()) return DEVICE_ERR; - *slotPointer = buffer_ + currentSlot.GetStart(); - *dataSize = currentSlot.GetLength(); - // (If metadata is stored per slot, populate md here.) + *imageDataPointer = buffer_ + currentSlot.GetStart() + sizeof(BufferSlotRecord); + const BufferSlotRecord* headerPointer = reinterpret_cast( (*imageDataPointer) - sizeof(BufferSlotRecord) ); + *imageDataSize = headerPointer->imageSize; + + // Populate the Metadata object from the stored metadata blob. + std::string metaStr; + if (headerPointer->metadataSize > 0) { + const char* metaDataStart = reinterpret_cast(*imageDataPointer + headerPointer->imageSize); + metaStr.assign(metaDataStart, headerPointer->metadataSize); + } + // Restore the metadata + // This is analogous to what is done in FrameBuffer.cpp: + md.Restore(metaStr.c_str()); + return DEVICE_OK; } -const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* dataSize, Metadata &md) { - +const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* imageDataSize, Metadata &md) { std::unique_lock lock(slotManagementMutex_); if (activeSlotsVector_.empty() || (currentSlotIndex_ + n) >= activeSlotsVector_.size()) { throw std::runtime_error("Not enough unread data available."); } - BufferSlot& slot = activeSlotsVector_[currentSlotIndex_ + n]; + // Access the nth slot (without advancing the read index) + BufferSlot& slot = *activeSlotsVector_[currentSlotIndex_ + n]; if (!slot.AcquireReadAccess()) throw std::runtime_error("Failed to acquire read access for the selected slot."); - // Assign the size from the slot. - if (dataSize != nullptr) { - *dataSize = slot.GetLength(); + // Obtain the pointer to the image data (skip the header) + const unsigned char* imageDataPointer = buffer_ + slot.GetStart() + sizeof(BufferSlotRecord); + const BufferSlotRecord* headerPointer = reinterpret_cast(imageDataPointer - sizeof(BufferSlotRecord)); + + // Return the image size via the pointer parameter + if (imageDataSize != nullptr) { + *imageDataSize = headerPointer->imageSize; + } + + // Retrieve the serialized metadata from the slot. + std::string metaStr; + if (headerPointer->metadataSize > 0) { + const char* metaDataStart = reinterpret_cast(imageDataPointer + headerPointer->imageSize); + metaStr.assign(metaDataStart, headerPointer->metadataSize); } - // Return a constant pointer to the data. - return reinterpret_cast(buffer_ + slot.GetStart()); + // Restore the metadata + // This is analogous to what is done in FrameBuffer.cpp: + // metadata_.Restore(md.Serialize().c_str()); + md.Restore(metaStr.c_str()); + + // Return a pointer to the image data only. + return imageDataPointer; } /** @@ -635,15 +700,13 @@ const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* da * overwriteWhenFull_ flag is true and the caller wants to release the * peeked slot for reuse. */ - -int DataBuffer::ReleasePeekDataReadPointer(void** slotPointer) { - if (slotPointer == nullptr || *slotPointer == nullptr) +int DataBuffer::ReleasePeekDataReadPointer(const unsigned char** imageDataPointer) { + if (imageDataPointer == nullptr || *imageDataPointer == nullptr) return DEVICE_ERR; - std::lock_guard lock(slotManagementMutex_); - char* ptr = static_cast(*slotPointer); - size_t offset = ptr - buffer_; + const unsigned char* headerPointer = *imageDataPointer - sizeof(BufferSlotRecord); + size_t offset = headerPointer - buffer_; // Look up the corresponding slot by its buffer offset. auto it = activeSlotsByStart_.find(offset); @@ -654,7 +717,7 @@ int DataBuffer::ReleasePeekDataReadPointer(void** slotPointer) { // Release the read access (this does NOT remove the slot from the active list) slot->ReleaseReadAccess(); - *slotPointer = nullptr; + *imageDataPointer = nullptr; return DEVICE_OK; } @@ -667,7 +730,7 @@ size_t DataBuffer::GetOccupiedMemory() const { std::lock_guard lock(slotManagementMutex_); size_t usedMemory = 0; for (const auto& slot : activeSlotsVector_) { - usedMemory += slot.GetLength(); + usedMemory += slot->GetLength(); } return usedMemory; } @@ -677,7 +740,7 @@ size_t DataBuffer::GetFreeMemory() const { // Free memory is the total buffer size minus the sum of all occupied memory. size_t usedMemory = 0; for (const auto& slot : activeSlotsVector_) { - usedMemory += slot.GetLength(); + usedMemory += slot->GetLength(); } return (bufferSize_ > usedMemory) ? (bufferSize_ - usedMemory) : 0; } @@ -700,8 +763,8 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { std::lock_guard lock(slotManagementMutex_); // Check that there are no outstanding readers or writers. - for (const BufferSlot &slot : activeSlotsVector_) { - if (!slot.IsAvailableForReading() || !slot.IsAvailableForWriting()) { + for (const std::unique_ptr& slot : activeSlotsVector_) { + if (!slot->IsAvailableForReading() || !slot->IsAvailableForWriting()) { throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); } } @@ -726,12 +789,3 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { return DEVICE_OK; } - -long DataBuffer::GetRemainingImageCount() const { - std::lock_guard lock(slotManagementMutex_); - // Return the number of unread slots. - // currentSlotIndex_ tracks the next slot to read, - // so unread count is the total slots minus this index. - return (activeSlotsVector_.size() > currentSlotIndex_) ? - static_cast(activeSlotsVector_.size() - currentSlotIndex_) : 0; -} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index b24bea5ef..b00077ede 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -41,6 +41,7 @@ #include #include #include +#include /** * BufferSlot represents a contiguous slot in the DataBuffer that holds image @@ -169,8 +170,13 @@ class BufferSlot { * 1. Copy-based access. * 2. Direct pointer access with an explicit release. * - * Reference counting is used to ensure that memory is managed safely. A slot - * is recycled when all references (readers and writers) have been released. + * Each slot begins with a header (BufferSlotRecord) that stores: + * - The image data length + * - The serialized metadata length (which might be zero) + * + * The user-visible routines (e.g. InsertData and CopyNextDataAndMetadata) + * automatically pack and unpack the header so that the caller need not worry + * about the extra bytes. */ class DataBuffer { public: @@ -208,186 +214,198 @@ class DataBuffer { int ReleaseBuffer(); /** - * Copies data into the next available slot in the buffer along with its metadata. - * The copy-based approach is implemented using a slot acquisition, memory copy, and then - * slot release. + * Inserts data into the next available slot. + * The data is stored together with its metadata and is arranged as: + * [BufferSlotRecord header][image data][serialized metadata] * - * @param data Pointer to the data to be inserted. - * @param dataSize The size of data (in bytes) being inserted. - * @param pMd Pointer to the metadata associated with the data. + * @param data Pointer to the raw image data. + * @param dataSize The image data byte count. + * @param pMd Pointer to the metadata. If null, no metadata is stored. * @return DEVICE_OK on success. */ - int InsertData(const void* data, size_t dataSize, const Metadata* pMd); + int InsertData(const unsigned char* data, size_t dataSize, const Metadata* pMd); /** - * Copies data and metadata from the next available slot in the buffer into the provided destination. + * Copies data and metadata from the next available slot in the buffer. + * The routine examines the header to determine the image byte count + * and the length of the stored metadata. * - * @param dataDestination Destination buffer into which data will be copied. - * @param dataSize On success, returns the size of the copied data. - * @param md Metadata object to be populated with the data's metadata. + * @param dataDestination Destination buffer where image data is copied. + * @param imageDataSize On success, returns the image data size (in bytes). + * @param md Metadata object to be populated (via deserialization of the stored blob). * @param waitForData If true, block until data becomes available. * @return DEVICE_OK on success. */ - int CopyNextDataAndMetadata(void* dataDestination, size_t* dataSize, Metadata &md, bool waitForData); + int CopyNextDataAndMetadata(unsigned char* dataDestination, size_t* imageDataSize, Metadata &md, bool waitForData); /** - * Sets whether the buffer should overwrite old data when it is full. - * If true, the buffer will recycle the oldest slot when no free slot is available; - * if false, an error is returned when writing new data fails due to a full buffer. + * Sets whether the buffer should overwrite older data when full. * - * @param overwrite True to enable overwriting, false to disable. + * @param overwrite True to enable overwriting, false otherwise. * @return DEVICE_OK on success. */ int SetOverwriteData(bool overwrite); /** - * Acquires a pointer to a free slot in the buffer for writing purposes. - * The caller must later call ReleaseDataWriteSlot after finishing writing. + * Acquires a write slot large enough to hold the image data and metadata. + * On success, provides two pointers: one to the image data region and one to the metadata region. + * + * The metadataSize parameter specifies the maximum size to reserve for metadata if the exact + * size is not known at call time. When the slot is released, the metadata will be automatically + * null-terminated at its actual length, which must not exceed the reserved size. * - * @param slotSize The required size of the write slot. - * @param slotPointer On success, receives a pointer within the buffer where data can be written. + * @param imageSize The number of bytes allocated for image data. + * @param metadataSize The maximum number of bytes to reserve for metadata. + * @param imageDataPointer On success, receives a pointer to the image data region. + * @param metadataPointer On success, receives a pointer to the metadata region. * @return DEVICE_OK on success. */ - int GetDataWriteSlot(size_t slotSize, void** slotPointer); + int GetDataWriteSlot(size_t imageSize, size_t metadataSize, + const unsigned char** imageDataPointer, + const unsigned char** metadataPointer); /** - * Releases the write slot after data writing is complete. - * This clears the write lock and notifies any waiting reader threads. + * Releases a write slot after data has been written. * - * @param slotPointer Pointer previously obtained from GetDataWriteSlot. + * @param imageDataPointer Pointer previously obtained from GetDataWriteSlot. + * This pointer references the start of the image data region. + * @param actualMetadataBytes Optionally, the actual number of metadata bytes written. + * If provided and less than the maximum metadata size reserved, this value + * is used to update the header's metadataSize field. + * Defaults to -1, which means no update is performed. * @return DEVICE_OK on success. */ - int ReleaseDataWriteSlot(void** slotPointer); + int ReleaseDataWriteSlot(unsigned char** imageDataPointer, int actualMetadataBytes = -1); /** - * Releases read access on a data slot after its contents have been completely read. - * This makes the slot available for recycling. + * Releases read access for the image data region after its content has been read. * - * @param slotPointer Pointer previously obtained from GetNextDataReadPointer. + * @param imageDataPointer Pointer previously obtained from reading routines. * @return DEVICE_OK on success. */ - int ReleaseDataReadPointer(void** slotPointer); + int ReleaseDataReadPointer(const unsigned char** imageDataPointer); /** - * Retrieves and removes (consumes) the next available data slot for reading. - * This method advances the internal reading index. + * Retrieves and removes (consumes) the next available data entry for reading, + * and populates the provided Metadata object with the associated metadata. + * The returned pointer points to the beginning of the image data region, + * immediately after the header. * - * @param dataSize On success, returns the size of the data. - * @param md Associated metadata for the data. + * @param md Metadata object to be populated from the stored blob. + * @param imageDataSize On success, returns the image data size (in bytes). * @param waitForData If true, block until data becomes available. - * @return Pointer to the next available data in the buffer. + * @return Pointer to the start of the image data region, or nullptr if none available. */ - const unsigned char* PopNextDataReadPointer(size_t* dataSize, Metadata &md, bool waitForData); - + const unsigned char* PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData); /** - * Peeks at the next unread data slot without consuming it. - * The slot remains available for subsequent acquisitions. + * Peeks at the next unread data entry without consuming it. + * The header is examined so that the actual image data size (excluding header) + * is returned. * - * @param slotPointer On success, receives the pointer to the data. - * @param dataSize On success, returns the size of the data. - * @param md Associated metadata for the data. - * @return DEVICE_OK on success, or an error code if no data is available. + * @param imageDataPointer On success, receives a pointer to the image data region. + * @param imageDataSize On success, returns the image data size (in bytes). + * @param md Metadata object populated from the stored metadata blob. + * @return DEVICE_OK on success, error code otherwise. */ - int PeekNextDataReadPointer(void** slotPointer, size_t* dataSize, Metadata &md); + int PeekNextDataReadPointer(const unsigned char** imageDataPointer, size_t* imageDataSize, Metadata &md); /** - * Peeks at the nth unread data slot without consuming it. + * Peeks at the nth unread data entry without consuming it. * (n = 0 is equivalent to PeekNextDataReadPointer.) * - * @param n The index of the unread slot. - * @param dataSize On success, returns the size of the data. - * @param md Associated metadata for the data. - * @return const pointer to the data. + * @param n The index of the data entry to peek at (0 is next available). + * @param imageDataSize On success, returns the image data size (in bytes). + * @param md Metadata object populated from the stored metadata blob. + * @return Pointer to the start of the image data region. */ - const unsigned char* PeekDataReadPointerAtIndex(size_t n, size_t* dataSize, Metadata &md); - + const unsigned char* PeekDataReadPointerAtIndex(size_t n, size_t* imageDataSize, Metadata &md); /** - * Releases the read access that was acquired by a peek operation. - * This method releases the temporary read access without consuming the slot. + * Releases read access that was acquired by a peek. * - * @param slotPointer Pointer previously obtained from a peek method. + * @param imageDataPointer Pointer previously obtained from a peek. * @return DEVICE_OK on success. */ - int ReleasePeekDataReadPointer(void** slotPointer); + int ReleasePeekDataReadPointer(const unsigned char** imageDataPointer); /** - * Returns the total memory size of the buffer in megabytes. + * Returns the total buffer memory size (in MB). * - * @return The buffer size in MB. + * @return Buffer size in MB. */ unsigned int GetMemorySizeMB() const; /** - * Returns the number of currently occupied slots in the buffer. + * Returns the number of occupied slots in the buffer. * - * @return The number of occupied slots. + * @return Occupied slot count. */ size_t GetOccupiedSlotCount() const; /** - * Returns the total occupied memory (in bytes) within the buffer. + * Returns the total occupied memory (in bytes). * - * @return The sum of the lengths of all active slots. + * @return Sum of active slot lengths. */ size_t GetOccupiedMemory() const; /** - * Returns the amount of free memory (in bytes) remaining in the buffer. + * Returns the amount of free memory (in bytes) remaining. * - * @return The number of free bytes available for new data. + * @return Free byte count. */ size_t GetFreeMemory() const; /** - * Returns whether the buffer has been overflowed (i.e. an attempt to - * allocate a write slot failed because there was no available space). + * Indicates whether a buffer overflow occurred (i.e. an insert failed because + * no appropriate slot was available). + * + * @return True if overflow has happened, false otherwise. */ bool Overflow() const; /** - * Returns the number of unread data slots in the buffer. + * Returns the number of unread slots in the buffer. * - * @return The number of unread data slots. + * @return Unread slot count. */ long GetRemainingImageCount() const; /** - * Reinitialize the DataBuffer by clearing all internal data structures, - * releasing the current buffer, and reallocating a new one. - * This method uses the existing slotManagementMutex_ to ensure thread safety. + * Reinitializes the DataBuffer by clearing its structures, releasing the current + * buffer, and allocating a new one. * - * @param memorySizeMB New size (in MB) for the buffer. + * @param memorySizeMB New buffer size (in MB). * @return DEVICE_OK on success. - * @throws std::runtime_error if any slot is still actively being read or written. + * @throws std::runtime_error if any slot is still actively in use. */ int ReinitializeBuffer(unsigned int memorySizeMB); private: - // Pointer to the allocated buffer memory. - char* buffer_; - // Total size (in bytes) of the allocated buffer. + // Pointer to the allocated block. + unsigned char* buffer_; + // Total allocated size in bytes. size_t bufferSize_; - // Whether the buffer should overwrite older data when full. + // Whether to overwrite old data when full. bool overwriteWhenFull_; - // New: overflow indicator (set to true if an insert fails because of buffer full) + // Overflow flag (set if insert fails due to full buffer). bool overflow_; - // Data structures for tracking active slot usage. - std::vector activeSlotsVector_; + // Data structures used to track active slots. + std::vector> activeSlotsVector_; std::map activeSlotsByStart_; std::vector releasedSlots_; - // The next available offset for a new data slot. + // Next free offset within the buffer. size_t nextAllocOffset_; - // Tracks the current slot index for read operations. + // Index tracking the next slot for read. size_t currentSlotIndex_; - // Synchronization primitives for managing slot access. + // Synchronization primitives for slot management. std::condition_variable dataCV_; mutable std::mutex slotManagementMutex_; }; From 0fd0823e2692888ed3e3576917ff498932b55030 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 5 Feb 2025 12:22:16 -0800 Subject: [PATCH 06/46] fix compiler warnings --- MMCore/BufferAdapter.cpp | 30 ++++++++++++++++-------------- MMCore/Buffer_v2.cpp | 10 ++++++---- MMCore/Buffer_v2.h | 6 ++---- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index ac12beb0a..6e8c3b4a3 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -94,7 +94,7 @@ bool BufferAdapter::Initialize(unsigned numChannels, unsigned width, unsigned he int ret = v2Buffer_->ReinitializeBuffer(v2Buffer_->GetMemorySizeMB()); if (ret != DEVICE_OK) return false; - } catch (const std::exception& ex) { + } catch (const std::exception&) { // Optionally log the exception return false; } @@ -146,7 +146,7 @@ long BufferAdapter::GetSize(long imageSize) const long BufferAdapter::GetFreeSize(long imageSize) const { if (useV2_) { - return v2Buffer_->GetFreeMemory() / imageSize; + return static_cast(v2Buffer_->GetFreeMemory()) / imageSize; } else { return circBuffer_->GetFreeSize(); } @@ -239,11 +239,13 @@ bool BufferAdapter::InsertMultiChannel(const unsigned char* buf, unsigned numCha if (useV2_) { // All the data needed to interpret the image is in the metadata // This function will copy data and metadata into the buffer - v2Buffer_->InsertData(buf, width * height * byteDepth *numChannels, &md); + int ret = v2Buffer_->InsertData(buf, width * height * byteDepth *numChannels, &md); + return ret == DEVICE_OK; } else { return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md); } + } void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) @@ -252,12 +254,12 @@ void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw // In v2, we use PeekNextDataReadPointer (which does not advance the internal pointer) // Note: the v2 buffer is not channel aware, so the 'channel' parameter is ignored. // TODO implement the channel aware version - unsigned char* slotPtr = nullptr; - size_t dataSize = 0; - int ret = v2Buffer_->PeekNextDataReadPointer(&slotPtr, &dataSize, md); - if (ret != DEVICE_OK || slotPtr == nullptr) + const unsigned char* ptr = nullptr; + size_t imageDataSize = 0; + int ret = v2Buffer_->PeekNextDataReadPointer(&ptr, &imageDataSize, md); + if (ret != DEVICE_OK || ptr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); - return slotPtr; + return const_cast(ptr); // TODO: make sure calling code releases the slot after use } else { const mm::ImgBuffer* pBuf = circBuffer_->GetTopImageBuffer(channel); @@ -274,11 +276,11 @@ void* BufferAdapter::GetNthImageMD(unsigned long n, Metadata& md) const throw (C { if (useV2_) { size_t dataSize = 0; - const unsigned char* slotPtr = v2Buffer_->PeekDataReadPointerAtIndex(n, &dataSize, md); - if (slotPtr == nullptr) + const unsigned char* ptr = v2Buffer_->PeekDataReadPointerAtIndex(n, &dataSize, md); + if (ptr == nullptr) throw CMMError("V2 buffer does not contain enough data.", MMERR_CircularBufferEmpty); // Return a non-const pointer (caller must be careful with the const_cast) - return const_cast(slotPtr); + return const_cast(ptr); // TODO: make sure calling code releases the slot after use } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNthFromTopImageBuffer(n); @@ -299,10 +301,10 @@ void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMEr // The caller is expected to call ReleaseDataReadPointer on the returned pointer once done. // TODO: make channel aware size_t dataSize = 0; - const unsigned char* slotPtr = v2Buffer_->PopNextDataReadPointer(md, &dataSize, false); - if (slotPtr == nullptr) + const unsigned char* ptr = v2Buffer_->PopNextDataReadPointer(md, &dataSize, false); + if (ptr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); - return const_cast(slotPtr); + return const_cast(ptr); // TODO: ensure that calling code releases the read pointer after use. } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNextImageBuffer(channel); diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 0c61ba83f..f0d492ac5 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -355,7 +355,7 @@ int DataBuffer::SetOverwriteData(bool overwrite) { * * The caller must release the slot using ReleaseDataSlot after writing is complete. */ -int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, const unsigned char** imageDataPointer, const unsigned char** metadataPointer) { +int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, unsigned char** imageDataPointer, unsigned char** metadataPointer) { // AllocateNextSlot allocates a slot for writing new data of variable size. // // First, it checks if there is a recently released slot start (from releasedSlots_). @@ -607,15 +607,13 @@ const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *im // Populate the metadata. if (header->metadataSize > 0) { const char* metaDataStart = reinterpret_cast(imageDataPointer + header->imageSize); - // Assuming Metadata::Restore takes a null-terminated string or similar. md.Restore(metaDataStart); } else { // If no metadata is available, clear the metadata object. md.Clear(); } - // Mark that this slot has been consumed. - // For this example, we simply move to the next slot. + // Consume this slot by advancing the index. currentSlotIndex_ = (currentSlotIndex_ + 1) % activeSlotsVector_.size(); // Unlock and return the pointer to the image data region. @@ -789,3 +787,7 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { return DEVICE_OK; } + +long DataBuffer::GetRemainingImageCount() const { + return static_cast(activeSlotsVector_.size()); +} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index b00077ede..993d99390 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -260,9 +260,7 @@ class DataBuffer { * @param metadataPointer On success, receives a pointer to the metadata region. * @return DEVICE_OK on success. */ - int GetDataWriteSlot(size_t imageSize, size_t metadataSize, - const unsigned char** imageDataPointer, - const unsigned char** metadataPointer); + int GetDataWriteSlot(size_t imageSize, size_t metadataSize, unsigned char** imageDataPointer, unsigned char** metadataPointer); /** * Releases a write slot after data has been written. @@ -296,7 +294,7 @@ class DataBuffer { * @param waitForData If true, block until data becomes available. * @return Pointer to the start of the image data region, or nullptr if none available. */ - const unsigned char* PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData); + const unsigned char* PopNextDataReadPointer(Metadata &md, size_t* imageDataSize, bool waitForData); /** * Peeks at the next unread data entry without consuming it. From 06fe9aed7b3258a2d748be5cd5a8c823119a42d5 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:45:27 -0800 Subject: [PATCH 07/46] cleanup --- MMCore/BufferAdapter.cpp | 33 +++++++++++++++++++++++++-------- MMCore/Buffer_v2.h | 2 +- MMCore/MMCore.cpp | 2 +- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index 6e8c3b4a3..c0afad55e 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -1,14 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: BufferAdapter.cpp +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +//----------------------------------------------------------------------------- +// DESCRIPTION: Generic implementation of a buffer for storing image data and +// metadata. Provides thread-safe access for reading and writing +// with configurable overflow behavior. +//// +// COPYRIGHT: Henry Pinkard, 2025 +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Henry Pinkard, 01/31/2025 + + #include "BufferAdapter.h" #include -// For demonstration, we assume DEVICE_OK and DEVICE_ERR macros are defined in MMCore.h or an included error header. -#ifndef DEVICE_OK - #define DEVICE_OK 0 -#endif -#ifndef DEVICE_ERR - #define DEVICE_ERR -1 -#endif - static std::string FormatLocalTime(std::chrono::time_point tp) { using namespace std::chrono; diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 993d99390..2c937f20e 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -294,7 +294,7 @@ class DataBuffer { * @param waitForData If true, block until data becomes available. * @return Pointer to the start of the image data region, or nullptr if none available. */ - const unsigned char* PopNextDataReadPointer(Metadata &md, size_t* imageDataSize, bool waitForData); + const unsigned char* PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData); /** * Peeks at the next unread data entry without consuming it. diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 150316540..c19765ee0 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -141,7 +141,7 @@ CMMCore::CMMCore() : pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), pPostedErrorsLock_(NULL), - useV2Buffer_(true) + useV2Buffer_(false) { configGroups_ = new ConfigGroupCollection(); pixelSizeGroup_ = new PixelSizeConfigGroup(); From e468f0fbb09df3bbf247281e990147992431e21d Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:46:14 -0800 Subject: [PATCH 08/46] cleanup --- MMCore/BufferAdapter.h | 25 +++++++++++++++++++++++++ MMCore/Buffer_v2.cpp | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index ca3627430..97aab6a9c 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -1,3 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: BufferAdapter.h +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +//----------------------------------------------------------------------------- +// DESCRIPTION: Generic implementation of a buffer for storing image data and +// metadata. Provides thread-safe access for reading and writing +// with configurable overflow behavior. +//// +// COPYRIGHT: Henry Pinkard, 2025 +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Henry Pinkard, 01/31/2025 + + #ifndef BUFFERADAPTER_H #define BUFFERADAPTER_H diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index f0d492ac5..3e45d3381 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -37,7 +37,7 @@ Buffer Structure: - Memory management through reference counting: - Writers get exclusive ownership during writes - Readers can get shared read-only access - - Slots are recycled when all references are released + - Slots are recycled when all references are released (In non-overwriting mode) Data Access: - Two access patterns supported: From 93860a923339d6c2c1decd69dd70594d2c3f6153 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:50:17 -0800 Subject: [PATCH 09/46] remove comment --- MMCore/MMCore.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 150316540..95cefcbf8 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -3197,7 +3197,6 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes sizeMB << " MB"; try { - // TODO: need to store a flag about which buffer to use bufferAdapter_ = new BufferAdapter(useV2Buffer_, sizeMB); } catch (std::bad_alloc& ex) From d87cc2e238e4d1c8d41d26ba62a4fe0ba0114612 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 6 Feb 2025 17:48:26 -0800 Subject: [PATCH 10/46] modify SWIG to read image size dynamically for each image --- MMCore/BufferAdapter.cpp | 87 ++++++++++++++++++++++++++- MMCore/BufferAdapter.h | 35 +++++++++++ MMCore/Buffer_v2.cpp | 123 +++++++++++++++++++++++++++++---------- MMCore/Buffer_v2.h | 71 ++++++++++++++-------- MMCore/MMCore.cpp | 87 +++++++++++++++++++-------- MMCore/MMCore.h | 17 ++++++ MMCoreJ_wrap/MMCoreJ.i | 35 +++++++++-- 7 files changed, 372 insertions(+), 83 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index c0afad55e..438785895 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -142,7 +142,7 @@ long BufferAdapter::GetRemainingImageCount() const void BufferAdapter::Clear() { if (useV2_) { - v2Buffer_->ReleaseBuffer(); + // v2Buffer_->ReleaseBuffer(); } else { circBuffer_->Clear(); } @@ -333,3 +333,88 @@ void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMEr } } } + +bool BufferAdapter::EnableV2Buffer(bool enable) { + // Don't do anything if we're already in the requested state + if (enable == useV2_) { + return true; + } + + // Create new buffer of requested type with same memory size + unsigned int memorySizeMB = GetMemorySizeMB(); + + try { + if (enable) { + // Switch to V2 buffer + DataBuffer* newBuffer = new DataBuffer(memorySizeMB); + delete circBuffer_; + circBuffer_ = nullptr; + v2Buffer_ = newBuffer; + v2Buffer_->ReinitializeBuffer(memorySizeMB); + } else { + // Switch to circular buffer + CircularBuffer* newBuffer = new CircularBuffer(memorySizeMB); + delete v2Buffer_; + v2Buffer_ = nullptr; + circBuffer_ = newBuffer; + // Require it to be initialized manually, which doesn't actually matter + // because it gets initialized before sequence acquisition starts anyway. + } + + useV2_ = enable; + Clear(); // Reset the new buffer + return true; + } catch (const std::exception&) { + // If allocation fails, keep the existing buffer + return false; + } +} + +bool BufferAdapter::IsUsingV2Buffer() const { + return useV2_; +} + +void BufferAdapter::ReleaseReadAccess(const unsigned char* ptr) { + if (useV2_ && ptr) { + v2Buffer_->ReleaseDataReadPointer(&ptr); + } +} + +unsigned BufferAdapter::GetImageWidth(const unsigned char* ptr) const { + if (!useV2_) throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); + return v2Buffer_->GetImageWidth(ptr); +} + +unsigned BufferAdapter::GetImageHeight(const unsigned char* ptr) const { + if (!useV2_) throw CMMError("GetImageHeight(ptr) only supported with V2 buffer"); + return v2Buffer_->GetImageHeight(ptr); +} + +unsigned BufferAdapter::GetBytesPerPixel(const unsigned char* ptr) const { + if (!useV2_) throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); + return v2Buffer_->GetBytesPerPixel(ptr); +} + +unsigned BufferAdapter::GetImageBitDepth(const unsigned char* ptr) const { + if (!useV2_) throw CMMError("GetImageBitDepth(ptr) only supported with V2 buffer"); + return v2Buffer_->GetImageBitDepth(ptr); +} + +unsigned BufferAdapter::GetNumberOfComponents(const unsigned char* ptr) const { + if (!useV2_) throw CMMError("GetNumberOfComponents(ptr) only supported with V2 buffer"); + return v2Buffer_->GetNumberOfComponents(ptr); +} + +long BufferAdapter::GetImageBufferSize(const unsigned char* ptr) const { + if (!useV2_) throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); + return v2Buffer_->GetImageBufferSize(ptr); +} + +bool BufferAdapter::SetOverwriteData(bool overwrite) { + if (useV2_) { + return v2Buffer_->SetOverwriteData(overwrite) == DEVICE_OK; + } else { + // CircularBuffer doesn't have this functionality + return false; + } +} diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index 97aab6a9c..f2352d0cc 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -47,6 +47,13 @@ class BufferAdapter { BufferAdapter(bool useV2Buffer, unsigned int memorySizeMB); ~BufferAdapter(); + /** + * Enable or disable v2 buffer usage. + * @param enable Set to true to use v2 buffer, false to use circular buffer. + * @return true if the switch was successful, false otherwise. + */ + bool EnableV2Buffer(bool enable); + /** * Get a pointer to the top (most recent) image. * @return Pointer to image data, or nullptr if unavailable. @@ -182,6 +189,34 @@ class BufferAdapter { void* GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError); void* PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError); + /** + * Check if this adapter is using the V2 buffer implementation. + * @return true if using V2 buffer, false if using circular buffer. + */ + bool IsUsingV2Buffer() const; + + /** + * Release a pointer obtained from the buffer. + * This is required when using the V2 buffer implementation. + * @param ptr The pointer to release. + */ + void ReleaseReadAccess(const unsigned char* ptr); + + // Methods for the v2 buffer where width and heigh must be gotton on a per-image basis + unsigned GetImageWidth(const unsigned char* ptr) const; + unsigned GetImageHeight(const unsigned char* ptr) const; + unsigned GetBytesPerPixel(const unsigned char* ptr) const; + unsigned GetImageBitDepth(const unsigned char* ptr) const; + unsigned GetNumberOfComponents(const unsigned char* ptr) const; + long GetImageBufferSize(const unsigned char* ptr) const; + + /** + * Configure whether to overwrite old data when buffer is full. + * @param overwrite If true, overwrite old data when buffer is full. + * @return true on success, false on error. + */ + bool SetOverwriteData(bool overwrite); + private: bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. CircularBuffer* circBuffer_; diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 3e45d3381..4e71c0629 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -106,30 +106,6 @@ std::size_t BufferSlot::GetLength() const { return length_; } -/** - * Sets a detail for this slot using the provided key and value. - * Typically used to store metadata information (e.g. width, height). - */ -void BufferSlot::SetDetail(const std::string &key, std::size_t value) { - details_[key] = value; -} - -/** - * Retrieves a previously set detail. - * Returns 0 if the key is not found. - */ -std::size_t BufferSlot::GetDetail(const std::string &key) const { - auto it = details_.find(key); - return (it != details_.end()) ? it->second : 0; -} - -/** - * Clears all additional details associated with this slot. - */ -void BufferSlot::ClearDetails() { - details_.clear(); -} - /** * Attempts to acquire exclusive write access. * This method first attempts to set the write flag atomically. @@ -147,7 +123,7 @@ bool BufferSlot::AcquireWriteAccess() { // Ensure no readers are active by checking the read counter. int expectedReaders = 0; if (!readAccessCountAtomicInt_.compare_exchange_strong(expectedReaders, 0, std::memory_order_acquire)) { - // Active readers are present; revert the write lock. + // Active readers are present; revert the write flag. writeAtomicBool_.store(false, std::memory_order_release); return false; } @@ -259,7 +235,6 @@ int DataBuffer::ReleaseBuffer() { if (buffer_ != nullptr) { delete[] buffer_; buffer_ = nullptr; - bufferSize_ = 0; return DEVICE_OK; } // TODO: Handle errors if other parts of the system still hold pointers. @@ -279,11 +254,12 @@ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Met // Total size is header + image data + metadata size_t totalSize = sizeof(BufferSlotRecord) + dataSize + metaSize; unsigned char* imageDataPointer = nullptr; - // TOFO: handle metadata pointer - int result = GetDataWriteSlot(totalSize, metaSize, &imageDataPointer, nullptr); + unsigned char* metadataPointer = nullptr; + int result = GetDataWriteSlot(totalSize, metaSize, &imageDataPointer, &metadataPointer); if (result != DEVICE_OK) return result; + // The externally returned imageDataPointer points to the image data. // Write out the header by subtracting the header size. BufferSlotRecord* headerPointer = reinterpret_cast(imageDataPointer - sizeof(BufferSlotRecord)); @@ -369,6 +345,10 @@ int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, unsigned // Lock to ensure exclusive allocation. std::lock_guard lock(slotManagementMutex_); + if (buffer_ == nullptr) { + return DEVICE_ERR; + } + // Total slot size is the header plus the image and metadata lengths. size_t totalSlotSize = sizeof(BufferSlotRecord) + imageSize + metadataSize; @@ -658,7 +638,7 @@ int DataBuffer::PeekNextDataReadPointer(const unsigned char** imageDataPointer, const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* imageDataSize, Metadata &md) { std::unique_lock lock(slotManagementMutex_); if (activeSlotsVector_.empty() || (currentSlotIndex_ + n) >= activeSlotsVector_.size()) { - throw std::runtime_error("Not enough unread data available."); + return nullptr; } // Access the nth slot (without advancing the read index) @@ -779,7 +759,6 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { if (buffer_ != nullptr) { delete[] buffer_; buffer_ = nullptr; - bufferSize_ = 0; } // Allocate a new buffer using the provided memory size. @@ -791,3 +770,87 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { long DataBuffer::GetRemainingImageCount() const { return static_cast(activeSlotsVector_.size()); } + +unsigned DataBuffer::GetImageWidth(const unsigned char* imageDataPtr) const { + const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); + if (header->metadataSize > 0) { + Metadata md; + md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); + return static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue().c_str())); + } + throw std::runtime_error("No metadata available for image width"); +} + +unsigned DataBuffer::GetImageHeight(const unsigned char* imageDataPtr) const { + const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); + if (header->metadataSize > 0) { + Metadata md; + md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); + return static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue().c_str())); + } + throw std::runtime_error("No metadata available for image height"); +} + +unsigned DataBuffer::GetBytesPerPixel(const unsigned char* imageDataPtr) const { + const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); + if (header->metadataSize > 0) { + Metadata md; + md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); + std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + if (pixelType == MM::g_Keyword_PixelType_GRAY8) + return 1; + else if (pixelType == MM::g_Keyword_PixelType_GRAY16) + return 2; + else if (pixelType == MM::g_Keyword_PixelType_GRAY32) + return 4; + else if (pixelType == MM::g_Keyword_PixelType_RGB32) + return 4; + else if (pixelType == MM::g_Keyword_PixelType_RGB64) + return 8; + } + throw std::runtime_error("No metadata available for bytes per pixel"); +} + + +unsigned DataBuffer::GetImageBitDepth(const unsigned char* imageDataPtr) const { + const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); + if (header->metadataSize > 0) { + Metadata md; + md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); + std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + if (pixelType == MM::g_Keyword_PixelType_GRAY8) + return 8; + else if (pixelType == MM::g_Keyword_PixelType_GRAY16) + return 16; + else if (pixelType == MM::g_Keyword_PixelType_GRAY32) + return 32; + else if (pixelType == MM::g_Keyword_PixelType_RGB32) + return 32; + else if (pixelType == MM::g_Keyword_PixelType_RGB64) + return 64; + } + throw std::runtime_error("No metadata available for bit depth"); +} + + +unsigned DataBuffer::GetNumberOfComponents(const unsigned char* imageDataPtr) const { + const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); + if (header->metadataSize > 0) { + Metadata md; + md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); + std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + if (pixelType == MM::g_Keyword_PixelType_GRAY8 || + pixelType == MM::g_Keyword_PixelType_GRAY16 || + pixelType == MM::g_Keyword_PixelType_GRAY32) + return 1; + else if (pixelType == MM::g_Keyword_PixelType_RGB32 || + pixelType == MM::g_Keyword_PixelType_RGB64) + return 4; + } + throw std::runtime_error("No metadata available for number of components"); +} + +long DataBuffer::GetImageBufferSize(const unsigned char* imageDataPtr) const { + const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); + return header->imageSize; +} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 2c937f20e..6a38a654c 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -79,28 +79,6 @@ class BufferSlot { */ std::size_t GetLength() const; - /** - * Stores a detail (for example, image width or height) associated with the slot. - * - * @param key The name of the detail. - * @param value The value of the detail. - */ - void SetDetail(const std::string &key, std::size_t value); - - /** - * Retrieves a previously stored detail. - * Returns 0 if the key is not found. - * - * @param key The detail key. - * @return The stored value or 0 if not found. - */ - std::size_t GetDetail(const std::string &key) const; - - /** - * Clears all stored details from the slot. - */ - void ClearDetails(); - /** * Attempts to acquire exclusive write access. * It first tries to atomically set the write flag. @@ -153,7 +131,6 @@ class BufferSlot { private: std::size_t start_; std::size_t length_; - std::map details_; std::atomic readAccessCountAtomicInt_; std::atomic writeAtomicBool_; mutable std::mutex writeCompleteConditionMutex_; @@ -380,6 +357,54 @@ class DataBuffer { */ int ReinitializeBuffer(unsigned int memorySizeMB); + /** + * Returns the image width from the metadata stored with the image data. + * + * @param imageDataPtr Pointer to the image data. + * @return Image width. + */ + unsigned GetImageWidth(const unsigned char* imageDataPtr) const; + + /** + * Returns the image height from the metadata stored with the image data. + * + * @param imageDataPtr Pointer to the image data. + * @return Image height. + */ + unsigned GetImageHeight(const unsigned char* imageDataPtr) const; + + /** + * Returns the bytes per pixel from the metadata stored with the image data. + * + * @param imageDataPtr Pointer to the image data. + * @return Bytes per pixel. + */ + unsigned GetBytesPerPixel(const unsigned char* imageDataPtr) const; + + /** + * Returns the image bit depth from the metadata stored with the image data. + * + * @param imageDataPtr Pointer to the image data. + * @return Image bit depth. + */ + unsigned GetImageBitDepth(const unsigned char* imageDataPtr) const; + + /** + * Returns the number of components in the image data from the metadata stored with the image data. + * + * @param imageDataPtr Pointer to the image data. + * @return Number of components. + */ + unsigned GetNumberOfComponents(const unsigned char* imageDataPtr) const; + + /** + * Returns the image buffer size from the metadata stored with the image data. + * + * @param imageDataPtr Pointer to the image data. + * @return Image buffer size. + */ + long GetImageBufferSize(const unsigned char* imageDataPtr) const; + private: // Pointer to the allocated block. unsigned char* buffer_; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 26919883a..d5ea9f5e4 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -141,7 +141,7 @@ CMMCore::CMMCore() : pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), pPostedErrorsLock_(NULL), - useV2Buffer_(false) + useV2Buffer_(true) { configGroups_ = new ConfigGroupCollection(); pixelSizeGroup_ = new PixelSizeConfigGroup(); @@ -2829,27 +2829,29 @@ void CMMCore::startSequenceAcquisition(long numImages, double intervalMs, bool s ,MMERR_NotAllowedDuringSequenceAcquisition); } - try - { - if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) - { - logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); - } - bufferAdapter_->Clear(); + try + { + if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + { + logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } + bufferAdapter_->Clear(); + // Disable overwriting for finite sequence acquisition + bufferAdapter_->SetOverwriteData(false); mm::DeviceModuleLockGuard guard(camera); LOG_DEBUG(coreLogger_) << "Will start sequence acquisition from default camera"; - int nRet = camera->StartSequenceAcquisition(numImages, intervalMs, stopOnOverflow); - if (nRet != DEVICE_OK) - throw CMMError(getDeviceErrorText(nRet, camera).c_str(), MMERR_DEVICE_GENERIC); - } - catch (std::bad_alloc& ex) - { - std::ostringstream messs; - messs << getCoreErrorText(MMERR_OutOfMemory).c_str() << " " << ex.what() << '\n'; - throw CMMError(messs.str().c_str() , MMERR_OutOfMemory); - } + int nRet = camera->StartSequenceAcquisition(numImages, intervalMs, stopOnOverflow); + if (nRet != DEVICE_OK) + throw CMMError(getDeviceErrorText(nRet, camera).c_str(), MMERR_DEVICE_GENERIC); + } + catch (std::bad_alloc& ex) + { + std::ostringstream messs; + messs << getCoreErrorText(MMERR_OutOfMemory).c_str() << " " << ex.what() << '\n'; + throw CMMError(messs.str().c_str() , MMERR_OutOfMemory); + } } else { @@ -2881,7 +2883,9 @@ void CMMCore::startSequenceAcquisition(const char* label, long numImages, double throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } bufferAdapter_->Clear(); - + // Disable overwriting for finite sequence acquisition + bufferAdapter_->SetOverwriteData(false); + LOG_DEBUG(coreLogger_) << "Will start sequence acquisition from camera " << label; int nRet = pCam->StartSequenceAcquisition(numImages, intervalMs, stopOnOverflow); @@ -2984,6 +2988,9 @@ void CMMCore::startContinuousSequenceAcquisition(double intervalMs) throw (CMMEr throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } bufferAdapter_->Clear(); + // Enable overwriting for continuous sequence acquisition. This only applies to the v2 buffer. + // and has no effect on the circular buffer. + bufferAdapter_->SetOverwriteData(true); LOG_DEBUG(coreLogger_) << "Will start continuous sequence acquisition from current camera"; int nRet = camera->StartSequenceAcquisition(intervalMs); if (nRet != DEVICE_OK) @@ -3186,6 +3193,15 @@ void CMMCore::clearCircularBuffer() throw (CMMError) bufferAdapter_->Clear(); } +/** + * Enables or disables the v2 buffer. + */ +void CMMCore::enableV2Buffer(bool enable) throw (CMMError) +{ + useV2Buffer_ = enable; + bufferAdapter_->EnableV2Buffer(enable); +} + /** * Reserve memory for the circular buffer. */ @@ -4233,6 +4249,34 @@ unsigned CMMCore::getNumberOfComponents() return 0; } +unsigned CMMCore::getImageWidth(const char* ptr) { + return useV2Buffer_ ? bufferAdapter_->GetImageWidth(reinterpret_cast(ptr)) : getImageWidth(); +} + +unsigned CMMCore::getImageHeight(const char* ptr) { + return useV2Buffer_ ? bufferAdapter_->GetImageHeight(reinterpret_cast(ptr)) : getImageHeight(); +} + +unsigned CMMCore::getBytesPerPixel(const char* ptr) { + return useV2Buffer_ ? bufferAdapter_->GetBytesPerPixel(reinterpret_cast(ptr)) : getBytesPerPixel(); +} + +unsigned CMMCore::getImageBitDepth(const char* ptr) { + return useV2Buffer_ ? bufferAdapter_->GetImageBitDepth(reinterpret_cast(ptr)) : getImageBitDepth(); +} + +unsigned CMMCore::getNumberOfComponents(const char* ptr) { + return useV2Buffer_ ? bufferAdapter_->GetNumberOfComponents(reinterpret_cast(ptr)) : getNumberOfComponents(); +} + +long CMMCore::getImageBufferSize(const char* ptr) { + return useV2Buffer_ ? bufferAdapter_->GetImageBufferSize(reinterpret_cast(ptr)) : getImageBufferSize(); +} + +void CMMCore::ReleaseReadAccess(const char* ptr) { + if (useV2Buffer_) bufferAdapter_->ReleaseReadAccess(reinterpret_cast(ptr)); +} + /** * Returns the number of simultaneous channels the default camera is returning. */ @@ -5559,9 +5603,6 @@ double CMMCore::getPixelSizeUm(bool cached) } } -/** - * Returns the pixel size in um for the requested pixel size group - */ double CMMCore::getPixelSizeUmByID(const char* resolutionID) throw (CMMError) { CheckConfigPresetName(resolutionID); diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 9e1f63394..0d8ff2f59 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -427,6 +427,22 @@ class CMMCore void initializeCircularBuffer() throw (CMMError); void clearCircularBuffer() throw (CMMError); + ///@} + + /** \name v2 buffer control. */ + ///@{ + void enableV2Buffer(bool enable) throw (CMMError); + bool usesV2Buffer() const { return useV2Buffer_; } + + unsigned getImageWidth(const char* ptr) throw (CMMError); + unsigned getImageHeight(const char* ptr) throw (CMMError); + unsigned getBytesPerPixel(const char* ptr) throw (CMMError); + unsigned getImageBitDepth(const char* ptr) throw (CMMError); + unsigned getNumberOfComponents(const char* ptr) throw (CMMError); + long getImageBufferSize(const char* ptr) throw (CMMError); + + void ReleaseReadAccess(const char* ptr) throw (CMMError); + bool isExposureSequenceable(const char* cameraLabel) throw (CMMError); void startExposureSequence(const char* cameraLabel) throw (CMMError); void stopExposureSequence(const char* cameraLabel) throw (CMMError); @@ -625,6 +641,7 @@ class CMMCore std::vector getLoadedPeripheralDevices(const char* hubLabel) throw (CMMError); ///@} + private: // make object non-copyable CMMCore(const CMMCore&); diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index 84a71459b..a99262b83 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -403,9 +403,12 @@ } %typemap(out) void* { - long lSize = (arg1)->getImageWidth() * (arg1)->getImageHeight(); + long lSize = (arg1)->getImageWidth((const char*)result) * + (arg1)->getImageHeight((const char*)result); + + unsigned bytesPerPixel = (arg1)->getBytesPerPixel((const char*)result); - if ((arg1)->getBytesPerPixel() == 1) + if (bytesPerPixel == 1) { // create a new byte[] object in Java jbyteArray data = JCALL1(NewByteArray, jenv, lSize); @@ -422,9 +425,13 @@ // copy pixels from the image buffer JCALL4(SetByteArrayRegion, jenv, data, 0, lSize, (jbyte*)result); + // Release the read access (required for V2 buffer, Core will figure out if it's V2 or not) + (arg1)->ReleaseReadAccess((const char*)result); + + $result = data; } - else if ((arg1)->getBytesPerPixel() == 2) + else if (bytesPerPixel == 2) { // create a new short[] object in Java jshortArray data = JCALL1(NewShortArray, jenv, lSize); @@ -440,13 +447,18 @@ // copy pixels from the image buffer JCALL4(SetShortArrayRegion, jenv, data, 0, lSize, (jshort*)result); + // Release the read access + (arg1)->ReleaseReadAccess((const char*)result); + + $result = data; } - else if ((arg1)->getBytesPerPixel() == 4) + else if (bytesPerPixel == 4) { - if ((arg1)->getNumberOfComponents() == 1) + if ((arg1)->getNumberOfComponents((const char*)result) == 1) { // create a new float[] object in Java + jfloatArray data = JCALL1(NewFloatArray, jenv, lSize); if (data == 0) { @@ -461,6 +473,10 @@ // copy pixels from the image buffer JCALL4(SetFloatArrayRegion, jenv, data, 0, lSize, (jfloat*)result); + // Release the read access + (arg1)->ReleaseReadAccess((const char*)result); + + $result = data; } else @@ -480,10 +496,14 @@ // copy pixels from the image buffer JCALL4(SetByteArrayRegion, jenv, data, 0, lSize * 4, (jbyte*)result); + // Release the read access + (arg1)->ReleaseReadAccess((const char*)result); + + $result = data; } } - else if ((arg1)->getBytesPerPixel() == 8) + else if (bytesPerPixel == 8) { // create a new short[] object in Java jshortArray data = JCALL1(NewShortArray, jenv, lSize * 4); @@ -499,6 +519,9 @@ // copy pixels from the image buffer JCALL4(SetShortArrayRegion, jenv, data, 0, lSize * 4, (jshort*)result); + // Release the read access + (arg1)->ReleaseReadAccess((const char*)result); + $result = data; } From c7883ee2f10c584c8c577fc6585503f9e00b8afe Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 11 Feb 2025 14:58:33 -0800 Subject: [PATCH 11/46] fixed bugs, implementation seems to work just like circularbuffer --- MMCore/BufferAdapter.cpp | 11 +- MMCore/Buffer_v2.cpp | 510 ++++++++++++++++++++------------------- MMCore/Buffer_v2.h | 70 +++++- 3 files changed, 332 insertions(+), 259 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index 438785895..e6241ec5e 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -133,7 +133,7 @@ unsigned BufferAdapter::GetMemorySizeMB() const long BufferAdapter::GetRemainingImageCount() const { if (useV2_) { - return v2Buffer_->GetRemainingImageCount(); + return v2Buffer_->GetActiveSlotCount(); } else { return circBuffer_->GetRemainingImageCount(); } @@ -153,7 +153,9 @@ void BufferAdapter::Clear() long BufferAdapter::GetSize(long imageSize) const { if (useV2_) { - return v2Buffer_->GetMemorySizeMB() * 1024 * 1024 / imageSize; + unsigned int mb = v2Buffer_->GetMemorySizeMB(); + unsigned int num_images = mb * 1024 * 1024 / imageSize; + return static_cast(num_images); } else { return circBuffer_->GetSize(); } @@ -163,7 +165,8 @@ long BufferAdapter::GetSize(long imageSize) const long BufferAdapter::GetFreeSize(long imageSize) const { if (useV2_) { - return static_cast(v2Buffer_->GetFreeMemory()) / imageSize; + unsigned int mb = v2Buffer_->GetFreeMemory(); + return static_cast(mb) / imageSize; } else { return circBuffer_->GetFreeSize(); } @@ -376,7 +379,7 @@ bool BufferAdapter::IsUsingV2Buffer() const { void BufferAdapter::ReleaseReadAccess(const unsigned char* ptr) { if (useV2_ && ptr) { - v2Buffer_->ReleaseDataReadPointer(&ptr); + v2Buffer_->ReleaseDataReadPointer(ptr); } } diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 4e71c0629..4af6d8c92 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -83,9 +83,9 @@ struct BufferSlotRecord { BufferSlot::BufferSlot(std::size_t start, std::size_t length) : start_(start), length_(length), readAccessCountAtomicInt_(0), - writeAtomicBool_(true) // The slot is created with write access held by default. + writeAtomicBool_(false) // The slot is created with no exclusive write access. { - // No readers are active and the slot starts with write access. + // No readers are active and the slot must be explicitly acquired for writing. } BufferSlot::~BufferSlot() { @@ -151,7 +151,7 @@ void BufferSlot::ReleaseWriteAccess() { * the calling thread will wait until the writer releases its lock. * Once unlocked, the method increments the reader count. */ -bool BufferSlot::AcquireReadAccess() { +void BufferSlot::AcquireReadAccess() { // Acquire the mutex associated with the condition variable. std::unique_lock lock(writeCompleteConditionMutex_); // Block until no writer is active. @@ -160,7 +160,6 @@ bool BufferSlot::AcquireReadAccess() { }); // Now that there is no writer, increment the reader counter. readAccessCountAtomicInt_.fetch_add(1, std::memory_order_acquire); - return true; } /** @@ -168,11 +167,13 @@ bool BufferSlot::AcquireReadAccess() { * The reader count is decremented using release semantics to ensure that all * prior read operations complete before the decrement is visible to other threads. */ -bool BufferSlot::ReleaseReadAccess() { - // fetch_sub returns the previous value. If that value was 1, - // then this call decrements the active reader count to zero. - int prevCount = readAccessCountAtomicInt_.fetch_sub(1, std::memory_order_release); - return (prevCount == 1); +void BufferSlot::ReleaseReadAccess() { + // fetch_sub returns the previous value + int previousCount = readAccessCountAtomicInt_.fetch_sub(1, std::memory_order_release); + if (previousCount <= 0) { + // This indicates a bug - we're releasing more times than we've acquired + throw std::runtime_error("Invalid read access release - counter would go negative"); + } } /** @@ -198,6 +199,12 @@ bool BufferSlot::IsAvailableForReading() const { // DataBuffer Implementation /////////////////////////////////////////////////////////////////////////////// +namespace { + inline size_t AlignTo(size_t value, size_t alignment) { + return ((value + alignment - 1) / alignment) * alignment; + } +} + DataBuffer::DataBuffer(unsigned int memorySizeMB) : buffer_(nullptr), bufferSize_(0), @@ -224,6 +231,8 @@ int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { buffer_ = new unsigned char[numBytes]; bufferSize_ = numBytes; overflow_ = false; + freeRegions_.clear(); + freeRegions_[0] = bufferSize_; return DEVICE_OK; } @@ -276,7 +285,7 @@ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Met } // Release the write slot - return ReleaseDataWriteSlot(&imageDataPointer, metaSize > 0 ? static_cast(metaSize) : -1); + return ReleaseDataWriteSlot(imageDataPointer, metaSize > 0 ? static_cast(metaSize) : -1); } /** @@ -303,7 +312,7 @@ int DataBuffer::CopyNextDataAndMetadata(unsigned char* dataDestination, size_t* // This is analogous to what is done in FrameBuffer.cpp: md.Restore(metaStr.c_str()); - return ReleaseDataReadPointer(&imageDataPointer); + return ReleaseDataReadPointer(imageDataPointer); } /** @@ -331,130 +340,76 @@ int DataBuffer::SetOverwriteData(bool overwrite) { * * The caller must release the slot using ReleaseDataSlot after writing is complete. */ -int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, unsigned char** imageDataPointer, unsigned char** metadataPointer) { - // AllocateNextSlot allocates a slot for writing new data of variable size. - // - // First, it checks if there is a recently released slot start (from releasedSlots_). - // For each candidate, it uses the activeSlotMap_ mechanism to - // verify that the candidate start yields a gap large enough to allocate slotSize bytes. - // This is done so to prefer recently released slots, in order to get performance - // boosts from reusing recently freed memory. - // - // If no released slot fits, then it falls back to using nextAllocOffset_ and similar - // collision checks. In overwrite mode, wrap-around is supported. - // Lock to ensure exclusive allocation. +int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, + unsigned char** imageDataPointer, unsigned char** metadataPointer) +{ std::lock_guard lock(slotManagementMutex_); - if (buffer_ == nullptr) { return DEVICE_ERR; } - // Total slot size is the header plus the image and metadata lengths. - size_t totalSlotSize = sizeof(BufferSlotRecord) + imageSize + metadataSize; - - // First, try using a released slot candidate (FILO order) - for (int i = static_cast(releasedSlots_.size()) - 1; i >= 0; i--) { - size_t candidateStart = releasedSlots_[i]; - size_t localCandidate = candidateStart; - - // Find first slot at or after our candidate position - auto nextIt = activeSlotsByStart_.lower_bound(localCandidate); - - // If a previous slot exists, adjust to avoid overlap. - if (nextIt != activeSlotsByStart_.begin()) { - size_t prevSlotEnd = std::prev(nextIt)->first + std::prev(nextIt)->second->GetLength(); - // If our candidate region [candidateStart, candidateStart+slotSize) overlaps the previous slot, - // bump candidateStart to the end of the conflicting slot and try again. - if (prevSlotEnd > localCandidate) { - localCandidate = prevSlotEnd; - } - } - - // Check if there's space before the next active slot - nextIt = activeSlotsByStart_.lower_bound(localCandidate); - bool candidateValid = true; - if (nextIt != activeSlotsByStart_.end()) { - // Case 1: There is a next slot - // Check if our proposed slot would overlap with the next slot - candidateValid = (localCandidate + totalSlotSize <= nextIt->first); - } else if (localCandidate + totalSlotSize > bufferSize_) { - // Case 2: No next slot, but we'd exceed buffer size - if (!overwriteWhenFull_) { - candidateValid = false; - } else { - // Try wrapping around to start of buffer - localCandidate = 0; - nextIt = activeSlotsByStart_.lower_bound(localCandidate); - - // If there are any slots, ensure we don't overlap with the last one - if (nextIt != activeSlotsByStart_.begin()) { - auto prevIt = std::prev(nextIt); - size_t prevSlotEnd = prevIt->first + prevIt->second->GetLength(); - if (prevSlotEnd > localCandidate) { - localCandidate = prevSlotEnd; - } - } + // Determine the required alignment based on our header. + size_t alignment = alignof(BufferSlotRecord); + // Compute the raw total size (header + image data + metadata) and then round up. + size_t rawTotalSize = sizeof(BufferSlotRecord) + imageSize + metadataSize; + size_t totalSlotSize = AlignTo(rawTotalSize, alignment); + size_t candidateStart = 0; + + if (!overwriteWhenFull_) { + // FIRST: Look for a fit in recycled slots (releasedSlots_) + for (int i = static_cast(releasedSlots_.size()) - 1; i >= 0; i--) { + // Align the candidate start position + candidateStart = AlignTo(releasedSlots_[i], alignment); + + // Find the free region that contains this position + auto it = freeRegions_.upper_bound(candidateStart); + if (it != freeRegions_.begin()) { + --it; + size_t freeStart = it->first; + size_t freeEnd = freeStart + it->second; - // Check if wrapped position would overlap with first slot - if (nextIt != activeSlotsByStart_.end()) { - candidateValid = (localCandidate + totalSlotSize <= nextIt->first); + // Check if the position is actually within this free region + if (candidateStart >= freeStart && candidateStart < freeEnd && + candidateStart + totalSlotSize <= freeEnd) { + releasedSlots_.erase(releasedSlots_.begin() + i); + return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, + imageDataPointer, metadataPointer, true); } } - } - - if (candidateValid) { - // Remove the candidate from releasedSlots_ (it was taken from the "back" if available). + + // If we get here, this released slot position isn't in a free region + // so remove it as it's no longer valid releasedSlots_.erase(releasedSlots_.begin() + i); - activeSlotsVector_.push_back(std::make_unique(localCandidate, totalSlotSize)); - BufferSlot* slot = activeSlotsVector_.back().get(); - activeSlotsByStart_[localCandidate] = slot; - *imageDataPointer = buffer_ + slot->GetStart() + sizeof(BufferSlotRecord); - *metadataPointer = *imageDataPointer + imageSize; - return DEVICE_OK; } - } - // If no released candidate fits, fall back to nextAllocOffset_. - size_t candidateStart = nextAllocOffset_; - if (candidateStart + totalSlotSize > bufferSize_) { - // Not enough space in the buffer: if we are not allowed to overwrite then set our overflow flag. - if (!overwriteWhenFull_) { - overflow_ = true; - *imageDataPointer = nullptr; - *metadataPointer = nullptr; - return DEVICE_ERR; - } - candidateStart = 0; // Reset to start of buffer - - // Since we're starting at position 0, remove any slots that start before our requested size - auto it = activeSlotsByStart_.begin(); - while (it != activeSlotsByStart_.end() && it->first < totalSlotSize) { - BufferSlot* slot = it->second; - if (!slot->IsAvailableForWriting() || !slot->IsAvailableForReading()) { - throw std::runtime_error("Cannot overwrite slot that is currently being accessed (has active readers or writers)"); + + // SECOND: Look in the free-region list as fallback. + for (auto it = freeRegions_.begin(); it != freeRegions_.end(); ++it) { + // Align the free region start. + size_t alignedCandidate = AlignTo(it->first, alignment); + // Check if the free region has enough space after alignment. + if (it->first + it->second >= alignedCandidate + totalSlotSize) { + candidateStart = alignedCandidate; + return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, + imageDataPointer, metadataPointer, true); } - - // Remove from both tracking structures - activeSlotsVector_.erase( - std::remove_if(activeSlotsVector_.begin(), activeSlotsVector_.end(), - [targetStart = it->first](const std::unique_ptr& slot) { - return slot->GetStart() == targetStart; - }), - activeSlotsVector_.end()); - it = activeSlotsByStart_.erase(it); } + // No recycled slot or free region can satisfy the allocation. + overflow_ = true; + *imageDataPointer = nullptr; + *metadataPointer = nullptr; + return DEVICE_ERR; + } else { + // Overwrite mode: use nextAllocOffset_. Ensure it is aligned. + candidateStart = AlignTo(nextAllocOffset_, alignment); + if (candidateStart + totalSlotSize > bufferSize_) { + candidateStart = 0; // Wrap around. + } + nextAllocOffset_ = candidateStart + totalSlotSize; + // Register a new slot using the selected candidateStart. + return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, + imageDataPointer, metadataPointer, false); } - - activeSlotsVector_.push_back(std::make_unique(candidateStart, totalSlotSize)); - BufferSlot* newSlot = activeSlotsVector_.back().get(); - activeSlotsByStart_[candidateStart] = newSlot; - nextAllocOffset_ = candidateStart + totalSlotSize; - if (nextAllocOffset_ >= bufferSize_) { - nextAllocOffset_ = 0; - } - *imageDataPointer = buffer_ + newSlot->GetStart() + sizeof(BufferSlotRecord); - *metadataPointer = *imageDataPointer + imageSize; - return DEVICE_OK; } /** @@ -464,15 +419,15 @@ int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, unsigned * @param buffer The buffer to be released. * @return Error code (0 on success). */ -int DataBuffer::ReleaseDataWriteSlot(unsigned char** imageDataPointer, int actualMetadataBytes) { - if (imageDataPointer == nullptr || *imageDataPointer == nullptr) +int DataBuffer::ReleaseDataWriteSlot(unsigned char* imageDataPointer, int actualMetadataBytes) { + if (imageDataPointer == nullptr) return DEVICE_ERR; std::lock_guard lock(slotManagementMutex_); // Convert the externally provided imageDataPointer (which points to the image data) // to the true slot start (header) by subtracting sizeof(BufferSlotRecord). - unsigned char* headerPointer = *imageDataPointer - sizeof(BufferSlotRecord); + unsigned char* headerPointer = imageDataPointer - sizeof(BufferSlotRecord); size_t offset = headerPointer - buffer_; // Locate the slot using the true header offset. @@ -493,164 +448,135 @@ int DataBuffer::ReleaseDataWriteSlot(unsigned char** imageDataPointer, int actua } } - // Clear the externally provided image data pointer. - *imageDataPointer = nullptr; - // Notify any waiting threads that new data is available. dataCV_.notify_all(); return DEVICE_OK; } - /** * ReleaseSlot is called after a slot's content has been fully read. * * This implementation pushes only the start of the released slot onto the FILO * (releasedSlots_) and removes the slot from the active slot map and activeSlots_. */ -int DataBuffer::ReleaseDataReadPointer(const unsigned char** imageDataPointer) { - if (imageDataPointer == nullptr || *imageDataPointer == nullptr) +int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { + if (imageDataPointer == nullptr ) return DEVICE_ERR; std::unique_lock lock(slotManagementMutex_); // Compute the header pointer by subtracting the header size. - const unsigned char* headerPointer = *imageDataPointer - sizeof(BufferSlotRecord); + const unsigned char* headerPointer = imageDataPointer - sizeof(BufferSlotRecord); size_t offset = headerPointer - buffer_; - // Find the slot in activeSlotsByStart_ auto it = activeSlotsByStart_.find(offset); - if (it != activeSlotsByStart_.end()) { - BufferSlot* slot = it->second; - // Release the previously acquired read access. - slot->ReleaseReadAccess(); + if (it == activeSlotsByStart_.end()) + return DEVICE_ERR; // Slot not found + + BufferSlot* slot = it->second; + // Release the read access (this does NOT remove the slot from the active list) + slot->ReleaseReadAccess(); + if (!overwriteWhenFull_) { // Now check if the slot is not being accessed // (i.e. this was the last/readers and no writer holds it) if (slot->IsAvailableForWriting() && slot->IsAvailableForReading()) { - // Ensure we do not exceed the maximum number of released slots. - if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) - releasedSlots_.erase(releasedSlots_.begin()); - releasedSlots_.push_back(offset); - - // Remove slot from the active tracking structures. - activeSlotsByStart_.erase(it); - for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { - if (vecIt->get()->GetStart() == offset) { - // Determine the index being removed. - size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); - activeSlotsVector_.erase(vecIt); - // Adjust currentSlotIndex_: - // If the deleted slot was before the current index, decrement it. - if (currentSlotIndex_ > indexDeleted) - currentSlotIndex_--; - break; - } - } + RemoveSlotFromActiveTracking(offset, it); + } else { + throw std::runtime_error("TESTING: this should not happen when copying data"); } - } else { - throw std::runtime_error("Cannot release slot that is not in the buffer."); } - *imageDataPointer = nullptr; + + return DEVICE_OK; } const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData) { - std::unique_lock lock(slotManagementMutex_); - - // Wait until there is data available if requested. - // (Here we check whether activeSlotsVector_ has an unread slot. - // Adjust the condition as appropriate for your implementation.) - while (activeSlotsVector_.empty()) { - if (!waitForData) - return nullptr; - dataCV_.wait(lock); - } - - // Assume that the next unread slot is at index currentSlotIndex_. - // (Depending on your data structure you might pop from a deque or update an iterator.) - BufferSlot* slot = activeSlotsVector_[currentSlotIndex_].get(); - // Get the starting offset for this slot. + BufferSlot* slot = nullptr; + { + std::unique_lock lock(slotManagementMutex_); + while (activeSlotsVector_.empty()) { + if (!waitForData) + return nullptr; + dataCV_.wait(lock); + } + // Atomically take the slot and advance the index. + slot = activeSlotsVector_[currentSlotIndex_].get(); + currentSlotIndex_ = (currentSlotIndex_ + 1) % activeSlotsVector_.size(); + } // Release lock + + // Now get read access outside the slot of the global mutex. + slot->AcquireReadAccess(); + + // Process the slot. size_t slotStart = slot->GetStart(); - - // The header is stored at the beginning of the slot. const BufferSlotRecord* header = reinterpret_cast(buffer_ + slotStart); - - // The image data region starts right after the header. const unsigned char* imageDataPointer = buffer_ + slotStart + sizeof(BufferSlotRecord); - - // Set the output image data size from the header. *imageDataSize = header->imageSize; - - // Populate the metadata. + if (header->metadataSize > 0) { const char* metaDataStart = reinterpret_cast(imageDataPointer + header->imageSize); md.Restore(metaDataStart); } else { - // If no metadata is available, clear the metadata object. md.Clear(); } - - // Consume this slot by advancing the index. - currentSlotIndex_ = (currentSlotIndex_ + 1) % activeSlotsVector_.size(); - - // Unlock and return the pointer to the image data region. - return imageDataPointer; -} -unsigned int DataBuffer::GetMemorySizeMB() const { - // Convert bytes to MB (1 MB = 1048576 bytes) - return static_cast(bufferSize_ >> 20); + return imageDataPointer; } int DataBuffer::PeekNextDataReadPointer(const unsigned char** imageDataPointer, size_t* imageDataSize, Metadata &md) { // Immediately check if there is an unread slot without waiting. - std::unique_lock lock(slotManagementMutex_); - if (activeSlotsVector_.empty() || currentSlotIndex_ >= activeSlotsVector_.size()) { - return DEVICE_ERR; // No unread data available. - } + BufferSlot* currentSlot = nullptr; + { + // Lock the global slot management mutex to safely access the active slots. + std::unique_lock lock(slotManagementMutex_); + if (activeSlotsVector_.empty() || currentSlotIndex_ >= activeSlotsVector_.size()) { + return DEVICE_ERR; // No unread data available. + } + currentSlot = activeSlotsVector_[currentSlotIndex_].get(); + } // Release slotManagementMutex_ before acquiring read access! - // Obtain the next available slot *without* advancing currentSlotIndex_. - BufferSlot& currentSlot = *activeSlotsVector_[currentSlotIndex_]; - if (!currentSlot.AcquireReadAccess()) - return DEVICE_ERR; + // Now safely get read access without holding the global lock. + currentSlot->AcquireReadAccess(); - *imageDataPointer = buffer_ + currentSlot.GetStart() + sizeof(BufferSlotRecord); + std::unique_lock lock(slotManagementMutex_); + *imageDataPointer = buffer_ + currentSlot->GetStart() + sizeof(BufferSlotRecord); const BufferSlotRecord* headerPointer = reinterpret_cast( (*imageDataPointer) - sizeof(BufferSlotRecord) ); *imageDataSize = headerPointer->imageSize; - - // Populate the Metadata object from the stored metadata blob. + + // Populate the metadata from the slot. std::string metaStr; if (headerPointer->metadataSize > 0) { const char* metaDataStart = reinterpret_cast(*imageDataPointer + headerPointer->imageSize); metaStr.assign(metaDataStart, headerPointer->metadataSize); } - // Restore the metadata - // This is analogous to what is done in FrameBuffer.cpp: - md.Restore(metaStr.c_str()); - + md.Restore(metaStr.c_str()); return DEVICE_OK; } const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* imageDataSize, Metadata &md) { - std::unique_lock lock(slotManagementMutex_); - if (activeSlotsVector_.empty() || (currentSlotIndex_ + n) >= activeSlotsVector_.size()) { - return nullptr; + BufferSlot* currentSlot = nullptr; + { + // Lock the global slot management mutex to safely access the active slots. + std::unique_lock lock(slotManagementMutex_); + if (activeSlotsVector_.empty() || n >= activeSlotsVector_.size()) { + return nullptr; + } + + // Instead of looking ahead from currentSlotIndex_, we look back from the end. + // For n==0, return the most recent slot; for n==1, the one before it; etc. + size_t index = activeSlotsVector_.size() - n - 1; + currentSlot = activeSlotsVector_[index].get(); } - // Access the nth slot (without advancing the read index) - BufferSlot& slot = *activeSlotsVector_[currentSlotIndex_ + n]; - if (!slot.AcquireReadAccess()) - throw std::runtime_error("Failed to acquire read access for the selected slot."); + currentSlot->AcquireReadAccess(); - // Obtain the pointer to the image data (skip the header) - const unsigned char* imageDataPointer = buffer_ + slot.GetStart() + sizeof(BufferSlotRecord); + // Obtain pointer to the image data by skipping over the header. + const unsigned char* imageDataPointer = buffer_ + currentSlot->GetStart() + sizeof(BufferSlotRecord); const BufferSlotRecord* headerPointer = reinterpret_cast(imageDataPointer - sizeof(BufferSlotRecord)); - - // Return the image size via the pointer parameter if (imageDataSize != nullptr) { *imageDataSize = headerPointer->imageSize; } @@ -671,32 +597,9 @@ const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* im return imageDataPointer; } -/** - * Releases the read access that was acquired by a peek. - * This is similar to ReleaseDataReadPointer except that it does not - * remove the slot from the active list. This should be used when the - * overwriteWhenFull_ flag is true and the caller wants to release the - * peeked slot for reuse. - */ -int DataBuffer::ReleasePeekDataReadPointer(const unsigned char** imageDataPointer) { - if (imageDataPointer == nullptr || *imageDataPointer == nullptr) - return DEVICE_ERR; - - std::lock_guard lock(slotManagementMutex_); - const unsigned char* headerPointer = *imageDataPointer - sizeof(BufferSlotRecord); - size_t offset = headerPointer - buffer_; - - // Look up the corresponding slot by its buffer offset. - auto it = activeSlotsByStart_.find(offset); - if (it == activeSlotsByStart_.end()) - return DEVICE_ERR; // Slot not found - - BufferSlot* slot = it->second; - // Release the read access (this does NOT remove the slot from the active list) - slot->ReleaseReadAccess(); - - *imageDataPointer = nullptr; - return DEVICE_OK; +unsigned int DataBuffer::GetMemorySizeMB() const { + // Convert bytes to MB (1 MB = 1048576 bytes) + return static_cast(bufferSize_ >> 20); } size_t DataBuffer::GetOccupiedSlotCount() const { @@ -767,7 +670,7 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { return DEVICE_OK; } -long DataBuffer::GetRemainingImageCount() const { +long DataBuffer::GetActiveSlotCount() const { return static_cast(activeSlotsVector_.size()); } @@ -854,3 +757,120 @@ long DataBuffer::GetImageBufferSize(const unsigned char* imageDataPtr) const { const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); return header->imageSize; } + +void DataBuffer::RemoveSlotFromActiveTracking(size_t offset, std::map::iterator it) { + // Assert that caller holds the mutex. + // This assertion checks that the mutex is already locked by ensuring try_lock() fails. + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + + // Only add to released slots in non-overwrite mode. + if (!overwriteWhenFull_) { + if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) + releasedSlots_.erase(releasedSlots_.begin()); + releasedSlots_.push_back(offset); + } + + // Update the freeRegions_ list to include the freed region and merge with adjacent regions + size_t freedRegionSize = it->second->GetLength(); + size_t newRegionStart = offset; + size_t newRegionEnd = offset + freedRegionSize; + + // Find the free region that starts at or after newEnd. + auto right = freeRegions_.lower_bound(newRegionEnd); + + // Check if there is a free region immediately preceding the new region. + auto left = freeRegions_.lower_bound(newRegionStart); + if (left != freeRegions_.begin()) { + auto prev = std::prev(left); + // If the previous region's end matches the new region's start... + if (prev->first + prev->second == newRegionStart) { + newRegionStart = prev->first; + freeRegions_.erase(prev); + } + } + + // Check if the region immediately to the right can be merged. + if (right != freeRegions_.end() && right->first == newRegionEnd) { + newRegionEnd = right->first + right->second; + freeRegions_.erase(right); + } + + // Insert the merged (or standalone) free region. + size_t newRegionSize = (newRegionEnd > newRegionStart ? newRegionEnd - newRegionStart : 0); + if (newRegionSize > 0) { + freeRegions_[newRegionStart] = newRegionSize; + } + + // Remove the slot from the active tracking structures. + activeSlotsByStart_.erase(it); + for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { + if (vecIt->get()->GetStart() == offset) { + // Determine the index being removed. + size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); + activeSlotsVector_.erase(vecIt); + // Adjust the currentSlotIndex_; if the deleted slot was before it, decrement. + if (currentSlotIndex_ > indexDeleted) + currentSlotIndex_--; + break; + } + } +} + +int DataBuffer::CreateAndRegisterNewSlot(size_t candidateStart, + size_t totalSlotSize, + size_t imageDataSize, + unsigned char** imageDataPointer, + unsigned char** metadataPointer, + bool fromFreeRegion) { + // Assert that caller holds the mutex. + // This assertion checks that the mutex is already locked by ensuring try_lock() fails. + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + + // Use the provided candidateStart to create and register a new slot. + BufferSlot* newSlot = new BufferSlot(candidateStart, totalSlotSize); + // Acquire the write access + newSlot->AcquireWriteAccess(); + activeSlotsVector_.push_back(std::unique_ptr(newSlot)); + activeSlotsByStart_[candidateStart] = newSlot; + + // Initialize header (the metadata sizes will be updated later). + BufferSlotRecord* headerPointer = reinterpret_cast(buffer_ + candidateStart); + headerPointer->imageSize = imageDataSize; + headerPointer->metadataSize = totalSlotSize - imageDataSize - sizeof(BufferSlotRecord); + + // Set the output pointers + *imageDataPointer = buffer_ + candidateStart + sizeof(BufferSlotRecord); + *metadataPointer = *imageDataPointer + imageDataSize; + + // If the candidate comes from a free region, update the freeRegions_ map. + if (fromFreeRegion) { + // Adjust the free region list to remove the allocated block. + auto it = freeRegions_.upper_bound(candidateStart); + if (it != freeRegions_.begin()) { + --it; + size_t freeRegionStart = it->first; + size_t freeRegionSize = it->second; + size_t freeRegionEnd = freeRegionStart + freeRegionSize; + // candidateStart must lie within [freeRegionStart, freeRegionEnd). + if (candidateStart >= freeRegionStart && + candidateStart + totalSlotSize <= freeRegionEnd) + { + freeRegions_.erase(it); + // If there is a gap before the candidate slot, add that as a free region. + if (candidateStart > freeRegionStart) { + size_t gap = candidateStart - freeRegionStart; + if (gap > 0) + freeRegions_.insert({freeRegionStart, gap}); + } + // If there is a gap after the candidate slot, add that as well. + if (freeRegionEnd > candidateStart + totalSlotSize) { + size_t gap = freeRegionEnd - (candidateStart + totalSlotSize); + if (gap > 0) + freeRegions_.insert({candidateStart + totalSlotSize, gap}); + } + } + } + } + + return DEVICE_OK; +} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 6a38a654c..34d6a3cef 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -99,18 +99,14 @@ class BufferSlot { * Acquires shared read access. * This is a blocking operation — if a writer is active, the caller waits * until the writer releases its lock. Once available, the reader count is incremented. - * - * @return True when read access has been successfully acquired. */ - bool AcquireReadAccess(); + void AcquireReadAccess(); /** * Releases shared read access. * Decrements the reader count. - * - * @return True if this call released the last active reader; false otherwise. */ - bool ReleaseReadAccess(); + void ReleaseReadAccess(); /** * Checks if the slot is currently available for writing. @@ -250,7 +246,7 @@ class DataBuffer { * Defaults to -1, which means no update is performed. * @return DEVICE_OK on success. */ - int ReleaseDataWriteSlot(unsigned char** imageDataPointer, int actualMetadataBytes = -1); + int ReleaseDataWriteSlot(unsigned char* imageDataPointer, int actualMetadataBytes = -1); /** * Releases read access for the image data region after its content has been read. @@ -258,7 +254,7 @@ class DataBuffer { * @param imageDataPointer Pointer previously obtained from reading routines. * @return DEVICE_OK on success. */ - int ReleaseDataReadPointer(const unsigned char** imageDataPointer); + int ReleaseDataReadPointer(const unsigned char* imageDataPointer); /** * Retrieves and removes (consumes) the next available data entry for reading, @@ -289,11 +285,13 @@ class DataBuffer { * Peeks at the nth unread data entry without consuming it. * (n = 0 is equivalent to PeekNextDataReadPointer.) * - * @param n The index of the data entry to peek at (0 is next available). + * @param n The index of the data entry to peek at (0 is next available), + * > 0 is looking back in the buffer. * @param imageDataSize On success, returns the image data size (in bytes). * @param md Metadata object populated from the stored metadata blob. * @return Pointer to the start of the image data region. */ + const unsigned char* PeekDataReadPointerAtIndex(size_t n, size_t* imageDataSize, Metadata &md); /** @@ -345,7 +343,7 @@ class DataBuffer { * * @return Unread slot count. */ - long GetRemainingImageCount() const; + long GetActiveSlotCount() const; /** * Reinitializes the DataBuffer by clearing its structures, releasing the current @@ -406,6 +404,51 @@ class DataBuffer { long GetImageBufferSize(const unsigned char* imageDataPtr) const; private: + /** + * Removes a slot from active tracking and adds it to the free region list. + * Caller must hold slotManagementMutex_. + * + * @param offset The buffer offset of the slot to remove. + * @param it Iterator to the slot in activeSlotsByStart_. + */ + void RemoveSlotFromActiveTracking(size_t offset, std::map::iterator it); + + /** + * Creates a new BufferSlot at an allocated region, registers it in the internal + * tracking structures, initializes its header, and sets the output pointers for image + * data and metadata. + * + * Allocation is performed differently based on the overwrite mode: + * + * - Overwrite mode: + * Uses nextAllocOffset_ with wrap-around. + * + * - Non-overwrite mode: + * First, we try to reuse recycled slots in order of their release. If none are + * available or usable, we then check if there is a free region large enough to hold + * the slot. If there is, we use that region. If there is no suitable region, an exception is + * thrown. + * + * @param candidateStart The starting offset candidate for the slot. + * @param totalSlotSize The total size (in bytes) of the slot, including header, image data, and metadata. + * @param imageDataSize The size (in bytes) to reserve for the image data. + * @param imageDataPointer Output pointer for the caller to access the image data region. + * @param metadataPointer Output pointer for the caller to access the metadata region. + * @param fromFreeRegion If true, indicates that the candidate was selected from a free region. + * @return DEVICE_OK on success, or an error code if allocation fails. + */ + int CreateAndRegisterNewSlot(size_t candidateStart, size_t totalSlotSize, size_t imageDataSize, + unsigned char** imageDataPointer, unsigned char** metadataPointer, + bool fromFreeRegion); + + /** + * Inserts a free region into the freeRegions_ list, merging with adjacent regions if necessary. + * + * @param offset The starting offset of the freed region. + * @param size The size (in bytes) of the freed region. + */ + void InsertFreeRegion(size_t offset, size_t size); + // Pointer to the allocated block. unsigned char* buffer_; // Total allocated size in bytes. @@ -420,9 +463,16 @@ class DataBuffer { // Data structures used to track active slots. std::vector> activeSlotsVector_; std::map activeSlotsByStart_; + + // Free region list for non-overwrite mode. + // Map from starting offset -> region size (in bytes). + std::map freeRegions_; + + // (Optional) Legacy structure—can be deprecated as freeRegions_ is used instead. std::vector releasedSlots_; // Next free offset within the buffer. + // In overwrite mode, new allocations will come from this pointer. size_t nextAllocOffset_; // Index tracking the next slot for read. From c29e114a6f34814a915c4d66df893c4613d03bda Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:32:01 -0800 Subject: [PATCH 12/46] add parallel copying and memory mapping --- MMCore/BufferAdapter.cpp | 7 ++-- MMCore/Buffer_v2.cpp | 83 ++++++++++++++++++++++------------------ MMCore/Buffer_v2.h | 18 +++------ 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index e6241ec5e..d686da7a3 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -250,10 +250,10 @@ bool BufferAdapter::InsertMultiChannel(const unsigned char *buf, unsigned numCha bool BufferAdapter::InsertMultiChannel(const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, Metadata* pMd) { - // Initialize metadata with either provided metadata or create empty + // Initialize metadata with either provided metadata or create empty Metadata md = (pMd != nullptr) ? *pMd : Metadata(); - - // Process common metadata + + // Process common metadata ProcessMetadata(md, width, height, byteDepth, nComponents); if (useV2_) { @@ -265,7 +265,6 @@ bool BufferAdapter::InsertMultiChannel(const unsigned char* buf, unsigned numCha return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md); } - } void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 4af6d8c92..30b276798 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -64,6 +64,7 @@ Metadata Handling: #include #include #include +#include "TaskSet_CopyMemory.h" // New internal header that precedes every slot's data. struct BufferSlotRecord { @@ -211,13 +212,22 @@ DataBuffer::DataBuffer(unsigned int memorySizeMB) overwriteWhenFull_(false), nextAllocOffset_(0), currentSlotIndex_(0), - overflow_(false) + overflow_(false), + threadPool_(std::make_shared()), + tasksMemCopy_(std::make_shared(threadPool_)) { AllocateBuffer(memorySizeMB); } DataBuffer::~DataBuffer() { - delete[] buffer_; + if (buffer_) { + #ifdef _WIN32 + VirtualFree(buffer_, 0, MEM_RELEASE); + #else + munmap(buffer_, bufferSize_); + #endif + buffer_ = nullptr; + } } /** @@ -228,7 +238,25 @@ DataBuffer::~DataBuffer() { int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { // Convert MB to bytes (1 MB = 1048576 bytes) size_t numBytes = static_cast(memorySizeMB) * (1ULL << 20); - buffer_ = new unsigned char[numBytes]; + + #ifdef _WIN32 + buffer_ = (unsigned char*)VirtualAlloc(nullptr, numBytes, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + if (!buffer_) { + return DEVICE_ERR; + } + #else + buffer_ = (unsigned char*)mmap(nullptr, numBytes, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (buffer_ == MAP_FAILED) { + buffer_ = nullptr; + return DEVICE_ERR; + } + #endif + bufferSize_ = numBytes; overflow_ = false; freeRegions_.clear(); @@ -242,7 +270,11 @@ int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { */ int DataBuffer::ReleaseBuffer() { if (buffer_ != nullptr) { - delete[] buffer_; + #ifdef _WIN32 + VirtualFree(buffer_, 0, MEM_RELEASE); + #else + munmap(buffer_, bufferSize_); + #endif buffer_ = nullptr; return DEVICE_OK; } @@ -276,7 +308,7 @@ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Met headerPointer->metadataSize = metaSize; // Copy the image data into the allocated slot (imageDataPointer is already at the image data). - std::memcpy(imageDataPointer, data, dataSize); + tasksMemCopy_->MemCopy((void*)imageDataPointer, data, dataSize); // If metadata is available, copy it right after the image data. if (metaSize > 0) { @@ -288,33 +320,6 @@ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Met return ReleaseDataWriteSlot(imageDataPointer, metaSize > 0 ? static_cast(metaSize) : -1); } -/** - * Reads the header from the slot, then copies the image data into the destination and - * uses the metadata blob (if any) to populate 'md'. - */ -int DataBuffer::CopyNextDataAndMetadata(unsigned char* dataDestination, size_t* imageDataSize, Metadata &md, bool waitForData) { - const unsigned char* imageDataPointer = PopNextDataReadPointer(md, imageDataSize, waitForData); - if (imageDataPointer == nullptr) - return DEVICE_ERR; - - const BufferSlotRecord* headerPointer = reinterpret_cast(imageDataPointer - sizeof(BufferSlotRecord)); - *imageDataSize = headerPointer->imageSize; - // imageDataPointer already points to the image data. - std::memcpy(dataDestination, imageDataPointer, headerPointer->imageSize); - - // Extract the metadata (if any) following the image data. - std::string metaStr; - if (headerPointer->metadataSize > 0) { - const char* metaDataStart = reinterpret_cast(imageDataPointer + headerPointer->imageSize); - metaStr.assign(metaDataStart, headerPointer->metadataSize); - } - // Restore the metadata - // This is analogous to what is done in FrameBuffer.cpp: - md.Restore(metaStr.c_str()); - - return ReleaseDataReadPointer(imageDataPointer); -} - /** * Configure whether to overwrite old data when buffer is full. * @@ -642,14 +647,14 @@ bool DataBuffer::Overflow() const { */ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { std::lock_guard lock(slotManagementMutex_); - + // Check that there are no outstanding readers or writers. for (const std::unique_ptr& slot : activeSlotsVector_) { if (!slot->IsAvailableForReading() || !slot->IsAvailableForWriting()) { throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); } } - + // Clear internal data structures. activeSlotsVector_.clear(); activeSlotsByStart_.clear(); @@ -657,13 +662,17 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { currentSlotIndex_ = 0; nextAllocOffset_ = 0; overflow_ = false; - + // Release the old buffer. if (buffer_ != nullptr) { - delete[] buffer_; + #ifdef _WIN32 + VirtualFree(buffer_, 0, MEM_RELEASE); + #else + munmap(buffer_, bufferSize_); + #endif buffer_ = nullptr; } - + // Allocate a new buffer using the provided memory size. AllocateBuffer(memorySizeMB); diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 34d6a3cef..7e78a8513 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -42,6 +42,7 @@ #include #include #include +#include "TaskSet_CopyMemory.h" /** * BufferSlot represents a contiguous slot in the DataBuffer that holds image @@ -198,19 +199,6 @@ class DataBuffer { */ int InsertData(const unsigned char* data, size_t dataSize, const Metadata* pMd); - /** - * Copies data and metadata from the next available slot in the buffer. - * The routine examines the header to determine the image byte count - * and the length of the stored metadata. - * - * @param dataDestination Destination buffer where image data is copied. - * @param imageDataSize On success, returns the image data size (in bytes). - * @param md Metadata object to be populated (via deserialization of the stored blob). - * @param waitForData If true, block until data becomes available. - * @return DEVICE_OK on success. - */ - int CopyNextDataAndMetadata(unsigned char* dataDestination, size_t* imageDataSize, Metadata &md, bool waitForData); - /** * Sets whether the buffer should overwrite older data when full. * @@ -481,4 +469,8 @@ class DataBuffer { // Synchronization primitives for slot management. std::condition_variable dataCV_; mutable std::mutex slotManagementMutex_; + + // Add these new members for multithreaded copying + std::shared_ptr threadPool_; + std::shared_ptr tasksMemCopy_; }; From b7a02a0fa4743d7eb06f88817b3e806f38c35049 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:58:04 -0800 Subject: [PATCH 13/46] switch to simpler mutexs --- MMCore/Buffer_v2.cpp | 107 ++++++++++++++++++------------------------- MMCore/Buffer_v2.h | 45 +++++++++++++++--- 2 files changed, 82 insertions(+), 70 deletions(-) diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 30b276798..0fadd6809 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -79,22 +79,22 @@ struct BufferSlotRecord { /** * Constructor. * Initializes the slot with the specified starting byte offset and length. - * Also initializes atomic variables that track reader and writer access. */ BufferSlot::BufferSlot(std::size_t start, std::size_t length) - : start_(start), length_(length), - readAccessCountAtomicInt_(0), - writeAtomicBool_(false) // The slot is created with no exclusive write access. + : start_(start), length_(length) { - // No readers are active and the slot must be explicitly acquired for writing. + // Using RAII-based locking with std::shared_timed_mutex. } +/** + * Destructor. + */ BufferSlot::~BufferSlot() { - // No explicit cleanup required here. + // No explicit cleanup required. } /** - * Returns the start offset (in bytes) of the slot from the start of the buffer. + * Returns the starting offset (in bytes) of the slot. */ std::size_t BufferSlot::GetStart() const { return start_; @@ -108,90 +108,71 @@ std::size_t BufferSlot::GetLength() const { } /** - * Attempts to acquire exclusive write access. - * This method first attempts to set the write flag atomically. - * If it fails, that indicates another writer holds the lock. - * Next, it attempts to confirm that no readers are active. - * If there are active readers, it reverts the write flag and returns false. + * Attempts to acquire exclusive write access without blocking. + * + * @return True if the exclusive lock was acquired, false otherwise. */ -bool BufferSlot::AcquireWriteAccess() { - bool expected = false; - // Attempt to atomically set the write flag. - if (!writeAtomicBool_.compare_exchange_strong(expected, true, std::memory_order_acquire)) { - // A writer is already active. - return false; - } - // Ensure no readers are active by checking the read counter. - int expectedReaders = 0; - if (!readAccessCountAtomicInt_.compare_exchange_strong(expectedReaders, 0, std::memory_order_acquire)) { - // Active readers are present; revert the write flag. - writeAtomicBool_.store(false, std::memory_order_release); - return false; - } - // Exclusive write access has been acquired. - return true; +bool BufferSlot::TryAcquireWriteAccess() { + return rwMutex_.try_lock(); +} + +/** + * Acquires exclusive write access (blocking call). + * This method will block until exclusive access is granted. + */ +void BufferSlot::AcquireWriteAccess() { + rwMutex_.lock(); } /** * Releases exclusive write access. - * The writer flag is cleared, and waiting readers are notified so that - * they may acquire shared read access once the write is complete. */ void BufferSlot::ReleaseWriteAccess() { - // Publish all writes by releasing the writer flag. - writeAtomicBool_.store(false, std::memory_order_release); - // Notify waiting readers (using the condition variable) - // that the slot is now available for read access. - std::lock_guard lock(writeCompleteConditionMutex_); - writeCompleteCondition_.notify_all(); + rwMutex_.unlock(); } /** - * Acquires shared read access. - * This is a blocking operation – if a writer is active, - * the calling thread will wait until the writer releases its lock. - * Once unlocked, the method increments the reader count. + * Acquires shared read access (blocking). */ void BufferSlot::AcquireReadAccess() { - // Acquire the mutex associated with the condition variable. - std::unique_lock lock(writeCompleteConditionMutex_); - // Block until no writer is active. - writeCompleteCondition_.wait(lock, [this]() { - return !writeAtomicBool_.load(std::memory_order_acquire); - }); - // Now that there is no writer, increment the reader counter. - readAccessCountAtomicInt_.fetch_add(1, std::memory_order_acquire); + rwMutex_.lock_shared(); } /** * Releases shared read access. - * The reader count is decremented using release semantics to ensure that all - * prior read operations complete before the decrement is visible to other threads. */ void BufferSlot::ReleaseReadAccess() { - // fetch_sub returns the previous value - int previousCount = readAccessCountAtomicInt_.fetch_sub(1, std::memory_order_release); - if (previousCount <= 0) { - // This indicates a bug - we're releasing more times than we've acquired - throw std::runtime_error("Invalid read access release - counter would go negative"); - } + rwMutex_.unlock_shared(); } /** - * Checks if the slot is available for acquiring write access. - * A slot is available for writing if there are no active readers and no writer. + * Checks if the slot is available for writing. + * The slot is considered available if no thread holds either an exclusive or a shared lock. + * + * @return True if available for writing. */ bool BufferSlot::IsAvailableForWriting() const { - return (readAccessCountAtomicInt_.load(std::memory_order_acquire) == 0) && - (!writeAtomicBool_.load(std::memory_order_acquire)); + // If we can acquire the lock exclusively, then no readers or writer are active. + if (rwMutex_.try_lock()) { + rwMutex_.unlock(); + return true; + } + return false; } /** - * Checks if the slot is available for acquiring read access. - * A slot is available for reading if no writer currently holds the lock. + * Checks if the slot is available for reading. + * A slot is available for reading if no exclusive lock is held (readers might already be active). + * + * @return True if available for reading. */ bool BufferSlot::IsAvailableForReading() const { - return !writeAtomicBool_.load(std::memory_order_acquire); + // If we can acquire a shared lock, then no writer is active. + if (rwMutex_.try_lock_shared()) { + rwMutex_.unlock_shared(); + return true; + } + return false; } diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 7e78a8513..8e673985b 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -34,6 +34,7 @@ #include "../MMDevice/ImageMetadata.h" #include "../MMDevice/MMDevice.h" #include +#include #include #include #include @@ -46,8 +47,8 @@ /** * BufferSlot represents a contiguous slot in the DataBuffer that holds image - * data and metadata. It supports exclusive write access with shared read access, - * using atomics, a mutex, and a condition variable. + * data and metadata. It uses RAII-based locking with std::shared_timed_mutex to + * support exclusive write access and concurrent shared read access. */ class BufferSlot { public: @@ -81,14 +82,20 @@ class BufferSlot { std::size_t GetLength() const; /** - * Attempts to acquire exclusive write access. + * Attempts to acquire exclusive write access without blocking. * It first tries to atomically set the write flag. * If another writer is active or if there are active readers, * the write lock is not acquired. * * @return True if the slot is locked for writing; false otherwise. */ - bool AcquireWriteAccess(); + bool TryAcquireWriteAccess(); + + /** + * Acquires exclusive write access (blocking call). + * This method will block until exclusive access is granted. + */ + void AcquireWriteAccess(); /** * Releases exclusive write access. @@ -97,7 +104,7 @@ class BufferSlot { void ReleaseWriteAccess(); /** - * Acquires shared read access. + * Acquires shared read access (blocking). * This is a blocking operation — if a writer is active, the caller waits * until the writer releases its lock. Once available, the reader count is incremented. */ @@ -130,10 +137,34 @@ class BufferSlot { std::size_t length_; std::atomic readAccessCountAtomicInt_; std::atomic writeAtomicBool_; - mutable std::mutex writeCompleteConditionMutex_; - mutable std::condition_variable writeCompleteCondition_; + mutable std::shared_timed_mutex rwMutex_; +}; + +/** + * WriteSlotGuard is an RAII helper that acquires exclusive write access on a BufferSlot. + * When this guard goes out of scope, the write lock is automatically released. + */ +class WriteSlotGuard { +public: + WriteSlotGuard(BufferSlot* slot); + ~WriteSlotGuard(); + +private: + BufferSlot* slot_; }; +/** + * ReadSlotGuard is an RAII helper that acquires shared read access on a BufferSlot. + * When this guard goes out of scope, the read lock is automatically released. + */ +class ReadSlotGuard { +public: + ReadSlotGuard(BufferSlot* slot); + ~ReadSlotGuard(); + +private: + BufferSlot* slot_; +}; /** * DataBuffer manages a contiguous block of memory divided into BufferSlot objects From 093fb1dd51e156239a9c77832c5491708476ffb5 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:13:05 -0800 Subject: [PATCH 14/46] fix docs and clean up --- MMCore/Buffer_v2.cpp | 10 +-- MMCore/Buffer_v2.h | 188 ++++++++++++------------------------------- 2 files changed, 58 insertions(+), 140 deletions(-) diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 0fadd6809..207be81b4 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -83,7 +83,7 @@ struct BufferSlotRecord { BufferSlot::BufferSlot(std::size_t start, std::size_t length) : start_(start), length_(length) { - // Using RAII-based locking with std::shared_timed_mutex. + // The std::shared_timed_mutex (rwMutex_) is default-constructed. } /** @@ -277,7 +277,7 @@ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Met size_t totalSize = sizeof(BufferSlotRecord) + dataSize + metaSize; unsigned char* imageDataPointer = nullptr; unsigned char* metadataPointer = nullptr; - int result = GetDataWriteSlot(totalSize, metaSize, &imageDataPointer, &metadataPointer); + int result = AcquireWriteSlot(totalSize, metaSize, &imageDataPointer, &metadataPointer); if (result != DEVICE_OK) return result; @@ -298,7 +298,7 @@ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Met } // Release the write slot - return ReleaseDataWriteSlot(imageDataPointer, metaSize > 0 ? static_cast(metaSize) : -1); + return FinalizeWriteSlot(imageDataPointer, metaSize > 0 ? static_cast(metaSize) : -1); } /** @@ -326,7 +326,7 @@ int DataBuffer::SetOverwriteData(bool overwrite) { * * The caller must release the slot using ReleaseDataSlot after writing is complete. */ -int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, +int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, unsigned char** imageDataPointer, unsigned char** metadataPointer) { std::lock_guard lock(slotManagementMutex_); @@ -405,7 +405,7 @@ int DataBuffer::GetDataWriteSlot(size_t imageSize, size_t metadataSize, * @param buffer The buffer to be released. * @return Error code (0 on success). */ -int DataBuffer::ReleaseDataWriteSlot(unsigned char* imageDataPointer, int actualMetadataBytes) { +int DataBuffer::FinalizeWriteSlot(unsigned char* imageDataPointer, int actualMetadataBytes) { if (imageDataPointer == nullptr) return DEVICE_ERR; diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 8e673985b..204525bbe 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -9,8 +9,8 @@ // // The buffer is organized into slots (BufferSlot objects), each of which // supports exclusive write access and shared read access. Read access is -// delivered using const pointers and is counted via an atomic counter, while -// write access requires acquiring an exclusive lock. This ensures that once a +// delivered using const pointers and is tracked via RAII-based synchronization. +// Write access is protected via an exclusive lock. This ensures that once a // read pointer is given out it cannot be misused for writing. // // COPYRIGHT: Henry Pinkard, 2025 @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include @@ -54,9 +53,6 @@ class BufferSlot { public: /** * Constructor. - * Initializes the slot with the specified starting byte offset and length. - * Also initializes atomic variables that track reader and writer access. - * * @param start The starting offset (in bytes) within the buffer. * @param length The length (in bytes) of the slot. */ @@ -68,8 +64,7 @@ class BufferSlot { ~BufferSlot(); /** - * Returns the starting offset (in bytes) of the slot in the buffer. - * + * Returns the starting offset (in bytes) of the slot. * @return The slot's start offset. */ std::size_t GetStart() const; @@ -83,11 +78,7 @@ class BufferSlot { /** * Attempts to acquire exclusive write access without blocking. - * It first tries to atomically set the write flag. - * If another writer is active or if there are active readers, - * the write lock is not acquired. - * - * @return True if the slot is locked for writing; false otherwise. + * @return True if the write lock was acquired; false otherwise. */ bool TryAcquireWriteAccess(); @@ -99,35 +90,28 @@ class BufferSlot { /** * Releases exclusive write access. - * Clears the write flag and notifies waiting readers. */ void ReleaseWriteAccess(); /** * Acquires shared read access (blocking). - * This is a blocking operation — if a writer is active, the caller waits - * until the writer releases its lock. Once available, the reader count is incremented. */ void AcquireReadAccess(); /** * Releases shared read access. - * Decrements the reader count. */ void ReleaseReadAccess(); /** * Checks if the slot is currently available for writing. - * A slot is available if there are no active readers and no active writer. - * - * @return True if available for writing, false otherwise. + * A slot is available if no thread holds either a write lock or any read lock. + * @return True if available for writing. */ bool IsAvailableForWriting() const; /** * Checks if the slot is available for acquiring read access. - * A slot is available for reading if no writer presently holds the lock. - * * @return True if available for reading. */ bool IsAvailableForReading() const; @@ -135,40 +119,14 @@ class BufferSlot { private: std::size_t start_; std::size_t length_; - std::atomic readAccessCountAtomicInt_; - std::atomic writeAtomicBool_; + // RAII-based locking using std::shared_timed_mutex. mutable std::shared_timed_mutex rwMutex_; }; -/** - * WriteSlotGuard is an RAII helper that acquires exclusive write access on a BufferSlot. - * When this guard goes out of scope, the write lock is automatically released. - */ -class WriteSlotGuard { -public: - WriteSlotGuard(BufferSlot* slot); - ~WriteSlotGuard(); - -private: - BufferSlot* slot_; -}; - -/** - * ReadSlotGuard is an RAII helper that acquires shared read access on a BufferSlot. - * When this guard goes out of scope, the read lock is automatically released. - */ -class ReadSlotGuard { -public: - ReadSlotGuard(BufferSlot* slot); - ~ReadSlotGuard(); - -private: - BufferSlot* slot_; -}; /** * DataBuffer manages a contiguous block of memory divided into BufferSlot objects - * for storing image data and metadata. It provides thread-safe access for both + * for storing image data and metadata. It ensures thread-safe access for both * reading and writing operations and supports configurable overflow behavior. * * Two data access patterns are provided: @@ -185,15 +143,10 @@ class ReadSlotGuard { */ class DataBuffer { public: - /** - * Maximum number of released slots to track. - */ static const size_t MAX_RELEASED_SLOTS = 50; /** * Constructor. - * Initializes the DataBuffer with a specified memory size in MB. - * * @param memorySizeMB The size (in megabytes) of the buffer. */ DataBuffer(unsigned int memorySizeMB); @@ -204,118 +157,95 @@ class DataBuffer { ~DataBuffer(); /** - * Allocates a contiguous block of memory for the buffer. - * - * @param memorySizeMB The amount of memory (in MB) to allocate. + * Allocates the memory buffer. + * @param memorySizeMB Size in megabytes. * @return DEVICE_OK on success. */ int AllocateBuffer(unsigned int memorySizeMB); /** - * Releases the allocated buffer. - * - * @return DEVICE_OK on success, or an error if the buffer is already released. + * Releases the memory buffer. + * @return DEVICE_OK on success. */ int ReleaseBuffer(); /** - * Inserts data into the next available slot. - * The data is stored together with its metadata and is arranged as: - * [BufferSlotRecord header][image data][serialized metadata] - * + * Inserts image data along with metadata into the buffer. * @param data Pointer to the raw image data. - * @param dataSize The image data byte count. - * @param pMd Pointer to the metadata. If null, no metadata is stored. + * @param dataSize The image data size in bytes. + * @param pMd Pointer to the metadata (can be null if not applicable). * @return DEVICE_OK on success. */ int InsertData(const unsigned char* data, size_t dataSize, const Metadata* pMd); /** - * Sets whether the buffer should overwrite older data when full. - * - * @param overwrite True to enable overwriting, false otherwise. + * Sets whether the buffer should overwrite old data when full. + * @param overwrite True to enable overwriting. * @return DEVICE_OK on success. */ int SetOverwriteData(bool overwrite); /** * Acquires a write slot large enough to hold the image data and metadata. - * On success, provides two pointers: one to the image data region and one to the metadata region. - * - * The metadataSize parameter specifies the maximum size to reserve for metadata if the exact - * size is not known at call time. When the slot is released, the metadata will be automatically - * null-terminated at its actual length, which must not exceed the reserved size. - * - * @param imageSize The number of bytes allocated for image data. - * @param metadataSize The maximum number of bytes to reserve for metadata. + * On success, returns pointers for the image data and metadata regions. + * * + * @param imageSize The number of bytes reserved for image data. + * @param metadataSize The maximum number of bytes reserved for metadata. * @param imageDataPointer On success, receives a pointer to the image data region. * @param metadataPointer On success, receives a pointer to the metadata region. * @return DEVICE_OK on success. */ - int GetDataWriteSlot(size_t imageSize, size_t metadataSize, unsigned char** imageDataPointer, unsigned char** metadataPointer); + int AcquireWriteSlot(size_t imageSize, size_t metadataSize, + unsigned char** imageDataPointer, unsigned char** metadataPointer); /** - * Releases a write slot after data has been written. - * - * @param imageDataPointer Pointer previously obtained from GetDataWriteSlot. - * This pointer references the start of the image data region. + * Finalizes (releases) a write slot after data has been written. + * * + * @param imageDataPointer Pointer previously obtained from AcquireWriteSlot. * @param actualMetadataBytes Optionally, the actual number of metadata bytes written. - * If provided and less than the maximum metadata size reserved, this value - * is used to update the header's metadataSize field. - * Defaults to -1, which means no update is performed. + * Defaults to -1 (no update). * @return DEVICE_OK on success. */ - int ReleaseDataWriteSlot(unsigned char* imageDataPointer, int actualMetadataBytes = -1); + int FinalizeWriteSlot(unsigned char* imageDataPointer, int actualMetadataBytes = -1); /** - * Releases read access for the image data region after its content has been read. - * + * Releases read access for the image data after reading. * @param imageDataPointer Pointer previously obtained from reading routines. * @return DEVICE_OK on success. */ int ReleaseDataReadPointer(const unsigned char* imageDataPointer); /** - * Retrieves and removes (consumes) the next available data entry for reading, - * and populates the provided Metadata object with the associated metadata. - * The returned pointer points to the beginning of the image data region, - * immediately after the header. - * - * @param md Metadata object to be populated from the stored blob. - * @param imageDataSize On success, returns the image data size (in bytes). - * @param waitForData If true, block until data becomes available. - * @return Pointer to the start of the image data region, or nullptr if none available. + * Retrieves and consumes the next available data entry for reading, + * populating the provided Metadata object. + * @param md Metadata object to populate. + * @param imageDataSize On success, returns the image data size in bytes. + * @param waitForData If true, blocks until data is available. + * @return Pointer to the image data region, or nullptr if none available. */ const unsigned char* PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData); /** * Peeks at the next unread data entry without consuming it. - * The header is examined so that the actual image data size (excluding header) - * is returned. - * * @param imageDataPointer On success, receives a pointer to the image data region. - * @param imageDataSize On success, returns the image data size (in bytes). - * @param md Metadata object populated from the stored metadata blob. - * @return DEVICE_OK on success, error code otherwise. + * @param imageDataSize On success, returns the image data size in bytes. + * @param md Metadata object populated from the stored metadata. + * @return DEVICE_OK on success. */ int PeekNextDataReadPointer(const unsigned char** imageDataPointer, size_t* imageDataSize, Metadata &md); /** * Peeks at the nth unread data entry without consuming it. - * (n = 0 is equivalent to PeekNextDataReadPointer.) - * - * @param n The index of the data entry to peek at (0 is next available), - * > 0 is looking back in the buffer. - * @param imageDataSize On success, returns the image data size (in bytes). - * @param md Metadata object populated from the stored metadata blob. + * (n = 0 is equivalent to PeekNextDataReadPointer). + * @param n Index of the data entry to peek at (0 for next available). + * @param imageDataSize On success, returns the image data size in bytes. + * @param md Metadata object populated from the stored metadata. * @return Pointer to the start of the image data region. */ - const unsigned char* PeekDataReadPointerAtIndex(size_t n, size_t* imageDataSize, Metadata &md); /** * Releases read access that was acquired by a peek. - * * @param imageDataPointer Pointer previously obtained from a peek. * @return DEVICE_OK on success. */ @@ -323,54 +253,45 @@ class DataBuffer { /** * Returns the total buffer memory size (in MB). - * * @return Buffer size in MB. */ unsigned int GetMemorySizeMB() const; /** - * Returns the number of occupied slots in the buffer. - * + * Returns the number of occupied buffer slots. * @return Occupied slot count. */ size_t GetOccupiedSlotCount() const; /** - * Returns the total occupied memory (in bytes). - * + * Returns the total occupied memory in bytes. * @return Sum of active slot lengths. */ size_t GetOccupiedMemory() const; /** - * Returns the amount of free memory (in bytes) remaining. - * + * Returns the amount of free memory remaining in bytes. * @return Free byte count. */ size_t GetFreeMemory() const; /** - * Indicates whether a buffer overflow occurred (i.e. an insert failed because - * no appropriate slot was available). - * - * @return True if overflow has happened, false otherwise. + * Indicates whether a buffer overflow has occurred. + * @return True if an insert failed (buffer full), false otherwise. */ bool Overflow() const; /** * Returns the number of unread slots in the buffer. - * * @return Unread slot count. */ long GetActiveSlotCount() const; /** - * Reinitializes the DataBuffer by clearing its structures, releasing the current - * buffer, and allocating a new one. - * + * Reinitializes the DataBuffer, clearing its structures and allocating a new buffer. * @param memorySizeMB New buffer size (in MB). * @return DEVICE_OK on success. - * @throws std::runtime_error if any slot is still actively in use. + * @throws std::runtime_error if any slot is still in use. */ int ReinitializeBuffer(unsigned int memorySizeMB); @@ -468,9 +389,8 @@ class DataBuffer { */ void InsertFreeRegion(size_t offset, size_t size); - // Pointer to the allocated block. + // Memory managed by the DataBuffer. unsigned char* buffer_; - // Total allocated size in bytes. size_t bufferSize_; // Whether to overwrite old data when full. @@ -479,15 +399,13 @@ class DataBuffer { // Overflow flag (set if insert fails due to full buffer). bool overflow_; - // Data structures used to track active slots. + // Tracking of active slots and free memory regions. std::vector> activeSlotsVector_; std::map activeSlotsByStart_; // Free region list for non-overwrite mode. // Map from starting offset -> region size (in bytes). std::map freeRegions_; - - // (Optional) Legacy structure—can be deprecated as freeRegions_ is used instead. std::vector releasedSlots_; // Next free offset within the buffer. @@ -497,11 +415,11 @@ class DataBuffer { // Index tracking the next slot for read. size_t currentSlotIndex_; - // Synchronization primitives for slot management. + // Synchronization for slot management. std::condition_variable dataCV_; mutable std::mutex slotManagementMutex_; - // Add these new members for multithreaded copying + // Members for multithreaded copying. std::shared_ptr threadPool_; std::shared_ptr tasksMemCopy_; }; From f37b964077e027a1004bad5b7e85cb4a27427459 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:20:51 -0800 Subject: [PATCH 15/46] clean up and remove headers from buffer --- MMCore/BufferAdapter.cpp | 82 +++++++-- MMCore/Buffer_v2.cpp | 386 ++++++++------------------------------- MMCore/Buffer_v2.h | 191 ++++++++----------- 3 files changed, 223 insertions(+), 436 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index d686da7a3..abf020737 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -383,33 +383,91 @@ void BufferAdapter::ReleaseReadAccess(const unsigned char* ptr) { } unsigned BufferAdapter::GetImageWidth(const unsigned char* ptr) const { - if (!useV2_) throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); - return v2Buffer_->GetImageWidth(ptr); + if (!useV2_) + throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); + Metadata md; + if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + throw CMMError("Failed to extract metadata for image width"); + std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue(); + return static_cast(atoi(sVal.c_str())); } unsigned BufferAdapter::GetImageHeight(const unsigned char* ptr) const { - if (!useV2_) throw CMMError("GetImageHeight(ptr) only supported with V2 buffer"); - return v2Buffer_->GetImageHeight(ptr); + if (!useV2_) + throw CMMError("GetImageHeight(ptr) only supported with V2 buffer"); + Metadata md; + if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + throw CMMError("Failed to extract metadata for image height"); + std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue(); + return static_cast(atoi(sVal.c_str())); } unsigned BufferAdapter::GetBytesPerPixel(const unsigned char* ptr) const { - if (!useV2_) throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); - return v2Buffer_->GetBytesPerPixel(ptr); + if (!useV2_) + throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); + Metadata md; + if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + throw CMMError("Failed to extract metadata for bytes per pixel"); + std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + if (pixelType == MM::g_Keyword_PixelType_GRAY8) + return 1; + else if (pixelType == MM::g_Keyword_PixelType_GRAY16) + return 2; + else if (pixelType == MM::g_Keyword_PixelType_GRAY32 || + pixelType == MM::g_Keyword_PixelType_RGB32) + return 4; + else if (pixelType == MM::g_Keyword_PixelType_RGB64) + return 8; + throw CMMError("Unknown pixel type for bytes per pixel"); } unsigned BufferAdapter::GetImageBitDepth(const unsigned char* ptr) const { - if (!useV2_) throw CMMError("GetImageBitDepth(ptr) only supported with V2 buffer"); - return v2Buffer_->GetImageBitDepth(ptr); + if (!useV2_) + throw CMMError("GetImageBitDepth(ptr) only supported with V2 buffer"); + Metadata md; + if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + throw CMMError("Failed to extract metadata for image bit depth"); + std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + if (pixelType == MM::g_Keyword_PixelType_GRAY8) + return 8; + else if (pixelType == MM::g_Keyword_PixelType_GRAY16) + return 16; + else if (pixelType == MM::g_Keyword_PixelType_GRAY32 || + pixelType == MM::g_Keyword_PixelType_RGB32) + return 32; + else if (pixelType == MM::g_Keyword_PixelType_RGB64) + return 64; + throw CMMError("Unknown pixel type for image bit depth"); } unsigned BufferAdapter::GetNumberOfComponents(const unsigned char* ptr) const { - if (!useV2_) throw CMMError("GetNumberOfComponents(ptr) only supported with V2 buffer"); - return v2Buffer_->GetNumberOfComponents(ptr); + if (!useV2_) + throw CMMError("GetNumberOfComponents(ptr) only supported with V2 buffer"); + Metadata md; + if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + throw CMMError("Failed to extract metadata for number of components"); + std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + if (pixelType == MM::g_Keyword_PixelType_GRAY8 || + pixelType == MM::g_Keyword_PixelType_GRAY16 || + pixelType == MM::g_Keyword_PixelType_GRAY32) + return 1; + else if (pixelType == MM::g_Keyword_PixelType_RGB32 || + pixelType == MM::g_Keyword_PixelType_RGB64) + return 4; + throw CMMError("Unknown pixel type for number of components"); } long BufferAdapter::GetImageBufferSize(const unsigned char* ptr) const { - if (!useV2_) throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); - return v2Buffer_->GetImageBufferSize(ptr); + if (!useV2_) + throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); + Metadata md; + if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + throw CMMError("Failed to extract metadata for image buffer size"); + // Suppose the image size is computed from width, height, and bytes per pixel: + unsigned width = static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue().c_str())); + unsigned height = static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue().c_str())); + unsigned bpp = GetBytesPerPixel(ptr); + return static_cast(width * height * bpp); } bool BufferAdapter::SetOverwriteData(bool overwrite) { diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 207be81b4..05e7f65fb 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -65,12 +65,7 @@ Metadata Handling: #include #include #include "TaskSet_CopyMemory.h" - -// New internal header that precedes every slot's data. -struct BufferSlotRecord { - size_t imageSize; - size_t metadataSize; -}; +#include /////////////////////////////////////////////////////////////////////////////// // BufferSlot Implementation @@ -80,10 +75,10 @@ struct BufferSlotRecord { * Constructor. * Initializes the slot with the specified starting byte offset and length. */ -BufferSlot::BufferSlot(std::size_t start, std::size_t length) - : start_(start), length_(length) +BufferSlot::BufferSlot(std::size_t start, std::size_t totalLength, size_t imageSize, size_t metadataSize) + : start_(start), length_(totalLength), imageSize_(imageSize), metadataSize_(metadataSize) { - // The std::shared_timed_mutex (rwMutex_) is default-constructed. + } /** @@ -273,32 +268,25 @@ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Met metaStr = pMd->Serialize(); metaSize = metaStr.size(); } - // Total size is header + image data + metadata - size_t totalSize = sizeof(BufferSlotRecord) + dataSize + metaSize; + size_t totalSlotSize = dataSize + metaSize; unsigned char* imageDataPointer = nullptr; unsigned char* metadataPointer = nullptr; - int result = AcquireWriteSlot(totalSize, metaSize, &imageDataPointer, &metadataPointer); + + // Pass the actual image and metadata sizes to AcquireWriteSlot. + int result = AcquireWriteSlot(dataSize, metaSize, &imageDataPointer, &metadataPointer); if (result != DEVICE_OK) return result; - - // The externally returned imageDataPointer points to the image data. - // Write out the header by subtracting the header size. - BufferSlotRecord* headerPointer = reinterpret_cast(imageDataPointer - sizeof(BufferSlotRecord)); - headerPointer->imageSize = dataSize; - headerPointer->metadataSize = metaSize; - - // Copy the image data into the allocated slot (imageDataPointer is already at the image data). + // Copy the image data. tasksMemCopy_->MemCopy((void*)imageDataPointer, data, dataSize); - // If metadata is available, copy it right after the image data. + // Copy the metadata. if (metaSize > 0) { - unsigned char* metaPtr = imageDataPointer + dataSize; - std::memcpy(metaPtr, metaStr.data(), metaSize); + std::memcpy(metadataPointer, metaStr.data(), metaSize); } - - // Release the write slot - return FinalizeWriteSlot(imageDataPointer, metaSize > 0 ? static_cast(metaSize) : -1); + + // Finalize the write slot. + return FinalizeWriteSlot(imageDataPointer, metaSize); } /** @@ -334,41 +322,40 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, return DEVICE_ERR; } - // Determine the required alignment based on our header. - size_t alignment = alignof(BufferSlotRecord); - // Compute the raw total size (header + image data + metadata) and then round up. - size_t rawTotalSize = sizeof(BufferSlotRecord) + imageSize + metadataSize; + + size_t alignment = alignof(std::max_align_t); + // Total size is the image data plus metadata. + size_t rawTotalSize = imageSize + metadataSize; size_t totalSlotSize = AlignTo(rawTotalSize, alignment); size_t candidateStart = 0; if (!overwriteWhenFull_) { // FIRST: Look for a fit in recycled slots (releasedSlots_) for (int i = static_cast(releasedSlots_.size()) - 1; i >= 0; i--) { - // Align the candidate start position + // Align the candidate start position. candidateStart = AlignTo(releasedSlots_[i], alignment); - // Find the free region that contains this position + // Find the free region that contains this position. auto it = freeRegions_.upper_bound(candidateStart); if (it != freeRegions_.begin()) { --it; size_t freeStart = it->first; size_t freeEnd = freeStart + it->second; - // Check if the position is actually within this free region + // Check if the position is actually within this free region. if (candidateStart >= freeStart && candidateStart < freeEnd && candidateStart + totalSlotSize <= freeEnd) { releasedSlots_.erase(releasedSlots_.begin() + i); - return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, - imageDataPointer, metadataPointer, true); + return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, metadataSize, + imageDataPointer, metadataPointer, true); } } - // If we get here, this released slot position isn't in a free region - // so remove it as it's no longer valid + // If we get here, this released slot position isn't in a free region, + // so remove it as it's no longer valid. releasedSlots_.erase(releasedSlots_.begin() + i); } - // SECOND: Look in the free-region list as fallback. for (auto it = freeRegions_.begin(); it != freeRegions_.end(); ++it) { // Align the free region start. @@ -376,8 +363,8 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, // Check if the free region has enough space after alignment. if (it->first + it->second >= alignedCandidate + totalSlotSize) { candidateStart = alignedCandidate; - return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, - imageDataPointer, metadataPointer, true); + return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, metadataSize, + imageDataPointer, metadataPointer, true); } } // No recycled slot or free region can satisfy the allocation. @@ -393,8 +380,8 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, } nextAllocOffset_ = candidateStart + totalSlotSize; // Register a new slot using the selected candidateStart. - return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, - imageDataPointer, metadataPointer, false); + return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, metadataSize, + imageDataPointer, metadataPointer, false); } } @@ -405,38 +392,20 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, * @param buffer The buffer to be released. * @return Error code (0 on success). */ -int DataBuffer::FinalizeWriteSlot(unsigned char* imageDataPointer, int actualMetadataBytes) { +int DataBuffer::FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actualMetadataBytes) { if (imageDataPointer == nullptr) return DEVICE_ERR; - std::lock_guard lock(slotManagementMutex_); - - // Convert the externally provided imageDataPointer (which points to the image data) - // to the true slot start (header) by subtracting sizeof(BufferSlotRecord). - unsigned char* headerPointer = imageDataPointer - sizeof(BufferSlotRecord); - size_t offset = headerPointer - buffer_; - - // Locate the slot using the true header offset. - auto it = activeSlotsByStart_.find(offset); - if (it == activeSlotsByStart_.end()) + // Use a unique_lock so that we can pass it to FindSlotForPointer + std::unique_lock lock(slotManagementMutex_); + BufferSlot* slot = const_cast(FindSlotForPointer(imageDataPointer)); + if (!slot) return DEVICE_ERR; // Slot not found - // Release the write access - BufferSlot* slot = it->second; slot->ReleaseWriteAccess(); - // If a valid actual metadata byte count is provided (i.e. not -1), - // update the header->metadataSize to the actual metadata length if it is less. - if (actualMetadataBytes != -1) { - BufferSlotRecord* hdr = reinterpret_cast(headerPointer); - if (static_cast(actualMetadataBytes) < hdr->metadataSize) { - hdr->metadataSize = actualMetadataBytes; - } - } - // Notify any waiting threads that new data is available. dataCV_.notify_all(); - return DEVICE_OK; } @@ -447,14 +416,13 @@ int DataBuffer::FinalizeWriteSlot(unsigned char* imageDataPointer, int actualMet * (releasedSlots_) and removes the slot from the active slot map and activeSlots_. */ int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { - if (imageDataPointer == nullptr ) + if (imageDataPointer == nullptr) return DEVICE_ERR; - + std::unique_lock lock(slotManagementMutex_); - - // Compute the header pointer by subtracting the header size. - const unsigned char* headerPointer = imageDataPointer - sizeof(BufferSlotRecord); - size_t offset = headerPointer - buffer_; + + // The slot starts directly at imageDataPointer. + size_t offset = imageDataPointer - buffer_; auto it = activeSlotsByStart_.find(offset); if (it == activeSlotsByStart_.end()) @@ -463,10 +431,10 @@ int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { BufferSlot* slot = it->second; // Release the read access (this does NOT remove the slot from the active list) slot->ReleaseReadAccess(); - + if (!overwriteWhenFull_) { - // Now check if the slot is not being accessed - // (i.e. this was the last/readers and no writer holds it) + // Now check if the slot is not being accessed. + // (i.e. this was the last reader and no writer holds it) if (slot->IsAvailableForWriting() && slot->IsAvailableForReading()) { RemoveSlotFromActiveTracking(offset, it); } else { @@ -474,7 +442,6 @@ int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { } } - return DEVICE_OK; } @@ -498,16 +465,11 @@ const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *im // Process the slot. size_t slotStart = slot->GetStart(); - const BufferSlotRecord* header = reinterpret_cast(buffer_ + slotStart); - const unsigned char* imageDataPointer = buffer_ + slotStart + sizeof(BufferSlotRecord); - *imageDataSize = header->imageSize; + const unsigned char* imageDataPointer = buffer_ + slotStart; + *imageDataSize = slot->GetImageSize(); - if (header->metadataSize > 0) { - const char* metaDataStart = reinterpret_cast(imageDataPointer + header->imageSize); - md.Restore(metaDataStart); - } else { - md.Clear(); - } + // Use the dedicated metadata extraction function. + this->ExtractMetadata(imageDataPointer, md); return imageDataPointer; } @@ -529,17 +491,13 @@ int DataBuffer::PeekNextDataReadPointer(const unsigned char** imageDataPointer, currentSlot->AcquireReadAccess(); std::unique_lock lock(slotManagementMutex_); - *imageDataPointer = buffer_ + currentSlot->GetStart() + sizeof(BufferSlotRecord); - const BufferSlotRecord* headerPointer = reinterpret_cast( (*imageDataPointer) - sizeof(BufferSlotRecord) ); - *imageDataSize = headerPointer->imageSize; + *imageDataPointer = buffer_ + currentSlot->GetStart(); + size_t imgSize = currentSlot->GetImageSize(); + *imageDataSize = imgSize; + + // Use the dedicated metadata extraction function. + this->ExtractMetadata(*imageDataPointer, md); - // Populate the metadata from the slot. - std::string metaStr; - if (headerPointer->metadataSize > 0) { - const char* metaDataStart = reinterpret_cast(*imageDataPointer + headerPointer->imageSize); - metaStr.assign(metaDataStart, headerPointer->metadataSize); - } - md.Restore(metaStr.c_str()); return DEVICE_OK; } @@ -560,24 +518,12 @@ const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* im currentSlot->AcquireReadAccess(); - // Obtain pointer to the image data by skipping over the header. - const unsigned char* imageDataPointer = buffer_ + currentSlot->GetStart() + sizeof(BufferSlotRecord); - const BufferSlotRecord* headerPointer = reinterpret_cast(imageDataPointer - sizeof(BufferSlotRecord)); - if (imageDataSize != nullptr) { - *imageDataSize = headerPointer->imageSize; - } - - // Retrieve the serialized metadata from the slot. - std::string metaStr; - if (headerPointer->metadataSize > 0) { - const char* metaDataStart = reinterpret_cast(imageDataPointer + headerPointer->imageSize); - metaStr.assign(metaDataStart, headerPointer->metadataSize); - } + const unsigned char* imageDataPointer = buffer_ + currentSlot->GetStart(); + size_t imgSize = currentSlot->GetImageSize(); + *imageDataSize = imgSize; - // Restore the metadata - // This is analogous to what is done in FrameBuffer.cpp: - // metadata_.Restore(md.Serialize().c_str()); - md.Restore(metaStr.c_str()); + + this->ExtractMetadata(imageDataPointer, md); // Return a pointer to the image data only. return imageDataPointer; @@ -664,203 +610,31 @@ long DataBuffer::GetActiveSlotCount() const { return static_cast(activeSlotsVector_.size()); } -unsigned DataBuffer::GetImageWidth(const unsigned char* imageDataPtr) const { - const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); - if (header->metadataSize > 0) { - Metadata md; - md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); - return static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue().c_str())); - } - throw std::runtime_error("No metadata available for image width"); -} - -unsigned DataBuffer::GetImageHeight(const unsigned char* imageDataPtr) const { - const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); - if (header->metadataSize > 0) { - Metadata md; - md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); - return static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue().c_str())); - } - throw std::runtime_error("No metadata available for image height"); -} - -unsigned DataBuffer::GetBytesPerPixel(const unsigned char* imageDataPtr) const { - const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); - if (header->metadataSize > 0) { - Metadata md; - md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); - std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - if (pixelType == MM::g_Keyword_PixelType_GRAY8) - return 1; - else if (pixelType == MM::g_Keyword_PixelType_GRAY16) - return 2; - else if (pixelType == MM::g_Keyword_PixelType_GRAY32) - return 4; - else if (pixelType == MM::g_Keyword_PixelType_RGB32) - return 4; - else if (pixelType == MM::g_Keyword_PixelType_RGB64) - return 8; - } - throw std::runtime_error("No metadata available for bytes per pixel"); -} - - -unsigned DataBuffer::GetImageBitDepth(const unsigned char* imageDataPtr) const { - const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); - if (header->metadataSize > 0) { - Metadata md; - md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); - std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - if (pixelType == MM::g_Keyword_PixelType_GRAY8) - return 8; - else if (pixelType == MM::g_Keyword_PixelType_GRAY16) - return 16; - else if (pixelType == MM::g_Keyword_PixelType_GRAY32) - return 32; - else if (pixelType == MM::g_Keyword_PixelType_RGB32) - return 32; - else if (pixelType == MM::g_Keyword_PixelType_RGB64) - return 64; - } - throw std::runtime_error("No metadata available for bit depth"); -} - - -unsigned DataBuffer::GetNumberOfComponents(const unsigned char* imageDataPtr) const { - const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); - if (header->metadataSize > 0) { - Metadata md; - md.Restore(reinterpret_cast(imageDataPtr + header->imageSize)); - std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - if (pixelType == MM::g_Keyword_PixelType_GRAY8 || - pixelType == MM::g_Keyword_PixelType_GRAY16 || - pixelType == MM::g_Keyword_PixelType_GRAY32) - return 1; - else if (pixelType == MM::g_Keyword_PixelType_RGB32 || - pixelType == MM::g_Keyword_PixelType_RGB64) - return 4; - } - throw std::runtime_error("No metadata available for number of components"); -} - -long DataBuffer::GetImageBufferSize(const unsigned char* imageDataPtr) const { - const BufferSlotRecord* header = reinterpret_cast(imageDataPtr - sizeof(BufferSlotRecord)); - return header->imageSize; -} - -void DataBuffer::RemoveSlotFromActiveTracking(size_t offset, std::map::iterator it) { - // Assert that caller holds the mutex. - // This assertion checks that the mutex is already locked by ensuring try_lock() fails. - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - - // Only add to released slots in non-overwrite mode. - if (!overwriteWhenFull_) { - if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) - releasedSlots_.erase(releasedSlots_.begin()); - releasedSlots_.push_back(offset); - } - - // Update the freeRegions_ list to include the freed region and merge with adjacent regions - size_t freedRegionSize = it->second->GetLength(); - size_t newRegionStart = offset; - size_t newRegionEnd = offset + freedRegionSize; - - // Find the free region that starts at or after newEnd. - auto right = freeRegions_.lower_bound(newRegionEnd); - - // Check if there is a free region immediately preceding the new region. - auto left = freeRegions_.lower_bound(newRegionStart); - if (left != freeRegions_.begin()) { - auto prev = std::prev(left); - // If the previous region's end matches the new region's start... - if (prev->first + prev->second == newRegionStart) { - newRegionStart = prev->first; - freeRegions_.erase(prev); - } - } - - // Check if the region immediately to the right can be merged. - if (right != freeRegions_.end() && right->first == newRegionEnd) { - newRegionEnd = right->first + right->second; - freeRegions_.erase(right); - } - - // Insert the merged (or standalone) free region. - size_t newRegionSize = (newRegionEnd > newRegionStart ? newRegionEnd - newRegionStart : 0); - if (newRegionSize > 0) { - freeRegions_[newRegionStart] = newRegionSize; - } +int DataBuffer::ExtractMetadata(const unsigned char* imageDataPtr, Metadata &md) const { + std::unique_lock lock(slotManagementMutex_); + const BufferSlot* slot = FindSlotForPointer(imageDataPtr); + if (!slot) + return DEVICE_ERR; // Slot not found or invalid pointer - // Remove the slot from the active tracking structures. - activeSlotsByStart_.erase(it); - for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { - if (vecIt->get()->GetStart() == offset) { - // Determine the index being removed. - size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); - activeSlotsVector_.erase(vecIt); - // Adjust the currentSlotIndex_; if the deleted slot was before it, decrement. - if (currentSlotIndex_ > indexDeleted) - currentSlotIndex_--; - break; - } + // If no metadata is stored in this slot, clear the metadata object. + if (slot->GetMetadataSize() == 0) { + md.Clear(); + } else { + // Metadata is stored immediately after the image data. + const char* metaDataStart = reinterpret_cast(imageDataPtr + slot->GetImageSize()); + // Create a temporary string from the metadata region. + std::string metaStr(metaDataStart, slot->GetMetadataSize()); + // Restore the metadata from the serialized string. + md.Restore(metaStr.c_str()); } + return DEVICE_OK; } -int DataBuffer::CreateAndRegisterNewSlot(size_t candidateStart, - size_t totalSlotSize, - size_t imageDataSize, - unsigned char** imageDataPointer, - unsigned char** metadataPointer, - bool fromFreeRegion) { - // Assert that caller holds the mutex. - // This assertion checks that the mutex is already locked by ensuring try_lock() fails. - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - - // Use the provided candidateStart to create and register a new slot. - BufferSlot* newSlot = new BufferSlot(candidateStart, totalSlotSize); - // Acquire the write access - newSlot->AcquireWriteAccess(); - activeSlotsVector_.push_back(std::unique_ptr(newSlot)); - activeSlotsByStart_[candidateStart] = newSlot; - - // Initialize header (the metadata sizes will be updated later). - BufferSlotRecord* headerPointer = reinterpret_cast(buffer_ + candidateStart); - headerPointer->imageSize = imageDataSize; - headerPointer->metadataSize = totalSlotSize - imageDataSize - sizeof(BufferSlotRecord); - - // Set the output pointers - *imageDataPointer = buffer_ + candidateStart + sizeof(BufferSlotRecord); - *metadataPointer = *imageDataPointer + imageDataSize; - - // If the candidate comes from a free region, update the freeRegions_ map. - if (fromFreeRegion) { - // Adjust the free region list to remove the allocated block. - auto it = freeRegions_.upper_bound(candidateStart); - if (it != freeRegions_.begin()) { - --it; - size_t freeRegionStart = it->first; - size_t freeRegionSize = it->second; - size_t freeRegionEnd = freeRegionStart + freeRegionSize; - // candidateStart must lie within [freeRegionStart, freeRegionEnd). - if (candidateStart >= freeRegionStart && - candidateStart + totalSlotSize <= freeRegionEnd) - { - freeRegions_.erase(it); - // If there is a gap before the candidate slot, add that as a free region. - if (candidateStart > freeRegionStart) { - size_t gap = candidateStart - freeRegionStart; - if (gap > 0) - freeRegions_.insert({freeRegionStart, gap}); - } - // If there is a gap after the candidate slot, add that as well. - if (freeRegionEnd > candidateStart + totalSlotSize) { - size_t gap = freeRegionEnd - (candidateStart + totalSlotSize); - if (gap > 0) - freeRegions_.insert({candidateStart + totalSlotSize, gap}); - } - } - } - } - - return DEVICE_OK; +// NOTE: Caller must hold slotManagementMutex_ for thread safety. +const BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* imageDataPtr) const { + if (buffer_ == nullptr) + return nullptr; + std::size_t offset = imageDataPtr - buffer_; + auto it = activeSlotsByStart_.find(offset); + return (it != activeSlotsByStart_.end()) ? it->second : nullptr; } diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 204525bbe..f31767d43 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -34,7 +34,7 @@ #include "../MMDevice/ImageMetadata.h" #include "../MMDevice/MMDevice.h" #include -#include +#include #include #include #include @@ -52,11 +52,16 @@ class BufferSlot { public: /** - * Constructor. + * Constructs a BufferSlot with all sizes specified up front. + * * @param start The starting offset (in bytes) within the buffer. - * @param length The length (in bytes) of the slot. + * @param totalLength The total length (in bytes) reserved for this slot, typically + * an aligned size (which includes image data and metadata). + * @param imageSize The exact number of bytes for the image data. + * @param metadataSize The exact number of bytes for the metadata. */ - BufferSlot(std::size_t start, std::size_t length); + BufferSlot(std::size_t start, std::size_t totalLength, size_t imageSize, size_t metadataSize) + : start_(start), length_(totalLength), imageSize_(imageSize), metadataSize_(metadataSize) {} /** * Destructor. @@ -67,79 +72,92 @@ class BufferSlot { * Returns the starting offset (in bytes) of the slot. * @return The slot's start offset. */ - std::size_t GetStart() const; + std::size_t GetStart() const { return start_; } /** * Returns the length (in bytes) of the slot. * * @return The slot's length. */ - std::size_t GetLength() const; + std::size_t GetLength() const { return length_; } + + /** + * Returns the size (in bytes) of the image data in the slot. + * @return The image data size. + */ + size_t GetImageSize() const { return imageSize_; } + + /** + * Returns the size (in bytes) of the metadata in the slot. + * @return The metadata size. + */ + size_t GetMetadataSize() const { return metadataSize_; } /** * Attempts to acquire exclusive write access without blocking. * @return True if the write lock was acquired; false otherwise. */ - bool TryAcquireWriteAccess(); + bool TryAcquireWriteAccess() { return rwMutex_.try_lock(); } /** * Acquires exclusive write access (blocking call). * This method will block until exclusive access is granted. */ - void AcquireWriteAccess(); + void AcquireWriteAccess() { rwMutex_.lock(); } /** * Releases exclusive write access. */ - void ReleaseWriteAccess(); + void ReleaseWriteAccess() { rwMutex_.unlock(); } /** * Acquires shared read access (blocking). */ - void AcquireReadAccess(); + void AcquireReadAccess() { rwMutex_.lock_shared(); } /** * Releases shared read access. */ - void ReleaseReadAccess(); + void ReleaseReadAccess() { rwMutex_.unlock_shared(); } /** * Checks if the slot is currently available for writing. * A slot is available if no thread holds either a write lock or any read lock. * @return True if available for writing. */ - bool IsAvailableForWriting() const; + bool IsAvailableForWriting() const { + if (rwMutex_.try_lock()) { + rwMutex_.unlock(); + return true; + } + return false; + } /** * Checks if the slot is available for acquiring read access. * @return True if available for reading. */ - bool IsAvailableForReading() const; + bool IsAvailableForReading() const { + if (rwMutex_.try_lock_shared()) { + rwMutex_.unlock_shared(); + return true; + } + return false; + } private: std::size_t start_; std::size_t length_; - // RAII-based locking using std::shared_timed_mutex. + size_t imageSize_; + size_t metadataSize_; mutable std::shared_timed_mutex rwMutex_; }; - /** * DataBuffer manages a contiguous block of memory divided into BufferSlot objects - * for storing image data and metadata. It ensures thread-safe access for both - * reading and writing operations and supports configurable overflow behavior. - * - * Two data access patterns are provided: - * 1. Copy-based access. - * 2. Direct pointer access with an explicit release. - * - * Each slot begins with a header (BufferSlotRecord) that stores: - * - The image data length - * - The serialized metadata length (which might be zero) - * - * The user-visible routines (e.g. InsertData and CopyNextDataAndMetadata) - * automatically pack and unpack the header so that the caller need not worry - * about the extra bytes. + * for storing image data and metadata. Each slot in memory holds + * only the image data (followed immediately by metadata), while header information + * is maintained in the BufferSlot objects. */ class DataBuffer { public: @@ -188,7 +206,7 @@ class DataBuffer { /** * Acquires a write slot large enough to hold the image data and metadata. * On success, returns pointers for the image data and metadata regions. - * * + * * @param imageSize The number of bytes reserved for image data. * @param metadataSize The maximum number of bytes reserved for metadata. * @param imageDataPointer On success, receives a pointer to the image data region. @@ -200,13 +218,13 @@ class DataBuffer { /** * Finalizes (releases) a write slot after data has been written. - * * + * Requires the actual number of metadata bytes written. + * * @param imageDataPointer Pointer previously obtained from AcquireWriteSlot. - * @param actualMetadataBytes Optionally, the actual number of metadata bytes written. - * Defaults to -1 (no update). + * @param actualMetadataBytes The actual number of metadata bytes written. * @return DEVICE_OK on success. */ - int FinalizeWriteSlot(unsigned char* imageDataPointer, int actualMetadataBytes = -1); + int FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actualMetadataBytes); /** * Releases read access for the image data after reading. @@ -296,98 +314,24 @@ class DataBuffer { int ReinitializeBuffer(unsigned int memorySizeMB); /** - * Returns the image width from the metadata stored with the image data. - * - * @param imageDataPtr Pointer to the image data. - * @return Image width. - */ - unsigned GetImageWidth(const unsigned char* imageDataPtr) const; - - /** - * Returns the image height from the metadata stored with the image data. - * - * @param imageDataPtr Pointer to the image data. - * @return Image height. - */ - unsigned GetImageHeight(const unsigned char* imageDataPtr) const; - - /** - * Returns the bytes per pixel from the metadata stored with the image data. + * Extracts and deserializes the metadata associated with the given image data pointer. + * Internally, it locates the corresponding slot and reads the metadata stored + * immediately after the image data. * * @param imageDataPtr Pointer to the image data. - * @return Bytes per pixel. - */ - unsigned GetBytesPerPixel(const unsigned char* imageDataPtr) const; - - /** - * Returns the image bit depth from the metadata stored with the image data. - * - * @param imageDataPtr Pointer to the image data. - * @return Image bit depth. - */ - unsigned GetImageBitDepth(const unsigned char* imageDataPtr) const; - - /** - * Returns the number of components in the image data from the metadata stored with the image data. - * - * @param imageDataPtr Pointer to the image data. - * @return Number of components. - */ - unsigned GetNumberOfComponents(const unsigned char* imageDataPtr) const; - - /** - * Returns the image buffer size from the metadata stored with the image data. - * - * @param imageDataPtr Pointer to the image data. - * @return Image buffer size. + * @param md Metadata object to populate. + * @return DEVICE_OK on success, or an error code if extraction fails. */ - long GetImageBufferSize(const unsigned char* imageDataPtr) const; + int ExtractMetadata(const unsigned char* imageDataPtr, Metadata &md) const; private: /** - * Removes a slot from active tracking and adds it to the free region list. - * Caller must hold slotManagementMutex_. - * - * @param offset The buffer offset of the slot to remove. - * @param it Iterator to the slot in activeSlotsByStart_. - */ - void RemoveSlotFromActiveTracking(size_t offset, std::map::iterator it); - - /** - * Creates a new BufferSlot at an allocated region, registers it in the internal - * tracking structures, initializes its header, and sets the output pointers for image - * data and metadata. - * - * Allocation is performed differently based on the overwrite mode: - * - * - Overwrite mode: - * Uses nextAllocOffset_ with wrap-around. - * - * - Non-overwrite mode: - * First, we try to reuse recycled slots in order of their release. If none are - * available or usable, we then check if there is a free region large enough to hold - * the slot. If there is, we use that region. If there is no suitable region, an exception is - * thrown. + * Internal helper function that finds the slot for a given pointer (remains hidden). * - * @param candidateStart The starting offset candidate for the slot. - * @param totalSlotSize The total size (in bytes) of the slot, including header, image data, and metadata. - * @param imageDataSize The size (in bytes) to reserve for the image data. - * @param imageDataPointer Output pointer for the caller to access the image data region. - * @param metadataPointer Output pointer for the caller to access the metadata region. - * @param fromFreeRegion If true, indicates that the candidate was selected from a free region. - * @return DEVICE_OK on success, or an error code if allocation fails. - */ - int CreateAndRegisterNewSlot(size_t candidateStart, size_t totalSlotSize, size_t imageDataSize, - unsigned char** imageDataPointer, unsigned char** metadataPointer, - bool fromFreeRegion); - - /** - * Inserts a free region into the freeRegions_ list, merging with adjacent regions if necessary. - * - * @param offset The starting offset of the freed region. - * @param size The size (in bytes) of the freed region. + * @param imageDataPtr Pointer to the image data. + * @return Pointer to the corresponding BufferSlot, or nullptr if not found. */ - void InsertFreeRegion(size_t offset, size_t size); + const BufferSlot* FindSlotForPointer(const unsigned char* imageDataPtr) const; // Memory managed by the DataBuffer. unsigned char* buffer_; @@ -422,4 +366,15 @@ class DataBuffer { // Members for multithreaded copying. std::shared_ptr threadPool_; std::shared_ptr tasksMemCopy_; + + /** + * Internal helper to register a new slot. + */ + int CreateAndRegisterNewSlot(size_t candidateStart, size_t totalSlotSize, size_t imageDataSize, + size_t metadataSize, + unsigned char** imageDataPointer, unsigned char** metadataPointer, + bool fromFreeRegion); + + void RemoveSlotFromActiveTracking(size_t offset, std::map::iterator it); + void InsertFreeRegion(size_t offset, size_t size); }; From 8564b15a31aa7b512499f765c8dc8d70cece68e1 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:00:08 -0800 Subject: [PATCH 16/46] restored delted functions, refactor, and recycle buffer slots --- MMCore/Buffer_v2.cpp | 325 +++++++++++++++++++++++++------------------ MMCore/Buffer_v2.h | 32 ++++- 2 files changed, 218 insertions(+), 139 deletions(-) diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 05e7f65fb..9f36a89ad 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -67,110 +67,6 @@ Metadata Handling: #include "TaskSet_CopyMemory.h" #include -/////////////////////////////////////////////////////////////////////////////// -// BufferSlot Implementation -/////////////////////////////////////////////////////////////////////////////// - -/** - * Constructor. - * Initializes the slot with the specified starting byte offset and length. - */ -BufferSlot::BufferSlot(std::size_t start, std::size_t totalLength, size_t imageSize, size_t metadataSize) - : start_(start), length_(totalLength), imageSize_(imageSize), metadataSize_(metadataSize) -{ - -} - -/** - * Destructor. - */ -BufferSlot::~BufferSlot() { - // No explicit cleanup required. -} - -/** - * Returns the starting offset (in bytes) of the slot. - */ -std::size_t BufferSlot::GetStart() const { - return start_; -} - -/** - * Returns the length (in bytes) of the slot. - */ -std::size_t BufferSlot::GetLength() const { - return length_; -} - -/** - * Attempts to acquire exclusive write access without blocking. - * - * @return True if the exclusive lock was acquired, false otherwise. - */ -bool BufferSlot::TryAcquireWriteAccess() { - return rwMutex_.try_lock(); -} - -/** - * Acquires exclusive write access (blocking call). - * This method will block until exclusive access is granted. - */ -void BufferSlot::AcquireWriteAccess() { - rwMutex_.lock(); -} - -/** - * Releases exclusive write access. - */ -void BufferSlot::ReleaseWriteAccess() { - rwMutex_.unlock(); -} - -/** - * Acquires shared read access (blocking). - */ -void BufferSlot::AcquireReadAccess() { - rwMutex_.lock_shared(); -} - -/** - * Releases shared read access. - */ -void BufferSlot::ReleaseReadAccess() { - rwMutex_.unlock_shared(); -} - -/** - * Checks if the slot is available for writing. - * The slot is considered available if no thread holds either an exclusive or a shared lock. - * - * @return True if available for writing. - */ -bool BufferSlot::IsAvailableForWriting() const { - // If we can acquire the lock exclusively, then no readers or writer are active. - if (rwMutex_.try_lock()) { - rwMutex_.unlock(); - return true; - } - return false; -} - -/** - * Checks if the slot is available for reading. - * A slot is available for reading if no exclusive lock is held (readers might already be active). - * - * @return True if available for reading. - */ -bool BufferSlot::IsAvailableForReading() const { - // If we can acquire a shared lock, then no writer is active. - if (rwMutex_.try_lock_shared()) { - rwMutex_.unlock_shared(); - return true; - } - return false; -} - - /////////////////////////////////////////////////////////////////////////////// // DataBuffer Implementation @@ -192,6 +88,11 @@ DataBuffer::DataBuffer(unsigned int memorySizeMB) threadPool_(std::make_shared()), tasksMemCopy_(std::make_shared(threadPool_)) { + // Pre-allocate slots (1 per MB) + for (unsigned int i = 0; i < memorySizeMB; i++) { + unusedSlots_.push_back(std::make_unique(0, 0, 0, 0)); + } + AllocateBuffer(memorySizeMB); } @@ -346,7 +247,7 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, if (candidateStart >= freeStart && candidateStart < freeEnd && candidateStart + totalSlotSize <= freeEnd) { releasedSlots_.erase(releasedSlots_.begin() + i); - return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, metadataSize, + return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, imageDataPointer, metadataPointer, true); } } @@ -363,7 +264,7 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, // Check if the free region has enough space after alignment. if (it->first + it->second >= alignedCandidate + totalSlotSize) { candidateStart = alignedCandidate; - return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, metadataSize, + return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, imageDataPointer, metadataPointer, true); } } @@ -380,7 +281,7 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, } nextAllocOffset_ = candidateStart + totalSlotSize; // Register a new slot using the selected candidateStart. - return CreateAndRegisterNewSlot(candidateStart, totalSlotSize, imageSize, metadataSize, + return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, imageDataPointer, metadataPointer, false); } } @@ -436,7 +337,7 @@ int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { // Now check if the slot is not being accessed. // (i.e. this was the last reader and no writer holds it) if (slot->IsAvailableForWriting() && slot->IsAvailableForReading()) { - RemoveSlotFromActiveTracking(offset, it); + DeleteSlot(offset, it); } else { throw std::runtime_error("TESTING: this should not happen when copying data"); } @@ -573,37 +474,42 @@ bool DataBuffer::Overflow() const { * @throws std::runtime_error if any slot is still actively being read or written. */ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { - std::lock_guard lock(slotManagementMutex_); + std::lock_guard lock(slotManagementMutex_); - // Check that there are no outstanding readers or writers. - for (const std::unique_ptr& slot : activeSlotsVector_) { - if (!slot->IsAvailableForReading() || !slot->IsAvailableForWriting()) { - throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); - } - } + // Check that there are no outstanding readers or writers + for (const std::unique_ptr& slot : activeSlotsVector_) { + if (!slot->IsAvailableForReading() || !slot->IsAvailableForWriting()) { + throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); + } + } - // Clear internal data structures. - activeSlotsVector_.clear(); - activeSlotsByStart_.clear(); - releasedSlots_.clear(); - currentSlotIndex_ = 0; - nextAllocOffset_ = 0; - overflow_ = false; + // Clear internal data structures + activeSlotsVector_.clear(); + activeSlotsByStart_.clear(); + releasedSlots_.clear(); + currentSlotIndex_ = 0; + nextAllocOffset_ = 0; + overflow_ = false; - // Release the old buffer. - if (buffer_ != nullptr) { - #ifdef _WIN32 - VirtualFree(buffer_, 0, MEM_RELEASE); - #else - munmap(buffer_, bufferSize_); - #endif - buffer_ = nullptr; - } + // Reset the slot pool + unusedSlots_.clear(); + + // Pre-allocate new slots (1 per MB) + for (unsigned int i = 0; i < memorySizeMB; i++) { + unusedSlots_.push_back(std::make_unique(0, 0, 0, 0)); + } - // Allocate a new buffer using the provided memory size. - AllocateBuffer(memorySizeMB); - - return DEVICE_OK; + // Release and reallocate the buffer + if (buffer_ != nullptr) { + #ifdef _WIN32 + VirtualFree(buffer_, 0, MEM_RELEASE); + #else + munmap(buffer_, bufferSize_); + #endif + buffer_ = nullptr; + } + + return AllocateBuffer(memorySizeMB); } long DataBuffer::GetActiveSlotCount() const { @@ -638,3 +544,152 @@ const BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* imageDataP auto it = activeSlotsByStart_.find(offset); return (it != activeSlotsByStart_.end()) ? it->second : nullptr; } + +void DataBuffer::AddToReleasedSlots(size_t offset) { + if (!overwriteWhenFull_) { + if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) + releasedSlots_.erase(releasedSlots_.begin()); + releasedSlots_.push_back(offset); + } +} + +void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, size_t freedRegionSize) { + // Find the free region that starts at or after newEnd + auto right = freeRegions_.lower_bound(newRegionEnd); + + // Check if there is a free region immediately preceding the new region + auto left = freeRegions_.lower_bound(newRegionStart); + if (left != freeRegions_.begin()) { + auto prev = std::prev(left); + // If the previous region's end matches the new region's start... + if (prev->first + prev->second == newRegionStart) { + newRegionStart = prev->first; + freeRegions_.erase(prev); + } + } + + // Check if the region immediately to the right can be merged + if (right != freeRegions_.end() && right->first == newRegionEnd) { + newRegionEnd = right->first + right->second; + freeRegions_.erase(right); + } + + // Insert the merged (or standalone) free region + size_t newRegionSize = (newRegionEnd > newRegionStart ? newRegionEnd - newRegionStart : 0); + if (newRegionSize > 0) { + freeRegions_[newRegionStart] = newRegionSize; + } +} + +void DataBuffer::RemoveFromActiveTracking(size_t offset, std::map::iterator it) { + activeSlotsByStart_.erase(it); + for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { + if (vecIt->get()->GetStart() == offset) { + // Determine the index being removed. + size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); + activeSlotsVector_.erase(vecIt); + // Adjust the currentSlotIndex_; if the deleted slot was before it, decrement. + if (currentSlotIndex_ > indexDeleted) + currentSlotIndex_--; + break; + } + } +} + +void DataBuffer::DeleteSlot(size_t offset, std::map::iterator it) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + + AddToReleasedSlots(offset); + + size_t freedRegionSize = it->second->GetLength(); + size_t newRegionStart = offset; + size_t newRegionEnd = offset + freedRegionSize; + + // Return the slot to the pool before removing from active tracking + ReturnSlotToPool(it->second); + + MergeFreeRegions(newRegionStart, newRegionEnd, freedRegionSize); + RemoveFromActiveTracking(offset, it); +} + +BufferSlot* DataBuffer::InitializeNewSlot(size_t candidateStart, size_t totalSlotSize, + size_t imageDataSize, size_t metadataSize) { + BufferSlot* newSlot = new BufferSlot(candidateStart, totalSlotSize, imageDataSize, metadataSize); + newSlot->AcquireWriteAccess(); + activeSlotsVector_.push_back(std::unique_ptr(newSlot)); + activeSlotsByStart_[candidateStart] = newSlot; + return newSlot; +} + +void DataBuffer::SetupSlotPointers(BufferSlot* newSlot, unsigned char** imageDataPointer, + unsigned char** metadataPointer) { + *imageDataPointer = buffer_ + newSlot->GetStart(); + *metadataPointer = *imageDataPointer + newSlot->GetImageSize(); +} + +void DataBuffer::UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize) { + auto it = freeRegions_.upper_bound(candidateStart); + if (it != freeRegions_.begin()) { + --it; + size_t freeRegionStart = it->first; + size_t freeRegionSize = it->second; + size_t freeRegionEnd = freeRegionStart + freeRegionSize; + + if (candidateStart >= freeRegionStart && + candidateStart + totalSlotSize <= freeRegionEnd) { + freeRegions_.erase(it); + + if (candidateStart > freeRegionStart) { + size_t gap = candidateStart - freeRegionStart; + if (gap > 0) + freeRegions_.insert({freeRegionStart, gap}); + } + + if (freeRegionEnd > candidateStart + totalSlotSize) { + size_t gap = freeRegionEnd - (candidateStart + totalSlotSize); + if (gap > 0) + freeRegions_.insert({candidateStart + totalSlotSize, gap}); + } + } + } +} + +int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, + size_t imageDataSize, size_t metadataSize, + unsigned char** imageDataPointer, unsigned char** metadataPointer, + bool fromFreeRegion) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + + BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, imageDataSize, metadataSize); + newSlot->AcquireWriteAccess(); + activeSlotsVector_.push_back(std::unique_ptr(newSlot)); + activeSlotsByStart_[candidateStart] = newSlot; + + SetupSlotPointers(newSlot, imageDataPointer, metadataPointer); + + if (fromFreeRegion) { + UpdateFreeRegions(candidateStart, totalSlotSize); + } + + return DEVICE_OK; +} + +BufferSlot* DataBuffer::GetSlotFromPool(size_t start, size_t totalLength, + size_t imageSize, size_t metadataSize) { + std::lock_guard lock(slotManagementMutex_); + + if (unusedSlots_.empty()) { + // Optionally grow the pool if needed + unusedSlots_.push_back(std::make_unique(start, totalLength, imageSize, metadataSize)); + } + + BufferSlot* slot = unusedSlots_.back().release(); // Transfer ownership + unusedSlots_.pop_back(); + slot->Reset(start, totalLength, imageSize, metadataSize); + return slot; +} + +void DataBuffer::ReturnSlotToPool(BufferSlot* slot) { + std::lock_guard lock(slotManagementMutex_); + unusedSlots_.push_back(std::unique_ptr(slot)); +} \ No newline at end of file diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index f31767d43..2acce1c46 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -66,7 +66,7 @@ class BufferSlot { /** * Destructor. */ - ~BufferSlot(); + ~BufferSlot() {}; /** * Returns the starting offset (in bytes) of the slot. @@ -145,6 +145,14 @@ class BufferSlot { return false; } + // Add reset method to reuse the slot + void Reset(std::size_t start, std::size_t totalLength, size_t imageSize, size_t metadataSize) { + start_ = start; + length_ = totalLength; + imageSize_ = imageSize; + metadataSize_ = metadataSize; + } + private: std::size_t start_; std::size_t length_; @@ -351,6 +359,8 @@ class DataBuffer { // Map from starting offset -> region size (in bytes). std::map freeRegions_; std::vector releasedSlots_; + + std::deque> unusedSlots_; // Next free offset within the buffer. // In overwrite mode, new allocations will come from this pointer. @@ -370,11 +380,25 @@ class DataBuffer { /** * Internal helper to register a new slot. */ - int CreateAndRegisterNewSlot(size_t candidateStart, size_t totalSlotSize, size_t imageDataSize, + int CreateSlot(size_t candidateStart, size_t totalSlotSize, size_t imageDataSize, size_t metadataSize, unsigned char** imageDataPointer, unsigned char** metadataPointer, bool fromFreeRegion); - void RemoveSlotFromActiveTracking(size_t offset, std::map::iterator it); - void InsertFreeRegion(size_t offset, size_t size); + void DeleteSlot(size_t offset, std::map::iterator it); + + void AddToReleasedSlots(size_t offset); + void MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, size_t freedRegionSize); + void RemoveFromActiveTracking(size_t offset, std::map::iterator it); + + BufferSlot* InitializeNewSlot(size_t candidateStart, size_t totalSlotSize, + size_t imageDataSize, size_t metadataSize); + void SetupSlotPointers(BufferSlot* newSlot, unsigned char** imageDataPointer, + unsigned char** metadataPointer); + void UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize); + + void ReturnSlotToPool(BufferSlot* slot); + + BufferSlot* GetSlotFromPool(size_t start, size_t totalLength, + size_t imageSize, size_t metadataSize); }; From 9887ae9f3a419a09e4f312e580d7e45ebe7689e0 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 13 Feb 2025 15:10:39 -0800 Subject: [PATCH 17/46] lots of bug fixes and refactoring --- MMCore/BufferAdapter.cpp | 12 +- MMCore/Buffer_v2.cpp | 239 ++++++++++++++++++++++++--------------- MMCore/Buffer_v2.h | 52 ++++++--- 3 files changed, 188 insertions(+), 115 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index abf020737..ab57df84e 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -386,7 +386,7 @@ unsigned BufferAdapter::GetImageWidth(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for image width"); std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue(); return static_cast(atoi(sVal.c_str())); @@ -396,7 +396,7 @@ unsigned BufferAdapter::GetImageHeight(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetImageHeight(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for image height"); std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue(); return static_cast(atoi(sVal.c_str())); @@ -406,7 +406,7 @@ unsigned BufferAdapter::GetBytesPerPixel(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for bytes per pixel"); std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); if (pixelType == MM::g_Keyword_PixelType_GRAY8) @@ -425,7 +425,7 @@ unsigned BufferAdapter::GetImageBitDepth(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetImageBitDepth(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for image bit depth"); std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); if (pixelType == MM::g_Keyword_PixelType_GRAY8) @@ -444,7 +444,7 @@ unsigned BufferAdapter::GetNumberOfComponents(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetNumberOfComponents(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for number of components"); std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); if (pixelType == MM::g_Keyword_PixelType_GRAY8 || @@ -461,7 +461,7 @@ long BufferAdapter::GetImageBufferSize(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadata(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for image buffer size"); // Suppose the image size is computed from width, height, and bytes per pixel: unsigned width = static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue().c_str())); diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 9f36a89ad..f7cd466d8 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -73,8 +73,27 @@ Metadata Handling: /////////////////////////////////////////////////////////////////////////////// namespace { - inline size_t AlignTo(size_t value, size_t alignment) { - return ((value + alignment - 1) / alignment) * alignment; + // Get system page size at runtime + inline size_t GetPageSize() { + #ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; + #else + return sysconf(_SC_PAGESIZE); + #endif + } + + // Cache the page size. + const size_t PAGE_SIZE = GetPageSize(); + + // Inline alignment function using bitwise operations. + // For a power-of-two alignment, this computes the smallest multiple + // of 'alignment' that is at least as large as 'value'. + inline size_t Align(size_t value) { + // Use PAGE_SIZE if value is large enough; otherwise use the sizeof(max_align_t) + size_t alignment = (value >= PAGE_SIZE) ? PAGE_SIZE : alignof(std::max_align_t); + return (value + alignment - 1) & ~(alignment - 1); } } @@ -88,9 +107,11 @@ DataBuffer::DataBuffer(unsigned int memorySizeMB) threadPool_(std::make_shared()), tasksMemCopy_(std::make_shared(threadPool_)) { - // Pre-allocate slots (1 per MB) + // Pre-allocate slots (one per MB) and store in both slotPool_ and unusedSlots_ for (unsigned int i = 0; i < memorySizeMB; i++) { - unusedSlots_.push_back(std::make_unique(0, 0, 0, 0)); + BufferSlot* bs = new BufferSlot(); + slotPool_.push_back(bs); + unusedSlots_.push_back(bs); } AllocateBuffer(memorySizeMB); @@ -105,6 +126,9 @@ DataBuffer::~DataBuffer() { #endif buffer_ = nullptr; } + for (BufferSlot* bs : slotPool_) { + delete bs; + } } /** @@ -118,11 +142,12 @@ int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { #ifdef _WIN32 buffer_ = (unsigned char*)VirtualAlloc(nullptr, numBytes, - MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE); + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); if (!buffer_) { return DEVICE_ERR; } + #else buffer_ = (unsigned char*)mmap(nullptr, numBytes, PROT_READ | PROT_WRITE, @@ -132,6 +157,11 @@ int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { buffer_ = nullptr; return DEVICE_ERR; } + + // Advise the kernel that we will need this memory soon. + madvise(buffer_, numBytes, MADV_WILLNEED); + + } #endif bufferSize_ = numBytes; @@ -169,19 +199,15 @@ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Met metaStr = pMd->Serialize(); metaSize = metaStr.size(); } - size_t totalSlotSize = dataSize + metaSize; + unsigned char* imageDataPointer = nullptr; unsigned char* metadataPointer = nullptr; - // Pass the actual image and metadata sizes to AcquireWriteSlot. int result = AcquireWriteSlot(dataSize, metaSize, &imageDataPointer, &metadataPointer); if (result != DEVICE_OK) return result; - // Copy the image data. tasksMemCopy_->MemCopy((void*)imageDataPointer, data, dataSize); - - // Copy the metadata. if (metaSize > 0) { std::memcpy(metadataPointer, metaStr.data(), metaSize); } @@ -223,18 +249,16 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, return DEVICE_ERR; } - - size_t alignment = alignof(std::max_align_t); // Total size is the image data plus metadata. size_t rawTotalSize = imageSize + metadataSize; - size_t totalSlotSize = AlignTo(rawTotalSize, alignment); + size_t totalSlotSize = Align(rawTotalSize); size_t candidateStart = 0; if (!overwriteWhenFull_) { // FIRST: Look for a fit in recycled slots (releasedSlots_) for (int i = static_cast(releasedSlots_.size()) - 1; i >= 0; i--) { // Align the candidate start position. - candidateStart = AlignTo(releasedSlots_[i], alignment); + candidateStart = Align(releasedSlots_[i]); // Find the free region that contains this position. auto it = freeRegions_.upper_bound(candidateStart); @@ -260,7 +284,7 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, // SECOND: Look in the free-region list as fallback. for (auto it = freeRegions_.begin(); it != freeRegions_.end(); ++it) { // Align the free region start. - size_t alignedCandidate = AlignTo(it->first, alignment); + size_t alignedCandidate = Align(it->first); // Check if the free region has enough space after alignment. if (it->first + it->second >= alignedCandidate + totalSlotSize) { candidateStart = alignedCandidate; @@ -275,7 +299,7 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, return DEVICE_ERR; } else { // Overwrite mode: use nextAllocOffset_. Ensure it is aligned. - candidateStart = AlignTo(nextAllocOffset_, alignment); + candidateStart = Align(nextAllocOffset_); if (candidateStart + totalSlotSize > bufferSize_) { candidateStart = 0; // Wrap around. } @@ -297,16 +321,22 @@ int DataBuffer::FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actual if (imageDataPointer == nullptr) return DEVICE_ERR; - // Use a unique_lock so that we can pass it to FindSlotForPointer - std::unique_lock lock(slotManagementMutex_); - BufferSlot* slot = const_cast(FindSlotForPointer(imageDataPointer)); - if (!slot) - return DEVICE_ERR; // Slot not found + BufferSlot* slot = nullptr; + { + std::lock_guard lock(slotManagementMutex_); + slot = FindSlotForPointer(imageDataPointer); + if (!slot) + return DEVICE_ERR; + } slot->ReleaseWriteAccess(); - // Notify any waiting threads that new data is available. - dataCV_.notify_all(); + // Notify waiting threads under a brief lock + { + std::lock_guard lock(slotManagementMutex_); + dataCV_.notify_all(); + } + return DEVICE_OK; } @@ -320,26 +350,25 @@ int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { if (imageDataPointer == nullptr) return DEVICE_ERR; - std::unique_lock lock(slotManagementMutex_); - - // The slot starts directly at imageDataPointer. - size_t offset = imageDataPointer - buffer_; - - auto it = activeSlotsByStart_.find(offset); - if (it == activeSlotsByStart_.end()) - return DEVICE_ERR; // Slot not found + // First find the slot without the global lock + BufferSlot* slot = nullptr; + { + std::lock_guard lock(slotManagementMutex_); + slot = FindSlotForPointer(imageDataPointer); + if (!slot) + return DEVICE_ERR; + } + const size_t offset = imageDataPointer - buffer_; - BufferSlot* slot = it->second; - // Release the read access (this does NOT remove the slot from the active list) + // Release the read access outside the global lock slot->ReleaseReadAccess(); if (!overwriteWhenFull_) { - // Now check if the slot is not being accessed. - // (i.e. this was the last reader and no writer holds it) + std::lock_guard lock(slotManagementMutex_); + // Now check if the slot is not being accessed if (slot->IsAvailableForWriting() && slot->IsAvailableForReading()) { + auto it = activeSlotsByStart_.find(offset); DeleteSlot(offset, it); - } else { - throw std::runtime_error("TESTING: this should not happen when copying data"); } } @@ -349,6 +378,9 @@ int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData) { BufferSlot* slot = nullptr; + size_t slotStart = 0; + + // First, get the slot under the global lock { std::unique_lock lock(slotManagementMutex_); while (activeSlotsVector_.empty()) { @@ -356,27 +388,26 @@ const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *im return nullptr; dataCV_.wait(lock); } - // Atomically take the slot and advance the index. - slot = activeSlotsVector_[currentSlotIndex_].get(); + // Atomically take the slot and advance the index + slot = activeSlotsVector_[currentSlotIndex_]; + slotStart = slot->GetStart(); currentSlotIndex_ = (currentSlotIndex_ + 1) % activeSlotsVector_.size(); - } // Release lock + } // Release global lock - // Now get read access outside the slot of the global mutex. + // Now acquire read access outside the global lock slot->AcquireReadAccess(); - - // Process the slot. - size_t slotStart = slot->GetStart(); + const unsigned char* imageDataPointer = buffer_ + slotStart; *imageDataSize = slot->GetImageSize(); - // Use the dedicated metadata extraction function. - this->ExtractMetadata(imageDataPointer, md); + const unsigned char* metadataPtr = imageDataPointer + slot->GetImageSize(); + this->ExtractMetadata(metadataPtr, slot->GetMetadataSize(), md); return imageDataPointer; } int DataBuffer::PeekNextDataReadPointer(const unsigned char** imageDataPointer, size_t* imageDataSize, - Metadata &md) { + Metadata &md) { // Immediately check if there is an unread slot without waiting. BufferSlot* currentSlot = nullptr; { @@ -385,19 +416,18 @@ int DataBuffer::PeekNextDataReadPointer(const unsigned char** imageDataPointer, if (activeSlotsVector_.empty() || currentSlotIndex_ >= activeSlotsVector_.size()) { return DEVICE_ERR; // No unread data available. } - currentSlot = activeSlotsVector_[currentSlotIndex_].get(); - } // Release slotManagementMutex_ before acquiring read access! + currentSlot = activeSlotsVector_[currentSlotIndex_]; + } // Now safely get read access without holding the global lock. currentSlot->AcquireReadAccess(); std::unique_lock lock(slotManagementMutex_); *imageDataPointer = buffer_ + currentSlot->GetStart(); - size_t imgSize = currentSlot->GetImageSize(); - *imageDataSize = imgSize; + *imageDataSize = currentSlot->GetImageSize(); - // Use the dedicated metadata extraction function. - this->ExtractMetadata(*imageDataPointer, md); + const unsigned char* metadataPtr = *imageDataPointer + currentSlot->GetImageSize(); + this->ExtractMetadata(metadataPtr, currentSlot->GetMetadataSize(), md); return DEVICE_OK; } @@ -414,19 +444,17 @@ const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* im // Instead of looking ahead from currentSlotIndex_, we look back from the end. // For n==0, return the most recent slot; for n==1, the one before it; etc. size_t index = activeSlotsVector_.size() - n - 1; - currentSlot = activeSlotsVector_[index].get(); + currentSlot = activeSlotsVector_[index]; } currentSlot->AcquireReadAccess(); const unsigned char* imageDataPointer = buffer_ + currentSlot->GetStart(); - size_t imgSize = currentSlot->GetImageSize(); - *imageDataSize = imgSize; + *imageDataSize = currentSlot->GetImageSize(); - - this->ExtractMetadata(imageDataPointer, md); + const unsigned char* metadataPtr = imageDataPointer + currentSlot->GetImageSize(); + this->ExtractMetadata(metadataPtr, currentSlot->GetMetadataSize(), md); - // Return a pointer to the image data only. return imageDataPointer; } @@ -476,8 +504,8 @@ bool DataBuffer::Overflow() const { int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { std::lock_guard lock(slotManagementMutex_); - // Check that there are no outstanding readers or writers - for (const std::unique_ptr& slot : activeSlotsVector_) { + // Ensure no active readers/writers exist. + for (BufferSlot* slot : activeSlotsVector_) { if (!slot->IsAvailableForReading() || !slot->IsAvailableForWriting()) { throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); } @@ -493,10 +521,8 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { // Reset the slot pool unusedSlots_.clear(); - - // Pre-allocate new slots (1 per MB) - for (unsigned int i = 0; i < memorySizeMB; i++) { - unusedSlots_.push_back(std::make_unique(0, 0, 0, 0)); + for (BufferSlot* bs : slotPool_) { + unusedSlots_.push_back(bs); } // Release and reallocate the buffer @@ -516,28 +542,26 @@ long DataBuffer::GetActiveSlotCount() const { return static_cast(activeSlotsVector_.size()); } -int DataBuffer::ExtractMetadata(const unsigned char* imageDataPtr, Metadata &md) const { - std::unique_lock lock(slotManagementMutex_); - const BufferSlot* slot = FindSlotForPointer(imageDataPtr); - if (!slot) - return DEVICE_ERR; // Slot not found or invalid pointer +int DataBuffer::ExtractMetadata(const unsigned char* metadataPtr, size_t metadataSize, Metadata &md) { + // No lock is required here because we assume the slot is already locked + if (!metadataPtr) + return DEVICE_ERR; // Invalid pointer - // If no metadata is stored in this slot, clear the metadata object. - if (slot->GetMetadataSize() == 0) { + // If no metadata is stored, clear the metadata object + if (metadataSize == 0) { md.Clear(); } else { - // Metadata is stored immediately after the image data. - const char* metaDataStart = reinterpret_cast(imageDataPtr + slot->GetImageSize()); - // Create a temporary string from the metadata region. - std::string metaStr(metaDataStart, slot->GetMetadataSize()); - // Restore the metadata from the serialized string. + // Create a temporary string from the metadata region + std::string metaStr(reinterpret_cast(metadataPtr), metadataSize); + // Restore the metadata from the serialized string md.Restore(metaStr.c_str()); } return DEVICE_OK; } // NOTE: Caller must hold slotManagementMutex_ for thread safety. -const BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* imageDataPtr) const { +BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* imageDataPtr) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); if (buffer_ == nullptr) return nullptr; std::size_t offset = imageDataPtr - buffer_; @@ -546,6 +570,7 @@ const BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* imageDataP } void DataBuffer::AddToReleasedSlots(size_t offset) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); if (!overwriteWhenFull_) { if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) releasedSlots_.erase(releasedSlots_.begin()); @@ -554,6 +579,7 @@ void DataBuffer::AddToReleasedSlots(size_t offset) { } void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, size_t freedRegionSize) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); // Find the free region that starts at or after newEnd auto right = freeRegions_.lower_bound(newRegionEnd); @@ -582,9 +608,10 @@ void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, si } void DataBuffer::RemoveFromActiveTracking(size_t offset, std::map::iterator it) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); activeSlotsByStart_.erase(it); for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { - if (vecIt->get()->GetStart() == offset) { + if ((*vecIt)->GetStart() == offset) { // Determine the index being removed. size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); activeSlotsVector_.erase(vecIt); @@ -614,9 +641,9 @@ void DataBuffer::DeleteSlot(size_t offset, std::map::iterat BufferSlot* DataBuffer::InitializeNewSlot(size_t candidateStart, size_t totalSlotSize, size_t imageDataSize, size_t metadataSize) { - BufferSlot* newSlot = new BufferSlot(candidateStart, totalSlotSize, imageDataSize, metadataSize); - newSlot->AcquireWriteAccess(); - activeSlotsVector_.push_back(std::unique_ptr(newSlot)); + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, imageDataSize, metadataSize); + activeSlotsVector_.push_back(newSlot); activeSlotsByStart_[candidateStart] = newSlot; return newSlot; } @@ -628,6 +655,7 @@ void DataBuffer::SetupSlotPointers(BufferSlot* newSlot, unsigned char** imageDat } void DataBuffer::UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); auto it = freeRegions_.upper_bound(candidateStart); if (it != freeRegions_.begin()) { --it; @@ -661,12 +689,11 @@ int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, imageDataSize, metadataSize); - newSlot->AcquireWriteAccess(); - activeSlotsVector_.push_back(std::unique_ptr(newSlot)); - activeSlotsByStart_[candidateStart] = newSlot; - SetupSlotPointers(newSlot, imageDataPointer, metadataPointer); + // Explicitly acquire write access here, after the slot is fully set up + newSlot->AcquireWriteAccess(); + if (fromFreeRegion) { UpdateFreeRegions(candidateStart, totalSlotSize); } @@ -676,20 +703,44 @@ int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, BufferSlot* DataBuffer::GetSlotFromPool(size_t start, size_t totalLength, size_t imageSize, size_t metadataSize) { - std::lock_guard lock(slotManagementMutex_); + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + // Grow the pool if needed. if (unusedSlots_.empty()) { - // Optionally grow the pool if needed - unusedSlots_.push_back(std::make_unique(start, totalLength, imageSize, metadataSize)); + BufferSlot* newSlot = new BufferSlot(); + slotPool_.push_back(newSlot); + unusedSlots_.push_back(newSlot); } - BufferSlot* slot = unusedSlots_.back().release(); // Transfer ownership - unusedSlots_.pop_back(); + // Get a slot from the front of the deque. + BufferSlot* slot = unusedSlots_.front(); + unusedSlots_.pop_front(); slot->Reset(start, totalLength, imageSize, metadataSize); + + // Add to active tracking. + activeSlotsVector_.push_back(slot); + activeSlotsByStart_[start] = slot; return slot; } void DataBuffer::ReturnSlotToPool(BufferSlot* slot) { - std::lock_guard lock(slotManagementMutex_); - unusedSlots_.push_back(std::unique_ptr(slot)); + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + unusedSlots_.push_back(slot); +} + +int DataBuffer::ExtractMetadataForImage(const unsigned char* imageDataPtr, Metadata &md) { + BufferSlot* slot = nullptr; + { + std::lock_guard lock(slotManagementMutex_); + slot = FindSlotForPointer(imageDataPtr); + if (!slot) { + return DEVICE_ERR; + } + } + // Get metadata pointer and size while under lock + const unsigned char* metadataPtr = imageDataPtr + slot->GetImageSize(); + size_t metadataSize = slot->GetMetadataSize(); + + // Extract metadata (internal method doesn't need lock) + return ExtractMetadata(metadataPtr, metadataSize, md); } \ No newline at end of file diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 2acce1c46..a246b2ad2 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -43,6 +43,7 @@ #include #include #include "TaskSet_CopyMemory.h" +#include /** * BufferSlot represents a contiguous slot in the DataBuffer that holds image @@ -60,8 +61,7 @@ class BufferSlot { * @param imageSize The exact number of bytes for the image data. * @param metadataSize The exact number of bytes for the metadata. */ - BufferSlot(std::size_t start, std::size_t totalLength, size_t imageSize, size_t metadataSize) - : start_(start), length_(totalLength), imageSize_(imageSize), metadataSize_(metadataSize) {} + BufferSlot() : start_(0), length_(0), imageSize_(0), metadataSize_(0), rwMutex_() {} /** * Destructor. @@ -145,12 +145,18 @@ class BufferSlot { return false; } - // Add reset method to reuse the slot - void Reset(std::size_t start, std::size_t totalLength, size_t imageSize, size_t metadataSize) { + void Reset(size_t start, size_t length, size_t imageSize, size_t metadataSize) { + // Assert that the mutex is available before recycling + assert(IsAvailableForWriting() && IsAvailableForReading() && + "BufferSlot mutex still locked during Reset - indicates a bug!"); + + // Set the new values start_ = start; - length_ = totalLength; + length_ = length; imageSize_ = imageSize; metadataSize_ = metadataSize; + + // The caller should explicitly acquire write access when needed } private: @@ -322,24 +328,24 @@ class DataBuffer { int ReinitializeBuffer(unsigned int memorySizeMB); /** - * Extracts and deserializes the metadata associated with the given image data pointer. - * Internally, it locates the corresponding slot and reads the metadata stored - * immediately after the image data. + * Extracts metadata for a given image data pointer. + * Thread-safe method that acquires necessary locks to lookup metadata location. * * @param imageDataPtr Pointer to the image data. * @param md Metadata object to populate. * @return DEVICE_OK on success, or an error code if extraction fails. */ - int ExtractMetadata(const unsigned char* imageDataPtr, Metadata &md) const; + int ExtractMetadataForImage(const unsigned char* imageDataPtr, Metadata &md); private: /** - * Internal helper function that finds the slot for a given pointer (remains hidden). + * Internal helper function that finds the slot for a given pointer. + * Returns non-const pointer since slots need to be modified for locking. * * @param imageDataPtr Pointer to the image data. * @return Pointer to the corresponding BufferSlot, or nullptr if not found. */ - const BufferSlot* FindSlotForPointer(const unsigned char* imageDataPtr) const; + BufferSlot* FindSlotForPointer(const unsigned char* imageDataPtr); // Memory managed by the DataBuffer. unsigned char* buffer_; @@ -351,16 +357,21 @@ class DataBuffer { // Overflow flag (set if insert fails due to full buffer). bool overflow_; - // Tracking of active slots and free memory regions. - std::vector> activeSlotsVector_; + // Active slots and their mapping. + std::vector activeSlotsVector_; std::map activeSlotsByStart_; // Free region list for non-overwrite mode. // Map from starting offset -> region size (in bytes). std::map freeRegions_; std::vector releasedSlots_; - - std::deque> unusedSlots_; + + // Instead of ownership via unique_ptr, store raw pointers + // Note: unusedSlots_ is now a deque of raw pointers. + std::deque unusedSlots_; + + // This container holds the ownership; they live for the lifetime of the buffer. + std::vector slotPool_; // Next free offset within the buffer. // In overwrite mode, new allocations will come from this pointer. @@ -401,4 +412,15 @@ class DataBuffer { BufferSlot* GetSlotFromPool(size_t start, size_t totalLength, size_t imageSize, size_t metadataSize); + + /** + * Extracts and deserializes the metadata from the given metadata pointer. + * Assumes the corresponding slot is already locked for reading. + * + * @param imageDataPtr Pointer to the image data. + * @param md Metadata object to populate. + * @return DEVICE_OK on success, or an error code if extraction fails. + */ + int ExtractMetadata(const unsigned char* metadataPtr, size_t metadataSize, Metadata &md); + }; From 23e1cc461d90b7d1b5bbccbed5ec5085604c1097 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:21:17 -0800 Subject: [PATCH 18/46] small perf improvements and bug fixes --- MMCore/BufferAdapter.cpp | 52 ++++++++++++++-------- MMCore/Buffer_v2.cpp | 93 +++++++++++++++++----------------------- MMCore/Buffer_v2.h | 17 ++++---- MMCore/MMCore.cpp | 3 ++ 4 files changed, 84 insertions(+), 81 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index ab57df84e..eda42dd1c 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -83,7 +83,7 @@ const unsigned char* BufferAdapter::GetLastImage() const if (useV2_) { Metadata dummyMetadata; return v2Buffer_->PeekDataReadPointerAtIndex(0, nullptr, dummyMetadata); - // TODO: ensure calling code releases the slot after use + // NOTE: ensure calling code releases the slot after use } else { return circBuffer_->GetTopImage(); } @@ -95,7 +95,7 @@ const unsigned char* BufferAdapter::PopNextImage() if (useV2_) { Metadata dummyMetadata; return v2Buffer_->PopNextDataReadPointer(dummyMetadata, nullptr, false); - // TODO: ensure calling code releases the slot after use + // NOTE: ensure calling code releases the slot after use } else { return circBuffer_->PopNextImage(); } @@ -270,16 +270,23 @@ bool BufferAdapter::InsertMultiChannel(const unsigned char* buf, unsigned numCha void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) { if (useV2_) { - // In v2, we use PeekNextDataReadPointer (which does not advance the internal pointer) - // Note: the v2 buffer is not channel aware, so the 'channel' parameter is ignored. - // TODO implement the channel aware version - const unsigned char* ptr = nullptr; + // In v2, we now use a channel-aware pointer arithmetic at the adapter level. + const unsigned char* basePtr = nullptr; size_t imageDataSize = 0; - int ret = v2Buffer_->PeekNextDataReadPointer(&ptr, &imageDataSize, md); - if (ret != DEVICE_OK || ptr == nullptr) + int ret = v2Buffer_->PeekNextDataReadPointer(&basePtr, &imageDataSize, md); + if (ret != DEVICE_OK || basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); - return const_cast(ptr); - // TODO: make sure calling code releases the slot after use + + // Calculate the size for one channel. + int width = GetImageWidth(basePtr); + int height = GetImageHeight(basePtr); + int pixDepth = GetBytesPerPixel(basePtr); + size_t singleChannelSize = width * height * pixDepth; + + // Advance the base pointer by the amount corresponding to the selected channel. + const unsigned char* channelPtr = basePtr + (channel * singleChannelSize); + return const_cast(channelPtr); + // NOTE: make sure calling code releases the slot after use. } else { const mm::ImgBuffer* pBuf = circBuffer_->GetTopImageBuffer(channel); if (pBuf != nullptr) { @@ -300,7 +307,7 @@ void* BufferAdapter::GetNthImageMD(unsigned long n, Metadata& md) const throw (C throw CMMError("V2 buffer does not contain enough data.", MMERR_CircularBufferEmpty); // Return a non-const pointer (caller must be careful with the const_cast) return const_cast(ptr); - // TODO: make sure calling code releases the slot after use + // NOTE: make sure calling code releases the slot after use } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNthFromTopImageBuffer(n); if (pBuf != nullptr) { @@ -315,16 +322,23 @@ void* BufferAdapter::GetNthImageMD(unsigned long n, Metadata& md) const throw (C void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError) { if (useV2_) { - // For v2, consume the data by calling PopNextDataReadPointer, - // which returns a const unsigned char* or throws an exception on error. - // The caller is expected to call ReleaseDataReadPointer on the returned pointer once done. - // TODO: make channel aware + // For v2, we now make the buffer channel aware at the adapter level. size_t dataSize = 0; - const unsigned char* ptr = v2Buffer_->PopNextDataReadPointer(md, &dataSize, false); - if (ptr == nullptr) + const unsigned char* basePtr = v2Buffer_->PopNextDataReadPointer(md, &dataSize, false); + if (basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); - return const_cast(ptr); - // TODO: ensure that calling code releases the read pointer after use. + + // Calculate the size of a single channel. + int width = GetImageWidth(basePtr); + int height = GetImageHeight(basePtr); + int pixDepth = GetBytesPerPixel(basePtr); + size_t singleChannelSize = width * height * pixDepth; + + // Offset the base pointer to get the requested channel's data. + // 'channel' is assumed to be passed as a parameter. + const unsigned char* channelPtr = basePtr + (channel * singleChannelSize); + return const_cast(channelPtr); + // NOTE: ensure that calling code releases the read pointer after use. } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNextImageBuffer(channel); if (pBuf != nullptr) { diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index f7cd466d8..bfb377e95 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -244,7 +244,6 @@ int DataBuffer::SetOverwriteData(bool overwrite) { int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, unsigned char** imageDataPointer, unsigned char** metadataPointer) { - std::lock_guard lock(slotManagementMutex_); if (buffer_ == nullptr) { return DEVICE_ERR; } @@ -255,58 +254,56 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, size_t candidateStart = 0; if (!overwriteWhenFull_) { - // FIRST: Look for a fit in recycled slots (releasedSlots_) - for (int i = static_cast(releasedSlots_.size()) - 1; i >= 0; i--) { - // Align the candidate start position. - candidateStart = Align(releasedSlots_[i]); - - // Find the free region that contains this position. - auto it = freeRegions_.upper_bound(candidateStart); - if (it != freeRegions_.begin()) { - --it; - size_t freeStart = it->first; - size_t freeEnd = freeStart + it->second; - - // Check if the position is actually within this free region. - if (candidateStart >= freeStart && candidateStart < freeEnd && - candidateStart + totalSlotSize <= freeEnd) { - releasedSlots_.erase(releasedSlots_.begin() + i); - return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, - imageDataPointer, metadataPointer, true); + std::lock_guard lock(slotManagementMutex_); + // Look in the free-region list as fallback using a cached cursor. + { + bool found = false; + size_t newCandidate = 0; + // Start search from freeRegionCursor_ + auto it = freeRegions_.lower_bound(freeRegionCursor_); + // Loop over free regions at most once (wrapping around if necessary). + for (size_t count = 0, sz = freeRegions_.size(); count < sz; count++) { + if (it == freeRegions_.end()) + it = freeRegions_.begin(); + size_t alignedCandidate = Align(it->first); + if (it->first + it->second >= alignedCandidate + totalSlotSize) { + newCandidate = alignedCandidate; + found = true; + break; } + ++it; } - - // If we get here, this released slot position isn't in a free region, - // so remove it as it's no longer valid. - releasedSlots_.erase(releasedSlots_.begin() + i); - } - - // SECOND: Look in the free-region list as fallback. - for (auto it = freeRegions_.begin(); it != freeRegions_.end(); ++it) { - // Align the free region start. - size_t alignedCandidate = Align(it->first); - // Check if the free region has enough space after alignment. - if (it->first + it->second >= alignedCandidate + totalSlotSize) { - candidateStart = alignedCandidate; + if (found) { + candidateStart = newCandidate; + // Update the cursor so that next search can start here. + freeRegionCursor_ = candidateStart + totalSlotSize; return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, imageDataPointer, metadataPointer, true); } } + // No recycled slot or free region can satisfy the allocation. overflow_ = true; *imageDataPointer = nullptr; *metadataPointer = nullptr; return DEVICE_ERR; } else { - // Overwrite mode: use nextAllocOffset_. Ensure it is aligned. - candidateStart = Align(nextAllocOffset_); - if (candidateStart + totalSlotSize > bufferSize_) { - candidateStart = 0; // Wrap around. + // Overwrite mode + size_t prevOffset, newOffset; + do { + prevOffset = nextAllocOffset_.load(std::memory_order_relaxed); + candidateStart = Align(prevOffset); + if (candidateStart + totalSlotSize > bufferSize_) + candidateStart = 0; // Wrap around if needed. + newOffset = candidateStart + totalSlotSize; + } while (!nextAllocOffset_.compare_exchange_weak(prevOffset, newOffset)); + + // Only now grab the lock to register the new slot. + { + std::lock_guard lock(slotManagementMutex_); + return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, + imageDataPointer, metadataPointer, false); } - nextAllocOffset_ = candidateStart + totalSlotSize; - // Register a new slot using the selected candidateStart. - return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, - imageDataPointer, metadataPointer, false); } } @@ -514,7 +511,6 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { // Clear internal data structures activeSlotsVector_.clear(); activeSlotsByStart_.clear(); - releasedSlots_.clear(); currentSlotIndex_ = 0; nextAllocOffset_ = 0; overflow_ = false; @@ -569,15 +565,6 @@ BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* imageDataPtr) { return (it != activeSlotsByStart_.end()) ? it->second : nullptr; } -void DataBuffer::AddToReleasedSlots(size_t offset) { - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - if (!overwriteWhenFull_) { - if (releasedSlots_.size() >= MAX_RELEASED_SLOTS) - releasedSlots_.erase(releasedSlots_.begin()); - releasedSlots_.push_back(offset); - } -} - void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, size_t freedRegionSize) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); // Find the free region that starts at or after newEnd @@ -607,7 +594,7 @@ void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, si } } -void DataBuffer::RemoveFromActiveTracking(size_t offset, std::map::iterator it) { +void DataBuffer::RemoveFromActiveTracking(size_t offset, std::unordered_map::iterator it) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); activeSlotsByStart_.erase(it); for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { @@ -623,11 +610,9 @@ void DataBuffer::RemoveFromActiveTracking(size_t offset, std::map::iterator it) { +void DataBuffer::DeleteSlot(size_t offset, std::unordered_map::iterator it) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - AddToReleasedSlots(offset); - size_t freedRegionSize = it->second->GetLength(); size_t newRegionStart = offset; size_t newRegionEnd = offset + freedRegionSize; diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index a246b2ad2..098afd118 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include #include @@ -44,6 +44,7 @@ #include #include "TaskSet_CopyMemory.h" #include +#include /** * BufferSlot represents a contiguous slot in the DataBuffer that holds image @@ -175,7 +176,6 @@ class BufferSlot { */ class DataBuffer { public: - static const size_t MAX_RELEASED_SLOTS = 50; /** * Constructor. @@ -359,12 +359,14 @@ class DataBuffer { // Active slots and their mapping. std::vector activeSlotsVector_; - std::map activeSlotsByStart_; + std::unordered_map activeSlotsByStart_; // Free region list for non-overwrite mode. // Map from starting offset -> region size (in bytes). std::map freeRegions_; - std::vector releasedSlots_; + + // Cached cursor for scanning free regions in non-overwrite mode. + size_t freeRegionCursor_; // Instead of ownership via unique_ptr, store raw pointers // Note: unusedSlots_ is now a deque of raw pointers. @@ -375,7 +377,7 @@ class DataBuffer { // Next free offset within the buffer. // In overwrite mode, new allocations will come from this pointer. - size_t nextAllocOffset_; + std::atomic nextAllocOffset_; // Index tracking the next slot for read. size_t currentSlotIndex_; @@ -396,11 +398,10 @@ class DataBuffer { unsigned char** imageDataPointer, unsigned char** metadataPointer, bool fromFreeRegion); - void DeleteSlot(size_t offset, std::map::iterator it); + void DeleteSlot(size_t offset, std::unordered_map::iterator it); - void AddToReleasedSlots(size_t offset); void MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, size_t freedRegionSize); - void RemoveFromActiveTracking(size_t offset, std::map::iterator it); + void RemoveFromActiveTracking(size_t offset, std::unordered_map::iterator it); BufferSlot* InitializeNewSlot(size_t candidateStart, size_t totalSlotSize, size_t imageDataSize, size_t metadataSize); diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index d5ea9f5e4..e55f38575 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -3208,6 +3208,9 @@ void CMMCore::enableV2Buffer(bool enable) throw (CMMError) void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes ) throw (CMMError) { + if (isSequenceRunning()) { + stopSequenceAcquisition(); + } delete bufferAdapter_; // discard old buffer LOG_DEBUG(coreLogger_) << "Will set circular buffer size to " << sizeMB << " MB"; From 37ab2cb2f835cbfa734a93986d4c447f6b280477 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 14 Feb 2025 12:02:15 -0800 Subject: [PATCH 19/46] expose direct getting of pointers from corecallback for writing into v2 buffer --- MMCore/BufferAdapter.cpp | 48 ++++++++-- MMCore/BufferAdapter.h | 26 ++++++ MMCore/Buffer_v2.cpp | 191 ++++++++++++++++++++++----------------- MMCore/Buffer_v2.h | 118 ++++++++++++++---------- MMCore/CoreCallback.cpp | 37 ++++++++ MMCore/CoreCallback.h | 11 +++ 6 files changed, 291 insertions(+), 140 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index eda42dd1c..c57c398f7 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -154,7 +154,8 @@ long BufferAdapter::GetSize(long imageSize) const { if (useV2_) { unsigned int mb = v2Buffer_->GetMemorySizeMB(); - unsigned int num_images = mb * 1024 * 1024 / imageSize; + size_t totalBytes = static_cast(mb) * 1024 * 1024; + size_t num_images = totalBytes / static_cast(imageSize); return static_cast(num_images); } else { return circBuffer_->GetSize(); @@ -400,7 +401,7 @@ unsigned BufferAdapter::GetImageWidth(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for image width"); std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue(); return static_cast(atoi(sVal.c_str())); @@ -410,7 +411,7 @@ unsigned BufferAdapter::GetImageHeight(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetImageHeight(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for image height"); std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue(); return static_cast(atoi(sVal.c_str())); @@ -420,7 +421,7 @@ unsigned BufferAdapter::GetBytesPerPixel(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for bytes per pixel"); std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); if (pixelType == MM::g_Keyword_PixelType_GRAY8) @@ -439,7 +440,7 @@ unsigned BufferAdapter::GetImageBitDepth(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetImageBitDepth(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for image bit depth"); std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); if (pixelType == MM::g_Keyword_PixelType_GRAY8) @@ -458,7 +459,7 @@ unsigned BufferAdapter::GetNumberOfComponents(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetNumberOfComponents(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for number of components"); std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); if (pixelType == MM::g_Keyword_PixelType_GRAY8 || @@ -475,7 +476,7 @@ long BufferAdapter::GetImageBufferSize(const unsigned char* ptr) const { if (!useV2_) throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); Metadata md; - if (v2Buffer_->ExtractMetadataForImage(ptr, md) != DEVICE_OK) + if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for image buffer size"); // Suppose the image size is computed from width, height, and bytes per pixel: unsigned width = static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue().c_str())); @@ -492,3 +493,36 @@ bool BufferAdapter::SetOverwriteData(bool overwrite) { return false; } } + +bool BufferAdapter::AcquireWriteSlot(size_t dataSize, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, size_t additionalMetadataSize, + unsigned char** dataPointer, unsigned char** additionalMetadataPointer, + Metadata* pInitialMetadata) { + if (!useV2_) { + // Not supported for circular buffer + return false; + } + + // Initialize metadata with either provided metadata or create empty + Metadata md = (pInitialMetadata != nullptr) ? *pInitialMetadata : Metadata(); + + // Add in width, height, byteDepth, and nComponents to the metadata so that when + // images are retrieved from the buffer, the data can be interpreted correctly + ProcessMetadata(md, width, height, byteDepth, nComponents); + + std::string serializedMetadata = md.Serialize(); + int ret = v2Buffer_->AcquireWriteSlot(dataSize, additionalMetadataSize, + dataPointer, additionalMetadataPointer, serializedMetadata); + return ret == DEVICE_OK; +} + +bool BufferAdapter::FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actualMetadataBytes) +{ + if (!useV2_) { + // Not supported for circular buffer + return false; + } + + int ret = v2Buffer_->FinalizeWriteSlot(imageDataPointer, actualMetadataBytes); + return ret == DEVICE_OK; +} diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index f2352d0cc..279ab114d 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -217,6 +217,32 @@ class BufferAdapter { */ bool SetOverwriteData(bool overwrite); + /** + * Acquires a write slot large enough to hold the image data and metadata. + * @param dataSize The number of bytes reserved for image or other primary data. + * @param width Image width. + * @param height Image height. + * @param byteDepth Bytes per pixel. + * @param nComponents Number of components in the image. + * @param additionalMetadataSize The maximum number of bytes reserved for metadata. + * @param dataPointer On success, receives a pointer to the image data region. + * @param additionalMetadataPointer On success, receives a pointer to the metadata region. + * @param pInitialMetadata Optionally, a pointer to a metadata object whose contents should be pre‐written. Defaults to nullptr. + * @return true on success, false on error. + */ + bool AcquireWriteSlot(size_t dataSize, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, size_t additionalMetadataSize, + unsigned char** dataPointer, unsigned char** additionalMetadataPointer, + Metadata* pInitialMetadata = nullptr); + + /** + * Finalizes (releases) a write slot after data has been written. + * @param imageDataPointer Pointer previously obtained from AcquireWriteSlot. + * @param actualMetadataBytes The actual number of metadata bytes written. + * @return true on success, false on error. + */ + bool FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actualMetadataBytes); + private: bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. CircularBuffer* circBuffer_; diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index bfb377e95..d952f16db 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -193,27 +193,24 @@ int DataBuffer::ReleaseBuffer() { * Pack the data as [BufferSlotRecord][image data][serialized metadata] */ int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Metadata* pMd) { - size_t metaSize = 0; - std::string metaStr; - if (pMd) { - metaStr = pMd->Serialize(); - metaSize = metaStr.size(); - } - unsigned char* imageDataPointer = nullptr; - unsigned char* metadataPointer = nullptr; + unsigned char* dataPointer = nullptr; + unsigned char* additionalMetadataPointer = nullptr; - int result = AcquireWriteSlot(dataSize, metaSize, &imageDataPointer, &metadataPointer); + // Convert metadata to serialized string if provided + std::string serializedMetadata; + if (pMd != nullptr) { + serializedMetadata = pMd->Serialize(); + } + // Initial metadata is all metadata because the image and metadata are already complete + int result = AcquireWriteSlot(dataSize, 0, &dataPointer, &additionalMetadataPointer, serializedMetadata); if (result != DEVICE_OK) return result; - tasksMemCopy_->MemCopy((void*)imageDataPointer, data, dataSize); - if (metaSize > 0) { - std::memcpy(metadataPointer, metaStr.data(), metaSize); - } + tasksMemCopy_->MemCopy((void*)dataPointer, data, dataSize); // Finalize the write slot. - return FinalizeWriteSlot(imageDataPointer, metaSize); + return FinalizeWriteSlot(dataPointer, 0); } /** @@ -241,15 +238,17 @@ int DataBuffer::SetOverwriteData(bool overwrite) { * * The caller must release the slot using ReleaseDataSlot after writing is complete. */ -int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, - unsigned char** imageDataPointer, unsigned char** metadataPointer) +int DataBuffer::AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, + unsigned char** dataPointer, + unsigned char** additionalMetadataPointer, + const std::string& serializedInitialMetadata) { if (buffer_ == nullptr) { return DEVICE_ERR; } - // Total size is the image data plus metadata. - size_t rawTotalSize = imageSize + metadataSize; + // Total size includes data, initial metadata, and any additional metadata space + size_t rawTotalSize = dataSize + serializedInitialMetadata.size() + additionalMetadataSize; size_t totalSlotSize = Align(rawTotalSize); size_t candidateStart = 0; @@ -277,15 +276,15 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, candidateStart = newCandidate; // Update the cursor so that next search can start here. freeRegionCursor_ = candidateStart + totalSlotSize; - return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, - imageDataPointer, metadataPointer, true); + return CreateSlot(candidateStart, totalSlotSize, dataSize, additionalMetadataSize, + dataPointer, additionalMetadataPointer, true, serializedInitialMetadata); } } // No recycled slot or free region can satisfy the allocation. overflow_ = true; - *imageDataPointer = nullptr; - *metadataPointer = nullptr; + *dataPointer = nullptr; + *additionalMetadataPointer = nullptr; return DEVICE_ERR; } else { // Overwrite mode @@ -301,8 +300,8 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, // Only now grab the lock to register the new slot. { std::lock_guard lock(slotManagementMutex_); - return CreateSlot(candidateStart, totalSlotSize, imageSize, metadataSize, - imageDataPointer, metadataPointer, false); + return CreateSlot(candidateStart, totalSlotSize, dataSize, additionalMetadataSize, + dataPointer, additionalMetadataPointer, false, serializedInitialMetadata); } } } @@ -314,16 +313,19 @@ int DataBuffer::AcquireWriteSlot(size_t imageSize, size_t metadataSize, * @param buffer The buffer to be released. * @return Error code (0 on success). */ -int DataBuffer::FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actualMetadataBytes) { - if (imageDataPointer == nullptr) +int DataBuffer::FinalizeWriteSlot(unsigned char* dataPointer, size_t actualMetadataBytes) { + if (dataPointer == nullptr) return DEVICE_ERR; BufferSlot* slot = nullptr; { std::lock_guard lock(slotManagementMutex_); - slot = FindSlotForPointer(imageDataPointer); + slot = FindSlotForPointer(dataPointer); if (!slot) return DEVICE_ERR; + + // Update the slot with actual metadata size + slot->UpdateAdditionalMetadataSize(actualMetadataBytes); } slot->ReleaseWriteAccess(); @@ -343,19 +345,19 @@ int DataBuffer::FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actual * This implementation pushes only the start of the released slot onto the FILO * (releasedSlots_) and removes the slot from the active slot map and activeSlots_. */ -int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { - if (imageDataPointer == nullptr) +int DataBuffer::ReleaseDataReadPointer(const unsigned char* dataPointer) { + if (dataPointer == nullptr) return DEVICE_ERR; // First find the slot without the global lock BufferSlot* slot = nullptr; { std::lock_guard lock(slotManagementMutex_); - slot = FindSlotForPointer(imageDataPointer); + slot = FindSlotForPointer(dataPointer); if (!slot) return DEVICE_ERR; } - const size_t offset = imageDataPointer - buffer_; + const size_t offset = dataPointer - buffer_; // Release the read access outside the global lock slot->ReleaseReadAccess(); @@ -372,7 +374,7 @@ int DataBuffer::ReleaseDataReadPointer(const unsigned char* imageDataPointer) { return DEVICE_OK; } -const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData) +const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *dataSize, bool waitForData) { BufferSlot* slot = nullptr; size_t slotStart = 0; @@ -394,16 +396,16 @@ const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *im // Now acquire read access outside the global lock slot->AcquireReadAccess(); - const unsigned char* imageDataPointer = buffer_ + slotStart; - *imageDataSize = slot->GetImageSize(); + const unsigned char* dataPointer = buffer_ + slotStart; + *dataSize = slot->GetDataSize(); - const unsigned char* metadataPtr = imageDataPointer + slot->GetImageSize(); - this->ExtractMetadata(metadataPtr, slot->GetMetadataSize(), md); + const unsigned char* metadataPtr = dataPointer + slot->GetDataSize(); + this->ExtractMetadata(dataPointer, slot, md); - return imageDataPointer; + return dataPointer; } -int DataBuffer::PeekNextDataReadPointer(const unsigned char** imageDataPointer, size_t* imageDataSize, +int DataBuffer::PeekNextDataReadPointer(const unsigned char** dataPointer, size_t* dataSize, Metadata &md) { // Immediately check if there is an unread slot without waiting. BufferSlot* currentSlot = nullptr; @@ -420,16 +422,16 @@ int DataBuffer::PeekNextDataReadPointer(const unsigned char** imageDataPointer, currentSlot->AcquireReadAccess(); std::unique_lock lock(slotManagementMutex_); - *imageDataPointer = buffer_ + currentSlot->GetStart(); - *imageDataSize = currentSlot->GetImageSize(); + *dataPointer = buffer_ + currentSlot->GetStart(); + *dataSize = currentSlot->GetDataSize(); - const unsigned char* metadataPtr = *imageDataPointer + currentSlot->GetImageSize(); - this->ExtractMetadata(metadataPtr, currentSlot->GetMetadataSize(), md); + const unsigned char* metadataPtr = *dataPointer + currentSlot->GetDataSize(); + this->ExtractMetadata(metadataPtr, currentSlot, md); return DEVICE_OK; } -const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* imageDataSize, Metadata &md) { +const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* dataSize, Metadata &md) { BufferSlot* currentSlot = nullptr; { // Lock the global slot management mutex to safely access the active slots. @@ -446,13 +448,13 @@ const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* im currentSlot->AcquireReadAccess(); - const unsigned char* imageDataPointer = buffer_ + currentSlot->GetStart(); - *imageDataSize = currentSlot->GetImageSize(); + const unsigned char* dataPointer = buffer_ + currentSlot->GetStart(); + *dataSize = currentSlot->GetDataSize(); - const unsigned char* metadataPtr = imageDataPointer + currentSlot->GetImageSize(); - this->ExtractMetadata(metadataPtr, currentSlot->GetMetadataSize(), md); + const unsigned char* metadataPtr = dataPointer + currentSlot->GetDataSize(); + this->ExtractMetadata(dataPointer, currentSlot, md); - return imageDataPointer; + return dataPointer; } unsigned int DataBuffer::GetMemorySizeMB() const { @@ -538,34 +540,48 @@ long DataBuffer::GetActiveSlotCount() const { return static_cast(activeSlotsVector_.size()); } -int DataBuffer::ExtractMetadata(const unsigned char* metadataPtr, size_t metadataSize, Metadata &md) { +int DataBuffer::ExtractMetadata(const unsigned char* dataPointer, BufferSlot* slot, Metadata &md) { // No lock is required here because we assume the slot is already locked - if (!metadataPtr) + + if (!dataPointer || !slot) return DEVICE_ERR; // Invalid pointer - // If no metadata is stored, clear the metadata object - if (metadataSize == 0) { - md.Clear(); - } else { - // Create a temporary string from the metadata region - std::string metaStr(reinterpret_cast(metadataPtr), metadataSize); - // Restore the metadata from the serialized string - md.Restore(metaStr.c_str()); + // Calculate metadata pointers and sizes from the slot + const unsigned char* initialMetadataPtr = dataPointer + slot->GetDataSize(); + size_t initialMetadataSize = slot->GetInitialMetadataSize(); + const unsigned char* additionalMetadataPtr = initialMetadataPtr + initialMetadataSize; + size_t additionalMetadataSize = slot->GetAdditionalMetadataSize(); + + // Handle initial metadata if present + if (initialMetadataSize > 0) { + Metadata initialMd; + std::string initialMetaStr(reinterpret_cast(initialMetadataPtr), initialMetadataSize); + initialMd.Restore(initialMetaStr.c_str()); + md.Merge(initialMd); + } + + // Handle additional metadata if present + if (additionalMetadataSize > 0) { + Metadata additionalMd; + std::string additionalMetaStr(reinterpret_cast(additionalMetadataPtr), additionalMetadataSize); + additionalMd.Restore(additionalMetaStr.c_str()); + md.Merge(additionalMd); } + return DEVICE_OK; } // NOTE: Caller must hold slotManagementMutex_ for thread safety. -BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* imageDataPtr) { +BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* dataPointer) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); if (buffer_ == nullptr) return nullptr; - std::size_t offset = imageDataPtr - buffer_; + std::size_t offset = dataPointer - buffer_; auto it = activeSlotsByStart_.find(offset); return (it != activeSlotsByStart_.end()) ? it->second : nullptr; } -void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, size_t freedRegionSize) { +void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); // Find the free region that starts at or after newEnd auto right = freeRegions_.lower_bound(newRegionEnd); @@ -613,32 +629,25 @@ void DataBuffer::RemoveFromActiveTracking(size_t offset, std::unordered_map::iterator it) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - size_t freedRegionSize = it->second->GetLength(); size_t newRegionStart = offset; - size_t newRegionEnd = offset + freedRegionSize; + size_t newRegionEnd = offset + it->second->GetLength(); // Return the slot to the pool before removing from active tracking ReturnSlotToPool(it->second); - MergeFreeRegions(newRegionStart, newRegionEnd, freedRegionSize); + MergeFreeRegions(newRegionStart, newRegionEnd); RemoveFromActiveTracking(offset, it); } BufferSlot* DataBuffer::InitializeNewSlot(size_t candidateStart, size_t totalSlotSize, - size_t imageDataSize, size_t metadataSize) { + size_t dataSize, size_t metadataSize) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, imageDataSize, metadataSize); + BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, dataSize, metadataSize, 0); activeSlotsVector_.push_back(newSlot); activeSlotsByStart_[candidateStart] = newSlot; return newSlot; } -void DataBuffer::SetupSlotPointers(BufferSlot* newSlot, unsigned char** imageDataPointer, - unsigned char** metadataPointer) { - *imageDataPointer = buffer_ + newSlot->GetStart(); - *metadataPointer = *imageDataPointer + newSlot->GetImageSize(); -} - void DataBuffer::UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); auto it = freeRegions_.upper_bound(candidateStart); @@ -668,17 +677,27 @@ void DataBuffer::UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize) } int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, - size_t imageDataSize, size_t metadataSize, - unsigned char** imageDataPointer, unsigned char** metadataPointer, - bool fromFreeRegion) { + size_t dataSize, size_t additionalMetadataSize, + unsigned char** dataPointer, unsigned char** additionalMetadataPointer, + bool fromFreeRegion, const std::string& serializedInitialMetadata) +{ assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, + dataSize, + serializedInitialMetadata.size(), // initialMetadataSize + additionalMetadataSize); // subsequentMetadataSize - BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, imageDataSize, metadataSize); - SetupSlotPointers(newSlot, imageDataPointer, metadataPointer); - // Explicitly acquire write access here, after the slot is fully set up newSlot->AcquireWriteAccess(); + if (!serializedInitialMetadata.empty()) { + std::memcpy(*dataPointer + dataSize, serializedInitialMetadata.data(), serializedInitialMetadata.size()); + newSlot->SetInitialMetadataSize(serializedInitialMetadata.size()); + } + + *dataPointer = buffer_ + newSlot->GetStart(); + *additionalMetadataPointer = *dataPointer + newSlot->GetDataSize() + newSlot->GetInitialMetadataSize(); + if (fromFreeRegion) { UpdateFreeRegions(candidateStart, totalSlotSize); } @@ -687,7 +706,7 @@ int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, } BufferSlot* DataBuffer::GetSlotFromPool(size_t start, size_t totalLength, - size_t imageSize, size_t metadataSize) { + size_t dataSize, size_t initialMetadataSize, size_t additionalMetadataSize) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); // Grow the pool if needed. @@ -700,7 +719,7 @@ BufferSlot* DataBuffer::GetSlotFromPool(size_t start, size_t totalLength, // Get a slot from the front of the deque. BufferSlot* slot = unusedSlots_.front(); unusedSlots_.pop_front(); - slot->Reset(start, totalLength, imageSize, metadataSize); + slot->Reset(start, totalLength, dataSize, initialMetadataSize, additionalMetadataSize); // Add to active tracking. activeSlotsVector_.push_back(slot); @@ -713,19 +732,21 @@ void DataBuffer::ReturnSlotToPool(BufferSlot* slot) { unusedSlots_.push_back(slot); } -int DataBuffer::ExtractMetadataForImage(const unsigned char* imageDataPtr, Metadata &md) { +int DataBuffer::ExtractCorrespondingMetadata(const unsigned char* dataPointer, Metadata &md) { BufferSlot* slot = nullptr; { std::lock_guard lock(slotManagementMutex_); - slot = FindSlotForPointer(imageDataPtr); + slot = FindSlotForPointer(dataPointer); if (!slot) { return DEVICE_ERR; } } // Get metadata pointer and size while under lock - const unsigned char* metadataPtr = imageDataPtr + slot->GetImageSize(); - size_t metadataSize = slot->GetMetadataSize(); + const unsigned char* initialMetadataPtr = dataPointer + slot->GetDataSize(); + size_t initialMetadataSize = slot->GetInitialMetadataSize(); + const unsigned char* additionalMetadataPtr = initialMetadataPtr + initialMetadataSize; + size_t additionalMetadataSize = slot->GetAdditionalMetadataSize(); // Extract metadata (internal method doesn't need lock) - return ExtractMetadata(metadataPtr, metadataSize, md); + return ExtractMetadata(dataPointer, slot, md); } \ No newline at end of file diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 098afd118..5570d9959 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -62,7 +62,7 @@ class BufferSlot { * @param imageSize The exact number of bytes for the image data. * @param metadataSize The exact number of bytes for the metadata. */ - BufferSlot() : start_(0), length_(0), imageSize_(0), metadataSize_(0), rwMutex_() {} + BufferSlot() : start_(0), length_(0), imageSize_(0), initialMetadataSize_(0), additionalMetadataSize_(0), rwMutex_() {} /** * Destructor. @@ -82,18 +82,6 @@ class BufferSlot { */ std::size_t GetLength() const { return length_; } - /** - * Returns the size (in bytes) of the image data in the slot. - * @return The image data size. - */ - size_t GetImageSize() const { return imageSize_; } - - /** - * Returns the size (in bytes) of the metadata in the slot. - * @return The metadata size. - */ - size_t GetMetadataSize() const { return metadataSize_; } - /** * Attempts to acquire exclusive write access without blocking. * @return True if the write lock was acquired; false otherwise. @@ -146,7 +134,7 @@ class BufferSlot { return false; } - void Reset(size_t start, size_t length, size_t imageSize, size_t metadataSize) { + void Reset(size_t start, size_t length, size_t imageSize, size_t initialMetadataSize, size_t additionalMetadataSize) { // Assert that the mutex is available before recycling assert(IsAvailableForWriting() && IsAvailableForReading() && "BufferSlot mutex still locked during Reset - indicates a bug!"); @@ -155,16 +143,47 @@ class BufferSlot { start_ = start; length_ = length; imageSize_ = imageSize; - metadataSize_ = metadataSize; + initialMetadataSize_ = initialMetadataSize; + additionalMetadataSize_ = initialMetadataSize + additionalMetadataSize; // The caller should explicitly acquire write access when needed } + /** + * Updates the metadata size after writing is complete. + * @param newSize The actual size of the written metadata. + */ + void UpdateAdditionalMetadataSize(size_t newSize) { additionalMetadataSize_ = newSize; } + + /** + * Record the number of bytes of the initial metadata that have been written to this slot. + */ + void SetInitialMetadataSize(size_t initialSize) { initialMetadataSize_ = initialSize; } + + /** + * Returns the size of the image data in bytes. + * @return The image data size. + */ + std::size_t GetDataSize() const { return imageSize_; } + + /** + * Returns the size of the initial metadata in bytes. + * @return The initial metadata size. + */ + std::size_t GetInitialMetadataSize() const { return initialMetadataSize_; } + + /** + * Returns the size of the additional metadata in bytes. + * @return The additional metadata size. + */ + std::size_t GetAdditionalMetadataSize() const { return additionalMetadataSize_; } + private: std::size_t start_; std::size_t length_; size_t imageSize_; - size_t metadataSize_; + size_t initialMetadataSize_ = 0; + size_t additionalMetadataSize_ = 0; mutable std::shared_timed_mutex rwMutex_; }; @@ -221,14 +240,17 @@ class DataBuffer { * Acquires a write slot large enough to hold the image data and metadata. * On success, returns pointers for the image data and metadata regions. * - * @param imageSize The number of bytes reserved for image data. - * @param metadataSize The maximum number of bytes reserved for metadata. - * @param imageDataPointer On success, receives a pointer to the image data region. - * @param metadataPointer On success, receives a pointer to the metadata region. + * @param dataSize The number of bytes reserved for data. + * @param additionalMetadataSize The maximum number of bytes reserved for additional metadata. + * @param dataPointer On success, receives a pointer to the data region. + * @param additionalMetadataPointer On success, receives a pointer to the additional metadata region. + * @param serializedInitialMetadata Optional string containing initial metadata to write. * @return DEVICE_OK on success. */ - int AcquireWriteSlot(size_t imageSize, size_t metadataSize, - unsigned char** imageDataPointer, unsigned char** metadataPointer); + int AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, + unsigned char** dataPointer, + unsigned char** additionalMetadataPointer, + const std::string& serializedInitialMetadata); /** * Finalizes (releases) a write slot after data has been written. @@ -331,11 +353,11 @@ class DataBuffer { * Extracts metadata for a given image data pointer. * Thread-safe method that acquires necessary locks to lookup metadata location. * - * @param imageDataPtr Pointer to the image data. + * @param dataPtr Pointer to the image data. * @param md Metadata object to populate. * @return DEVICE_OK on success, or an error code if extraction fails. */ - int ExtractMetadataForImage(const unsigned char* imageDataPtr, Metadata &md); + int ExtractCorrespondingMetadata(const unsigned char* dataPtr, Metadata &md); private: /** @@ -390,38 +412,38 @@ class DataBuffer { std::shared_ptr threadPool_; std::shared_ptr tasksMemCopy_; - /** - * Internal helper to register a new slot. - */ - int CreateSlot(size_t candidateStart, size_t totalSlotSize, size_t imageDataSize, - size_t metadataSize, - unsigned char** imageDataPointer, unsigned char** metadataPointer, - bool fromFreeRegion); - void DeleteSlot(size_t offset, std::unordered_map::iterator it); - void MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd, size_t freedRegionSize); + void MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd); void RemoveFromActiveTracking(size_t offset, std::unordered_map::iterator it); - BufferSlot* InitializeNewSlot(size_t candidateStart, size_t totalSlotSize, - size_t imageDataSize, size_t metadataSize); - void SetupSlotPointers(BufferSlot* newSlot, unsigned char** imageDataPointer, - unsigned char** metadataPointer); - void UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize); - - void ReturnSlotToPool(BufferSlot* slot); + void UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize); BufferSlot* GetSlotFromPool(size_t start, size_t totalLength, - size_t imageSize, size_t metadataSize); + size_t dataSize, size_t initialMetadataSize, + size_t additionalMetadataSize); /** - * Extracts and deserializes the metadata from the given metadata pointer. - * Assumes the corresponding slot is already locked for reading. - * - * @param imageDataPtr Pointer to the image data. - * @param md Metadata object to populate. - * @return DEVICE_OK on success, or an error code if extraction fails. + * Creates a new slot with the specified parameters. + * Caller must hold slotManagementMutex_. + */ + int CreateSlot(size_t candidateStart, size_t totalSlotSize, + size_t dataSize, size_t additionalMetadataSize, + unsigned char** dataPointer, + unsigned char** subsequentMetadataPointer, + bool fromFreeRegion, + const std::string& serializedInitialMetadata); + + /** + * Initializes a new slot with the given parameters. + * Caller must hold slotManagementMutex_. */ - int ExtractMetadata(const unsigned char* metadataPtr, size_t metadataSize, Metadata &md); + BufferSlot* InitializeNewSlot(size_t candidateStart, size_t totalSlotSize, + size_t dataSize, size_t metadataSize); + void ReturnSlotToPool(BufferSlot* slot); + + int ExtractMetadata(const unsigned char* dataPointer, + BufferSlot* slot, + Metadata &md); }; diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 5dff1539c..4d8e56487 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -31,6 +31,7 @@ #include "CircularBuffer.h" #include "CoreCallback.h" #include "DeviceManager.h" +#include "BufferAdapter.h" #include #include @@ -320,6 +321,42 @@ int CoreCallback::InsertImage(const MM::Device* caller, const ImgBuffer & imgBuf imgBuf.Height(), imgBuf.Depth(), &md); } +// This method is explicitly for camera devices. It requires width, height, byteDepth, and nComponents +// to be passed in, so that higher level code retrieving data from the buffer knows how to interpret the data +// For other data types, analogous methods could be added in the future + +/////// TODO: uncomment to activate these methods once tested with camera + +// int CoreCallback::AcquireImageWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, +// unsigned char** dataPointer, unsigned char** metadataPointer, +// unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents) +// { +// if (core_->deviceManager_->GetDevice(caller)->GetType() == MM::CameraDevice) +// { +// Metadata md = AddCameraMetadata(caller, nullptr); + +// if (!core_->bufferAdapter_->AcquireWriteSlot(dataSize, width, height, +// byteDepth, nComponents, metadataSize, +// dataPointer, metadataPointer, &md)) +// { +// return DEVICE_ERR; +// } +// return DEVICE_OK; +// } + +// return DEVICE_ERR; +// } + +// int CoreCallback::FinalizeWriteSlot(unsigned char* dataPointer, +// size_t actualMetadataBytes) +// { +// if (core_->bufferAdapter_->FinalizeWriteSlot(dataPointer, actualMetadataBytes)) +// { +// return DEVICE_OK; +// } +// return DEVICE_ERR; +// } + void CoreCallback::ClearImageBuffer(const MM::Device* /*caller*/) { core_->bufferAdapter_->Clear(); diff --git a/MMCore/CoreCallback.h b/MMCore/CoreCallback.h index 275493b42..9f46bc63b 100644 --- a/MMCore/CoreCallback.h +++ b/MMCore/CoreCallback.h @@ -91,6 +91,16 @@ class CoreCallback : public MM::Core /*Deprecated*/ int InsertImage(const MM::Device* caller, const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, const Metadata* pMd = 0, const bool doProcess = true); /*Deprecated*/ int InsertMultiChannel(const MM::Device* caller, const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd = 0); + + // Direct writing into V2 buffer instead of using InsertImage (which has to copy the data again) + ///////// TODO: uncomment to activate these methods once tested with camera +// int AcquireImageWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, +// unsigned char** dataPointer, unsigned char** metadataPointer, +// unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents); +// int FinalizeWriteSlot(unsigned char* imageDataPointer, +// size_t actualMetadataBytes); + + void ClearImageBuffer(const MM::Device* caller); bool InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth); @@ -136,6 +146,7 @@ class CoreCallback : public MM::Core void GetLoadedDeviceOfType(const MM::Device* caller, MM::DeviceType devType, char* deviceName, const unsigned int deviceIterator); + private: CMMCore* core_; MMThreadLock* pValueChangeLock_; From 8697a85256135077d9cda872693661fce1f3a1ba Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:13:42 -0800 Subject: [PATCH 20/46] fix bug --- MMCore/Buffer_v2.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index d952f16db..2af3b1e0e 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -687,15 +687,16 @@ int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, serializedInitialMetadata.size(), // initialMetadataSize additionalMetadataSize); // subsequentMetadataSize - newSlot->AcquireWriteAccess(); + // Initialize the data pointer before using it. + *dataPointer = buffer_ + newSlot->GetStart(); + if (!serializedInitialMetadata.empty()) { std::memcpy(*dataPointer + dataSize, serializedInitialMetadata.data(), serializedInitialMetadata.size()); newSlot->SetInitialMetadataSize(serializedInitialMetadata.size()); } - *dataPointer = buffer_ + newSlot->GetStart(); *additionalMetadataPointer = *dataPointer + newSlot->GetDataSize() + newSlot->GetInitialMetadataSize(); if (fromFreeRegion) { From 6aae6b63a0fe0f20d48f830ff5ec2e3b2e860716 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 15 Feb 2025 11:41:16 -0800 Subject: [PATCH 21/46] fix bug getting image without metadta and standardize internal pointer types --- MMCore/BufferAdapter.cpp | 64 ++++++++++++++++------------------------ MMCore/BufferAdapter.h | 47 +++++++++-------------------- MMCore/Buffer_v2.cpp | 57 +++++++++++++++++------------------ MMCore/Buffer_v2.h | 43 +++++++++++++-------------- MMCore/MMCore.cpp | 14 ++++----- 5 files changed, 94 insertions(+), 131 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index c57c398f7..ed7864fcf 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -78,24 +78,23 @@ BufferAdapter::~BufferAdapter() } } -const unsigned char* BufferAdapter::GetLastImage() const +const void* BufferAdapter::GetLastImage() const { if (useV2_) { Metadata dummyMetadata; - return v2Buffer_->PeekDataReadPointerAtIndex(0, nullptr, dummyMetadata); // NOTE: ensure calling code releases the slot after use + return v2Buffer_->PeekDataReadPointerAtIndex(0, dummyMetadata); } else { return circBuffer_->GetTopImage(); } - } -const unsigned char* BufferAdapter::PopNextImage() +const void* BufferAdapter::PopNextImage() { if (useV2_) { Metadata dummyMetadata; - return v2Buffer_->PopNextDataReadPointer(dummyMetadata, nullptr, false); // NOTE: ensure calling code releases the slot after use + return v2Buffer_->PopNextDataReadPointer(dummyMetadata, false); } else { return circBuffer_->PopNextImage(); } @@ -268,13 +267,12 @@ bool BufferAdapter::InsertMultiChannel(const unsigned char* buf, unsigned numCha } } -void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) +const void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) { if (useV2_) { // In v2, we now use a channel-aware pointer arithmetic at the adapter level. - const unsigned char* basePtr = nullptr; - size_t imageDataSize = 0; - int ret = v2Buffer_->PeekNextDataReadPointer(&basePtr, &imageDataSize, md); + const void* basePtr = nullptr; + int ret = v2Buffer_->PeekNextDataReadPointer(&basePtr, md); if (ret != DEVICE_OK || basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); @@ -285,47 +283,40 @@ void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw size_t singleChannelSize = width * height * pixDepth; // Advance the base pointer by the amount corresponding to the selected channel. - const unsigned char* channelPtr = basePtr + (channel * singleChannelSize); - return const_cast(channelPtr); // NOTE: make sure calling code releases the slot after use. + return static_cast(basePtr) + (channel * singleChannelSize); } else { const mm::ImgBuffer* pBuf = circBuffer_->GetTopImageBuffer(channel); if (pBuf != nullptr) { md = pBuf->GetMetadata(); - return const_cast(pBuf->GetPixels()); + return pBuf->GetPixels(); } else { throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty); } } } -void* BufferAdapter::GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError) +const void* BufferAdapter::GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError) { if (useV2_) { - size_t dataSize = 0; - const unsigned char* ptr = v2Buffer_->PeekDataReadPointerAtIndex(n, &dataSize, md); - if (ptr == nullptr) - throw CMMError("V2 buffer does not contain enough data.", MMERR_CircularBufferEmpty); - // Return a non-const pointer (caller must be careful with the const_cast) - return const_cast(ptr); - // NOTE: make sure calling code releases the slot after use + // NOTE: make sure calling code releases the slot after use. + return v2Buffer_->PeekDataReadPointerAtIndex(n, md); } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNthFromTopImageBuffer(n); if (pBuf != nullptr) { md = pBuf->GetMetadata(); - return const_cast(pBuf->GetPixels()); + return pBuf->GetPixels(); } else { throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty); } } } -void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError) +const void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError) { if (useV2_) { // For v2, we now make the buffer channel aware at the adapter level. - size_t dataSize = 0; - const unsigned char* basePtr = v2Buffer_->PopNextDataReadPointer(md, &dataSize, false); + const void* basePtr = v2Buffer_->PopNextDataReadPointer(md, false); if (basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); @@ -336,15 +327,12 @@ void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMEr size_t singleChannelSize = width * height * pixDepth; // Offset the base pointer to get the requested channel's data. - // 'channel' is assumed to be passed as a parameter. - const unsigned char* channelPtr = basePtr + (channel * singleChannelSize); - return const_cast(channelPtr); - // NOTE: ensure that calling code releases the read pointer after use. + return static_cast(basePtr) + (channel * singleChannelSize); } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNextImageBuffer(channel); if (pBuf != nullptr) { md = pBuf->GetMetadata(); - return const_cast(pBuf->GetPixels()); + return pBuf->GetPixels(); } else { throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty); } @@ -391,13 +379,13 @@ bool BufferAdapter::IsUsingV2Buffer() const { return useV2_; } -void BufferAdapter::ReleaseReadAccess(const unsigned char* ptr) { +void BufferAdapter::ReleaseReadAccess(const void* ptr) { if (useV2_ && ptr) { v2Buffer_->ReleaseDataReadPointer(ptr); } } -unsigned BufferAdapter::GetImageWidth(const unsigned char* ptr) const { +unsigned BufferAdapter::GetImageWidth(const void* ptr) const { if (!useV2_) throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); Metadata md; @@ -407,7 +395,7 @@ unsigned BufferAdapter::GetImageWidth(const unsigned char* ptr) const { return static_cast(atoi(sVal.c_str())); } -unsigned BufferAdapter::GetImageHeight(const unsigned char* ptr) const { +unsigned BufferAdapter::GetImageHeight(const void* ptr) const { if (!useV2_) throw CMMError("GetImageHeight(ptr) only supported with V2 buffer"); Metadata md; @@ -417,7 +405,7 @@ unsigned BufferAdapter::GetImageHeight(const unsigned char* ptr) const { return static_cast(atoi(sVal.c_str())); } -unsigned BufferAdapter::GetBytesPerPixel(const unsigned char* ptr) const { +unsigned BufferAdapter::GetBytesPerPixel(const void* ptr) const { if (!useV2_) throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); Metadata md; @@ -436,7 +424,7 @@ unsigned BufferAdapter::GetBytesPerPixel(const unsigned char* ptr) const { throw CMMError("Unknown pixel type for bytes per pixel"); } -unsigned BufferAdapter::GetImageBitDepth(const unsigned char* ptr) const { +unsigned BufferAdapter::GetImageBitDepth(const void* ptr) const { if (!useV2_) throw CMMError("GetImageBitDepth(ptr) only supported with V2 buffer"); Metadata md; @@ -455,7 +443,7 @@ unsigned BufferAdapter::GetImageBitDepth(const unsigned char* ptr) const { throw CMMError("Unknown pixel type for image bit depth"); } -unsigned BufferAdapter::GetNumberOfComponents(const unsigned char* ptr) const { +unsigned BufferAdapter::GetNumberOfComponents(const void* ptr) const { if (!useV2_) throw CMMError("GetNumberOfComponents(ptr) only supported with V2 buffer"); Metadata md; @@ -472,7 +460,7 @@ unsigned BufferAdapter::GetNumberOfComponents(const unsigned char* ptr) const { throw CMMError("Unknown pixel type for number of components"); } -long BufferAdapter::GetImageBufferSize(const unsigned char* ptr) const { +long BufferAdapter::GetImageBufferSize(const void* ptr) const { if (!useV2_) throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); Metadata md; @@ -496,7 +484,7 @@ bool BufferAdapter::SetOverwriteData(bool overwrite) { bool BufferAdapter::AcquireWriteSlot(size_t dataSize, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, size_t additionalMetadataSize, - unsigned char** dataPointer, unsigned char** additionalMetadataPointer, + void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata) { if (!useV2_) { // Not supported for circular buffer @@ -516,7 +504,7 @@ bool BufferAdapter::AcquireWriteSlot(size_t dataSize, unsigned width, unsigned h return ret == DEVICE_OK; } -bool BufferAdapter::FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actualMetadataBytes) +bool BufferAdapter::FinalizeWriteSlot(void* imageDataPointer, size_t actualMetadataBytes) { if (!useV2_) { // Not supported for circular buffer diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index 279ab114d..bd246a5e5 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -58,27 +58,14 @@ class BufferAdapter { * Get a pointer to the top (most recent) image. * @return Pointer to image data, or nullptr if unavailable. */ - const unsigned char* GetLastImage() const; + const void* GetLastImage() const; /** * Get a pointer to the next image from the buffer. * @return Pointer to image data, or nullptr if unavailable. */ - const unsigned char* PopNextImage(); + const void* PopNextImage(); - /** - * Get a pointer to the nth image from the top of the buffer. - * @param n The index from the top. - * @return Pointer to image data, or nullptr if unavailable. - */ - const mm::ImgBuffer* GetNthFromTopImageBuffer(unsigned long n) const; - - /** - * Get a pointer to the next image buffer for a specific channel. - * @param channel The channel number. - * @return Pointer to image data, or nullptr if unavailable. - */ - const mm::ImgBuffer* GetNextImageBuffer(unsigned channel); /** * Initialize the buffer with the given parameters. @@ -178,16 +165,10 @@ class BufferAdapter { */ bool Overflow() const; - /** - * Get a pointer to the top image buffer for a specific channel. - * @param channel The channel number. - * @return Pointer to image data, or nullptr if unavailable. - */ - const mm::ImgBuffer* GetTopImageBuffer(unsigned channel) const; - void* GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError); - void* GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError); - void* PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError); + const void* GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError); + const void* GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError); + const void* PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError); /** * Check if this adapter is using the V2 buffer implementation. @@ -200,15 +181,15 @@ class BufferAdapter { * This is required when using the V2 buffer implementation. * @param ptr The pointer to release. */ - void ReleaseReadAccess(const unsigned char* ptr); + void ReleaseReadAccess(const void* ptr); // Methods for the v2 buffer where width and heigh must be gotton on a per-image basis - unsigned GetImageWidth(const unsigned char* ptr) const; - unsigned GetImageHeight(const unsigned char* ptr) const; - unsigned GetBytesPerPixel(const unsigned char* ptr) const; - unsigned GetImageBitDepth(const unsigned char* ptr) const; - unsigned GetNumberOfComponents(const unsigned char* ptr) const; - long GetImageBufferSize(const unsigned char* ptr) const; + unsigned GetImageWidth(const void* ptr) const; + unsigned GetImageHeight(const void* ptr) const; + unsigned GetBytesPerPixel(const void* ptr) const; + unsigned GetImageBitDepth(const void* ptr) const; + unsigned GetNumberOfComponents(const void* ptr) const; + long GetImageBufferSize(const void* ptr) const; /** * Configure whether to overwrite old data when buffer is full. @@ -232,7 +213,7 @@ class BufferAdapter { */ bool AcquireWriteSlot(size_t dataSize, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, size_t additionalMetadataSize, - unsigned char** dataPointer, unsigned char** additionalMetadataPointer, + void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata = nullptr); /** @@ -241,7 +222,7 @@ class BufferAdapter { * @param actualMetadataBytes The actual number of metadata bytes written. * @return true on success, false on error. */ - bool FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actualMetadataBytes); + bool FinalizeWriteSlot(void* imageDataPointer, size_t actualMetadataBytes); private: bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 2af3b1e0e..89afcea0d 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -192,10 +192,10 @@ int DataBuffer::ReleaseBuffer() { /** * Pack the data as [BufferSlotRecord][image data][serialized metadata] */ -int DataBuffer::InsertData(const unsigned char* data, size_t dataSize, const Metadata* pMd) { +int DataBuffer::InsertData(const void* data, size_t dataSize, const Metadata* pMd) { - unsigned char* dataPointer = nullptr; - unsigned char* additionalMetadataPointer = nullptr; + void* dataPointer = nullptr; + void* additionalMetadataPointer = nullptr; // Convert metadata to serialized string if provided std::string serializedMetadata; @@ -239,8 +239,8 @@ int DataBuffer::SetOverwriteData(bool overwrite) { * The caller must release the slot using ReleaseDataSlot after writing is complete. */ int DataBuffer::AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, - unsigned char** dataPointer, - unsigned char** additionalMetadataPointer, + void** dataPointer, + void** additionalMetadataPointer, const std::string& serializedInitialMetadata) { if (buffer_ == nullptr) { @@ -313,7 +313,7 @@ int DataBuffer::AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, * @param buffer The buffer to be released. * @return Error code (0 on success). */ -int DataBuffer::FinalizeWriteSlot(unsigned char* dataPointer, size_t actualMetadataBytes) { +int DataBuffer::FinalizeWriteSlot(const void* dataPointer, size_t actualMetadataBytes) { if (dataPointer == nullptr) return DEVICE_ERR; @@ -345,7 +345,7 @@ int DataBuffer::FinalizeWriteSlot(unsigned char* dataPointer, size_t actualMetad * This implementation pushes only the start of the released slot onto the FILO * (releasedSlots_) and removes the slot from the active slot map and activeSlots_. */ -int DataBuffer::ReleaseDataReadPointer(const unsigned char* dataPointer) { +int DataBuffer::ReleaseDataReadPointer(const void* dataPointer) { if (dataPointer == nullptr) return DEVICE_ERR; @@ -357,7 +357,7 @@ int DataBuffer::ReleaseDataReadPointer(const unsigned char* dataPointer) { if (!slot) return DEVICE_ERR; } - const size_t offset = dataPointer - buffer_; + const size_t offset = static_cast(dataPointer) - buffer_; // Release the read access outside the global lock slot->ReleaseReadAccess(); @@ -374,7 +374,7 @@ int DataBuffer::ReleaseDataReadPointer(const unsigned char* dataPointer) { return DEVICE_OK; } -const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *dataSize, bool waitForData) +const void* DataBuffer::PopNextDataReadPointer(Metadata &md, bool waitForData) { BufferSlot* slot = nullptr; size_t slotStart = 0; @@ -396,8 +396,7 @@ const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *da // Now acquire read access outside the global lock slot->AcquireReadAccess(); - const unsigned char* dataPointer = buffer_ + slotStart; - *dataSize = slot->GetDataSize(); + const unsigned char* dataPointer = static_cast(buffer_) + slotStart; const unsigned char* metadataPtr = dataPointer + slot->GetDataSize(); this->ExtractMetadata(dataPointer, slot, md); @@ -405,8 +404,7 @@ const unsigned char* DataBuffer::PopNextDataReadPointer(Metadata &md, size_t *da return dataPointer; } -int DataBuffer::PeekNextDataReadPointer(const unsigned char** dataPointer, size_t* dataSize, - Metadata &md) { +int DataBuffer::PeekNextDataReadPointer(const void** dataPointer, Metadata &md) { // Immediately check if there is an unread slot without waiting. BufferSlot* currentSlot = nullptr; { @@ -422,16 +420,15 @@ int DataBuffer::PeekNextDataReadPointer(const unsigned char** dataPointer, size_ currentSlot->AcquireReadAccess(); std::unique_lock lock(slotManagementMutex_); - *dataPointer = buffer_ + currentSlot->GetStart(); - *dataSize = currentSlot->GetDataSize(); + *dataPointer = static_cast(buffer_) + currentSlot->GetStart(); - const unsigned char* metadataPtr = *dataPointer + currentSlot->GetDataSize(); + const unsigned char* metadataPtr = static_cast(*dataPointer) + currentSlot->GetDataSize(); this->ExtractMetadata(metadataPtr, currentSlot, md); return DEVICE_OK; } -const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* dataSize, Metadata &md) { +const void* DataBuffer::PeekDataReadPointerAtIndex(size_t n, Metadata &md) { BufferSlot* currentSlot = nullptr; { // Lock the global slot management mutex to safely access the active slots. @@ -448,8 +445,7 @@ const unsigned char* DataBuffer::PeekDataReadPointerAtIndex(size_t n, size_t* da currentSlot->AcquireReadAccess(); - const unsigned char* dataPointer = buffer_ + currentSlot->GetStart(); - *dataSize = currentSlot->GetDataSize(); + const unsigned char* dataPointer = static_cast(buffer_) + currentSlot->GetStart(); const unsigned char* metadataPtr = dataPointer + currentSlot->GetDataSize(); this->ExtractMetadata(dataPointer, currentSlot, md); @@ -540,14 +536,14 @@ long DataBuffer::GetActiveSlotCount() const { return static_cast(activeSlotsVector_.size()); } -int DataBuffer::ExtractMetadata(const unsigned char* dataPointer, BufferSlot* slot, Metadata &md) { +int DataBuffer::ExtractMetadata(const void* dataPointer, BufferSlot* slot, Metadata &md) { // No lock is required here because we assume the slot is already locked if (!dataPointer || !slot) return DEVICE_ERR; // Invalid pointer // Calculate metadata pointers and sizes from the slot - const unsigned char* initialMetadataPtr = dataPointer + slot->GetDataSize(); + const unsigned char* initialMetadataPtr = static_cast(dataPointer) + slot->GetDataSize(); size_t initialMetadataSize = slot->GetInitialMetadataSize(); const unsigned char* additionalMetadataPtr = initialMetadataPtr + initialMetadataSize; size_t additionalMetadataSize = slot->GetAdditionalMetadataSize(); @@ -572,11 +568,11 @@ int DataBuffer::ExtractMetadata(const unsigned char* dataPointer, BufferSlot* sl } // NOTE: Caller must hold slotManagementMutex_ for thread safety. -BufferSlot* DataBuffer::FindSlotForPointer(const unsigned char* dataPointer) { +BufferSlot* DataBuffer::FindSlotForPointer(const void* dataPointer) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); if (buffer_ == nullptr) return nullptr; - std::size_t offset = dataPointer - buffer_; + std::size_t offset = static_cast(dataPointer) - buffer_; auto it = activeSlotsByStart_.find(offset); return (it != activeSlotsByStart_.end()) ? it->second : nullptr; } @@ -678,7 +674,7 @@ void DataBuffer::UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize) int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, size_t dataSize, size_t additionalMetadataSize, - unsigned char** dataPointer, unsigned char** additionalMetadataPointer, + void** dataPointer, void** additionalMetadataPointer, bool fromFreeRegion, const std::string& serializedInitialMetadata) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); @@ -690,14 +686,15 @@ int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, newSlot->AcquireWriteAccess(); // Initialize the data pointer before using it. - *dataPointer = buffer_ + newSlot->GetStart(); + *dataPointer = static_cast(buffer_) + newSlot->GetStart(); if (!serializedInitialMetadata.empty()) { - std::memcpy(*dataPointer + dataSize, serializedInitialMetadata.data(), serializedInitialMetadata.size()); + std::memcpy(static_cast(*dataPointer) + dataSize, serializedInitialMetadata.data(), + serializedInitialMetadata.size()); newSlot->SetInitialMetadataSize(serializedInitialMetadata.size()); } - *additionalMetadataPointer = *dataPointer + newSlot->GetDataSize() + newSlot->GetInitialMetadataSize(); + *additionalMetadataPointer = static_cast(*dataPointer) + newSlot->GetDataSize() + newSlot->GetInitialMetadataSize(); if (fromFreeRegion) { UpdateFreeRegions(candidateStart, totalSlotSize); @@ -733,7 +730,7 @@ void DataBuffer::ReturnSlotToPool(BufferSlot* slot) { unusedSlots_.push_back(slot); } -int DataBuffer::ExtractCorrespondingMetadata(const unsigned char* dataPointer, Metadata &md) { +int DataBuffer::ExtractCorrespondingMetadata(const void* dataPointer, Metadata &md) { BufferSlot* slot = nullptr; { std::lock_guard lock(slotManagementMutex_); @@ -743,9 +740,9 @@ int DataBuffer::ExtractCorrespondingMetadata(const unsigned char* dataPointer, M } } // Get metadata pointer and size while under lock - const unsigned char* initialMetadataPtr = dataPointer + slot->GetDataSize(); + const void* initialMetadataPtr = static_cast(dataPointer) + slot->GetDataSize(); size_t initialMetadataSize = slot->GetInitialMetadataSize(); - const unsigned char* additionalMetadataPtr = initialMetadataPtr + initialMetadataSize; + const void* additionalMetadataPtr = static_cast(initialMetadataPtr) + initialMetadataSize; size_t additionalMetadataSize = slot->GetAdditionalMetadataSize(); // Extract metadata (internal method doesn't need lock) diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 5570d9959..a1d938270 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -227,7 +227,7 @@ class DataBuffer { * @param pMd Pointer to the metadata (can be null if not applicable). * @return DEVICE_OK on success. */ - int InsertData(const unsigned char* data, size_t dataSize, const Metadata* pMd); + int InsertData(const void* data, size_t dataSize, const Metadata* pMd); /** * Sets whether the buffer should overwrite old data when full. @@ -248,8 +248,8 @@ class DataBuffer { * @return DEVICE_OK on success. */ int AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, - unsigned char** dataPointer, - unsigned char** additionalMetadataPointer, + void** dataPointer, + void** additionalMetadataPointer, const std::string& serializedInitialMetadata); /** @@ -260,50 +260,47 @@ class DataBuffer { * @param actualMetadataBytes The actual number of metadata bytes written. * @return DEVICE_OK on success. */ - int FinalizeWriteSlot(unsigned char* imageDataPointer, size_t actualMetadataBytes); + int FinalizeWriteSlot(const void* dataPointer, size_t actualMetadataBytes); /** * Releases read access for the image data after reading. * @param imageDataPointer Pointer previously obtained from reading routines. * @return DEVICE_OK on success. */ - int ReleaseDataReadPointer(const unsigned char* imageDataPointer); + int ReleaseDataReadPointer(const void* dataPointer); /** * Retrieves and consumes the next available data entry for reading, * populating the provided Metadata object. * @param md Metadata object to populate. - * @param imageDataSize On success, returns the image data size in bytes. * @param waitForData If true, blocks until data is available. * @return Pointer to the image data region, or nullptr if none available. */ - const unsigned char* PopNextDataReadPointer(Metadata &md, size_t *imageDataSize, bool waitForData); + const void* PopNextDataReadPointer(Metadata &md, bool waitForData); /** * Peeks at the next unread data entry without consuming it. - * @param imageDataPointer On success, receives a pointer to the image data region. - * @param imageDataSize On success, returns the image data size in bytes. + * @param dataPointer On success, receives a pointer to the (usually image) data region. * @param md Metadata object populated from the stored metadata. * @return DEVICE_OK on success. */ - int PeekNextDataReadPointer(const unsigned char** imageDataPointer, size_t* imageDataSize, Metadata &md); + int PeekNextDataReadPointer(const void** dataPointer, Metadata &md); /** * Peeks at the nth unread data entry without consuming it. * (n = 0 is equivalent to PeekNextDataReadPointer). * @param n Index of the data entry to peek at (0 for next available). - * @param imageDataSize On success, returns the image data size in bytes. * @param md Metadata object populated from the stored metadata. - * @return Pointer to the start of the image data region. + * @return Pointer to the start of the data region. */ - const unsigned char* PeekDataReadPointerAtIndex(size_t n, size_t* imageDataSize, Metadata &md); + const void* PeekDataReadPointerAtIndex(size_t n, Metadata &md); /** * Releases read access that was acquired by a peek. - * @param imageDataPointer Pointer previously obtained from a peek. + * @param dataPointer Pointer previously obtained from a peek. * @return DEVICE_OK on success. */ - int ReleasePeekDataReadPointer(const unsigned char** imageDataPointer); + int ReleasePeekDataReadPointer(const void** dataPointer); /** * Returns the total buffer memory size (in MB). @@ -353,24 +350,24 @@ class DataBuffer { * Extracts metadata for a given image data pointer. * Thread-safe method that acquires necessary locks to lookup metadata location. * - * @param dataPtr Pointer to the image data. + * @param dataPtr Pointer to the (usuallyimage data. * @param md Metadata object to populate. * @return DEVICE_OK on success, or an error code if extraction fails. */ - int ExtractCorrespondingMetadata(const unsigned char* dataPtr, Metadata &md); + int ExtractCorrespondingMetadata(const void* dataPtr, Metadata &md); private: /** * Internal helper function that finds the slot for a given pointer. * Returns non-const pointer since slots need to be modified for locking. * - * @param imageDataPtr Pointer to the image data. + * @param dataPtr Pointer to the data. * @return Pointer to the corresponding BufferSlot, or nullptr if not found. */ - BufferSlot* FindSlotForPointer(const unsigned char* imageDataPtr); + BufferSlot* FindSlotForPointer(const void* dataPtr); // Memory managed by the DataBuffer. - unsigned char* buffer_; + void* buffer_; size_t bufferSize_; // Whether to overwrite old data when full. @@ -429,8 +426,8 @@ class DataBuffer { */ int CreateSlot(size_t candidateStart, size_t totalSlotSize, size_t dataSize, size_t additionalMetadataSize, - unsigned char** dataPointer, - unsigned char** subsequentMetadataPointer, + void** dataPointer, + void** subsequentMetadataPointer, bool fromFreeRegion, const std::string& serializedInitialMetadata); @@ -442,7 +439,7 @@ class DataBuffer { size_t dataSize, size_t metadataSize); void ReturnSlotToPool(BufferSlot* slot); - int ExtractMetadata(const unsigned char* dataPointer, + int ExtractMetadata(const void* dataPointer, BufferSlot* slot, Metadata &md); diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index e55f38575..5351ee0ca 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -3086,9 +3086,9 @@ void* CMMCore::getLastImage() throw (CMMError) } } - unsigned char* pBuf = const_cast(bufferAdapter_->GetLastImage()); + const void* pBuf = bufferAdapter_->GetLastImage(); if (pBuf != 0) - return pBuf; + return const_cast(pBuf); else { logError("CMMCore::getLastImage", getCoreErrorText(MMERR_CircularBufferEmpty).c_str()); @@ -3102,7 +3102,7 @@ void* CMMCore::getLastImageMD(unsigned channel, unsigned slice, Metadata& md) co if (slice != 0) throw CMMError("Slice must be 0"); - return bufferAdapter_->GetLastImageMD(channel, md); + return const_cast(bufferAdapter_->GetLastImageMD(channel, md)); } /** @@ -3136,7 +3136,7 @@ void* CMMCore::getLastImageMD(Metadata& md) const throw (CMMError) */ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw (CMMError) { - return bufferAdapter_->GetNthImageMD(n, md); + return const_cast(bufferAdapter_->GetNthImageMD(n, md)); } /** @@ -3153,9 +3153,9 @@ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw */ void* CMMCore::popNextImage() throw (CMMError) { - unsigned char* pBuf = const_cast(bufferAdapter_->PopNextImage()); + const void* pBuf = bufferAdapter_->PopNextImage(); if (pBuf != 0) - return pBuf; + return const_cast(pBuf); else throw CMMError(getCoreErrorText(MMERR_CircularBufferEmpty).c_str(), MMERR_CircularBufferEmpty); } @@ -3171,7 +3171,7 @@ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) th if (slice != 0) throw CMMError("Slice must be 0"); - return bufferAdapter_->PopNextImageMD(channel, md); + return const_cast(bufferAdapter_->PopNextImageMD(channel, md)); } /** From 0f1b64539ddd9c5bfee4ac3f51fe0f97a2a353de Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 15 Feb 2025 12:46:13 -0800 Subject: [PATCH 22/46] fix bit depth fn, which is not stored in v2 buffer, and map image pointers to java longs --- MMCore/MMCore.cpp | 46 ++++-- MMCore/MMCore.h | 19 ++- MMCoreJ_wrap/MMCoreJ.i | 328 ++++++++++++++++++++++------------------- 3 files changed, 215 insertions(+), 178 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 5351ee0ca..fd25d9e5b 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -4230,6 +4230,24 @@ unsigned CMMCore::getImageBitDepth() return 0; } +unsigned CMMCore::getImageBitDepth(const char* cameraLabel) +{ + std::shared_ptr camera = deviceManager_->GetDeviceOfType(cameraLabel); + if (camera) + { + try + { + mm::DeviceModuleLockGuard guard(camera); + return camera->GetBitDepth(); + } + catch (const CMMError&) // Possibly uninitialized camera + { + // Fall through + } + } + return 0; +} + /** * Returns the number of components the default camera is returning. * For example color camera will return 4 components (RGBA) on each snap. @@ -4252,32 +4270,28 @@ unsigned CMMCore::getNumberOfComponents() return 0; } -unsigned CMMCore::getImageWidth(const char* ptr) { - return useV2Buffer_ ? bufferAdapter_->GetImageWidth(reinterpret_cast(ptr)) : getImageWidth(); -} - -unsigned CMMCore::getImageHeight(const char* ptr) { - return useV2Buffer_ ? bufferAdapter_->GetImageHeight(reinterpret_cast(ptr)) : getImageHeight(); +unsigned CMMCore::getImageWidth(DataPtr ptr) { + return useV2Buffer_ ? bufferAdapter_->GetImageWidth(ptr) : getImageWidth(); } -unsigned CMMCore::getBytesPerPixel(const char* ptr) { - return useV2Buffer_ ? bufferAdapter_->GetBytesPerPixel(reinterpret_cast(ptr)) : getBytesPerPixel(); +unsigned CMMCore::getImageHeight(DataPtr ptr) { + return useV2Buffer_ ? bufferAdapter_->GetImageHeight(ptr) : getImageHeight(); } -unsigned CMMCore::getImageBitDepth(const char* ptr) { - return useV2Buffer_ ? bufferAdapter_->GetImageBitDepth(reinterpret_cast(ptr)) : getImageBitDepth(); +unsigned CMMCore::getBytesPerPixel(DataPtr ptr) { + return useV2Buffer_ ? bufferAdapter_->GetBytesPerPixel(ptr) : getBytesPerPixel(); } -unsigned CMMCore::getNumberOfComponents(const char* ptr) { - return useV2Buffer_ ? bufferAdapter_->GetNumberOfComponents(reinterpret_cast(ptr)) : getNumberOfComponents(); +unsigned CMMCore::getNumberOfComponents(DataPtr ptr) { + return useV2Buffer_ ? bufferAdapter_->GetNumberOfComponents(ptr) : getNumberOfComponents(); } -long CMMCore::getImageBufferSize(const char* ptr) { - return useV2Buffer_ ? bufferAdapter_->GetImageBufferSize(reinterpret_cast(ptr)) : getImageBufferSize(); +long CMMCore::getImageBufferSize(DataPtr ptr) { + return useV2Buffer_ ? bufferAdapter_->GetImageBufferSize(ptr) : getImageBufferSize(); } -void CMMCore::ReleaseReadAccess(const char* ptr) { - if (useV2Buffer_) bufferAdapter_->ReleaseReadAccess(reinterpret_cast(ptr)); +void CMMCore::ReleaseReadAccess(DataPtr ptr) { + if (useV2Buffer_) bufferAdapter_->ReleaseReadAccess(ptr); } /** diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 0d8ff2f59..f2c52b40f 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -114,6 +114,10 @@ namespace mm { } // namespace mm typedef unsigned int* imgRGB32; +// This is needed for SWIG Java wrapping because +// void* is converted to Objects to copy arrays of data but we want to be able to +// maps these pointers to longs +typedef const void* DataPtr; enum DeviceInitializationState { Uninitialized, @@ -384,6 +388,7 @@ class CMMCore unsigned getImageHeight(); unsigned getBytesPerPixel(); unsigned getImageBitDepth(); + unsigned getImageBitDepth(const char* cameraLabel); unsigned getNumberOfComponents(); unsigned getNumberOfCameraChannels(); std::string getCameraChannelName(unsigned int channelNr); @@ -434,14 +439,12 @@ class CMMCore void enableV2Buffer(bool enable) throw (CMMError); bool usesV2Buffer() const { return useV2Buffer_; } - unsigned getImageWidth(const char* ptr) throw (CMMError); - unsigned getImageHeight(const char* ptr) throw (CMMError); - unsigned getBytesPerPixel(const char* ptr) throw (CMMError); - unsigned getImageBitDepth(const char* ptr) throw (CMMError); - unsigned getNumberOfComponents(const char* ptr) throw (CMMError); - long getImageBufferSize(const char* ptr) throw (CMMError); - - void ReleaseReadAccess(const char* ptr) throw (CMMError); + unsigned getImageWidth(DataPtr ptr) throw (CMMError); + unsigned getImageHeight(DataPtr ptr) throw (CMMError); + unsigned getBytesPerPixel(DataPtr ptr) throw (CMMError); + unsigned getNumberOfComponents(DataPtr ptr) throw (CMMError); + long getImageBufferSize(DataPtr ptr) throw (CMMError); + void ReleaseReadAccess(DataPtr ptr) throw (CMMError); bool isExposureSequenceable(const char* cameraLabel) throw (CMMError); void startExposureSequence(const char* cameraLabel) throw (CMMError); diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index a99262b83..92ebbbde7 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -367,14 +367,14 @@ for (int i = 0; i < listSize; ++i) { jbyteArray pixels = (jbyteArray) jenv->CallObjectMethod($input, getMethodID, i); long receivedLength = jenv->GetArrayLength(pixels); - if (receivedLength != expectedLength && receivedLength != expectedLength*4) - { - jclass excep = jenv->FindClass("java/lang/Exception"); - if (excep) - jenv->ThrowNew(excep, "Image dimensions are wrong for this SLM."); - return; - } - inputVector.push_back((unsigned char *) JCALL2(GetByteArrayElements, jenv, pixels, 0)); + if (receivedLength != expectedLength && receivedLength != expectedLength*4) + { + jclass excep = jenv->FindClass("java/lang/Exception"); + if (excep) + jenv->ThrowNew(excep, "Image dimensions are wrong for this SLM."); + return; + } + inputVector.push_back((unsigned char *) JCALL2(GetByteArrayElements, jenv, pixels, 0)); } $1 = inputVector; } @@ -395,18 +395,19 @@ // unsigned GetImageWidth() // unsigned GetImageHeight() -%typemap(jni) void* "jobject" -%typemap(jtype) void* "Object" -%typemap(jstype) void* "Object" + +%typemap(jni) void* "jobject" +%typemap(jtype) void* "Object" +%typemap(jstype) void* "Object" %typemap(javaout) void* { return $jnicall; } %typemap(out) void* { - long lSize = (arg1)->getImageWidth((const char*)result) * - (arg1)->getImageHeight((const char*)result); + long lSize = (arg1)->getImageWidth((void*)result) * + (arg1)->getImageHeight((void*)result); - unsigned bytesPerPixel = (arg1)->getBytesPerPixel((const char*)result); + unsigned bytesPerPixel = (arg1)->getBytesPerPixel((void*)result); if (bytesPerPixel == 1) { @@ -426,7 +427,7 @@ JCALL4(SetByteArrayRegion, jenv, data, 0, lSize, (jbyte*)result); // Release the read access (required for V2 buffer, Core will figure out if it's V2 or not) - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->ReleaseReadAccess((void*)result); $result = data; @@ -448,14 +449,14 @@ JCALL4(SetShortArrayRegion, jenv, data, 0, lSize, (jshort*)result); // Release the read access - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->ReleaseReadAccess((void*)result); $result = data; } else if (bytesPerPixel == 4) { - if ((arg1)->getNumberOfComponents((const char*)result) == 1) + if ((arg1)->getNumberOfComponents((void*)result) == 1) { // create a new float[] object in Java @@ -474,7 +475,7 @@ JCALL4(SetFloatArrayRegion, jenv, data, 0, lSize, (jfloat*)result); // Release the read access - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->ReleaseReadAccess((void*)result); $result = data; @@ -497,7 +498,7 @@ JCALL4(SetByteArrayRegion, jenv, data, 0, lSize * 4, (jbyte*)result); // Release the read access - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->ReleaseReadAccess((void*)result); $result = data; @@ -520,7 +521,7 @@ JCALL4(SetShortArrayRegion, jenv, data, 0, lSize * 4, (jshort*)result); // Release the read access - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->ReleaseReadAccess((void*)result); $result = data; } @@ -533,6 +534,25 @@ } } +// Define typemaps for DataPtr +%typemap(jni) DataPtr "jlong" +%typemap(jtype) DataPtr "long" +%typemap(jstype) DataPtr "long" +%typemap(javain) DataPtr "$javainput" +%typemap(in) DataPtr { + $1 = (DataPtr)$input; +} + +%typemap(jni) const DataPtr "jlong" +%typemap(jtype) const DataPtr "long" +%typemap(jstype) const DataPtr "long" +%typemap(javain) const DataPtr "$javainput" +%typemap(in) const DataPtr { + $1 = (DataPtr)$input; +} + + + // Java typemap // change default SWIG mapping of void* return values // to return CObject containing array of pixel values @@ -717,21 +737,21 @@ } private String getMultiCameraChannel(JSONObject tags, int cameraChannelIndex) { - try { - String camera = tags.getString("Core-Camera"); - String physCamKey = camera + "-Physical Camera " + (1 + cameraChannelIndex); - if (tags.has(physCamKey)) { - try { - return tags.getString(physCamKey); - } catch (Exception e2) { - return null; - } - } else { - return null; - } - } catch (Exception e) { - return null; - } + try { + String camera = tags.getString("Core-Camera"); + String physCamKey = camera + "-Physical Camera " + (1 + cameraChannelIndex); + if (tags.has(physCamKey)) { + try { + return tags.getString(physCamKey); + } catch (Exception e2) { + return null; + } + } else { + return null; + } + } catch (Exception e) { + return null; + } } @@ -950,12 +970,12 @@ // instantiate STL mappings namespace std { - %typemap(javaimports) vector %{ - import java.lang.Iterable; - import java.util.Iterator; - import java.util.NoSuchElementException; - import java.lang.UnsupportedOperationException; - %} + %typemap(javaimports) vector %{ + import java.lang.Iterable; + import java.util.Iterator; + import java.util.NoSuchElementException; + import java.lang.UnsupportedOperationException; + %} %typemap(javainterfaces) vector %{ Iterable%} @@ -1002,11 +1022,11 @@ namespace std { */ %typemap(javaimports) vector %{ - import java.lang.Iterable; - import java.util.Iterator; - import java.util.NoSuchElementException; - import java.lang.UnsupportedOperationException; - %} + import java.lang.Iterable; + import java.util.Iterator; + import java.util.NoSuchElementException; + import java.lang.UnsupportedOperationException; + %} %typemap(javainterfaces) vector %{ Iterable%} @@ -1048,11 +1068,11 @@ namespace std { %} %typemap(javaimports) vector %{ - import java.lang.Iterable; - import java.util.Iterator; - import java.util.NoSuchElementException; - import java.lang.UnsupportedOperationException; - %} + import java.lang.Iterable; + import java.util.Iterator; + import java.util.NoSuchElementException; + import java.lang.UnsupportedOperationException; + %} %typemap(javainterfaces) vector %{ Iterable%} @@ -1094,111 +1114,111 @@ namespace std { %} - %typemap(javaimports) vector %{ - import java.lang.Iterable; - import java.util.Iterator; - import java.util.NoSuchElementException; - import java.lang.UnsupportedOperationException; - %} - - %typemap(javainterfaces) vector %{ Iterable%} - - %typemap(javacode) vector %{ - - public Iterator iterator() { - return new Iterator() { - - private int i_=0; - - public boolean hasNext() { - return (i_ %{ + import java.lang.Iterable; + import java.util.Iterator; + import java.util.NoSuchElementException; + import java.lang.UnsupportedOperationException; + %} + + %typemap(javainterfaces) vector %{ Iterable%} + + %typemap(javacode) vector %{ + + public Iterator iterator() { + return new Iterator() { + + private int i_=0; + + public boolean hasNext() { + return (i_ %{ - import java.lang.Iterable; - import java.util.Iterator; - import java.util.NoSuchElementException; - import java.lang.UnsupportedOperationException; - %} - - %typemap(javainterfaces) vector %{ Iterable%} - - %typemap(javacode) vector %{ - - public Iterator iterator() { - return new Iterator() { - - private int i_=0; - - public boolean hasNext() { - return (i_ %{ - import java.lang.Iterable; - import java.util.Iterator; - import java.util.NoSuchElementException; - import java.lang.UnsupportedOperationException; - %} + %typemap(javaimports) vector %{ + import java.lang.Iterable; + import java.util.Iterator; + import java.util.NoSuchElementException; + import java.lang.UnsupportedOperationException; + %} + + %typemap(javainterfaces) vector %{ Iterable%} + + %typemap(javacode) vector %{ + + public Iterator iterator() { + return new Iterator() { + + private int i_=0; + + public boolean hasNext() { + return (i_ %{ + import java.lang.Iterable; + import java.util.Iterator; + import java.util.NoSuchElementException; + import java.lang.UnsupportedOperationException; + %} %typemap(javainterfaces) vector %{ Iterable%} From 567cb2b82e8dee2105fff22b53cc237ed05fc149 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 15 Feb 2025 12:48:38 -0800 Subject: [PATCH 23/46] remove unused typemap --- MMCoreJ_wrap/MMCoreJ.i | 51 ------------------------------------------ 1 file changed, 51 deletions(-) diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index 92ebbbde7..adbd8fa91 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -552,57 +552,6 @@ } - -// Java typemap -// change default SWIG mapping of void* return values -// to return CObject containing array of pixel values -// -// Assumes that class has the following methods defined: -// unsigned GetImageWidth() -// unsigned GetImageHeight() -// unsigned GetImageDepth() -// unsigned GetNumberOfComponents() - - -%typemap(jni) unsigned int* "jobject" -%typemap(jtype) unsigned int* "Object" -%typemap(jstype) unsigned int* "Object" -%typemap(javaout) unsigned int* { - return $jnicall; -} -%typemap(out) unsigned int* -{ - long lSize = (arg1)->getImageWidth() * (arg1)->getImageHeight(); - unsigned numComponents = (arg1)->getNumberOfComponents(); - - if ((arg1)->getBytesPerPixel() == 1 && numComponents == 4) - { - // assuming RGB32 format - // create a new int[] object in Java - jintArray data = JCALL1(NewIntArray, jenv, lSize); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - $result = 0; - return $result; - } - - // copy pixels from the image buffer - JCALL4(SetIntArrayRegion, jenv, data, 0, lSize, (jint*)result); - - $result = data; - } - else - { - // don't know how to map - // TODO: thow exception? - $result = 0; - } -} - - %typemap(jni) imgRGB32 "jintArray" %typemap(jtype) imgRGB32 "int[]" %typemap(jstype) imgRGB32 "int[]" From a5a69b7f4049836c5fb5ed7bd5d238c88daa0034 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 15 Feb 2025 14:53:03 -0800 Subject: [PATCH 24/46] add ability to manipulate data pointers inside v2 buffer. Also make sure metadata is accurate for v2 buffer images --- MMCore/BufferAdapter.cpp | 5 +- MMCore/BufferAdapter.h | 3 +- MMCore/MMCore.cpp | 64 ++++++++- MMCore/MMCore.h | 14 +- MMCoreJ_wrap/MMCoreJ.i | 115 +++++++++++----- .../main/java/mmcorej/TaggedImagePointer.java | 127 ++++++++++++++++++ 6 files changed, 287 insertions(+), 41 deletions(-) create mode 100644 MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index ed7864fcf..24e675a8e 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -379,10 +379,11 @@ bool BufferAdapter::IsUsingV2Buffer() const { return useV2_; } -void BufferAdapter::ReleaseReadAccess(const void* ptr) { +bool BufferAdapter::ReleaseReadAccess(const void* ptr) { if (useV2_ && ptr) { - v2Buffer_->ReleaseDataReadPointer(ptr); + return v2Buffer_->ReleaseDataReadPointer(ptr); } + return true; } unsigned BufferAdapter::GetImageWidth(const void* ptr) const { diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index bd246a5e5..52aa5616e 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -180,8 +180,9 @@ class BufferAdapter { * Release a pointer obtained from the buffer. * This is required when using the V2 buffer implementation. * @param ptr The pointer to release. + * @return true on success, false on error. */ - void ReleaseReadAccess(const void* ptr); + bool ReleaseReadAccess(const void* ptr); // Methods for the v2 buffer where width and heigh must be gotton on a per-image basis unsigned GetImageWidth(const void* ptr) const; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index fd25d9e5b..2cae19f45 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -3182,6 +3182,64 @@ void* CMMCore::popNextImageMD(Metadata& md) throw (CMMError) return popNextImageMD(0, 0, md); } +DataPtr CMMCore::getLastImagePointer() throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + return getLastImage(); +} + +DataPtr CMMCore::popNextImagePointer() throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + return popNextImage(); +} + +DataPtr CMMCore::getLastImageMDPointer(unsigned channel, unsigned slice, Metadata& md) const throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + return getLastImageMD(channel, slice, md); +} + +DataPtr CMMCore::popNextImageMDPointer(unsigned channel, unsigned slice, Metadata& md) throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + return popNextImageMD(channel, slice, md); +} + +DataPtr CMMCore::getLastImageMDPointer(Metadata& md) const throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + return getLastImageMD(md); +} + +DataPtr CMMCore::getNBeforeLastImageMDPointer(unsigned long n, Metadata& md) const throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + return getNBeforeLastImageMD(n, md); +} + +DataPtr CMMCore::popNextImageMDPointer(Metadata& md) throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + return popNextImageMD(md); +} + +void* CMMCore::copyDataAtPointer(DataPtr ptr) throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + // This just checks the buffer and returns the pointer. Since the return type is void*, + // SWIG will automatically copy the pointer into a Java array. + return (void*) ptr; +} + /** * Removes all images from the circular buffer. * @@ -4290,8 +4348,10 @@ long CMMCore::getImageBufferSize(DataPtr ptr) { return useV2Buffer_ ? bufferAdapter_->GetImageBufferSize(ptr) : getImageBufferSize(); } -void CMMCore::ReleaseReadAccess(DataPtr ptr) { - if (useV2Buffer_) bufferAdapter_->ReleaseReadAccess(ptr); +void CMMCore::releaseReadAccess(DataPtr ptr) { + if (useV2Buffer_) { + bufferAdapter_->ReleaseReadAccess(ptr); + } } /** diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index f2c52b40f..f4c356f9e 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -423,6 +423,18 @@ class CMMCore const throw (CMMError); void* popNextImageMD(Metadata& md) throw (CMMError); + // Same functionality as above but, enables alternative wrappers in SWIG for + // pointer-based access to the image data. + DataPtr getLastImagePointer() throw (CMMError); + DataPtr popNextImagePointer() throw (CMMError); + DataPtr getLastImageMDPointer(unsigned channel, unsigned slice, Metadata& md) const throw (CMMError); + DataPtr popNextImageMDPointer(unsigned channel, unsigned slice, Metadata& md) throw (CMMError); + DataPtr getLastImageMDPointer(Metadata& md) const throw (CMMError); + DataPtr getNBeforeLastImageMDPointer(unsigned long n, Metadata& md) const throw (CMMError); + DataPtr popNextImageMDPointer(Metadata& md) throw (CMMError); + + void* copyDataAtPointer(DataPtr ptr) throw (CMMError); + long getRemainingImageCount(); long getBufferTotalCapacity(); long getBufferFreeCapacity(); @@ -444,7 +456,7 @@ class CMMCore unsigned getBytesPerPixel(DataPtr ptr) throw (CMMError); unsigned getNumberOfComponents(DataPtr ptr) throw (CMMError); long getImageBufferSize(DataPtr ptr) throw (CMMError); - void ReleaseReadAccess(DataPtr ptr) throw (CMMError); + void releaseReadAccess(DataPtr ptr) throw (CMMError); bool isExposureSequenceable(const char* cameraLabel) throw (CMMError); void startExposureSequence(const char* cameraLabel) throw (CMMError); diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index adbd8fa91..0f80b1454 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -427,7 +427,7 @@ JCALL4(SetByteArrayRegion, jenv, data, 0, lSize, (jbyte*)result); // Release the read access (required for V2 buffer, Core will figure out if it's V2 or not) - (arg1)->ReleaseReadAccess((void*)result); + (arg1)->releaseReadAccess((void*)result); $result = data; @@ -449,7 +449,7 @@ JCALL4(SetShortArrayRegion, jenv, data, 0, lSize, (jshort*)result); // Release the read access - (arg1)->ReleaseReadAccess((void*)result); + (arg1)->releaseReadAccess((void*)result); $result = data; @@ -475,7 +475,7 @@ JCALL4(SetFloatArrayRegion, jenv, data, 0, lSize, (jfloat*)result); // Release the read access - (arg1)->ReleaseReadAccess((void*)result); + (arg1)->releaseReadAccess((void*)result); $result = data; @@ -498,7 +498,7 @@ JCALL4(SetByteArrayRegion, jenv, data, 0, lSize * 4, (jbyte*)result); // Release the read access - (arg1)->ReleaseReadAccess((void*)result); + (arg1)->releaseReadAccess((void*)result); $result = data; @@ -521,7 +521,7 @@ JCALL4(SetShortArrayRegion, jenv, data, 0, lSize * 4, (jshort*)result); // Release the read access - (arg1)->ReleaseReadAccess((void*)result); + (arg1)->releaseReadAccess((void*)result); $result = data; } @@ -539,18 +539,12 @@ %typemap(jtype) DataPtr "long" %typemap(jstype) DataPtr "long" %typemap(javain) DataPtr "$javainput" +%typemap(javaout) DataPtr { return $jnicall; } +%typemap(out) DataPtr { $result = (jlong)$1; } %typemap(in) DataPtr { $1 = (DataPtr)$input; } -%typemap(jni) const DataPtr "jlong" -%typemap(jtype) const DataPtr "long" -%typemap(jstype) const DataPtr "long" -%typemap(javain) const DataPtr "$javainput" -%typemap(in) const DataPtr { - $1 = (DataPtr)$input; -} - %typemap(jni) imgRGB32 "jintArray" %typemap(jtype) imgRGB32 "int[]" @@ -655,19 +649,21 @@ } private String getROITag() throws java.lang.Exception { + return getROITag(getCameraDevice()); + } + + private String getROITag(String cameraLabel) throws java.lang.Exception { String roi = ""; int [] x = new int[1]; int [] y = new int[1]; int [] xSize = new int[1]; int [] ySize = new int[1]; - getROI(x, y, xSize, ySize); + getROI(cameraLabel, x, y, xSize, ySize); roi += x[0] + "-" + y[0] + "-" + xSize[0] + "-" + ySize[0]; return roi; } - private String getPixelType() { - int depth = (int) getBytesPerPixel(); - int numComponents = (int) getNumberOfComponents(); + private String getPixelType(int depth, int numComponents) throws java.lang.Exception { switch (depth) { case 1: return "GRAY8"; @@ -704,8 +700,8 @@ } - private TaggedImage createTaggedImage(Object pixels, Metadata md, int cameraChannelIndex) throws java.lang.Exception { - TaggedImage image = createTaggedImage(pixels, md); + private TaggedImage createTaggedImage(Object pixelsOrPtr, Metadata md, int cameraChannelIndex, boolean fromSnapImage) throws java.lang.Exception { + TaggedImage image = createTaggedImage(pixelsOrPtr, md, fromSnapImage); JSONObject tags = image.tags; if (!tags.has("CameraChannelIndex")) { @@ -722,7 +718,7 @@ return image; } - private TaggedImage createTaggedImage(Object pixels, Metadata md) throws java.lang.Exception { + private TaggedImage createTaggedImage(Object pixelsOrPtr, Metadata md, boolean fromSnapImage) throws java.lang.Exception { JSONObject tags = metadataToMap(md); PropertySetting setting; if (includeSystemStateCache_) { @@ -734,13 +730,35 @@ tags.put(key, value); } } - tags.put("BitDepth", getImageBitDepth()); + + // The Camera tag should have been added in CoreCallback::InsertImage + // We need to check it when getting bitDepth and ROI, because the v2 buffer + // supports multiple cameras with different settings inserting images at once + String cameraLabel = fromSnapImage ? getCameraDevice() : tags.getString("Camera"); + tags.put("BitDepth", getImageBitDepth(cameraLabel)); + tags.put("ROI", getROITag(cameraLabel)); + + // TODO: unclear if pixelSize and Affine are can be accurate if images coming + // from different cameras when using the v2 buffer tags.put("PixelSizeUm", getPixelSizeUm(true)); tags.put("PixelSizeAffine", getPixelSizeAffineAsString()); - tags.put("ROI", getROITag()); - tags.put("Width", getImageWidth()); - tags.put("Height", getImageHeight()); - tags.put("PixelType", getPixelType()); + if (!fromSnapImage && pixelsOrPtr instanceof Long) { + long ptr = (Long) pixelsOrPtr; + int depth = (int) getBytesPerPixel(ptr); + int numComponents = (int) getNumberOfComponents(ptr); + tags.put("Width", getImageWidth(ptr)); + tags.put("Height", getImageHeight(ptr)); + tags.put("PixelType", getPixelType(depth, numComponents)); + } else { + // using snap or the not the v2 buffer + int depth = (int) getBytesPerPixel(); + int numComponents = (int) getNumberOfComponents(); + tags.put("Width", getImageWidth()); + tags.put("Height", getImageHeight()); + tags.put("PixelType", getPixelType(depth, numComponents)); + } + + // This seems like it doesn't belong here, but unclear what it would break if removed tags.put("Frame", 0); tags.put("FrameIndex", 0); tags.put("Position", "Default"); @@ -756,26 +774,43 @@ try { - tags.put("Binning", getProperty(getCameraDevice(), "Binning")); - } catch (Exception ex) {} + // "Camera" gets added in CoreCallback::InsertImage + tags.put("Binning", getProperty(cameraLabel, "Binning")); + } catch (java.lang.Exception ex) {} - return new TaggedImage(pixels, tags); + // Copy the pixels out using the pointer (this will release the pointer) + if (pixelsOrPtr instanceof Long) { + // This is a pointer to an image in the v2 buffer + return new TaggedImagePointer((Long) pixelsOrPtr, tags, this); + } else { + return new TaggedImage(pixelsOrPtr, tags); + } } + //////// Snap Image versions + // This is for getting a snapped image. Snap image does not interact with v2 buffer. + // currently, so trying to release its pointer is undefined and not needed public TaggedImage getTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); Object pixels = getImage(cameraChannelIndex); - return createTaggedImage(pixels, md, cameraChannelIndex); + return createTaggedImage(pixels, md, cameraChannelIndex, true); } + // See comment for above function public TaggedImage getTaggedImage() throws java.lang.Exception { return getTaggedImage(0); } + /////// Data Buffer versions public TaggedImage getLastTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); - Object pixels = getLastImageMD(cameraChannelIndex, 0, md); - return createTaggedImage(pixels, md, cameraChannelIndex); + Object pixelsOrPtr; + if (!this.usesV2Buffer()) { + pixelsOrPtr = getLastImageMD(cameraChannelIndex, 0, md); + } else { + pixelsOrPtr = getLastImageMDPointer(cameraChannelIndex, 0, md); + } + return createTaggedImage(pixelsOrPtr, md, cameraChannelIndex, false); } public TaggedImage getLastTaggedImage() throws java.lang.Exception { @@ -784,14 +819,24 @@ public TaggedImage getNBeforeLastTaggedImage(long n) throws java.lang.Exception { Metadata md = new Metadata(); - Object pixels = getNBeforeLastImageMD(n, md); - return createTaggedImage(pixels, md); + Object pixelsOrPtr; + if (!this.usesV2Buffer()) { + pixelsOrPtr = getNBeforeLastImageMD(n, md); + } else { + pixelsOrPtr = getNBeforeLastImageMDPointer(n, md); + } + return createTaggedImage(pixelsOrPtr, md, false); } public TaggedImage popNextTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); - Object pixels = popNextImageMD(cameraChannelIndex, 0, md); - return createTaggedImage(pixels, md, cameraChannelIndex); + Object pixelsOrPtr; + if (!this.usesV2Buffer()) { + pixelsOrPtr = popNextImageMD(cameraChannelIndex, 0, md); + } else { + pixelsOrPtr = popNextImageMDPointer(cameraChannelIndex, 0, md); + } + return createTaggedImage(pixelsOrPtr, md, cameraChannelIndex, false); } public TaggedImage popNextTaggedImage() throws java.lang.Exception { diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java new file mode 100644 index 000000000..76969c1cf --- /dev/null +++ b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java @@ -0,0 +1,127 @@ +package mmcorej; + +import mmcorej.org.json.JSONObject; + +/** + * TaggedImagePointer is a wrapper around a pointer to an image in the v2 buffer. + * It provides copy-free access to data in the C++ layer until the data is actually + * needed. This class implements lazy loading of image data to optimize memory usage + * and performance. + * + *

This class extends TaggedImage and manages the lifecycle of image data stored + * in native memory. It ensures proper release of resources when the image data is + * no longer needed.

+ */ +public class TaggedImagePointer extends TaggedImage { + + public JSONObject tags; + + private final long address_; + private final CMMCore core_; + private boolean released_ = false; + private Object copiedPixels_ = null; + + /** + * Constructs a new TaggedImagePointer. + * + * @param address Memory address of the image data in native code + * @param tags JSONObject containing metadata associated with the image + * @param core Reference to the CMMCore instance managing the native resources + */ + public TaggedImagePointer(long address, JSONObject tags, CMMCore core) { + super(null, tags); // Initialize parent with null pix + this.address_ = address; + this.core_ = core; + } + + /** + * Retrieves the pixel data associated with this image. + * + *

The first call to this method will copy the data from native memory + * to Java memory and release the native buffer. Subsequent calls will + * return the cached copy.

+ * + * @return Object containing the pixel data + * @throws IllegalStateException if the image has already been released + */ + public Object get_pixels() throws IllegalStateException { + if (released_) { + throw new IllegalStateException("Image has been released"); + } + + if (copiedPixels_ == null) { + try { + copiedPixels_ = core_.copyDataAtPointer(address_); + } catch (Exception e) { + throw new IllegalStateException("Failed to copy data at pointer", e); + } + release(); + } + return copiedPixels_; + } + + /** + * Releases the native memory associated with this image. + * + *

This method is synchronized to prevent concurrent access to the + * release mechanism. Once released, the native memory cannot be accessed + * again.

+ * + * @throws IllegalStateException if releasing the read access fails + */ + public synchronized void release() { + if (!released_) { + try { + core_.releaseReadAccess(address_); + } catch (Exception e) { + throw new IllegalStateException("Failed to release read access to image buffer", e); + } + released_ = true; + } + } + + /** + * Ensures proper cleanup of native resources when this object is garbage collected. + * + * @throws Throwable if an error occurs during finalization + */ + @Override + protected void finalize() throws Throwable { + release(); + super.finalize(); + } +} + + +// class LazyJSONObject extends JSONObject { +// private final long metadataPtr_; +// private final CMMCore core_; +// private boolean initialized_ = false; + +// public LazyJSONObject(long metadataPtr, CMMCore core) { +// this.metadataPtr_ = metadataPtr; +// this.core_ = core; +// } + +// private synchronized void initializeIfNeeded() { +// if (!initialized_) { +// // Use core's existing metadataToMap helper +// JSONObject tags = core_.metadataToMap(metadataPtr_); + +// // Copy all key/value pairs from the metadata map +// for (String key : tags.keySet()) { +// try { +// put(key, tags.get(key)); +// } catch (Exception e) {} +// } + +// initialized_ = true; +// } +// } + +// @Override +// public Object get(String key) { +// initializeIfNeeded(); +// return super.get(key); +// } +// } From 1408c8ec629bb8a2628331e85be46c7038eac66b Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 15 Feb 2025 21:15:33 -0800 Subject: [PATCH 25/46] fix bugs with snap. pointer-based taggedimages WIP --- MMCore/BufferAdapter.cpp | 15 ++ MMCore/BufferAdapter.h | 8 + MMCore/MMCore.cpp | 14 +- MMCore/MMCore.h | 7 +- MMCoreJ_wrap/MMCoreJ.i | 178 ++++++++++++++++-- .../src/main/java/mmcorej/TaggedImage.java | 2 +- .../main/java/mmcorej/TaggedImagePointer.java | 86 +++++---- 7 files changed, 253 insertions(+), 57 deletions(-) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferAdapter.cpp index 24e675a8e..9e6dc6f7f 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferAdapter.cpp @@ -515,3 +515,18 @@ bool BufferAdapter::FinalizeWriteSlot(void* imageDataPointer, size_t actualMetad int ret = v2Buffer_->FinalizeWriteSlot(imageDataPointer, actualMetadataBytes); return ret == DEVICE_OK; } + +void BufferAdapter::ExtractMetadata(const void* dataPtr, Metadata& md) const { + if (!useV2_) { + throw CMMError("ExtractMetadata is only supported with V2 buffer enabled"); + } + + if (v2Buffer_ == nullptr) { + throw CMMError("V2 buffer is null"); + } + + int result = v2Buffer_->ExtractCorrespondingMetadata(dataPtr, md); + if (result != DEVICE_OK) { + throw CMMError("Failed to extract metadata"); + } +} diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferAdapter.h index 52aa5616e..b6d39a982 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferAdapter.h @@ -225,6 +225,14 @@ class BufferAdapter { */ bool FinalizeWriteSlot(void* imageDataPointer, size_t actualMetadataBytes); + /** + * Extracts metadata for a given image data pointer. + * @param dataPtr Pointer to the image data. + * @param md Metadata object to populate. + * @throws CMMError if V2 buffer is not enabled or extraction fails. + */ + void ExtractMetadata(const void* dataPtr, Metadata& md) const; + private: bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. CircularBuffer* circBuffer_; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 2cae19f45..d0b587908 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -2680,7 +2680,7 @@ bool CMMCore::getShutterOpen() throw (CMMError) * @return a pointer to the internal image buffer. * @throws CMMError when the camera returns no data */ -void* CMMCore::getImage() throw (CMMError) +SnapBufferPtr CMMCore::getImage() throw (CMMError) { std::shared_ptr camera = currentCameraDevice_.lock(); if (!camera) @@ -2746,7 +2746,7 @@ void* CMMCore::getImage() throw (CMMError) * @param channelNr Channel number for which the image buffer is requested * @return a pointer to the internal image buffer. */ -void* CMMCore::getImage(unsigned channelNr) throw (CMMError) +SnapBufferPtr CMMCore::getImage(unsigned channelNr) throw (CMMError) { std::shared_ptr camera = currentCameraDevice_.lock(); if (!camera) @@ -3240,6 +3240,12 @@ void* CMMCore::copyDataAtPointer(DataPtr ptr) throw (CMMError) { return (void*) ptr; } +void CMMCore::copyMetadataAtPointer(DataPtr ptr, Metadata& md) throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + bufferAdapter_->ExtractMetadata(ptr, md); +} /** * Removes all images from the circular buffer. * @@ -4329,6 +4335,10 @@ unsigned CMMCore::getNumberOfComponents() } unsigned CMMCore::getImageWidth(DataPtr ptr) { + if (ptr == getImage()) { + // This is refering to the snap image + return getImageWidth(); + } return useV2Buffer_ ? bufferAdapter_->GetImageWidth(ptr) : getImageWidth(); } diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index f4c356f9e..f03d0fbbf 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -118,6 +118,8 @@ typedef unsigned int* imgRGB32; // void* is converted to Objects to copy arrays of data but we want to be able to // maps these pointers to longs typedef const void* DataPtr; +// making this alias simplifies the SWIG wrapping +typedef const void* SnapBufferPtr; enum DeviceInitializationState { Uninitialized, @@ -381,8 +383,8 @@ class CMMCore double getExposure(const char* label) throw (CMMError); void snapImage() throw (CMMError); - void* getImage() throw (CMMError); - void* getImage(unsigned numChannel) throw (CMMError); + SnapBufferPtr getImage() throw (CMMError); + SnapBufferPtr getImage(unsigned numChannel) throw (CMMError); unsigned getImageWidth(); unsigned getImageHeight(); @@ -434,6 +436,7 @@ class CMMCore DataPtr popNextImageMDPointer(Metadata& md) throw (CMMError); void* copyDataAtPointer(DataPtr ptr) throw (CMMError); + void copyMetadataAtPointer(DataPtr ptr, Metadata& md) throw (CMMError); long getRemainingImageCount(); long getBufferTotalCapacity(); diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index 0f80b1454..1d3b3103f 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -408,6 +408,7 @@ (arg1)->getImageHeight((void*)result); unsigned bytesPerPixel = (arg1)->getBytesPerPixel((void*)result); + unsigned numComponents = (arg1)->getNumberOfComponents((void*)result); if (bytesPerPixel == 1) { @@ -456,7 +457,7 @@ } else if (bytesPerPixel == 4) { - if ((arg1)->getNumberOfComponents((void*)result) == 1) + if (numComponents == 1) { // create a new float[] object in Java @@ -534,6 +535,130 @@ } } +%typemap(jni) SnapBufferPtr "jobject" +%typemap(jtype) SnapBufferPtr "Object" +%typemap(jstype) SnapBufferPtr "Object" +%typemap(javaout) SnapBufferPtr { + return $jnicall; +} +%typemap(out) SnapBufferPtr +{ + long lSize = (arg1)->getImageWidth() * + (arg1)->getImageHeight(); + + unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); + unsigned numComponents = (arg1)->getNumberOfComponents(); + + if (bytesPerPixel == 1) + { + // create a new byte[] object in Java + jbyteArray data = JCALL1(NewByteArray, jenv, lSize); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetByteArrayRegion, jenv, data, 0, lSize, (jbyte*)result); + + + $result = data; + } + else if (bytesPerPixel == 2) + { + // create a new short[] object in Java + jshortArray data = JCALL1(NewShortArray, jenv, lSize); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetShortArrayRegion, jenv, data, 0, lSize, (jshort*)result); + + + $result = data; + } + else if (bytesPerPixel == 4) + { + if (numComponents == 1) + { + // create a new float[] object in Java + + jfloatArray data = JCALL1(NewFloatArray, jenv, lSize); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetFloatArrayRegion, jenv, data, 0, lSize, (jfloat*)result); + + + $result = data; + } + else + { + // create a new byte[] object in Java + jbyteArray data = JCALL1(NewByteArray, jenv, lSize * 4); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetByteArrayRegion, jenv, data, 0, lSize * 4, (jbyte*)result); + + $result = data; + } + } + else if (bytesPerPixel == 8) + { + // create a new short[] object in Java + jshortArray data = JCALL1(NewShortArray, jenv, lSize * 4); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetShortArrayRegion, jenv, data, 0, lSize * 4, (jshort*)result); + + $result = data; + } + + else + { + // don't know how to map + // TODO: throw exception? + $result = 0; + } +} + + // Define typemaps for DataPtr %typemap(jni) DataPtr "jlong" %typemap(jtype) DataPtr "long" @@ -781,7 +906,7 @@ // Copy the pixels out using the pointer (this will release the pointer) if (pixelsOrPtr instanceof Long) { // This is a pointer to an image in the v2 buffer - return new TaggedImagePointer((Long) pixelsOrPtr, tags, this); + return new TaggedImagePointer((Long) pixelsOrPtr, this); } else { return new TaggedImage(pixelsOrPtr, tags); } @@ -801,11 +926,14 @@ return getTaggedImage(0); } - /////// Data Buffer versions - public TaggedImage getLastTaggedImage(int cameraChannelIndex) throws java.lang.Exception { + /////// Data Buffer versions with pointer control + public TaggedImage getLastTaggedImage(int cameraChannelIndex, boolean usePointer) throws java.lang.Exception { + if (usePointer && !this.usesV2Buffer()) { + throw new Exception("Pointer mode is only supported when V2 buffer is enabled"); + } Metadata md = new Metadata(); Object pixelsOrPtr; - if (!this.usesV2Buffer()) { + if (!usePointer) { pixelsOrPtr = getLastImageMD(cameraChannelIndex, 0, md); } else { pixelsOrPtr = getLastImageMDPointer(cameraChannelIndex, 0, md); @@ -813,14 +941,26 @@ return createTaggedImage(pixelsOrPtr, md, cameraChannelIndex, false); } + public TaggedImage getLastTaggedImage(boolean usePointer) throws java.lang.Exception { + return getLastTaggedImage(0, usePointer); + } + + // Existing methods now default to checking v2Buffer + public TaggedImage getLastTaggedImage(int cameraChannelIndex) throws java.lang.Exception { + return getLastTaggedImage(cameraChannelIndex, false); + } + public TaggedImage getLastTaggedImage() throws java.lang.Exception { - return getLastTaggedImage(0); + return getLastTaggedImage(0, false); } - public TaggedImage getNBeforeLastTaggedImage(long n) throws java.lang.Exception { + public TaggedImage getNBeforeLastTaggedImage(long n, boolean usePointer) throws java.lang.Exception { + if (usePointer && !this.usesV2Buffer()) { + throw new Exception("Pointer mode is only supported when V2 buffer is enabled"); + } Metadata md = new Metadata(); Object pixelsOrPtr; - if (!this.usesV2Buffer()) { + if (!usePointer) { pixelsOrPtr = getNBeforeLastImageMD(n, md); } else { pixelsOrPtr = getNBeforeLastImageMDPointer(n, md); @@ -828,10 +968,17 @@ return createTaggedImage(pixelsOrPtr, md, false); } - public TaggedImage popNextTaggedImage(int cameraChannelIndex) throws java.lang.Exception { + public TaggedImage getNBeforeLastTaggedImage(long n) throws java.lang.Exception { + return getNBeforeLastTaggedImage(n, false); + } + + public TaggedImage popNextTaggedImage(int cameraChannelIndex, boolean usePointer) throws java.lang.Exception { + if (usePointer && !this.usesV2Buffer()) { + throw new Exception("Pointer mode is only supported when V2 buffer is enabled"); + } Metadata md = new Metadata(); Object pixelsOrPtr; - if (!this.usesV2Buffer()) { + if (!usePointer) { pixelsOrPtr = popNextImageMD(cameraChannelIndex, 0, md); } else { pixelsOrPtr = popNextImageMDPointer(cameraChannelIndex, 0, md); @@ -839,8 +986,17 @@ return createTaggedImage(pixelsOrPtr, md, cameraChannelIndex, false); } + public TaggedImage popNextTaggedImage(boolean usePointer) throws java.lang.Exception { + return popNextTaggedImage(0, usePointer); + } + + // Existing methods now default to checking v2Buffer + public TaggedImage popNextTaggedImage(int cameraChannelIndex) throws java.lang.Exception { + return popNextTaggedImage(cameraChannelIndex, false); + } + public TaggedImage popNextTaggedImage() throws java.lang.Exception { - return popNextTaggedImage(0); + return popNextTaggedImage(0, false); } // convenience functions follow diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java index 7e85e0da1..dd43a4666 100644 --- a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java +++ b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java @@ -7,7 +7,7 @@ */ public class TaggedImage { - public final Object pix; + public Object pix; public JSONObject tags; public TaggedImage(Object pix, JSONObject tags) { diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java index 76969c1cf..4d248b51a 100644 --- a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java +++ b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java @@ -1,6 +1,7 @@ package mmcorej; import mmcorej.org.json.JSONObject; +import mmcorej.org.json.JSONException; /** * TaggedImagePointer is a wrapper around a pointer to an image in the v2 buffer. @@ -14,12 +15,11 @@ */ public class TaggedImagePointer extends TaggedImage { - public JSONObject tags; + public LazyJSONObject tags; private final long address_; private final CMMCore core_; private boolean released_ = false; - private Object copiedPixels_ = null; /** * Constructs a new TaggedImagePointer. @@ -28,36 +28,37 @@ public class TaggedImagePointer extends TaggedImage { * @param tags JSONObject containing metadata associated with the image * @param core Reference to the CMMCore instance managing the native resources */ - public TaggedImagePointer(long address, JSONObject tags, CMMCore core) { - super(null, tags); // Initialize parent with null pix + public TaggedImagePointer(long address, CMMCore core) { + super(null, null); // Initialize parent with null pix this.address_ = address; this.core_ = core; + this.tags = new LazyJSONObject(address, core); } /** - * Retrieves the pixel data associated with this image. + * Retrieves the pixels and metadata associated with this image. * *

The first call to this method will copy the data from native memory * to Java memory and release the native buffer. Subsequent calls will * return the cached copy.

* - * @return Object containing the pixel data - * @throws IllegalStateException if the image has already been released + * @throws IllegalStateException if te image has already been released */ - public Object get_pixels() throws IllegalStateException { + public synchronized void loadData() throws IllegalStateException { if (released_) { throw new IllegalStateException("Image has been released"); } - if (copiedPixels_ == null) { + if (this.pix == null) { try { - copiedPixels_ = core_.copyDataAtPointer(address_); + this.pix = core_.copyDataAtPointer(address_); + tags.initializeIfNeeded(); } catch (Exception e) { throw new IllegalStateException("Failed to copy data at pointer", e); } - release(); + // Now that we have the data, release the pointer + release(); } - return copiedPixels_; } /** @@ -93,35 +94,38 @@ protected void finalize() throws Throwable { } -// class LazyJSONObject extends JSONObject { -// private final long metadataPtr_; -// private final CMMCore core_; -// private boolean initialized_ = false; +class LazyJSONObject extends JSONObject { + private final long metadataPtr_; + private final CMMCore core_; + private boolean initialized_ = false; -// public LazyJSONObject(long metadataPtr, CMMCore core) { -// this.metadataPtr_ = metadataPtr; -// this.core_ = core; -// } -// private synchronized void initializeIfNeeded() { -// if (!initialized_) { -// // Use core's existing metadataToMap helper -// JSONObject tags = core_.metadataToMap(metadataPtr_); - -// // Copy all key/value pairs from the metadata map -// for (String key : tags.keySet()) { -// try { -// put(key, tags.get(key)); -// } catch (Exception e) {} -// } - -// initialized_ = true; -// } -// } + public LazyJSONObject(long metadataPtr, CMMCore core) { + this.metadataPtr_ = metadataPtr; + this.core_ = core; + } -// @Override -// public Object get(String key) { -// initializeIfNeeded(); -// return super.get(key); -// } -// } + synchronized void initializeIfNeeded() throws Exception { + if (!initialized_) { + Metadata md = new Metadata(); + this.core_.copyMetadataAtPointer(metadataPtr_, md); + + for (String key:md.GetKeys()) { + try { + this.put(key, md.GetSingleTag(key).GetValue()); + } catch (Exception e) {} + } + initialized_ = true; + } + } + + @Override + public Object get(String key) throws JSONException { + try { + initializeIfNeeded(); + } catch (Exception e) { + throw new RuntimeException("Failed to initialize metadata", e); + } + return super.get(key); + } +} From 2847d1c80d8d2e24dc2aa1191721bdb53427df75 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sun, 16 Feb 2025 13:01:09 -0800 Subject: [PATCH 26/46] Refactor to Metadata to only maintain essential metadata in buffer --- .../{BufferAdapter.cpp => BufferManager.cpp} | 166 +++++++---------- MMCore/{BufferAdapter.h => BufferManager.h} | 77 +++++--- MMCore/Buffer_v2.h | 1 + MMCore/CoreCallback.cpp | 85 ++++++--- MMCore/CoreCallback.h | 12 +- MMCore/MMCore.cpp | 167 +++++++++++++----- MMCore/MMCore.h | 12 +- MMDevice/MMDeviceConstants.h | 1 + 8 files changed, 321 insertions(+), 200 deletions(-) rename MMCore/{BufferAdapter.cpp => BufferManager.cpp} (74%) rename MMCore/{BufferAdapter.h => BufferManager.h} (74%) diff --git a/MMCore/BufferAdapter.cpp b/MMCore/BufferManager.cpp similarity index 74% rename from MMCore/BufferAdapter.cpp rename to MMCore/BufferManager.cpp index 9e6dc6f7f..8e4108020 100644 --- a/MMCore/BufferAdapter.cpp +++ b/MMCore/BufferManager.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// FILE: BufferAdapter.cpp +// FILE: BufferManager.cpp // PROJECT: Micro-Manager // SUBSYSTEM: MMCore //----------------------------------------------------------------------------- @@ -23,7 +23,7 @@ // AUTHOR: Henry Pinkard, 01/31/2025 -#include "BufferAdapter.h" +#include "BufferManager.h" #include @@ -55,7 +55,7 @@ static std::string FormatLocalTime(std::chrono::time_pointReinitializeBuffer(v2Buffer_->GetMemorySizeMB()); - if (ret != DEVICE_OK) - return false; - } catch (const std::exception&) { - // Optionally log the exception - return false; - } - return true; + // This in not required for v2 buffer because it can interleave multiple data types/image sizes } else { return circBuffer_->Initialize(numChannels, width, height, bytesPerPixel); } } -unsigned BufferAdapter::GetMemorySizeMB() const +unsigned BufferManager::GetMemorySizeMB() const { if (useV2_) { return v2Buffer_->GetMemorySizeMB(); @@ -129,7 +120,7 @@ unsigned BufferAdapter::GetMemorySizeMB() const } } -long BufferAdapter::GetRemainingImageCount() const +long BufferManager::GetRemainingImageCount() const { if (useV2_) { return v2Buffer_->GetActiveSlotCount(); @@ -138,10 +129,12 @@ long BufferAdapter::GetRemainingImageCount() const } } -void BufferAdapter::Clear() +void BufferManager::Clear() { if (useV2_) { - // v2Buffer_->ReleaseBuffer(); + // This has no effect on v2 buffer, because devices do not have authority to clear the buffer + // since higher level code may hold pointers to data in the buffer. + // It seems to be mostly used in live mode, where data is overwritten by default anyway. } else { circBuffer_->Clear(); } @@ -149,7 +142,7 @@ void BufferAdapter::Clear() imageNumbers_.clear(); } -long BufferAdapter::GetSize(long imageSize) const +long BufferManager::GetSize(long imageSize) const { if (useV2_) { unsigned int mb = v2Buffer_->GetMemorySizeMB(); @@ -162,7 +155,7 @@ long BufferAdapter::GetSize(long imageSize) const } -long BufferAdapter::GetFreeSize(long imageSize) const +long BufferManager::GetFreeSize(long imageSize) const { if (useV2_) { unsigned int mb = v2Buffer_->GetFreeMemory(); @@ -172,7 +165,7 @@ long BufferAdapter::GetFreeSize(long imageSize) const } } -bool BufferAdapter::Overflow() const +bool BufferManager::Overflow() const { if (useV2_) { return v2Buffer_->Overflow(); @@ -181,96 +174,71 @@ bool BufferAdapter::Overflow() const } } -void BufferAdapter::ProcessMetadata(Metadata& md, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents) { - // Track image numbers per camera - { - std::lock_guard lock(imageNumbersMutex_); - std::string cameraName = md.GetSingleTag(MM::g_Keyword_Metadata_CameraLabel).GetValue(); - if (imageNumbers_.end() == imageNumbers_.find(cameraName)) - { - imageNumbers_[cameraName] = 0; - } - - // insert image number - md.put(MM::g_Keyword_Metadata_ImageNumber, CDeviceUtils::ConvertToString(imageNumbers_[cameraName])); - ++imageNumbers_[cameraName]; - } - - if (!md.HasTag(MM::g_Keyword_Elapsed_Time_ms)) - { - // if time tag was not supplied by the camera insert current timestamp - using namespace std::chrono; - auto elapsed = steady_clock::now() - startTime_; - md.PutImageTag(MM::g_Keyword_Elapsed_Time_ms, - std::to_string(duration_cast(elapsed).count())); - } - - // Note: It is not ideal to use local time. I think this tag is rarely - // used. Consider replacing with UTC (micro)seconds-since-epoch (with - // different tag key) after addressing current usage. - auto now = std::chrono::system_clock::now(); - md.PutImageTag(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); - - md.PutImageTag(MM::g_Keyword_Metadata_Width, width); - md.PutImageTag(MM::g_Keyword_Metadata_Height, height); - if (byteDepth == 1) +void BufferManager::PopulateMetadata(Metadata& md, const char* deviceLabel, + unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents) { + // Add the device label (can be used to route different devices to different buffers) + md.put(MM::g_Keyword_Metadata_DataSourceDeviceLabel, deviceLabel); + + // Add essential image metadata needed for interpreting the image: + md.PutImageTag(MM::g_Keyword_Metadata_Width, width); + md.PutImageTag(MM::g_Keyword_Metadata_Height, height); + + if (byteDepth == 1) md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY8); - else if (byteDepth == 2) + else if (byteDepth == 2) md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY16); - else if (byteDepth == 4) - { + else if (byteDepth == 4) { if (nComponents == 1) md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY32); else md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB32); - } - else if (byteDepth == 8) + } + else if (byteDepth == 8) md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB64); - else + else md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_Unknown); } -bool BufferAdapter::InsertImage(const unsigned char* buf, +bool BufferManager::InsertImage(const char* callerLabel, const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd) { - return InsertMultiChannel(buf, 1, width, height, byteDepth, 1, pMd); + return InsertMultiChannel(callerLabel, buf, 1, width, height, byteDepth, 1, pMd); } -bool BufferAdapter::InsertImage(const unsigned char *buf, unsigned width, unsigned height, +bool BufferManager::InsertImage(const char* callerLabel, const unsigned char *buf, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, Metadata *pMd) { - return InsertMultiChannel(buf, 1, width, height, byteDepth, nComponents, pMd); + return InsertMultiChannel(callerLabel, buf, 1, width, height, byteDepth, nComponents, pMd); } -bool BufferAdapter::InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, - unsigned height, unsigned byteDepth, Metadata *pMd) { - return InsertMultiChannel(buf, numChannels, width, height, byteDepth, 1, pMd); +bool BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned char *buf, + unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata *pMd) { + return InsertMultiChannel(callerLabel, buf, numChannels, width, height, byteDepth, 1, pMd); } -bool BufferAdapter::InsertMultiChannel(const unsigned char* buf, unsigned numChannels, - unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, Metadata* pMd) { +bool BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned char* buf, + unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, Metadata* pMd) { - // Initialize metadata with either provided metadata or create empty + // Initialize metadata with either provided metadata or create empty Metadata md = (pMd != nullptr) ? *pMd : Metadata(); - // Process common metadata - ProcessMetadata(md, width, height, byteDepth, nComponents); + // Add required and useful metadata. + PopulateMetadata(md, callerLabel, width, height, byteDepth, nComponents); if (useV2_) { - // All the data needed to interpret the image is in the metadata - // This function will copy data and metadata into the buffer - int ret = v2Buffer_->InsertData(buf, width * height * byteDepth *numChannels, &md); - return ret == DEVICE_OK; + // All the data needed to interpret the image is in the metadata + // This function will copy data and metadata into the buffer + int ret = v2Buffer_->InsertData(buf, width * height * byteDepth *numChannels, &md); + return ret == DEVICE_OK; } else { return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md); } } -const void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) +const void* BufferManager::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) { if (useV2_) { - // In v2, we now use a channel-aware pointer arithmetic at the adapter level. + // In v2, we now use a channel-aware pointer arithmetic at the manager level. const void* basePtr = nullptr; int ret = v2Buffer_->PeekNextDataReadPointer(&basePtr, md); if (ret != DEVICE_OK || basePtr == nullptr) @@ -296,7 +264,7 @@ const void* BufferAdapter::GetLastImageMD(unsigned channel, Metadata& md) const } } -const void* BufferAdapter::GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError) +const void* BufferManager::GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError) { if (useV2_) { // NOTE: make sure calling code releases the slot after use. @@ -312,10 +280,10 @@ const void* BufferAdapter::GetNthImageMD(unsigned long n, Metadata& md) const th } } -const void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError) +const void* BufferManager::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError) { if (useV2_) { - // For v2, we now make the buffer channel aware at the adapter level. + // For v2, we now make the buffer channel aware at the manager level. const void* basePtr = v2Buffer_->PopNextDataReadPointer(md, false); if (basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); @@ -339,7 +307,7 @@ const void* BufferAdapter::PopNextImageMD(unsigned channel, Metadata& md) throw } } -bool BufferAdapter::EnableV2Buffer(bool enable) { +bool BufferManager::EnableV2Buffer(bool enable) { // Don't do anything if we're already in the requested state if (enable == useV2_) { return true; @@ -375,18 +343,18 @@ bool BufferAdapter::EnableV2Buffer(bool enable) { } } -bool BufferAdapter::IsUsingV2Buffer() const { +bool BufferManager::IsUsingV2Buffer() const { return useV2_; } -bool BufferAdapter::ReleaseReadAccess(const void* ptr) { +bool BufferManager::ReleaseReadAccess(const void* ptr) { if (useV2_ && ptr) { return v2Buffer_->ReleaseDataReadPointer(ptr); } return true; } -unsigned BufferAdapter::GetImageWidth(const void* ptr) const { +unsigned BufferManager::GetImageWidth(const void* ptr) const { if (!useV2_) throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); Metadata md; @@ -396,7 +364,7 @@ unsigned BufferAdapter::GetImageWidth(const void* ptr) const { return static_cast(atoi(sVal.c_str())); } -unsigned BufferAdapter::GetImageHeight(const void* ptr) const { +unsigned BufferManager::GetImageHeight(const void* ptr) const { if (!useV2_) throw CMMError("GetImageHeight(ptr) only supported with V2 buffer"); Metadata md; @@ -406,7 +374,7 @@ unsigned BufferAdapter::GetImageHeight(const void* ptr) const { return static_cast(atoi(sVal.c_str())); } -unsigned BufferAdapter::GetBytesPerPixel(const void* ptr) const { +unsigned BufferManager::GetBytesPerPixel(const void* ptr) const { if (!useV2_) throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); Metadata md; @@ -425,7 +393,7 @@ unsigned BufferAdapter::GetBytesPerPixel(const void* ptr) const { throw CMMError("Unknown pixel type for bytes per pixel"); } -unsigned BufferAdapter::GetImageBitDepth(const void* ptr) const { +unsigned BufferManager::GetImageBitDepth(const void* ptr) const { if (!useV2_) throw CMMError("GetImageBitDepth(ptr) only supported with V2 buffer"); Metadata md; @@ -444,7 +412,7 @@ unsigned BufferAdapter::GetImageBitDepth(const void* ptr) const { throw CMMError("Unknown pixel type for image bit depth"); } -unsigned BufferAdapter::GetNumberOfComponents(const void* ptr) const { +unsigned BufferManager::GetNumberOfComponents(const void* ptr) const { if (!useV2_) throw CMMError("GetNumberOfComponents(ptr) only supported with V2 buffer"); Metadata md; @@ -461,7 +429,7 @@ unsigned BufferAdapter::GetNumberOfComponents(const void* ptr) const { throw CMMError("Unknown pixel type for number of components"); } -long BufferAdapter::GetImageBufferSize(const void* ptr) const { +long BufferManager::GetImageBufferSize(const void* ptr) const { if (!useV2_) throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); Metadata md; @@ -474,7 +442,7 @@ long BufferAdapter::GetImageBufferSize(const void* ptr) const { return static_cast(width * height * bpp); } -bool BufferAdapter::SetOverwriteData(bool overwrite) { +bool BufferManager::SetOverwriteData(bool overwrite) { if (useV2_) { return v2Buffer_->SetOverwriteData(overwrite) == DEVICE_OK; } else { @@ -483,7 +451,7 @@ bool BufferAdapter::SetOverwriteData(bool overwrite) { } } -bool BufferAdapter::AcquireWriteSlot(size_t dataSize, unsigned width, unsigned height, +bool BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, size_t additionalMetadataSize, void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata) { @@ -497,7 +465,7 @@ bool BufferAdapter::AcquireWriteSlot(size_t dataSize, unsigned width, unsigned h // Add in width, height, byteDepth, and nComponents to the metadata so that when // images are retrieved from the buffer, the data can be interpreted correctly - ProcessMetadata(md, width, height, byteDepth, nComponents); + PopulateMetadata(md, deviceLabel, width, height, byteDepth, nComponents); std::string serializedMetadata = md.Serialize(); int ret = v2Buffer_->AcquireWriteSlot(dataSize, additionalMetadataSize, @@ -505,7 +473,7 @@ bool BufferAdapter::AcquireWriteSlot(size_t dataSize, unsigned width, unsigned h return ret == DEVICE_OK; } -bool BufferAdapter::FinalizeWriteSlot(void* imageDataPointer, size_t actualMetadataBytes) +bool BufferManager::FinalizeWriteSlot(void* imageDataPointer, size_t actualMetadataBytes) { if (!useV2_) { // Not supported for circular buffer @@ -516,7 +484,7 @@ bool BufferAdapter::FinalizeWriteSlot(void* imageDataPointer, size_t actualMetad return ret == DEVICE_OK; } -void BufferAdapter::ExtractMetadata(const void* dataPtr, Metadata& md) const { +void BufferManager::ExtractMetadata(const void* dataPtr, Metadata& md) const { if (!useV2_) { throw CMMError("ExtractMetadata is only supported with V2 buffer enabled"); } diff --git a/MMCore/BufferAdapter.h b/MMCore/BufferManager.h similarity index 74% rename from MMCore/BufferAdapter.h rename to MMCore/BufferManager.h index b6d39a982..d67e3e6ef 100644 --- a/MMCore/BufferAdapter.h +++ b/MMCore/BufferManager.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////////// -// FILE: BufferAdapter.h +// FILE: BufferManager.h // PROJECT: Micro-Manager // SUBSYSTEM: MMCore //----------------------------------------------------------------------------- @@ -23,8 +23,8 @@ // AUTHOR: Henry Pinkard, 01/31/2025 -#ifndef BUFFERADAPTER_H -#define BUFFERADAPTER_H +#ifndef BUFFERMANAGER_H +#define BUFFERMANAGER_H #include "CircularBuffer.h" #include "Buffer_v2.h" @@ -33,9 +33,9 @@ #include #include -// BufferAdapter provides a common interface for buffer operations +// BufferManager provides a common interface for buffer operations // used by MMCore. It currently supports only a minimal set of functions. -class BufferAdapter { +class BufferManager { public: static const char* const DEFAULT_V2_BUFFER_NAME; @@ -44,8 +44,8 @@ class BufferAdapter { * @param useV2Buffer Set to true to use the new DataBuffer (v2); false to use CircularBuffer. * @param memorySizeMB Memory size for the buffer (in megabytes). */ - BufferAdapter(bool useV2Buffer, unsigned int memorySizeMB); - ~BufferAdapter(); + BufferManager(bool useV2Buffer, unsigned int memorySizeMB); + ~BufferManager(); /** * Enable or disable v2 buffer usage. @@ -96,6 +96,7 @@ class BufferAdapter { /** * Insert an image into the buffer. + * @param caller The device inserting the image. * @param buf The image data. * @param width Image width. * @param height Image height. @@ -103,11 +104,12 @@ class BufferAdapter { * @param pMd Metadata associated with the image. * @return true on success, false on error. */ - bool InsertImage(const unsigned char *buf, unsigned width, unsigned height, - unsigned byteDepth, Metadata *pMd); + bool InsertImage(const char* deviceLabel, const unsigned char *buf, + unsigned width, unsigned height, unsigned byteDepth, Metadata *pMd); /** * Insert an image into the buffer with specified number of components. + * @param caller The device inserting the image. * @param buf The image data. * @param width Image width. * @param height Image height. @@ -116,11 +118,12 @@ class BufferAdapter { * @param pMd Metadata associated with the image. * @return true on success, false on error. */ - bool InsertImage(const unsigned char *buf, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, Metadata *pMd); + bool InsertImage(const char* deviceLabel, const unsigned char *buf, unsigned width, + unsigned height, unsigned byteDepth, unsigned nComponents, Metadata *pMd); /** * Insert a multi-channel image into the buffer. + * @param caller The device inserting the image. * @param buf The image data. * @param numChannels Number of channels in the image. * @param width Image width. @@ -129,11 +132,13 @@ class BufferAdapter { * @param pMd Metadata associated with the image. * @return true on success, false on error. */ - bool InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, - unsigned height, unsigned byteDepth, Metadata *pMd); + bool InsertMultiChannel(const char* deviceLabel, const unsigned char *buf, + unsigned numChannels, unsigned width, unsigned height, + unsigned byteDepth, Metadata *pMd); /** * Insert a multi-channel image into the buffer with specified number of components. + * @param caller The device inserting the image. * @param buf The image data. * @param numChannels Number of channels in the image. * @param width Image width. @@ -143,8 +148,9 @@ class BufferAdapter { * @param pMd Metadata associated with the image. * @return true on success, false on error. */ - bool InsertMultiChannel(const unsigned char *buf, unsigned numChannels, unsigned width, - unsigned height, unsigned byteDepth, unsigned nComponents, Metadata *pMd); + bool InsertMultiChannel(const char* deviceLabel, const unsigned char *buf, + unsigned numChannels, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, Metadata *pMd); /** * Get the total capacity of the buffer. @@ -171,7 +177,7 @@ class BufferAdapter { const void* PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError); /** - * Check if this adapter is using the V2 buffer implementation. + * Check if this manager is using the V2 buffer implementation. * @return true if using V2 buffer, false if using circular buffer. */ bool IsUsingV2Buffer() const; @@ -201,6 +207,7 @@ class BufferAdapter { /** * Acquires a write slot large enough to hold the image data and metadata. + * @param deviceLabel The label of the device requesting the write slot * @param dataSize The number of bytes reserved for image or other primary data. * @param width Image width. * @param height Image height. @@ -212,7 +219,7 @@ class BufferAdapter { * @param pInitialMetadata Optionally, a pointer to a metadata object whose contents should be pre‐written. Defaults to nullptr. * @return true on success, false on error. */ - bool AcquireWriteSlot(size_t dataSize, unsigned width, unsigned height, + bool AcquireWriteSlot(const char* deviceLabel, size_t dataSize, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, size_t additionalMetadataSize, void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata = nullptr); @@ -233,17 +240,41 @@ class BufferAdapter { */ void ExtractMetadata(const void* dataPtr, Metadata& md) const; + /** + * Add basic metadata tags for the data source device. + * @param deviceLabel The label of the device inserting the metadata + * @param pMd Optional pointer to existing metadata to merge with + * @return Metadata object containing merged metadata + */ + Metadata AddDeviceLabel(const char* deviceLabel, const Metadata* pMd); + + + private: bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. CircularBuffer* circBuffer_; DataBuffer* v2Buffer_; - std::chrono::steady_clock::time_point startTime_; - std::map imageNumbers_; // Track image numbers per camera - std::mutex imageNumbersMutex_; // Mutex to protect access to imageNumbers_ + /** + * Add essential metadata tags required for interpreting stored data and routing it + * if multiple buffers are used. Only minimal parameters (width, height, pixel type) + * are added. + * + * Future data-producer devices (ie those that dont produce conventional images) may + * need alternative versions of this function. + */ + void PopulateMetadata(Metadata& md, const char* deviceLabel, + unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents); + - void ProcessMetadata(Metadata& md, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents); + /** + * Get the metadata tags attached to device caller, and merge them with metadata + * in pMd (if not null). Returns a metadata object. + * @param caller The device inserting the metadata + * @param pMd Optional pointer to existing metadata to merge with + * @return Metadata object containing merged metadata + */ + Metadata AddCallerMetadata(const MM::Device* caller, const Metadata* pMd); }; -#endif // BUFFERADAPTER_H \ No newline at end of file +#endif // BUFFERMANAGER_H \ No newline at end of file diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index a1d938270..cd3829b27 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -222,6 +222,7 @@ class DataBuffer { /** * Inserts image data along with metadata into the buffer. + * @param caller The calling device that is the source of the data. * @param data Pointer to the raw image data. * @param dataSize The image data size in bytes. * @param pMd Pointer to the metadata (can be null if not applicable). diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 4d8e56487..5ab440c9c 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -31,13 +31,14 @@ #include "CircularBuffer.h" #include "CoreCallback.h" #include "DeviceManager.h" -#include "BufferAdapter.h" +#include "BufferManager.h" #include #include #include #include #include +#include CoreCallback::CoreCallback(CMMCore* c) : @@ -46,6 +47,9 @@ CoreCallback::CoreCallback(CMMCore* c) : { assert(core_); pValueChangeLock_ = new MMThreadLock(); + + // Initialize the start time for time-stamp calculations + startTime_ = std::chrono::steady_clock::now(); } @@ -219,26 +223,56 @@ CoreCallback::AddCameraMetadata(const MM::Device* caller, const Metadata* pMd) newMD = *pMd; } - std::shared_ptr camera = - std::static_pointer_cast( - core_->deviceManager_->GetDevice(caller)); - - std::string label = camera->GetLabel(); - newMD.put(MM::g_Keyword_Metadata_CameraLabel, label); + std::shared_ptr device = core_->deviceManager_->GetDevice(caller); - std::string serializedMD; - try - { - serializedMD = camera->GetTags(); - } - catch (const CMMError&) + if (device->GetType() == MM::CameraDevice) { - return newMD; - } + std::shared_ptr camera = + std::static_pointer_cast(device); + + // Ensure the camera label is present + if (!newMD.HasTag(MM::g_Keyword_Metadata_CameraLabel)) { + newMD.put(MM::g_Keyword_Metadata_CameraLabel, camera->GetLabel()); + } + + // Add image number metadata + { + std::lock_guard lock(imageNumbersMutex_); + std::string cameraName = newMD.GetSingleTag(MM::g_Keyword_Metadata_CameraLabel).GetValue(); + if (imageNumbers_.find(cameraName) == imageNumbers_.end()) + { + imageNumbers_[cameraName] = 0; + } + newMD.put(MM::g_Keyword_Metadata_ImageNumber, CDeviceUtils::ConvertToString(imageNumbers_[cameraName])); + ++imageNumbers_[cameraName]; + } + + // Add elapsed time metadata if not already set + if (!newMD.HasTag(MM::g_Keyword_Elapsed_Time_ms)) + { + using namespace std::chrono; + auto elapsed = steady_clock::now() - startTime_; + newMD.PutImageTag(MM::g_Keyword_Elapsed_Time_ms, + std::to_string(duration_cast(elapsed).count())); + } + + // Add current system time (as formatted local time) + auto now = std::chrono::system_clock::now(); + newMD.PutImageTag(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); - Metadata devMD; - devMD.Restore(serializedMD.c_str()); - newMD.Merge(devMD); + // Merge any additional camera-specific tags + try + { + std::string serializedMD = camera->GetTags(); + Metadata devMD; + devMD.Restore(serializedMD.c_str()); + newMD.Merge(devMD); + } + catch (const CMMError&) + { + // Ignore errors getting tags + } + } return newMD; } @@ -264,7 +298,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf ip->Process(const_cast(buf), width, height, byteDepth); } } - if (core_->bufferAdapter_->InsertImage(buf, width, height, byteDepth, &md)) + if (core_->bufferManager_->InsertImage(caller, buf, width, height, byteDepth, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; @@ -296,7 +330,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf ip->Process(const_cast(buf), width, height, byteDepth); } } - if (core_->bufferAdapter_->InsertImage(buf, width, height, byteDepth, nComponents, &md)) + if (core_->bufferManager_->InsertImage(caller, buf, width, height, byteDepth, nComponents, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; @@ -333,7 +367,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const ImgBuffer & imgBuf // { // if (core_->deviceManager_->GetDevice(caller)->GetType() == MM::CameraDevice) // { -// Metadata md = AddCameraMetadata(caller, nullptr); +// Metadata md = AddCallerMetadata(caller, nullptr); // if (!core_->bufferAdapter_->AcquireWriteSlot(dataSize, width, height, // byteDepth, nComponents, metadataSize, @@ -359,9 +393,12 @@ int CoreCallback::InsertImage(const MM::Device* caller, const ImgBuffer & imgBuf void CoreCallback::ClearImageBuffer(const MM::Device* /*caller*/) { - core_->bufferAdapter_->Clear(); + // This has no effect on v2 buffer, because devices do not have authority to clear the buffer + // since higher level code may hold pointers to data in the buffer. + core_->bufferManager_->Clear(); } +// Note: this not required and has not effect on v2 buffer bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth) { @@ -369,7 +406,7 @@ bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, if (slices != 1) return false; - return core_->bufferAdapter_->Initialize(channels, w, h, pixDepth); + return core_->bufferManager_->Initialize(channels, w, h, pixDepth); } int CoreCallback::InsertMultiChannel(const MM::Device* caller, @@ -389,7 +426,7 @@ int CoreCallback::InsertMultiChannel(const MM::Device* caller, { ip->Process( const_cast(buf), width, height, byteDepth); } - if (core_->bufferAdapter_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md)) + if (core_->bufferManager_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; diff --git a/MMCore/CoreCallback.h b/MMCore/CoreCallback.h index 9f46bc63b..524770fed 100644 --- a/MMCore/CoreCallback.h +++ b/MMCore/CoreCallback.h @@ -100,7 +100,9 @@ class CoreCallback : public MM::Core // int FinalizeWriteSlot(unsigned char* imageDataPointer, // size_t actualMetadataBytes); - + // Note: these are not required and have no effect on v2 buffer, + // because devices do not have authority to clear the buffer since higher level + //code may hold pointers to data in the buffer. void ClearImageBuffer(const MM::Device* caller); bool InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth); @@ -147,11 +149,19 @@ class CoreCallback : public MM::Core char* deviceName, const unsigned int deviceIterator); + private: CMMCore* core_; MMThreadLock* pValueChangeLock_; + /** + * Add camera-specific metadata tags including image numbering and timestamps, + * and merge them with any existing metadata. + */ Metadata AddCameraMetadata(const MM::Device* caller, const Metadata* pMd); + std::map imageNumbers_; // Track image numbers per camera + std::mutex imageNumbersMutex_; + std::chrono::steady_clock::time_point startTime_; // Start time for elapsed time calculations int OnConfigGroupChanged(const char* groupName, const char* newConfigName); int OnPixelSizeChanged(double newPixelSizeUm); diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index d0b587908..d8c0c38b5 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -137,7 +137,7 @@ CMMCore::CMMCore() : properties_(0), externalCallback_(0), pixelSizeGroup_(0), - bufferAdapter_(nullptr), + bufferManager_(nullptr), pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), pPostedErrorsLock_(NULL), @@ -152,7 +152,7 @@ CMMCore::CMMCore() : callback_ = new CoreCallback(this); const unsigned seqBufMegabytes = (sizeof(void*) > 4) ? 250 : 25; - bufferAdapter_ = new BufferAdapter(useV2Buffer_, seqBufMegabytes); + bufferManager_ = new BufferManager(useV2Buffer_, seqBufMegabytes); nullAffine_ = new std::vector(6); for (int i = 0; i < 6; i++) { @@ -182,7 +182,7 @@ CMMCore::~CMMCore() delete callback_; delete configGroups_; delete properties_; - delete bufferAdapter_; + delete bufferManager_; delete pixelSizeGroup_; delete pPostedErrorsLock_; @@ -2680,7 +2680,7 @@ bool CMMCore::getShutterOpen() throw (CMMError) * @return a pointer to the internal image buffer. * @throws CMMError when the camera returns no data */ -SnapBufferPtr CMMCore::getImage() throw (CMMError) +void* CMMCore::getImage() throw (CMMError) { std::shared_ptr camera = currentCameraDevice_.lock(); if (!camera) @@ -2746,7 +2746,7 @@ SnapBufferPtr CMMCore::getImage() throw (CMMError) * @param channelNr Channel number for which the image buffer is requested * @return a pointer to the internal image buffer. */ -SnapBufferPtr CMMCore::getImage(unsigned channelNr) throw (CMMError) +void* CMMCore::getImage(unsigned channelNr) throw (CMMError) { std::shared_ptr camera = currentCameraDevice_.lock(); if (!camera) @@ -2781,6 +2781,79 @@ SnapBufferPtr CMMCore::getImage(unsigned channelNr) throw (CMMError) } } +/** + * For use with V2 buffer + * + * Get a pointer to the image acquired by snapImage. This will copy the image + * from the camera buffer into the V2 buffer so that it can be persistently + * accessed (i.e. subsequent calls snapImage will not overwrite the pointer + * returned by this function). + * + * This method relies on the current Core-Camera to determine which camera + * buffer to copy. + * + * @return a pointer to the internal image buffer. + * @throws CMMError when the camera returns no data + */ +DataPtr CMMCore::getImagePointer() throw (CMMError) +{ + if (!useV2Buffer_ ) + throw CMMError("Only valid when V2 buffer in use"); + + std::shared_ptr camera = currentCameraDevice_.lock(); + if (!camera) + throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); + else + { + if( ! everSnapped_) + { + logError("CMMCore::getImage()", getCoreErrorText(MMERR_InvalidImageSequence).c_str()); + throw CMMError(getCoreErrorText(MMERR_InvalidImageSequence).c_str(), MMERR_InvalidImageSequence); + } + + { + MMThreadGuard g(*pPostedErrorsLock_); + // Copied from above getImage() calls, unclear if needed + if(0 < postedErrors_.size()) + { + std::pair< int, std::string> toThrow(postedErrors_[0]); + // todo, process the collection of posted errors. + postedErrors_.clear(); + throw CMMError( toThrow.second.c_str(), toThrow.first); + } + } + + void* pBuf(0); + try { + mm::DeviceModuleLockGuard guard(camera); + pBuf = const_cast (camera->GetImageBuffer()); + + INSERT DATA HERE + + std::shared_ptr imageProcessor = + currentImageProcessor_.lock(); + if (imageProcessor) + { + imageProcessor->Process((unsigned char*)pBuf, camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel() ); + } + } catch( CMMError& e){ + throw e; + } catch (...) { + logError("CMMCore::getImage()", getCoreErrorText(MMERR_UnhandledException).c_str()); + throw CMMError(getCoreErrorText(MMERR_UnhandledException).c_str(), MMERR_UnhandledException); + } + + if (pBuf != 0) + return pBuf; + else + { + logError("CMMCore::getImage()", getCoreErrorText(MMERR_CameraBufferReadFailed).c_str()); + throw CMMError(getCoreErrorText(MMERR_CameraBufferReadFailed).c_str(), MMERR_CameraBufferReadFailed); + } + } +} + + /** * Returns the size of the internal image buffer. * @@ -2831,14 +2904,14 @@ void CMMCore::startSequenceAcquisition(long numImages, double intervalMs, bool s try { - if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferManager_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - bufferAdapter_->Clear(); + bufferManager_->Clear(); // Disable overwriting for finite sequence acquisition - bufferAdapter_->SetOverwriteData(false); + bufferManager_->SetOverwriteData(false); mm::DeviceModuleLockGuard guard(camera); LOG_DEBUG(coreLogger_) << "Will start sequence acquisition from default camera"; @@ -2877,14 +2950,14 @@ void CMMCore::startSequenceAcquisition(const char* label, long numImages, double throw CMMError(getCoreErrorText(MMERR_NotAllowedDuringSequenceAcquisition).c_str(), MMERR_NotAllowedDuringSequenceAcquisition); - if (!bufferAdapter_->Initialize(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), pCam->GetImageHeight(), pCam->GetImageBytesPerPixel())) + if (!bufferManager_->Initialize(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), pCam->GetImageHeight(), pCam->GetImageBytesPerPixel())) { logError(getDeviceName(pCam).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - bufferAdapter_->Clear(); + bufferManager_->Clear(); // Disable overwriting for finite sequence acquisition - bufferAdapter_->SetOverwriteData(false); + bufferManager_->SetOverwriteData(false); LOG_DEBUG(coreLogger_) << "Will start sequence acquisition from camera " << label; @@ -2930,12 +3003,12 @@ void CMMCore::initializeCircularBuffer() throw (CMMError) if (camera) { mm::DeviceModuleLockGuard guard(camera); - if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferManager_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - bufferAdapter_->Clear(); + bufferManager_->Clear(); } else { @@ -2982,15 +3055,15 @@ void CMMCore::startContinuousSequenceAcquisition(double intervalMs) throw (CMMEr ,MMERR_NotAllowedDuringSequenceAcquisition); } - if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferManager_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - bufferAdapter_->Clear(); + bufferManager_->Clear(); // Enable overwriting for continuous sequence acquisition. This only applies to the v2 buffer. // and has no effect on the circular buffer. - bufferAdapter_->SetOverwriteData(true); + bufferManager_->SetOverwriteData(true); LOG_DEBUG(coreLogger_) << "Will start continuous sequence acquisition from current camera"; int nRet = camera->StartSequenceAcquisition(intervalMs); if (nRet != DEVICE_OK) @@ -3086,7 +3159,7 @@ void* CMMCore::getLastImage() throw (CMMError) } } - const void* pBuf = bufferAdapter_->GetLastImage(); + const void* pBuf = bufferManager_->GetLastImage(); if (pBuf != 0) return const_cast(pBuf); else @@ -3102,7 +3175,7 @@ void* CMMCore::getLastImageMD(unsigned channel, unsigned slice, Metadata& md) co if (slice != 0) throw CMMError("Slice must be 0"); - return const_cast(bufferAdapter_->GetLastImageMD(channel, md)); + return const_cast(bufferManager_->GetLastImageMD(channel, md)); } /** @@ -3136,7 +3209,7 @@ void* CMMCore::getLastImageMD(Metadata& md) const throw (CMMError) */ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw (CMMError) { - return const_cast(bufferAdapter_->GetNthImageMD(n, md)); + return const_cast(bufferManager_->GetNthImageMD(n, md)); } /** @@ -3153,7 +3226,7 @@ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw */ void* CMMCore::popNextImage() throw (CMMError) { - const void* pBuf = bufferAdapter_->PopNextImage(); + const void* pBuf = bufferManager_->PopNextImage(); if (pBuf != 0) return const_cast(pBuf); else @@ -3171,7 +3244,7 @@ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) th if (slice != 0) throw CMMError("Slice must be 0"); - return const_cast(bufferAdapter_->PopNextImageMD(channel, md)); + return const_cast(bufferManager_->PopNextImageMD(channel, md)); } /** @@ -3244,7 +3317,7 @@ void CMMCore::copyMetadataAtPointer(DataPtr ptr, Metadata& md) throw (CMMError) if (!useV2Buffer_) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - bufferAdapter_->ExtractMetadata(ptr, md); + bufferManager_->ExtractMetadata(ptr, md); } /** * Removes all images from the circular buffer. @@ -3254,7 +3327,7 @@ void CMMCore::copyMetadataAtPointer(DataPtr ptr, Metadata& md) throw (CMMError) */ void CMMCore::clearCircularBuffer() throw (CMMError) { - bufferAdapter_->Clear(); + bufferManager_->Clear(); } /** @@ -3263,7 +3336,7 @@ void CMMCore::clearCircularBuffer() throw (CMMError) void CMMCore::enableV2Buffer(bool enable) throw (CMMError) { useV2Buffer_ = enable; - bufferAdapter_->EnableV2Buffer(enable); + bufferManager_->EnableV2Buffer(enable); } /** @@ -3275,12 +3348,12 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes if (isSequenceRunning()) { stopSequenceAcquisition(); } - delete bufferAdapter_; // discard old buffer + delete bufferManager_; // discard old buffer LOG_DEBUG(coreLogger_) << "Will set circular buffer size to " << sizeMB << " MB"; try { - bufferAdapter_ = new BufferAdapter(useV2Buffer_, sizeMB); + bufferManager_ = new BufferManager(useV2Buffer_, sizeMB); } catch (std::bad_alloc& ex) { @@ -3289,7 +3362,7 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes messs << getCoreErrorText(MMERR_OutOfMemory).c_str() << " " << ex.what() << '\n'; throw CMMError(messs.str().c_str() , MMERR_OutOfMemory); } - if (NULL == bufferAdapter_) throw CMMError(getCoreErrorText(MMERR_OutOfMemory).c_str(), MMERR_OutOfMemory); + if (NULL == bufferManager_) throw CMMError(getCoreErrorText(MMERR_OutOfMemory).c_str(), MMERR_OutOfMemory); try @@ -3300,7 +3373,7 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes if (camera) { mm::DeviceModuleLockGuard guard(camera); - if (!bufferAdapter_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferManager_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } @@ -3313,7 +3386,7 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes messs << getCoreErrorText(MMERR_OutOfMemory).c_str() << " " << ex.what() << '\n'; throw CMMError(messs.str().c_str() , MMERR_OutOfMemory); } - if (NULL == bufferAdapter_) + if (NULL == bufferManager_) throw CMMError(getCoreErrorText(MMERR_OutOfMemory).c_str(), MMERR_OutOfMemory); } @@ -3322,9 +3395,9 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes */ unsigned CMMCore::getCircularBufferMemoryFootprint() { - if (bufferAdapter_) + if (bufferManager_) { - return bufferAdapter_->GetMemorySizeMB(); + return bufferManager_->GetMemorySizeMB(); } return 0; } @@ -3334,9 +3407,9 @@ unsigned CMMCore::getCircularBufferMemoryFootprint() */ long CMMCore::getRemainingImageCount() { - if (bufferAdapter_) + if (bufferManager_) { - return bufferAdapter_->GetRemainingImageCount(); + return bufferManager_->GetRemainingImageCount(); } return 0; } @@ -3346,12 +3419,12 @@ long CMMCore::getRemainingImageCount() */ long CMMCore::getBufferTotalCapacity() { - if (bufferAdapter_) + if (bufferManager_) { // Compute image size from the current camera parameters. long imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel(); // Pass the computed image size as an argument to the adapter. - return bufferAdapter_->GetSize(imageSize); + return bufferManager_->GetSize(imageSize); } return 0; } @@ -3363,11 +3436,11 @@ long CMMCore::getBufferTotalCapacity() */ long CMMCore::getBufferFreeCapacity() { - if (bufferAdapter_) + if (bufferManager_) { // Compute image size from the current camera parameters. long imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel(); - return bufferAdapter_->GetFreeSize(imageSize); + return bufferManager_->GetFreeSize(imageSize); } return 0; } @@ -3377,7 +3450,7 @@ long CMMCore::getBufferFreeCapacity() */ bool CMMCore::isBufferOverflowed() const { - return bufferAdapter_->Overflow(); + return bufferManager_->Overflow(); } /** @@ -4339,28 +4412,28 @@ unsigned CMMCore::getImageWidth(DataPtr ptr) { // This is refering to the snap image return getImageWidth(); } - return useV2Buffer_ ? bufferAdapter_->GetImageWidth(ptr) : getImageWidth(); + return useV2Buffer_ ? bufferManager_->GetImageWidth(ptr) : getImageWidth(); } unsigned CMMCore::getImageHeight(DataPtr ptr) { - return useV2Buffer_ ? bufferAdapter_->GetImageHeight(ptr) : getImageHeight(); + return useV2Buffer_ ? bufferManager_->GetImageHeight(ptr) : getImageHeight(); } unsigned CMMCore::getBytesPerPixel(DataPtr ptr) { - return useV2Buffer_ ? bufferAdapter_->GetBytesPerPixel(ptr) : getBytesPerPixel(); + return useV2Buffer_ ? bufferManager_->GetBytesPerPixel(ptr) : getBytesPerPixel(); } unsigned CMMCore::getNumberOfComponents(DataPtr ptr) { - return useV2Buffer_ ? bufferAdapter_->GetNumberOfComponents(ptr) : getNumberOfComponents(); + return useV2Buffer_ ? bufferManager_->GetNumberOfComponents(ptr) : getNumberOfComponents(); } long CMMCore::getImageBufferSize(DataPtr ptr) { - return useV2Buffer_ ? bufferAdapter_->GetImageBufferSize(ptr) : getImageBufferSize(); + return useV2Buffer_ ? bufferManager_->GetImageBufferSize(ptr) : getImageBufferSize(); } void CMMCore::releaseReadAccess(DataPtr ptr) { if (useV2Buffer_) { - bufferAdapter_->ReleaseReadAccess(ptr); + bufferManager_->ReleaseReadAccess(ptr); } } @@ -4528,7 +4601,7 @@ void CMMCore::setROI(int x, int y, int xSize, int ySize) throw (CMMError) // inconsistent with the current image size. There is no way to "fix" // popNextImage() to handle this correctly, so we need to make sure we // discard such images. - bufferAdapter_->Clear(); + bufferManager_->Clear(); } else throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); @@ -4607,7 +4680,7 @@ void CMMCore::setROI(const char* label, int x, int y, int xSize, int ySize) thro // inconsistent with the current image size. There is no way to "fix" // popNextImage() to handle this correctly, so we need to make sure we // discard such images. - bufferAdapter_->Clear(); + bufferManager_->Clear(); } else throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); @@ -4666,7 +4739,7 @@ void CMMCore::clearROI() throw (CMMError) // inconsistent with the current image size. There is no way to "fix" // popNextImage() to handle this correctly, so we need to make sure we // discard such images. - bufferAdapter_->Clear(); + bufferManager_->Clear(); } } diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index f03d0fbbf..d37662ecd 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -65,7 +65,7 @@ #include "Error.h" #include "ErrorCodes.h" #include "Logging/Logger.h" -#include "BufferAdapter.h" +#include "BufferManager.h" #include #include @@ -118,8 +118,6 @@ typedef unsigned int* imgRGB32; // void* is converted to Objects to copy arrays of data but we want to be able to // maps these pointers to longs typedef const void* DataPtr; -// making this alias simplifies the SWIG wrapping -typedef const void* SnapBufferPtr; enum DeviceInitializationState { Uninitialized, @@ -383,8 +381,10 @@ class CMMCore double getExposure(const char* label) throw (CMMError); void snapImage() throw (CMMError); - SnapBufferPtr getImage() throw (CMMError); - SnapBufferPtr getImage(unsigned numChannel) throw (CMMError); + void* getImage() throw (CMMError); + void* getImage(unsigned numChannel) throw (CMMError); + DataPtr getImagePointer() throw (CMMError); + unsigned getImageWidth(); unsigned getImageHeight(); @@ -694,7 +694,7 @@ class CMMCore MMEventCallback* externalCallback_; // notification hook to the higher layer (e.g. GUI) PixelSizeConfigGroup* pixelSizeGroup_; // New adapter to wrap either the circular buffer or the DataBuffer (v2) - BufferAdapter* bufferAdapter_; + BufferManager* bufferManager_; std::shared_ptr pluginManager_; std::shared_ptr deviceManager_; diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index efde9f5d7..690d4e9a2 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -165,6 +165,7 @@ namespace MM { const char* const g_Keyword_Metadata_Width = "Width"; const char* const g_Keyword_Metadata_Height = "Height"; const char* const g_Keyword_Metadata_CameraLabel = "Camera"; + const char* const g_Keyword_Metadata_DataSourceDeviceLabel = "DataSourceDevice"; const char* const g_Keyword_Metadata_Exposure = "Exposure-ms"; MM_DEPRECATED(const char* const g_Keyword_Meatdata_Exposure) = "Exposure-ms"; // Typo const char* const g_Keyword_Metadata_Score = "Score"; From 893642f4d21248a1b514aea91ac826363e89f2f2 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sun, 16 Feb 2025 17:19:22 -0800 Subject: [PATCH 27/46] refactor and simplify to make safer pointer handling and less SWIG compelxity --- MMCore/BufferDataPointer.h | 87 +++++++++++ MMCore/BufferManager.cpp | 184 +++++++++++----------- MMCore/BufferManager.h | 46 +++++- MMCore/Buffer_v2.cpp | 115 ++++++++++---- MMCore/Buffer_v2.h | 62 +++++--- MMCore/CoreCallback.cpp | 51 +++++- MMCore/MMCore.cpp | 137 +++++++++------- MMCore/MMCore.h | 33 ++-- MMCore/MMCore.vcxproj | 5 +- MMCore/MMCore.vcxproj.filters | 9 +- MMCoreJ_wrap/MMCoreJ.i | 283 +++++----------------------------- 11 files changed, 532 insertions(+), 480 deletions(-) create mode 100644 MMCore/BufferDataPointer.h diff --git a/MMCore/BufferDataPointer.h b/MMCore/BufferDataPointer.h new file mode 100644 index 000000000..cdd35e345 --- /dev/null +++ b/MMCore/BufferDataPointer.h @@ -0,0 +1,87 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: DataPointer.h +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +//----------------------------------------------------------------------------- +// DESCRIPTION: A read-only wrapper class for accessing image data and metadata +// from a buffer slot. Provides safe access to image data by +// automatically releasing read access when the object is destroyed. +// Includes methods for retrieving pixel data and associated +// metadata.. +// +// COPYRIGHT: Henry Pinkard, 2025 +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Henry Pinkard, 2/16/2025 +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "MMCore.h" +#include "../MMDevice/ImageMetadata.h" +#include + +/// A read-only wrapper for accessing image data and metadata from a buffer slot. +/// Automatically releases the read access when destroyed. +class BufferDataPointer { + +public: + + BufferDataPointer(DataBuffer* bufferv2, const void* ptr) + : bufferv2_(bufferv2), ptr_(ptr) + { + } + + // Returns a pointer to the pixel data (read-only) + const void* getPixels() const { + if (!ptr_) { + return nullptr; + } + return ptr_; + } + + // Fills the provided Metadata object with metadata extracted from the pointer. + // It encapsulates calling the core API function that copies metadata from the buffer. + void getMetadata(Metadata &md) const { + if (bufferv2_ && ptr_) { + bufferv2_->ExtractCorrespondingMetadata(ptr_, md); + } + } + + // Explicitly release the pointer before destruction if needed + void release() { + std::lock_guard lock(mutex_); + if (bufferv2_ && ptr_) { + try { + bufferv2_->ReleaseDataReadPointer(ptr_); + ptr_ = nullptr; // Mark as released + } catch (...) { + // Release must not throw + } + } + } + + // Destructor: releases the read access to the pointer if not already released + ~BufferDataPointer() { + release(); + } + + // Disable copy semantics to avoid double releasing the pointer. + BufferDataPointer(const BufferDataPointer&) = delete; + BufferDataPointer& operator=(const BufferDataPointer&) = delete; + +private: + DataBuffer* bufferv2_; + const void* ptr_; + mutable std::mutex mutex_; +}; \ No newline at end of file diff --git a/MMCore/BufferManager.cpp b/MMCore/BufferManager.cpp index 8e4108020..ab173e21c 100644 --- a/MMCore/BufferManager.cpp +++ b/MMCore/BufferManager.cpp @@ -27,34 +27,6 @@ #include -static std::string FormatLocalTime(std::chrono::time_point tp) { - using namespace std::chrono; - auto us = duration_cast(tp.time_since_epoch()); - auto secs = duration_cast(us); - auto whole = duration_cast(secs); - auto frac = static_cast((us - whole).count()); - - // As of C++14/17, it is simpler (and probably faster) to use C functions for - // date-time formatting - - std::time_t t(secs.count()); // time_t is seconds on platforms we support - std::tm *ptm; -#ifdef _WIN32 // Windows localtime() is documented thread-safe - ptm = std::localtime(&t); -#else // POSIX has localtime_r() - std::tm tmstruct; - ptm = localtime_r(&t, &tmstruct); -#endif - - // Format as "yyyy-mm-dd hh:mm:ss.uuuuuu" (26 chars) - const char *timeFmt = "%Y-%m-%d %H:%M:%S"; - char buf[32]; - std::size_t len = std::strftime(buf, sizeof(buf), timeFmt, ptm); - std::snprintf(buf + len, sizeof(buf) - len, ".%06d", frac); - return buf; -} - - BufferManager::BufferManager(bool useV2Buffer, unsigned int memorySizeMB) : useV2_(useV2Buffer), circBuffer_(nullptr), v2Buffer_(nullptr) { @@ -78,7 +50,7 @@ BufferManager::~BufferManager() } } -const void* BufferManager::GetLastImage() const +const void* BufferManager::GetLastImage() { if (useV2_) { Metadata dummyMetadata; @@ -102,8 +74,6 @@ const void* BufferManager::PopNextImage() bool BufferManager::Initialize(unsigned numChannels, unsigned width, unsigned height, unsigned bytesPerPixel) { - startTime_ = std::chrono::steady_clock::now(); // Initialize start time - imageNumbers_.clear(); if (useV2_) { // This in not required for v2 buffer because it can interleave multiple data types/image sizes } else { @@ -138,8 +108,6 @@ void BufferManager::Clear() } else { circBuffer_->Clear(); } - // Reset image counters when buffer is cleared - imageNumbers_.clear(); } long BufferManager::GetSize(long imageSize) const @@ -227,32 +195,29 @@ bool BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned c if (useV2_) { // All the data needed to interpret the image is in the metadata // This function will copy data and metadata into the buffer - int ret = v2Buffer_->InsertData(buf, width * height * byteDepth *numChannels, &md); + int ret = v2Buffer_->InsertData(buf, width * height * byteDepth * numChannels, &md, callerLabel); return ret == DEVICE_OK; } else { return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md); - } + } +} + +const void* BufferManager::GetLastImageMD(Metadata& md) const throw (CMMError) +{ + return GetLastImageMD(0, md); } const void* BufferManager::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) { if (useV2_) { - // In v2, we now use a channel-aware pointer arithmetic at the manager level. - const void* basePtr = nullptr; - int ret = v2Buffer_->PeekNextDataReadPointer(&basePtr, md); - if (ret != DEVICE_OK || basePtr == nullptr) + if (channel != 0) { + throw CMMError("V2 buffer does not support channels.", MMERR_InvalidContents); + } + const void* basePtr = v2Buffer_->PeekLastDataReadPointer(md); + if (basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); - - // Calculate the size for one channel. - int width = GetImageWidth(basePtr); - int height = GetImageHeight(basePtr); - int pixDepth = GetBytesPerPixel(basePtr); - size_t singleChannelSize = width * height * pixDepth; - - // Advance the base pointer by the amount corresponding to the selected channel. - // NOTE: make sure calling code releases the slot after use. - return static_cast(basePtr) + (channel * singleChannelSize); + return basePtr; } else { const mm::ImgBuffer* pBuf = circBuffer_->GetTopImageBuffer(channel); if (pBuf != nullptr) { @@ -280,22 +245,21 @@ const void* BufferManager::GetNthImageMD(unsigned long n, Metadata& md) const th } } +const void* BufferManager::PopNextImageMD(Metadata& md) throw (CMMError) +{ + return PopNextImageMD(0, md); +} + const void* BufferManager::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError) { if (useV2_) { - // For v2, we now make the buffer channel aware at the manager level. + if (channel != 0) { + throw CMMError("V2 buffer does not support channels.", MMERR_InvalidContents); + } const void* basePtr = v2Buffer_->PopNextDataReadPointer(md, false); if (basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); - - // Calculate the size of a single channel. - int width = GetImageWidth(basePtr); - int height = GetImageHeight(basePtr); - int pixDepth = GetBytesPerPixel(basePtr); - size_t singleChannelSize = width * height * pixDepth; - - // Offset the base pointer to get the requested channel's data. - return static_cast(basePtr) + (channel * singleChannelSize); + return basePtr; } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNextImageBuffer(channel); if (pBuf != nullptr) { @@ -349,11 +313,14 @@ bool BufferManager::IsUsingV2Buffer() const { bool BufferManager::ReleaseReadAccess(const void* ptr) { if (useV2_ && ptr) { - return v2Buffer_->ReleaseDataReadPointer(ptr); + return v2Buffer_->ReleaseDataReadPointer(ptr) == DEVICE_OK; } - return true; + return false; } +// TODO: these methods each copy and create the metadata object. Since +// they are called together, this could be made more efficient by +// returning a struct with the metadata and the values. unsigned BufferManager::GetImageWidth(const void* ptr) const { if (!useV2_) throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); @@ -374,13 +341,7 @@ unsigned BufferManager::GetImageHeight(const void* ptr) const { return static_cast(atoi(sVal.c_str())); } -unsigned BufferManager::GetBytesPerPixel(const void* ptr) const { - if (!useV2_) - throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); - Metadata md; - if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) - throw CMMError("Failed to extract metadata for bytes per pixel"); - std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); +unsigned BufferManager::GetBytesPerPixelFromType(const std::string& pixelType) const { if (pixelType == MM::g_Keyword_PixelType_GRAY8) return 1; else if (pixelType == MM::g_Keyword_PixelType_GRAY16) @@ -393,23 +354,25 @@ unsigned BufferManager::GetBytesPerPixel(const void* ptr) const { throw CMMError("Unknown pixel type for bytes per pixel"); } -unsigned BufferManager::GetImageBitDepth(const void* ptr) const { +unsigned BufferManager::GetComponentsFromType(const std::string& pixelType) const { + if (pixelType == MM::g_Keyword_PixelType_GRAY8 || + pixelType == MM::g_Keyword_PixelType_GRAY16 || + pixelType == MM::g_Keyword_PixelType_GRAY32) + return 1; + else if (pixelType == MM::g_Keyword_PixelType_RGB32 || + pixelType == MM::g_Keyword_PixelType_RGB64) + return 4; + throw CMMError("Unknown pixel type for number of components"); +} + +unsigned BufferManager::GetBytesPerPixel(const void* ptr) const { if (!useV2_) - throw CMMError("GetImageBitDepth(ptr) only supported with V2 buffer"); + throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); Metadata md; if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) - throw CMMError("Failed to extract metadata for image bit depth"); + throw CMMError("Failed to extract metadata for bytes per pixel"); std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - if (pixelType == MM::g_Keyword_PixelType_GRAY8) - return 8; - else if (pixelType == MM::g_Keyword_PixelType_GRAY16) - return 16; - else if (pixelType == MM::g_Keyword_PixelType_GRAY32 || - pixelType == MM::g_Keyword_PixelType_RGB32) - return 32; - else if (pixelType == MM::g_Keyword_PixelType_RGB64) - return 64; - throw CMMError("Unknown pixel type for image bit depth"); + return GetBytesPerPixelFromType(pixelType); } unsigned BufferManager::GetNumberOfComponents(const void* ptr) const { @@ -419,27 +382,13 @@ unsigned BufferManager::GetNumberOfComponents(const void* ptr) const { if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) throw CMMError("Failed to extract metadata for number of components"); std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - if (pixelType == MM::g_Keyword_PixelType_GRAY8 || - pixelType == MM::g_Keyword_PixelType_GRAY16 || - pixelType == MM::g_Keyword_PixelType_GRAY32) - return 1; - else if (pixelType == MM::g_Keyword_PixelType_RGB32 || - pixelType == MM::g_Keyword_PixelType_RGB64) - return 4; - throw CMMError("Unknown pixel type for number of components"); + return GetComponentsFromType(pixelType); } long BufferManager::GetImageBufferSize(const void* ptr) const { if (!useV2_) throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); - Metadata md; - if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) - throw CMMError("Failed to extract metadata for image buffer size"); - // Suppose the image size is computed from width, height, and bytes per pixel: - unsigned width = static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue().c_str())); - unsigned height = static_cast(atoi(md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue().c_str())); - unsigned bpp = GetBytesPerPixel(ptr); - return static_cast(width * height * bpp); + return static_cast(v2Buffer_->GetDataSize(ptr)); } bool BufferManager::SetOverwriteData(bool overwrite) { @@ -469,7 +418,7 @@ bool BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, u std::string serializedMetadata = md.Serialize(); int ret = v2Buffer_->AcquireWriteSlot(dataSize, additionalMetadataSize, - dataPointer, additionalMetadataPointer, serializedMetadata); + dataPointer, additionalMetadataPointer, serializedMetadata, deviceLabel); return ret == DEVICE_OK; } @@ -498,3 +447,42 @@ void BufferManager::ExtractMetadata(const void* dataPtr, Metadata& md) const { throw CMMError("Failed to extract metadata"); } } + +const void* BufferManager::GetLastImageFromDevice(const std::string& deviceLabel) throw (CMMError) { + if (!useV2_) { + throw CMMError("V2 buffer must be enabled for device-specific image access"); + } + Metadata md; + return GetLastImageMDFromDevice(deviceLabel, md); +} + +const void* BufferManager::GetLastImageMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError) { + if (!useV2_) { + throw CMMError("V2 buffer must be enabled for device-specific image access"); + } + + const void* basePtr = v2Buffer_->PeekLastDataReadPointerFromDevice(deviceLabel, md); + if (basePtr == nullptr) { + throw CMMError("No image found for device: " + deviceLabel, MMERR_InvalidContents); + } + return basePtr; +} + +bool BufferManager::IsPointerInBuffer(const void* ptr) const throw (CMMError) { + if (!useV2_) { + throw CMMError("IsPointerInBuffer is only supported with V2 buffer enabled"); + } + + if (v2Buffer_ == nullptr) { + throw CMMError("V2 buffer is null"); + } + + return v2Buffer_->IsPointerInBuffer(ptr); +} + +DataBuffer* BufferManager::GetV2Buffer() const { + if (!useV2_) { + return nullptr; + } + return v2Buffer_; +} diff --git a/MMCore/BufferManager.h b/MMCore/BufferManager.h index d67e3e6ef..819325e87 100644 --- a/MMCore/BufferManager.h +++ b/MMCore/BufferManager.h @@ -58,7 +58,7 @@ class BufferManager { * Get a pointer to the top (most recent) image. * @return Pointer to image data, or nullptr if unavailable. */ - const void* GetLastImage() const; + const void* GetLastImage(); /** * Get a pointer to the next image from the buffer. @@ -172,10 +172,17 @@ class BufferManager { bool Overflow() const; - const void* GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError); + const void* GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError); + + // Channels are not directly supported in v2 buffer, these are for backwards compatibility + // with circular buffer + const void* GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError); const void* PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError); + const void* GetLastImageMD(Metadata& md) const throw (CMMError); + const void* PopNextImageMD(Metadata& md) throw (CMMError); + /** * Check if this manager is using the V2 buffer implementation. * @return true if using V2 buffer, false if using circular buffer. @@ -194,7 +201,6 @@ class BufferManager { unsigned GetImageWidth(const void* ptr) const; unsigned GetImageHeight(const void* ptr) const; unsigned GetBytesPerPixel(const void* ptr) const; - unsigned GetImageBitDepth(const void* ptr) const; unsigned GetNumberOfComponents(const void* ptr) const; long GetImageBufferSize(const void* ptr) const; @@ -248,10 +254,42 @@ class BufferManager { */ Metadata AddDeviceLabel(const char* deviceLabel, const Metadata* pMd); + /** + * Get the last image inserted by a specific device. + * @param deviceLabel The label of the device to get the image from. + * @return Pointer to the image data. + * @throws CMMError if no image is found or V2 buffer is not enabled. + */ + const void* GetLastImageFromDevice(const std::string& deviceLabel) throw (CMMError); + /** + * Get the last image and metadata inserted by a specific device. + * @param deviceLabel The label of the device to get the image from. + * @param md Metadata object to populate. + * @return Pointer to the image data. + * @throws CMMError if no image is found or V2 buffer is not enabled. + */ + const void* GetLastImageMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError); + + /** + * Check if a pointer is currently managed by the buffer. + * @param ptr The pointer to check. + * @return true if the pointer is in the buffer, false otherwise. + * @throws CMMError if V2 buffer is not enabled. + */ + bool IsPointerInBuffer(const void* ptr) const throw (CMMError); + + /** + * Get a pointer to the V2 buffer. + * @return Pointer to the V2 buffer, or nullptr if V2 buffer is not enabled. + */ + DataBuffer* GetV2Buffer() const; private: - bool useV2_; // if true use DataBuffer, otherwise use CircularBuffer. + unsigned GetBytesPerPixelFromType(const std::string& pixelType) const; + unsigned GetComponentsFromType(const std::string& pixelType) const; + + bool useV2_; CircularBuffer* circBuffer_; DataBuffer* v2Buffer_; diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 89afcea0d..7b046be99 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -192,7 +192,7 @@ int DataBuffer::ReleaseBuffer() { /** * Pack the data as [BufferSlotRecord][image data][serialized metadata] */ -int DataBuffer::InsertData(const void* data, size_t dataSize, const Metadata* pMd) { +int DataBuffer::InsertData(const void* data, size_t dataSize, const Metadata* pMd, const std::string& deviceLabel) { void* dataPointer = nullptr; void* additionalMetadataPointer = nullptr; @@ -203,7 +203,8 @@ int DataBuffer::InsertData(const void* data, size_t dataSize, const Metadata* pM serializedMetadata = pMd->Serialize(); } // Initial metadata is all metadata because the image and metadata are already complete - int result = AcquireWriteSlot(dataSize, 0, &dataPointer, &additionalMetadataPointer, serializedMetadata); + int result = AcquireWriteSlot(dataSize, 0, &dataPointer, &additionalMetadataPointer, + serializedMetadata, deviceLabel); if (result != DEVICE_OK) return result; @@ -241,7 +242,8 @@ int DataBuffer::SetOverwriteData(bool overwrite) { int DataBuffer::AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, void** dataPointer, void** additionalMetadataPointer, - const std::string& serializedInitialMetadata) + const std::string& serializedInitialMetadata, + const std::string& deviceLabel) { if (buffer_ == nullptr) { return DEVICE_ERR; @@ -277,7 +279,8 @@ int DataBuffer::AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, // Update the cursor so that next search can start here. freeRegionCursor_ = candidateStart + totalSlotSize; return CreateSlot(candidateStart, totalSlotSize, dataSize, additionalMetadataSize, - dataPointer, additionalMetadataPointer, true, serializedInitialMetadata); + dataPointer, additionalMetadataPointer, + true, serializedInitialMetadata, deviceLabel); } } @@ -301,7 +304,7 @@ int DataBuffer::AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, { std::lock_guard lock(slotManagementMutex_); return CreateSlot(candidateStart, totalSlotSize, dataSize, additionalMetadataSize, - dataPointer, additionalMetadataPointer, false, serializedInitialMetadata); + dataPointer, additionalMetadataPointer, false, serializedInitialMetadata, deviceLabel); } } } @@ -404,28 +407,28 @@ const void* DataBuffer::PopNextDataReadPointer(Metadata &md, bool waitForData) return dataPointer; } -int DataBuffer::PeekNextDataReadPointer(const void** dataPointer, Metadata &md) { - // Immediately check if there is an unread slot without waiting. +const void* DataBuffer::PeekLastDataReadPointer(Metadata &md) { BufferSlot* currentSlot = nullptr; { - // Lock the global slot management mutex to safely access the active slots. std::unique_lock lock(slotManagementMutex_); - if (activeSlotsVector_.empty() || currentSlotIndex_ >= activeSlotsVector_.size()) { - return DEVICE_ERR; // No unread data available. + if (activeSlotsVector_.empty()) { + return nullptr; } - currentSlot = activeSlotsVector_[currentSlotIndex_]; + + // Get the most recent slot (last in vector) + currentSlot = activeSlotsVector_.back(); } - // Now safely get read access without holding the global lock. currentSlot->AcquireReadAccess(); - - std::unique_lock lock(slotManagementMutex_); - *dataPointer = static_cast(buffer_) + currentSlot->GetStart(); - const unsigned char* metadataPtr = static_cast(*dataPointer) + currentSlot->GetDataSize(); - this->ExtractMetadata(metadataPtr, currentSlot, md); + const void* result = static_cast(buffer_) + currentSlot->GetStart(); - return DEVICE_OK; + if (ExtractMetadata(result, currentSlot, md) != DEVICE_OK) { + currentSlot->ReleaseReadAccess(); + return nullptr; + } + + return result; } const void* DataBuffer::PeekDataReadPointerAtIndex(size_t n, Metadata &md) { @@ -453,6 +456,37 @@ const void* DataBuffer::PeekDataReadPointerAtIndex(size_t n, Metadata &md) { return dataPointer; } +const void* DataBuffer::PeekLastDataReadPointerFromDevice(const std::string& deviceLabel, Metadata& md) { + BufferSlot* matchingSlot = nullptr; + { + std::unique_lock lock(slotManagementMutex_); + + // Search backwards through activeSlotsVector_ to find most recent matching slot + for (auto it = activeSlotsVector_.rbegin(); it != activeSlotsVector_.rend(); ++it) { + if ((*it)->GetDeviceLabel() == deviceLabel) { + matchingSlot = *it; + break; + } + } + + if (!matchingSlot) { + return nullptr; + } + } + + // Acquire read access and get data pointer + matchingSlot->AcquireReadAccess(); + + const void* result = static_cast(buffer_) + matchingSlot->GetStart(); + + if (ExtractMetadata(result, matchingSlot, md) != DEVICE_OK) { + matchingSlot->ReleaseReadAccess(); + return nullptr; + } + + return result; +} + unsigned int DataBuffer::GetMemorySizeMB() const { // Convert bytes to MB (1 MB = 1048576 bytes) return static_cast(bufferSize_ >> 20); @@ -635,15 +669,6 @@ void DataBuffer::DeleteSlot(size_t offset, std::unordered_mapAcquireWriteAccess(); @@ -704,7 +729,9 @@ int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, } BufferSlot* DataBuffer::GetSlotFromPool(size_t start, size_t totalLength, - size_t dataSize, size_t initialMetadataSize, size_t additionalMetadataSize) { + size_t dataSize, size_t initialMetadataSize, + size_t additionalMetadataSize, + const std::string& deviceLabel) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); // Grow the pool if needed. @@ -717,7 +744,7 @@ BufferSlot* DataBuffer::GetSlotFromPool(size_t start, size_t totalLength, // Get a slot from the front of the deque. BufferSlot* slot = unusedSlots_.front(); unusedSlots_.pop_front(); - slot->Reset(start, totalLength, dataSize, initialMetadataSize, additionalMetadataSize); + slot->Reset(start, totalLength, dataSize, initialMetadataSize, additionalMetadataSize, deviceLabel); // Add to active tracking. activeSlotsVector_.push_back(slot); @@ -747,4 +774,24 @@ int DataBuffer::ExtractCorrespondingMetadata(const void* dataPointer, Metadata & // Extract metadata (internal method doesn't need lock) return ExtractMetadata(dataPointer, slot, md); -} \ No newline at end of file +} + +size_t DataBuffer::GetDataSize(const void* dataPointer) { + std::lock_guard lock(slotManagementMutex_); + BufferSlot* slot = FindSlotForPointer(dataPointer); + if (!slot) { + return 0; + } + return slot->GetDataSize(); +} + +bool DataBuffer::IsPointerInBuffer(const void* ptr) { + if (buffer_ == nullptr || ptr == nullptr) { + return false; + } + // get the mutex + std::lock_guard lock(slotManagementMutex_); + // find the slot + BufferSlot* slot = FindSlotForPointer(ptr); + return slot != nullptr; +} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index cd3829b27..00ec66e85 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -62,7 +62,7 @@ class BufferSlot { * @param imageSize The exact number of bytes for the image data. * @param metadataSize The exact number of bytes for the metadata. */ - BufferSlot() : start_(0), length_(0), imageSize_(0), initialMetadataSize_(0), additionalMetadataSize_(0), rwMutex_() {} + BufferSlot() : start_(0), length_(0), imageSize_(0), initialMetadataSize_(0), additionalMetadataSize_(0), deviceLabel_(), rwMutex_() {} /** * Destructor. @@ -134,7 +134,8 @@ class BufferSlot { return false; } - void Reset(size_t start, size_t length, size_t imageSize, size_t initialMetadataSize, size_t additionalMetadataSize) { + void Reset(size_t start, size_t length, size_t imageSize, size_t initialMetadataSize, + size_t additionalMetadataSize, const std::string& deviceLabel) { // Assert that the mutex is available before recycling assert(IsAvailableForWriting() && IsAvailableForReading() && "BufferSlot mutex still locked during Reset - indicates a bug!"); @@ -145,6 +146,7 @@ class BufferSlot { imageSize_ = imageSize; initialMetadataSize_ = initialMetadataSize; additionalMetadataSize_ = initialMetadataSize + additionalMetadataSize; + deviceLabel_ = deviceLabel; // Reset the device label // The caller should explicitly acquire write access when needed } @@ -178,12 +180,15 @@ class BufferSlot { */ std::size_t GetAdditionalMetadataSize() const { return additionalMetadataSize_; } + const std::string& GetDeviceLabel() const { return deviceLabel_; } + private: std::size_t start_; std::size_t length_; size_t imageSize_; - size_t initialMetadataSize_ = 0; - size_t additionalMetadataSize_ = 0; + size_t initialMetadataSize_; + size_t additionalMetadataSize_; + std::string deviceLabel_; // New member mutable std::shared_timed_mutex rwMutex_; }; @@ -222,13 +227,13 @@ class DataBuffer { /** * Inserts image data along with metadata into the buffer. - * @param caller The calling device that is the source of the data. * @param data Pointer to the raw image data. * @param dataSize The image data size in bytes. * @param pMd Pointer to the metadata (can be null if not applicable). + * @param deviceLabel The label of the device that is the source of the data. * @return DEVICE_OK on success. */ - int InsertData(const void* data, size_t dataSize, const Metadata* pMd); + int InsertData(const void* data, size_t dataSize, const Metadata* pMd, const std::string& deviceLabel); /** * Sets whether the buffer should overwrite old data when full. @@ -251,7 +256,8 @@ class DataBuffer { int AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, void** dataPointer, void** additionalMetadataPointer, - const std::string& serializedInitialMetadata); + const std::string& serializedInitialMetadata, + const std::string& deviceLabel); /** * Finalizes (releases) a write slot after data has been written. @@ -280,12 +286,11 @@ class DataBuffer { const void* PopNextDataReadPointer(Metadata &md, bool waitForData); /** - * Peeks at the next unread data entry without consuming it. - * @param dataPointer On success, receives a pointer to the (usually image) data region. + * Peeks at the most recently added data entry. * @param md Metadata object populated from the stored metadata. - * @return DEVICE_OK on success. + * @return Pointer to the start of the data region. */ - int PeekNextDataReadPointer(const void** dataPointer, Metadata &md); + const void* PeekLastDataReadPointer(Metadata &md); /** * Peeks at the nth unread data entry without consuming it. @@ -297,11 +302,12 @@ class DataBuffer { const void* PeekDataReadPointerAtIndex(size_t n, Metadata &md); /** - * Releases read access that was acquired by a peek. - * @param dataPointer Pointer previously obtained from a peek. - * @return DEVICE_OK on success. + * Get the last image inserted by a specific device. + * @param deviceLabel The label of the device to get the image from. + * @param md Metadata object to populate. + * @return Pointer to the image data, or nullptr if not found. */ - int ReleasePeekDataReadPointer(const void** dataPointer); + const void* PeekLastDataReadPointerFromDevice(const std::string& deviceLabel, Metadata& md); /** * Returns the total buffer memory size (in MB). @@ -357,6 +363,20 @@ class DataBuffer { */ int ExtractCorrespondingMetadata(const void* dataPtr, Metadata &md); + /** + * Returns the size of the data portion of the slot in bytes. + * @param dataPointer Pointer to the data portion of a slot. + * @return Size in bytes of the data portion, or 0 if pointer is invalid. + */ + size_t GetDataSize(const void* dataPointer); + + /** + * Check if a pointer is within the buffer's memory range. + * @param ptr The pointer to check. + * @return true if the pointer is within the buffer, false otherwise. + */ + bool IsPointerInBuffer(const void* ptr); + private: /** * Internal helper function that finds the slot for a given pointer. @@ -419,7 +439,7 @@ class DataBuffer { BufferSlot* GetSlotFromPool(size_t start, size_t totalLength, size_t dataSize, size_t initialMetadataSize, - size_t additionalMetadataSize); + size_t additionalMetadataSize, const std::string& deviceLabel); /** * Creates a new slot with the specified parameters. @@ -430,14 +450,10 @@ class DataBuffer { void** dataPointer, void** subsequentMetadataPointer, bool fromFreeRegion, - const std::string& serializedInitialMetadata); + const std::string& serializedInitialMetadata, + const std::string& deviceLabel); + - /** - * Initializes a new slot with the given parameters. - * Caller must hold slotManagementMutex_. - */ - BufferSlot* InitializeNewSlot(size_t candidateStart, size_t totalSlotSize, - size_t dataSize, size_t metadataSize); void ReturnSlotToPool(BufferSlot* slot); int ExtractMetadata(const void* dataPointer, diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 5ab440c9c..ad395c571 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -41,6 +41,34 @@ #include +static std::string FormatLocalTime(std::chrono::time_point tp) { + using namespace std::chrono; + auto us = duration_cast(tp.time_since_epoch()); + auto secs = duration_cast(us); + auto whole = duration_cast(secs); + auto frac = static_cast((us - whole).count()); + + // As of C++14/17, it is simpler (and probably faster) to use C functions for + // date-time formatting + + std::time_t t(secs.count()); // time_t is seconds on platforms we support + std::tm *ptm; +#ifdef _WIN32 // Windows localtime() is documented thread-safe + ptm = std::localtime(&t); +#else // POSIX has localtime_r() + std::tm tmstruct; + ptm = localtime_r(&t, &tmstruct); +#endif + + // Format as "yyyy-mm-dd hh:mm:ss.uuuuuu" (26 chars) + const char *timeFmt = "%Y-%m-%d %H:%M:%S"; + char buf[32]; + std::size_t len = std::strftime(buf, sizeof(buf), timeFmt, ptm); + std::snprintf(buf + len, sizeof(buf) - len, ".%06d", frac); + return buf; +} + + CoreCallback::CoreCallback(CMMCore* c) : core_(c), pValueChangeLock_(NULL) @@ -298,7 +326,10 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf ip->Process(const_cast(buf), width, height, byteDepth); } } - if (core_->bufferManager_->InsertImage(caller, buf, width, height, byteDepth, &md)) + char labelBuffer[MM::MaxStrLength]; + caller->GetLabel(labelBuffer); + std::string callerLabel(labelBuffer); + if (core_->bufferManager_->InsertImage(callerLabel.c_str(), buf, width, height, byteDepth, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; @@ -330,7 +361,10 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf ip->Process(const_cast(buf), width, height, byteDepth); } } - if (core_->bufferManager_->InsertImage(caller, buf, width, height, byteDepth, nComponents, &md)) + char labelBuffer[MM::MaxStrLength]; + caller->GetLabel(labelBuffer); + std::string callerLabel(labelBuffer); + if (core_->bufferManager_->InsertImage(callerLabel.c_str(), buf, width, height, byteDepth, nComponents, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; @@ -351,6 +385,9 @@ int CoreCallback::InsertImage(const MM::Device* caller, const ImgBuffer & imgBuf ip->Process(p, imgBuf.Width(), imgBuf.Height(), imgBuf.Depth()); } + char labelBuffer[MM::MaxStrLength]; + caller->GetLabel(labelBuffer); + std::string callerLabel(labelBuffer); return InsertImage(caller, imgBuf.GetPixels(), imgBuf.Width(), imgBuf.Height(), imgBuf.Depth(), &md); } @@ -396,6 +433,8 @@ void CoreCallback::ClearImageBuffer(const MM::Device* /*caller*/) // This has no effect on v2 buffer, because devices do not have authority to clear the buffer // since higher level code may hold pointers to data in the buffer. core_->bufferManager_->Clear(); + // Reset image counters when buffer is cleared + imageNumbers_.clear(); } // Note: this not required and has not effect on v2 buffer @@ -406,6 +445,9 @@ bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, if (slices != 1) return false; + startTime_ = std::chrono::steady_clock::now(); // Initialize start time + imageNumbers_.clear(); + return core_->bufferManager_->Initialize(channels, w, h, pixDepth); } @@ -426,7 +468,10 @@ int CoreCallback::InsertMultiChannel(const MM::Device* caller, { ip->Process( const_cast(buf), width, height, byteDepth); } - if (core_->bufferManager_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md)) + char labelBuffer[MM::MaxStrLength]; + caller->GetLabel(labelBuffer); + std::string callerLabel(labelBuffer); + if (core_->bufferManager_->InsertMultiChannel(callerLabel.c_str(), buf, numChannels, width, height, byteDepth, &md)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index d8c0c38b5..e705688cd 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -54,6 +54,7 @@ #include "MMCore.h" #include "MMEventCallback.h" #include "PluginManager.h" +#include "BufferDataPointer.h" #include #include @@ -2795,7 +2796,7 @@ void* CMMCore::getImage(unsigned channelNr) throw (CMMError) * @return a pointer to the internal image buffer. * @throws CMMError when the camera returns no data */ -DataPtr CMMCore::getImagePointer() throw (CMMError) +BufferDataPointer* CMMCore::getImagePointer() throw (CMMError) { if (!useV2Buffer_ ) throw CMMError("Only valid when V2 buffer in use"); @@ -2827,32 +2828,35 @@ DataPtr CMMCore::getImagePointer() throw (CMMError) try { mm::DeviceModuleLockGuard guard(camera); pBuf = const_cast (camera->GetImageBuffer()); - - INSERT DATA HERE - + std::shared_ptr imageProcessor = currentImageProcessor_.lock(); if (imageProcessor) { imageProcessor->Process((unsigned char*)pBuf, camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel() ); } + + // Unlike the other getImage() methods, this one copies the image + // into the v2 buffer. This is faster than the copying that would otherwise + // occure in the SWIG wrapper (at least in Java) because it can use multi-threaded, + // low-level copying. + bufferManager_->InsertImage(camera->GetLabel().c_str(), (unsigned char*)pBuf, + camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel(), + camera->GetNumberOfComponents(), nullptr); + + // TODO: could add a check here that there there is one and only one image to pop + // because otherwise the user may be using this incorrectly. + const void* ptr = bufferManager_->PopNextImage(); + return new BufferDataPointer(bufferManager_->GetV2Buffer(), ptr); + } catch( CMMError& e){ throw e; } catch (...) { logError("CMMCore::getImage()", getCoreErrorText(MMERR_UnhandledException).c_str()); throw CMMError(getCoreErrorText(MMERR_UnhandledException).c_str(), MMERR_UnhandledException); } - - if (pBuf != 0) - return pBuf; - else - { - logError("CMMCore::getImage()", getCoreErrorText(MMERR_CameraBufferReadFailed).c_str()); - throw CMMError(getCoreErrorText(MMERR_CameraBufferReadFailed).c_str(), MMERR_CameraBufferReadFailed); - } - } } - +} /** * Returns the size of the internal image buffer. @@ -3255,70 +3259,55 @@ void* CMMCore::popNextImageMD(Metadata& md) throw (CMMError) return popNextImageMD(0, 0, md); } -DataPtr CMMCore::getLastImagePointer() throw (CMMError) { - if (!useV2Buffer_) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); - } - return getLastImage(); -} - -DataPtr CMMCore::popNextImagePointer() throw (CMMError) { - if (!useV2Buffer_) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); - } - return popNextImage(); -} - -DataPtr CMMCore::getLastImageMDPointer(unsigned channel, unsigned slice, Metadata& md) const throw (CMMError) { +//// Data pointer access for v2 Buffer +BufferDataPointer* CMMCore::getLastImagePointer() throw (CMMError) { if (!useV2Buffer_) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - return getLastImageMD(channel, slice, md); + const void* rawPtr = bufferManager_->GetLastImage(); + return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); } -DataPtr CMMCore::popNextImageMDPointer(unsigned channel, unsigned slice, Metadata& md) throw (CMMError) { +BufferDataPointer* CMMCore::popNextImagePointer() throw (CMMError) { if (!useV2Buffer_) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - return popNextImageMD(channel, slice, md); + const void* rawPtr = bufferManager_->PopNextImage(); + return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); } -DataPtr CMMCore::getLastImageMDPointer(Metadata& md) const throw (CMMError) { +BufferDataPointer* CMMCore::getLastImageMDPointer(Metadata& md) const throw (CMMError) { if (!useV2Buffer_) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - return getLastImageMD(md); + const void* rawPtr = bufferManager_->GetLastImageMD(md); + return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); } -DataPtr CMMCore::getNBeforeLastImageMDPointer(unsigned long n, Metadata& md) const throw (CMMError) { +BufferDataPointer* CMMCore::popNextImageMDPointer(Metadata& md) throw (CMMError) { if (!useV2Buffer_) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - return getNBeforeLastImageMD(n, md); + const void* rawPtr = bufferManager_->PopNextImageMD(md); + return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); } -DataPtr CMMCore::popNextImageMDPointer(Metadata& md) throw (CMMError) { +BufferDataPointer* CMMCore::getLastImageFromDevicePointer(std::string deviceLabel) throw (CMMError) { if (!useV2Buffer_) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - return popNextImageMD(md); + const void* rawPtr = bufferManager_->GetLastImageFromDevice(deviceLabel); + return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); } -void* CMMCore::copyDataAtPointer(DataPtr ptr) throw (CMMError) { - if (!useV2Buffer_) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); - } - // This just checks the buffer and returns the pointer. Since the return type is void*, - // SWIG will automatically copy the pointer into a Java array. - return (void*) ptr; +BufferDataPointer* CMMCore::getLastImageMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError) { + if (!useV2Buffer_) { + throw CMMError("V2 buffer must be enabled for pointer-based image access"); + } + const void* rawPtr = bufferManager_->GetLastImageMDFromDevice(deviceLabel, md); + return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); } -void CMMCore::copyMetadataAtPointer(DataPtr ptr, Metadata& md) throw (CMMError) { - if (!useV2Buffer_) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); - } - bufferManager_->ExtractMetadata(ptr, md); -} /** * Removes all images from the circular buffer. * @@ -4407,28 +4396,60 @@ unsigned CMMCore::getNumberOfComponents() return 0; } +// For the below functions that get properties of the image based on a pointer, +// Cant assume that this a v2 buffer pointer, because Java SWIG wrapper +// will call this after copying from a snap buffer. So we'll check if the +// buffer knows about this pointer. If not, it's a snap buffer pointer. +// We don't want want to compare to the snap buffer pointer directly because +// its unclear what the device adapter might do when this is called. unsigned CMMCore::getImageWidth(DataPtr ptr) { - if (ptr == getImage()) { - // This is refering to the snap image + if (!useV2Buffer_) { return getImageWidth(); } - return useV2Buffer_ ? bufferManager_->GetImageWidth(ptr) : getImageWidth(); + if (bufferManager_->IsPointerInBuffer(ptr)) { + return bufferManager_->GetImageWidth(ptr); + } + return getImageWidth(); } unsigned CMMCore::getImageHeight(DataPtr ptr) { - return useV2Buffer_ ? bufferManager_->GetImageHeight(ptr) : getImageHeight(); + if (!useV2Buffer_) { + return getImageWidth(); + } + if (bufferManager_->IsPointerInBuffer(ptr)) { + return bufferManager_->GetImageHeight(ptr); + } + return getImageHeight(); } unsigned CMMCore::getBytesPerPixel(DataPtr ptr) { - return useV2Buffer_ ? bufferManager_->GetBytesPerPixel(ptr) : getBytesPerPixel(); + if (!useV2Buffer_) { + return getBytesPerPixel(); + } + if (bufferManager_->IsPointerInBuffer(ptr)) { + return bufferManager_->GetBytesPerPixel(ptr); + } + return getBytesPerPixel(); } unsigned CMMCore::getNumberOfComponents(DataPtr ptr) { - return useV2Buffer_ ? bufferManager_->GetNumberOfComponents(ptr) : getNumberOfComponents(); + if (!useV2Buffer_) { + return getNumberOfComponents(); + } + if (bufferManager_->IsPointerInBuffer(ptr)) { + return bufferManager_->GetNumberOfComponents(ptr); + } + return getNumberOfComponents(); } long CMMCore::getImageBufferSize(DataPtr ptr) { - return useV2Buffer_ ? bufferManager_->GetImageBufferSize(ptr) : getImageBufferSize(); + if (!useV2Buffer_) { + return getImageWidth() * getImageHeight() * getBytesPerPixel(); + } + if (bufferManager_->IsPointerInBuffer(ptr)) { + return bufferManager_->GetImageBufferSize(ptr); + } + return getImageBufferSize(); } void CMMCore::releaseReadAccess(DataPtr ptr) { diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index d37662ecd..9550092c7 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -95,6 +95,7 @@ class CorePropertyCollection; class MMEventCallback; class Metadata; class PixelSizeConfigGroup; +class BufferDataPointer; class AutoFocusInstance; class CameraInstance; @@ -383,7 +384,6 @@ class CMMCore void snapImage() throw (CMMError); void* getImage() throw (CMMError); void* getImage(unsigned numChannel) throw (CMMError); - DataPtr getImagePointer() throw (CMMError); unsigned getImageWidth(); @@ -425,18 +425,6 @@ class CMMCore const throw (CMMError); void* popNextImageMD(Metadata& md) throw (CMMError); - // Same functionality as above but, enables alternative wrappers in SWIG for - // pointer-based access to the image data. - DataPtr getLastImagePointer() throw (CMMError); - DataPtr popNextImagePointer() throw (CMMError); - DataPtr getLastImageMDPointer(unsigned channel, unsigned slice, Metadata& md) const throw (CMMError); - DataPtr popNextImageMDPointer(unsigned channel, unsigned slice, Metadata& md) throw (CMMError); - DataPtr getLastImageMDPointer(Metadata& md) const throw (CMMError); - DataPtr getNBeforeLastImageMDPointer(unsigned long n, Metadata& md) const throw (CMMError); - DataPtr popNextImageMDPointer(Metadata& md) throw (CMMError); - - void* copyDataAtPointer(DataPtr ptr) throw (CMMError); - void copyMetadataAtPointer(DataPtr ptr, Metadata& md) throw (CMMError); long getRemainingImageCount(); long getBufferTotalCapacity(); @@ -454,13 +442,32 @@ class CMMCore void enableV2Buffer(bool enable) throw (CMMError); bool usesV2Buffer() const { return useV2Buffer_; } + // These functions are used by the Java SWIG wrapper to get properties of the image + // based on a pointer. The DataPtr alias to void* is so they don't get converted to + // Object in the Java SWIG wrapper. unsigned getImageWidth(DataPtr ptr) throw (CMMError); unsigned getImageHeight(DataPtr ptr) throw (CMMError); unsigned getBytesPerPixel(DataPtr ptr) throw (CMMError); unsigned getNumberOfComponents(DataPtr ptr) throw (CMMError); long getImageBufferSize(DataPtr ptr) throw (CMMError); + void releaseReadAccess(DataPtr ptr) throw (CMMError); + // Same functionality as non pointer versions above, but + // enables alternative wrappers in SWIG for pointer-based access to the image data. + BufferDataPointer* getImagePointer() throw (CMMError); + + BufferDataPointer* getLastImagePointer() throw (CMMError); + BufferDataPointer* popNextImagePointer() throw (CMMError); + BufferDataPointer* getLastImageMDPointer(Metadata& md) const throw (CMMError); + BufferDataPointer* popNextImageMDPointer(Metadata& md) throw (CMMError); + BufferDataPointer* getLastImageFromDevicePointer(std::string deviceLabel) throw (CMMError); + BufferDataPointer* getLastImageMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError); + + ///@} + + /** \name Exposure sequence control. */ + ///@{ bool isExposureSequenceable(const char* cameraLabel) throw (CMMError); void startExposureSequence(const char* cameraLabel) throw (CMMError); void stopExposureSequence(const char* cameraLabel) throw (CMMError); diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index cefe0ccd2..4e7583a17 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -75,7 +75,7 @@ - + @@ -115,7 +115,8 @@ - + + diff --git a/MMCore/MMCore.vcxproj.filters b/MMCore/MMCore.vcxproj.filters index 77ca63b99..bb7c9cece 100644 --- a/MMCore/MMCore.vcxproj.filters +++ b/MMCore/MMCore.vcxproj.filters @@ -141,10 +141,10 @@ Source Files - + Source Files - + Source Files @@ -314,7 +314,10 @@ Header Files - + + Header Files + + Header Files diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index 1d3b3103f..6018154ff 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -427,10 +427,9 @@ // copy pixels from the image buffer JCALL4(SetByteArrayRegion, jenv, data, 0, lSize, (jbyte*)result); - // Release the read access (required for V2 buffer, Core will figure out if it's V2 or not) + // Release the read access if not v2 buffer, this will be ignored in the core (arg1)->releaseReadAccess((void*)result); - $result = data; } else if (bytesPerPixel == 2) @@ -535,129 +534,6 @@ } } -%typemap(jni) SnapBufferPtr "jobject" -%typemap(jtype) SnapBufferPtr "Object" -%typemap(jstype) SnapBufferPtr "Object" -%typemap(javaout) SnapBufferPtr { - return $jnicall; -} -%typemap(out) SnapBufferPtr -{ - long lSize = (arg1)->getImageWidth() * - (arg1)->getImageHeight(); - - unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); - unsigned numComponents = (arg1)->getNumberOfComponents(); - - if (bytesPerPixel == 1) - { - // create a new byte[] object in Java - jbyteArray data = JCALL1(NewByteArray, jenv, lSize); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - - $result = 0; - return $result; - } - - // copy pixels from the image buffer - JCALL4(SetByteArrayRegion, jenv, data, 0, lSize, (jbyte*)result); - - - $result = data; - } - else if (bytesPerPixel == 2) - { - // create a new short[] object in Java - jshortArray data = JCALL1(NewShortArray, jenv, lSize); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - $result = 0; - return $result; - } - - // copy pixels from the image buffer - JCALL4(SetShortArrayRegion, jenv, data, 0, lSize, (jshort*)result); - - - $result = data; - } - else if (bytesPerPixel == 4) - { - if (numComponents == 1) - { - // create a new float[] object in Java - - jfloatArray data = JCALL1(NewFloatArray, jenv, lSize); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - - $result = 0; - return $result; - } - - // copy pixels from the image buffer - JCALL4(SetFloatArrayRegion, jenv, data, 0, lSize, (jfloat*)result); - - - $result = data; - } - else - { - // create a new byte[] object in Java - jbyteArray data = JCALL1(NewByteArray, jenv, lSize * 4); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - - $result = 0; - return $result; - } - - // copy pixels from the image buffer - JCALL4(SetByteArrayRegion, jenv, data, 0, lSize * 4, (jbyte*)result); - - $result = data; - } - } - else if (bytesPerPixel == 8) - { - // create a new short[] object in Java - jshortArray data = JCALL1(NewShortArray, jenv, lSize * 4); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - $result = 0; - return $result; - } - - // copy pixels from the image buffer - JCALL4(SetShortArrayRegion, jenv, data, 0, lSize * 4, (jshort*)result); - - $result = data; - } - - else - { - // don't know how to map - // TODO: throw exception? - $result = 0; - } -} - // Define typemaps for DataPtr %typemap(jni) DataPtr "jlong" @@ -807,26 +683,26 @@ } private String getMultiCameraChannel(JSONObject tags, int cameraChannelIndex) { - try { - String camera = tags.getString("Core-Camera"); - String physCamKey = camera + "-Physical Camera " + (1 + cameraChannelIndex); - if (tags.has(physCamKey)) { - try { - return tags.getString(physCamKey); - } catch (Exception e2) { - return null; - } - } else { - return null; - } - } catch (Exception e) { - return null; - } + try { + String camera = tags.getString("Core-Camera"); + String physCamKey = camera + "-Physical Camera " + (1 + cameraChannelIndex); + if (tags.has(physCamKey)) { + try { + return tags.getString(physCamKey); + } catch (Exception e2) { + return null; + } + } else { + return null; + } + } catch (Exception e) { + return null; + } } - private TaggedImage createTaggedImage(Object pixelsOrPtr, Metadata md, int cameraChannelIndex, boolean fromSnapImage) throws java.lang.Exception { - TaggedImage image = createTaggedImage(pixelsOrPtr, md, fromSnapImage); + private TaggedImage createTaggedImage(Object pixels, Metadata md, int cameraChannelIndex) throws java.lang.Exception { + TaggedImage image = createTaggedImage(pixels, md); JSONObject tags = image.tags; if (!tags.has("CameraChannelIndex")) { @@ -843,7 +719,7 @@ return image; } - private TaggedImage createTaggedImage(Object pixelsOrPtr, Metadata md, boolean fromSnapImage) throws java.lang.Exception { + private TaggedImage createTaggedImage(Object pixels, Metadata md) throws java.lang.Exception { JSONObject tags = metadataToMap(md); PropertySetting setting; if (includeSystemStateCache_) { @@ -855,35 +731,13 @@ tags.put(key, value); } } - - // The Camera tag should have been added in CoreCallback::InsertImage - // We need to check it when getting bitDepth and ROI, because the v2 buffer - // supports multiple cameras with different settings inserting images at once - String cameraLabel = fromSnapImage ? getCameraDevice() : tags.getString("Camera"); - tags.put("BitDepth", getImageBitDepth(cameraLabel)); - tags.put("ROI", getROITag(cameraLabel)); - - // TODO: unclear if pixelSize and Affine are can be accurate if images coming - // from different cameras when using the v2 buffer + tags.put("BitDepth", getImageBitDepth()); tags.put("PixelSizeUm", getPixelSizeUm(true)); tags.put("PixelSizeAffine", getPixelSizeAffineAsString()); - if (!fromSnapImage && pixelsOrPtr instanceof Long) { - long ptr = (Long) pixelsOrPtr; - int depth = (int) getBytesPerPixel(ptr); - int numComponents = (int) getNumberOfComponents(ptr); - tags.put("Width", getImageWidth(ptr)); - tags.put("Height", getImageHeight(ptr)); - tags.put("PixelType", getPixelType(depth, numComponents)); - } else { - // using snap or the not the v2 buffer - int depth = (int) getBytesPerPixel(); - int numComponents = (int) getNumberOfComponents(); - tags.put("Width", getImageWidth()); - tags.put("Height", getImageHeight()); - tags.put("PixelType", getPixelType(depth, numComponents)); - } - - // This seems like it doesn't belong here, but unclear what it would break if removed + tags.put("ROI", getROITag()); + tags.put("Width", getImageWidth()); + tags.put("Height", getImageHeight()); + tags.put("PixelType", getPixelType()); tags.put("Frame", 0); tags.put("FrameIndex", 0); tags.put("Position", "Default"); @@ -899,106 +753,51 @@ try { - // "Camera" gets added in CoreCallback::InsertImage - tags.put("Binning", getProperty(cameraLabel, "Binning")); - } catch (java.lang.Exception ex) {} + tags.put("Binning", getProperty(getCameraDevice(), "Binning")); + } catch (Exception ex) {} - // Copy the pixels out using the pointer (this will release the pointer) - if (pixelsOrPtr instanceof Long) { - // This is a pointer to an image in the v2 buffer - return new TaggedImagePointer((Long) pixelsOrPtr, this); - } else { - return new TaggedImage(pixelsOrPtr, tags); - } + return new TaggedImage(pixels, tags); } - //////// Snap Image versions - // This is for getting a snapped image. Snap image does not interact with v2 buffer. - // currently, so trying to release its pointer is undefined and not needed + // Snap image functions public TaggedImage getTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); Object pixels = getImage(cameraChannelIndex); - return createTaggedImage(pixels, md, cameraChannelIndex, true); + return createTaggedImage(pixels, md, cameraChannelIndex); } - // See comment for above function public TaggedImage getTaggedImage() throws java.lang.Exception { return getTaggedImage(0); } - /////// Data Buffer versions with pointer control - public TaggedImage getLastTaggedImage(int cameraChannelIndex, boolean usePointer) throws java.lang.Exception { - if (usePointer && !this.usesV2Buffer()) { - throw new Exception("Pointer mode is only supported when V2 buffer is enabled"); - } - Metadata md = new Metadata(); - Object pixelsOrPtr; - if (!usePointer) { - pixelsOrPtr = getLastImageMD(cameraChannelIndex, 0, md); - } else { - pixelsOrPtr = getLastImageMDPointer(cameraChannelIndex, 0, md); - } - return createTaggedImage(pixelsOrPtr, md, cameraChannelIndex, false); - } - - public TaggedImage getLastTaggedImage(boolean usePointer) throws java.lang.Exception { - return getLastTaggedImage(0, usePointer); - } - - // Existing methods now default to checking v2Buffer + // sequence acq functions public TaggedImage getLastTaggedImage(int cameraChannelIndex) throws java.lang.Exception { - return getLastTaggedImage(cameraChannelIndex, false); + Metadata md = new Metadata(); + Object pixels = getLastImageMD(cameraChannelIndex, 0, md); + return createTaggedImage(pixels, md, cameraChannelIndex); } public TaggedImage getLastTaggedImage() throws java.lang.Exception { - return getLastTaggedImage(0, false); - } - - public TaggedImage getNBeforeLastTaggedImage(long n, boolean usePointer) throws java.lang.Exception { - if (usePointer && !this.usesV2Buffer()) { - throw new Exception("Pointer mode is only supported when V2 buffer is enabled"); - } - Metadata md = new Metadata(); - Object pixelsOrPtr; - if (!usePointer) { - pixelsOrPtr = getNBeforeLastImageMD(n, md); - } else { - pixelsOrPtr = getNBeforeLastImageMDPointer(n, md); - } - return createTaggedImage(pixelsOrPtr, md, false); + return getLastTaggedImage(0); } public TaggedImage getNBeforeLastTaggedImage(long n) throws java.lang.Exception { - return getNBeforeLastTaggedImage(n, false); - } - - public TaggedImage popNextTaggedImage(int cameraChannelIndex, boolean usePointer) throws java.lang.Exception { - if (usePointer && !this.usesV2Buffer()) { - throw new Exception("Pointer mode is only supported when V2 buffer is enabled"); - } Metadata md = new Metadata(); - Object pixelsOrPtr; - if (!usePointer) { - pixelsOrPtr = popNextImageMD(cameraChannelIndex, 0, md); - } else { - pixelsOrPtr = popNextImageMDPointer(cameraChannelIndex, 0, md); - } - return createTaggedImage(pixelsOrPtr, md, cameraChannelIndex, false); + Object pixels = getNBeforeLastImageMD(n, md); + return createTaggedImage(pixels, md); } - public TaggedImage popNextTaggedImage(boolean usePointer) throws java.lang.Exception { - return popNextTaggedImage(0, usePointer); - } - - // Existing methods now default to checking v2Buffer public TaggedImage popNextTaggedImage(int cameraChannelIndex) throws java.lang.Exception { - return popNextTaggedImage(cameraChannelIndex, false); + Metadata md = new Metadata(); + Object pixels = popNextImageMD(cameraChannelIndex, 0, md); + return createTaggedImage(pixels, md, cameraChannelIndex); } public TaggedImage popNextTaggedImage() throws java.lang.Exception { - return popNextTaggedImage(0, false); + return popNextTaggedImage(0); } + // convenience functions follow /* From 6b2fd6d2d84f6c543e6c49c06e036e62a06e4240 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Mon, 17 Feb 2025 21:03:26 -0800 Subject: [PATCH 28/46] Working bufferdatapointer class and snapimage --- MMCore/BufferDataPointer.h | 73 ++++++++-- MMCore/BufferManager.cpp | 16 +- MMCore/BufferManager.h | 7 +- MMCore/Buffer_v2.cpp | 4 +- MMCore/Buffer_v2.h | 2 +- MMCore/CircularBuffer.cpp | 2 +- MMCore/CircularBuffer.h | 5 + MMCore/MMCore.cpp | 53 ++++--- MMCore/MMCore.h | 5 +- MMCoreJ_wrap/MMCoreJ.i | 291 +++++++++++++++++++++++++++++++++---- 10 files changed, 361 insertions(+), 97 deletions(-) diff --git a/MMCore/BufferDataPointer.h b/MMCore/BufferDataPointer.h index cdd35e345..5c06e6b01 100644 --- a/MMCore/BufferDataPointer.h +++ b/MMCore/BufferDataPointer.h @@ -31,19 +31,27 @@ #include "../MMDevice/ImageMetadata.h" #include +// This is needed for SWIG Java wrapping to differentiate its void* +// from the void* that MMCore uses for returning data +typedef const void* BufferDataPointerVoidStar; + /// A read-only wrapper for accessing image data and metadata from a buffer slot. /// Automatically releases the read access when destroyed. class BufferDataPointer { public: - BufferDataPointer(DataBuffer* bufferv2, const void* ptr) - : bufferv2_(bufferv2), ptr_(ptr) + BufferDataPointer(BufferManager* bufferManager, DataPtr ptr) + : bufferManager_(bufferManager), ptr_(ptr) { + // check for v2 buffer use + if (!bufferManager_->IsUsingV2Buffer()) { + throw CMMError("V2 buffer must be enabled for BufferDataPointer"); + } } // Returns a pointer to the pixel data (read-only) - const void* getPixels() const { + BufferDataPointerVoidStar getPixels() const { if (!ptr_) { return nullptr; } @@ -53,17 +61,23 @@ class BufferDataPointer { // Fills the provided Metadata object with metadata extracted from the pointer. // It encapsulates calling the core API function that copies metadata from the buffer. void getMetadata(Metadata &md) const { - if (bufferv2_ && ptr_) { - bufferv2_->ExtractCorrespondingMetadata(ptr_, md); + if (bufferManager_ && ptr_) { + bufferManager_->ExtractMetadata(ptr_, md); } } + // Destructor: releases the read access to the pointer if not already released + ~BufferDataPointer() { + release(); + } + + // Explicitly release the pointer before destruction if needed void release() { std::lock_guard lock(mutex_); - if (bufferv2_ && ptr_) { + if (bufferManager_ && ptr_) { try { - bufferv2_->ReleaseDataReadPointer(ptr_); + bufferManager_->ReleaseReadAccess(ptr_); ptr_ = nullptr; // Mark as released } catch (...) { // Release must not throw @@ -71,17 +85,48 @@ class BufferDataPointer { } } - // Destructor: releases the read access to the pointer if not already released - ~BufferDataPointer() { - release(); + // TODO if an when these are needed, make them a call a single functions that reads width and height together + // unsigned getImageWidth() const { + // if (bufferManager_ && ptr_) { + // return bufferManager_->GetImageWidth(ptr_); + // } + // return 0; + // } + + // unsigned getImageHeight() const { + // if (bufferManager_ && ptr_) { + // return bufferManager_->GetImageHeight(ptr_); + // } + // return 0; + // } + + unsigned getBytesPerPixel() const { + if (bufferManager_ && ptr_) { + return bufferManager_->GetBytesPerPixel(ptr_); + } + return 0; } - // Disable copy semantics to avoid double releasing the pointer. - BufferDataPointer(const BufferDataPointer&) = delete; - BufferDataPointer& operator=(const BufferDataPointer&) = delete; + unsigned getNumberOfComponents() const { + if (bufferManager_ && ptr_) { + return bufferManager_->GetNumberOfComponents(ptr_); + } + return 0; + } + + unsigned getSizeBytes() const { + if (bufferManager_ && ptr_) { + return bufferManager_->GetDatumSize(ptr_); + } + return 0; + } private: - DataBuffer* bufferv2_; + // Disable copy semantics to avoid double releasing the pointer + BufferDataPointer(const BufferDataPointer&); + BufferDataPointer& operator=(const BufferDataPointer&); + + BufferManager* bufferManager_; const void* ptr_; mutable std::mutex mutex_; }; \ No newline at end of file diff --git a/MMCore/BufferManager.cpp b/MMCore/BufferManager.cpp index ab173e21c..943fc58fa 100644 --- a/MMCore/BufferManager.cpp +++ b/MMCore/BufferManager.cpp @@ -318,9 +318,6 @@ bool BufferManager::ReleaseReadAccess(const void* ptr) { return false; } -// TODO: these methods each copy and create the metadata object. Since -// they are called together, this could be made more efficient by -// returning a struct with the metadata and the values. unsigned BufferManager::GetImageWidth(const void* ptr) const { if (!useV2_) throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); @@ -385,10 +382,11 @@ unsigned BufferManager::GetNumberOfComponents(const void* ptr) const { return GetComponentsFromType(pixelType); } -long BufferManager::GetImageBufferSize(const void* ptr) const { +unsigned BufferManager::GetDatumSize(const void* ptr) const { if (!useV2_) - throw CMMError("GetImageBufferSize(ptr) only supported with V2 buffer"); - return static_cast(v2Buffer_->GetDataSize(ptr)); + return circBuffer_->GetImageSizeBytes(); + else + return static_cast(v2Buffer_->GetDatumSize(ptr)); } bool BufferManager::SetOverwriteData(bool overwrite) { @@ -480,9 +478,3 @@ bool BufferManager::IsPointerInBuffer(const void* ptr) const throw (CMMError) { return v2Buffer_->IsPointerInBuffer(ptr); } -DataBuffer* BufferManager::GetV2Buffer() const { - if (!useV2_) { - return nullptr; - } - return v2Buffer_; -} diff --git a/MMCore/BufferManager.h b/MMCore/BufferManager.h index 819325e87..4367b6043 100644 --- a/MMCore/BufferManager.h +++ b/MMCore/BufferManager.h @@ -202,7 +202,7 @@ class BufferManager { unsigned GetImageHeight(const void* ptr) const; unsigned GetBytesPerPixel(const void* ptr) const; unsigned GetNumberOfComponents(const void* ptr) const; - long GetImageBufferSize(const void* ptr) const; + unsigned GetDatumSize(const void* ptr) const; /** * Configure whether to overwrite old data when buffer is full. @@ -279,11 +279,6 @@ class BufferManager { */ bool IsPointerInBuffer(const void* ptr) const throw (CMMError); - /** - * Get a pointer to the V2 buffer. - * @return Pointer to the V2 buffer, or nullptr if V2 buffer is not enabled. - */ - DataBuffer* GetV2Buffer() const; private: unsigned GetBytesPerPixelFromType(const std::string& pixelType) const; diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 7b046be99..b87a53688 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -776,11 +776,11 @@ int DataBuffer::ExtractCorrespondingMetadata(const void* dataPointer, Metadata & return ExtractMetadata(dataPointer, slot, md); } -size_t DataBuffer::GetDataSize(const void* dataPointer) { +size_t DataBuffer::GetDatumSize(const void* dataPointer) { std::lock_guard lock(slotManagementMutex_); BufferSlot* slot = FindSlotForPointer(dataPointer); if (!slot) { - return 0; + throw std::runtime_error("DataBuffer::GetDatumSize: pointer not found in buffer"); } return slot->GetDataSize(); } diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 00ec66e85..30443dc26 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -368,7 +368,7 @@ class DataBuffer { * @param dataPointer Pointer to the data portion of a slot. * @return Size in bytes of the data portion, or 0 if pointer is invalid. */ - size_t GetDataSize(const void* dataPointer); + size_t GetDatumSize(const void* dataPointer); /** * Check if a pointer is within the buffer's memory range. diff --git a/MMCore/CircularBuffer.cpp b/MMCore/CircularBuffer.cpp index c30a1b58e..19cc79662 100644 --- a/MMCore/CircularBuffer.cpp +++ b/MMCore/CircularBuffer.cpp @@ -96,7 +96,7 @@ bool CircularBuffer::Initialize(unsigned channels, unsigned int w, unsigned int // calculate the size of the entire buffer array once all images get allocated // the actual size at the time of the creation is going to be less, because // images are not allocated until pixels become available - unsigned long frameSizeBytes = width_ * height_ * pixDepth_ * numChannels_; + unsigned long frameSizeBytes = GetImageSizeBytes(); unsigned long cbSize = (unsigned long) ((memorySizeMB_ * bytesInMB) / frameSizeBytes); if (cbSize == 0) diff --git a/MMCore/CircularBuffer.h b/MMCore/CircularBuffer.h index c3d7b86b0..c412711c9 100644 --- a/MMCore/CircularBuffer.h +++ b/MMCore/CircularBuffer.h @@ -77,6 +77,11 @@ class CircularBuffer bool Overflow() {MMThreadGuard guard(g_bufferLock); return overflow_;} + unsigned GetImageSizeBytes() const { + MMThreadGuard guard(g_bufferLock); + return width_ * height_ * pixDepth_ * numChannels_; + } + mutable MMThreadLock g_bufferLock; mutable MMThreadLock g_insertLock; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index e705688cd..44c8877a6 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -141,8 +141,7 @@ CMMCore::CMMCore() : bufferManager_(nullptr), pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), - pPostedErrorsLock_(NULL), - useV2Buffer_(true) + pPostedErrorsLock_(NULL) { configGroups_ = new ConfigGroupCollection(); pixelSizeGroup_ = new PixelSizeConfigGroup(); @@ -153,7 +152,8 @@ CMMCore::CMMCore() : callback_ = new CoreCallback(this); const unsigned seqBufMegabytes = (sizeof(void*) > 4) ? 250 : 25; - bufferManager_ = new BufferManager(useV2Buffer_, seqBufMegabytes); + // TODO: change to v1 buffer before PR + bufferManager_ = new BufferManager(true, seqBufMegabytes); // Default to v2 buffer nullAffine_ = new std::vector(6); for (int i = 0; i < 6; i++) { @@ -2798,7 +2798,7 @@ void* CMMCore::getImage(unsigned channelNr) throw (CMMError) */ BufferDataPointer* CMMCore::getImagePointer() throw (CMMError) { - if (!useV2Buffer_ ) + if (!bufferManager_->IsUsingV2Buffer() ) throw CMMError("Only valid when V2 buffer in use"); std::shared_ptr camera = currentCameraDevice_.lock(); @@ -2847,7 +2847,7 @@ BufferDataPointer* CMMCore::getImagePointer() throw (CMMError) // TODO: could add a check here that there there is one and only one image to pop // because otherwise the user may be using this incorrectly. const void* ptr = bufferManager_->PopNextImage(); - return new BufferDataPointer(bufferManager_->GetV2Buffer(), ptr); + return new BufferDataPointer(bufferManager_, ptr); } catch( CMMError& e){ throw e; @@ -3261,51 +3261,51 @@ void* CMMCore::popNextImageMD(Metadata& md) throw (CMMError) //// Data pointer access for v2 Buffer BufferDataPointer* CMMCore::getLastImagePointer() throw (CMMError) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->GetLastImage(); - return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); + return new BufferDataPointer(bufferManager_, rawPtr); } BufferDataPointer* CMMCore::popNextImagePointer() throw (CMMError) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->PopNextImage(); - return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); + return new BufferDataPointer(bufferManager_, rawPtr); } BufferDataPointer* CMMCore::getLastImageMDPointer(Metadata& md) const throw (CMMError) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->GetLastImageMD(md); - return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); + return new BufferDataPointer(bufferManager_, rawPtr); } BufferDataPointer* CMMCore::popNextImageMDPointer(Metadata& md) throw (CMMError) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->PopNextImageMD(md); - return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); + return new BufferDataPointer(bufferManager_, rawPtr); } BufferDataPointer* CMMCore::getLastImageFromDevicePointer(std::string deviceLabel) throw (CMMError) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->GetLastImageFromDevice(deviceLabel); - return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); + return new BufferDataPointer(bufferManager_, rawPtr); } BufferDataPointer* CMMCore::getLastImageMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->GetLastImageMDFromDevice(deviceLabel, md); - return new BufferDataPointer(bufferManager_->GetV2Buffer(), rawPtr); + return new BufferDataPointer(bufferManager_, rawPtr); } /** @@ -3324,7 +3324,6 @@ void CMMCore::clearCircularBuffer() throw (CMMError) */ void CMMCore::enableV2Buffer(bool enable) throw (CMMError) { - useV2Buffer_ = enable; bufferManager_->EnableV2Buffer(enable); } @@ -3342,7 +3341,7 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes sizeMB << " MB"; try { - bufferManager_ = new BufferManager(useV2Buffer_, sizeMB); + bufferManager_ = new BufferManager(bufferManager_->IsUsingV2Buffer(), sizeMB); } catch (std::bad_alloc& ex) { @@ -4403,7 +4402,7 @@ unsigned CMMCore::getNumberOfComponents() // We don't want want to compare to the snap buffer pointer directly because // its unclear what the device adapter might do when this is called. unsigned CMMCore::getImageWidth(DataPtr ptr) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { return getImageWidth(); } if (bufferManager_->IsPointerInBuffer(ptr)) { @@ -4413,7 +4412,7 @@ unsigned CMMCore::getImageWidth(DataPtr ptr) { } unsigned CMMCore::getImageHeight(DataPtr ptr) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { return getImageWidth(); } if (bufferManager_->IsPointerInBuffer(ptr)) { @@ -4423,7 +4422,7 @@ unsigned CMMCore::getImageHeight(DataPtr ptr) { } unsigned CMMCore::getBytesPerPixel(DataPtr ptr) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { return getBytesPerPixel(); } if (bufferManager_->IsPointerInBuffer(ptr)) { @@ -4433,7 +4432,7 @@ unsigned CMMCore::getBytesPerPixel(DataPtr ptr) { } unsigned CMMCore::getNumberOfComponents(DataPtr ptr) { - if (!useV2Buffer_) { + if (!bufferManager_->IsUsingV2Buffer()) { return getNumberOfComponents(); } if (bufferManager_->IsPointerInBuffer(ptr)) { @@ -4442,18 +4441,18 @@ unsigned CMMCore::getNumberOfComponents(DataPtr ptr) { return getNumberOfComponents(); } -long CMMCore::getImageBufferSize(DataPtr ptr) { - if (!useV2Buffer_) { +unsigned CMMCore::getSizeBytes(DataPtr ptr) { + if (!bufferManager_->IsUsingV2Buffer()) { return getImageWidth() * getImageHeight() * getBytesPerPixel(); } if (bufferManager_->IsPointerInBuffer(ptr)) { - return bufferManager_->GetImageBufferSize(ptr); + return bufferManager_->GetDatumSize(ptr); } return getImageBufferSize(); } void CMMCore::releaseReadAccess(DataPtr ptr) { - if (useV2Buffer_) { + if (bufferManager_->IsUsingV2Buffer()) { bufferManager_->ReleaseReadAccess(ptr); } } diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 9550092c7..62e75a3a7 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -440,7 +440,7 @@ class CMMCore /** \name v2 buffer control. */ ///@{ void enableV2Buffer(bool enable) throw (CMMError); - bool usesV2Buffer() const { return useV2Buffer_; } + bool usesV2Buffer() const { return bufferManager_->IsUsingV2Buffer(); } // These functions are used by the Java SWIG wrapper to get properties of the image // based on a pointer. The DataPtr alias to void* is so they don't get converted to @@ -449,7 +449,7 @@ class CMMCore unsigned getImageHeight(DataPtr ptr) throw (CMMError); unsigned getBytesPerPixel(DataPtr ptr) throw (CMMError); unsigned getNumberOfComponents(DataPtr ptr) throw (CMMError); - long getImageBufferSize(DataPtr ptr) throw (CMMError); + unsigned getSizeBytes(DataPtr ptr) throw (CMMError); void releaseReadAccess(DataPtr ptr) throw (CMMError); @@ -714,7 +714,6 @@ class CMMCore MMThreadLock* pPostedErrorsLock_; mutable std::deque > postedErrors_; - bool useV2Buffer_; // Whether to use the V2 buffer implementation private: void InitializeErrorMessages(); diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index 6018154ff..e7eb7d711 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -404,16 +404,16 @@ } %typemap(out) void* { - long lSize = (arg1)->getImageWidth((void*)result) * - (arg1)->getImageHeight((void*)result); - - unsigned bytesPerPixel = (arg1)->getBytesPerPixel((void*)result); - unsigned numComponents = (arg1)->getNumberOfComponents((void*)result); + + unsigned numBytes = (arg1)->getImageBufferSize(); + unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); + unsigned numPixels = numBytes / bytesPerPixel; + unsigned numComponents = (arg1)->getNumberOfComponents(); if (bytesPerPixel == 1) { // create a new byte[] object in Java - jbyteArray data = JCALL1(NewByteArray, jenv, lSize); + jbyteArray data = JCALL1(NewByteArray, jenv, numPixels); if (data == 0) { jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); @@ -425,17 +425,15 @@ } // copy pixels from the image buffer - JCALL4(SetByteArrayRegion, jenv, data, 0, lSize, (jbyte*)result); - - // Release the read access if not v2 buffer, this will be ignored in the core - (arg1)->releaseReadAccess((void*)result); + JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels, (jbyte*)result); + (arg1)->releaseReadAccess(result); $result = data; } else if (bytesPerPixel == 2) { // create a new short[] object in Java - jshortArray data = JCALL1(NewShortArray, jenv, lSize); + jshortArray data = JCALL1(NewShortArray, jenv, numPixels); if (data == 0) { jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); @@ -446,11 +444,8 @@ } // copy pixels from the image buffer - JCALL4(SetShortArrayRegion, jenv, data, 0, lSize, (jshort*)result); - - // Release the read access - (arg1)->releaseReadAccess((void*)result); - + JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels, (jshort*)result); + (arg1)->releaseReadAccess(result); $result = data; } @@ -459,8 +454,27 @@ if (numComponents == 1) { // create a new float[] object in Java + jfloatArray data = JCALL1(NewFloatArray, jenv, numPixels); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); - jfloatArray data = JCALL1(NewFloatArray, jenv, lSize); + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetFloatArrayRegion, jenv, data, 0, numPixels, (jfloat*)result); + (arg1)->releaseReadAccess(result); + + $result = data; + } + else + { + // create a new byte[] object in Java + jbyteArray data = JCALL1(NewByteArray, jenv, numPixels * 4); if (data == 0) { jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); @@ -472,18 +486,117 @@ } // copy pixels from the image buffer - JCALL4(SetFloatArrayRegion, jenv, data, 0, lSize, (jfloat*)result); + JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels * 4, (jbyte*)result); + (arg1)->releaseReadAccess(result); + + $result = data; + } + } + else if (bytesPerPixel == 8) + { + // create a new short[] object in Java + jshortArray data = JCALL1(NewShortArray, jenv, numPixels * 4); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels * 4, (jshort*)result); + (arg1)->releaseReadAccess(result); - // Release the read access - (arg1)->releaseReadAccess((void*)result); + $result = data; + } + else + { + // don't know how to map + // TODO: throw exception? + $result = 0; + } +} +%typemap(jni) BufferDataPointerVoidStar "jobject" +%typemap(jtype) BufferDataPointerVoidStar "Object" +%typemap(jstype) BufferDataPointerVoidStar "Object" +%typemap(javaout) BufferDataPointerVoidStar { + return $jnicall; +} +%typemap(out) BufferDataPointerVoidStar +{ + + unsigned numBytes = (arg1)->getSizeBytes(); + // Return null if no bytes + if (numBytes == 0) { + $result = 0; + return $result; + } + unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); + unsigned numPixels = numBytes / bytesPerPixel; + unsigned numComponents = (arg1)->getNumberOfComponents(); + + if (bytesPerPixel == 1) + { + // create a new byte[] object in Java + jbyteArray data = JCALL1(NewByteArray, jenv, numPixels); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels, (jbyte*)result); + $result = data; + } + else if (bytesPerPixel == 2) + { + // create a new short[] object in Java + jshortArray data = JCALL1(NewShortArray, jenv, numPixels); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels, (jshort*)result); + $result = data; + } + else if (bytesPerPixel == 4) + { + if (numComponents == 1) + { + // create a new float[] object in Java + jfloatArray data = JCALL1(NewFloatArray, jenv, numPixels); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + + $result = 0; + return $result; + } + // copy pixels from the image buffer + JCALL4(SetFloatArrayRegion, jenv, data, 0, numPixels, (jfloat*)result); $result = data; } else { // create a new byte[] object in Java - jbyteArray data = JCALL1(NewByteArray, jenv, lSize * 4); + jbyteArray data = JCALL1(NewByteArray, jenv, numPixels * 4); if (data == 0) { jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); @@ -495,11 +608,127 @@ } // copy pixels from the image buffer - JCALL4(SetByteArrayRegion, jenv, data, 0, lSize * 4, (jbyte*)result); + JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels * 4, (jbyte*)result); + $result = data; + } + } + else if (bytesPerPixel == 8) + { + // create a new short[] object in Java + jshortArray data = JCALL1(NewShortArray, jenv, numPixels * 4); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels * 4, (jshort*)result); + $result = data; + } + else + { + // don't know how to map + // TODO: throw exception? + $result = 0; + } +} - // Release the read access - (arg1)->releaseReadAccess((void*)result); +// This is conceptually similar to the void* typemap above, +// but requires slightly different calls because BufferDataPointer +// is different from the data-returning void* methods of the Core. +%typemap(jni) BufferDataPointer "jobject" +%typemap(jtype) BufferDataPointer "Object" +%typemap(jstype) BufferDataPointer "Object" +%typemap(javaout) BufferDataPointer { + return $jnicall; +} +%typemap(out) BufferDataPointer +{ + + unsigned numBytes = (arg1)->getSizeBytes(); + unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); + unsigned numPixels = numBytes / bytesPerPixel; + unsigned numComponents = (arg1)->getNumberOfComponents(); + + + if (bytesPerPixel == 1) + { + // create a new byte[] object in Java + jbyteArray data = JCALL1(NewByteArray, jenv, numPixels); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + + $result = 0; + return $result; + } + // copy pixels from the image buffer + JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels, (jbyte*)result); + + $result = data; + } + else if (bytesPerPixel == 2) + { + // create a new short[] object in Java + jshortArray data = JCALL1(NewShortArray, jenv, numPixels); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels, (jshort*)result); + + $result = data; + } + else if (bytesPerPixel == 4) + { + if (numComponents == 1) + { + // create a new float[] object in Java + jfloatArray data = JCALL1(NewFloatArray, jenv, numPixels); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetFloatArrayRegion, jenv, data, 0, numPixels, (jfloat*)result); + + $result = data; + } + else + { + // create a new byte[] object in Java + jbyteArray data = JCALL1(NewByteArray, jenv, numPixels * 4); + if (data == 0) + { + jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); + if (excep) + jenv->ThrowNew(excep, "The system ran out of memory!"); + + $result = 0; + return $result; + } + + // copy pixels from the image buffer + JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels * 4, (jbyte*)result); $result = data; } @@ -507,7 +736,7 @@ else if (bytesPerPixel == 8) { // create a new short[] object in Java - jshortArray data = JCALL1(NewShortArray, jenv, lSize * 4); + jshortArray data = JCALL1(NewShortArray, jenv, numPixels * 4); if (data == 0) { jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); @@ -518,14 +747,10 @@ } // copy pixels from the image buffer - JCALL4(SetShortArrayRegion, jenv, data, 0, lSize * 4, (jshort*)result); - - // Release the read access - (arg1)->releaseReadAccess((void*)result); + JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels * 4, (jshort*)result); $result = data; } - else { // don't know how to map @@ -737,7 +962,9 @@ tags.put("ROI", getROITag()); tags.put("Width", getImageWidth()); tags.put("Height", getImageHeight()); - tags.put("PixelType", getPixelType()); + int bytesPerPixel = (int) getBytesPerPixel(); + int numComponents = (int) getNumberOfComponents(); + tags.put("PixelType", getPixelType(bytesPerPixel, numComponents)); tags.put("Frame", 0); tags.put("FrameIndex", 0); tags.put("Position", "Default"); @@ -913,6 +1140,7 @@ #include "../MMDevice/ImageMetadata.h" #include "../MMCore/MMEventCallback.h" #include "../MMCore/MMCore.h" +#include "../MMCore/BufferDataPointer.h" %} @@ -1231,4 +1459,5 @@ namespace std { %include "../MMCore/MMCore.h" %include "../MMDevice/ImageMetadata.h" %include "../MMCore/MMEventCallback.h" +%include "../MMCore/BufferDataPointer.h" From 785e995fb71d1b6f31bbfa617833e7b0a735a437 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:32:01 -0800 Subject: [PATCH 29/46] major refactor to make v2 buffer and buffer manager data type agnostic; Also centralized Metadata generation into the core from SWIG, core callback, device base --- MMCore/BufferDataPointer.h | 30 +- MMCore/BufferManager.cpp | 257 +++------ MMCore/BufferManager.h | 202 +++---- MMCore/Buffer_v2.cpp | 17 +- MMCore/Buffer_v2.h | 27 +- MMCore/CircularBuffer.cpp | 11 +- MMCore/CircularBuffer.h | 5 +- MMCore/CoreCallback.cpp | 262 +++------ MMCore/CoreCallback.h | 69 ++- MMCore/MMCore.cpp | 527 ++++++++++++++---- MMCore/MMCore.h | 57 +- MMCoreJ_wrap/MMCoreJ.i | 117 ++-- .../src/main/java/mmcorej/LazyJSONObject.java | 97 ++++ .../main/java/mmcorej/TaggedImagePointer.java | 63 +-- MMDevice/DeviceBase.h | 1 - MMDevice/MMDeviceConstants.h | 2 +- 16 files changed, 1000 insertions(+), 744 deletions(-) create mode 100644 MMCoreJ_wrap/src/main/java/mmcorej/LazyJSONObject.java diff --git a/MMCore/BufferDataPointer.h b/MMCore/BufferDataPointer.h index 5c06e6b01..e6767abef 100644 --- a/MMCore/BufferDataPointer.h +++ b/MMCore/BufferDataPointer.h @@ -51,7 +51,7 @@ class BufferDataPointer { } // Returns a pointer to the pixel data (read-only) - BufferDataPointerVoidStar getPixels() const { + BufferDataPointerVoidStar getData() const { if (!ptr_) { return nullptr; } @@ -100,23 +100,23 @@ class BufferDataPointer { // return 0; // } - unsigned getBytesPerPixel() const { - if (bufferManager_ && ptr_) { - return bufferManager_->GetBytesPerPixel(ptr_); - } - return 0; - } - - unsigned getNumberOfComponents() const { - if (bufferManager_ && ptr_) { - return bufferManager_->GetNumberOfComponents(ptr_); - } - return 0; - } + // unsigned getBytesPerPixel() const { + // if (bufferManager_ && ptr_) { + // return bufferManager_->GetBytesPerPixel(ptr_); + // } + // return 0; + // } + // + // unsigned getNumberOfComponents() const { + // if (bufferManager_ && ptr_) { + // return bufferManager_->GetNumberOfComponents(ptr_); + // } + // return 0; + // } unsigned getSizeBytes() const { if (bufferManager_ && ptr_) { - return bufferManager_->GetDatumSize(ptr_); + return bufferManager_->GetDataSize(ptr_); } return 0; } diff --git a/MMCore/BufferManager.cpp b/MMCore/BufferManager.cpp index 943fc58fa..7bbbae470 100644 --- a/MMCore/BufferManager.cpp +++ b/MMCore/BufferManager.cpp @@ -50,7 +50,17 @@ BufferManager::~BufferManager() } } -const void* BufferManager::GetLastImage() +void BufferManager::ReallocateBuffer(unsigned int memorySizeMB) { + if (useV2_) { + delete v2Buffer_; + v2Buffer_ = new DataBuffer(memorySizeMB); + } else { + delete circBuffer_; + circBuffer_ = new CircularBuffer(memorySizeMB); + } +} + +const void* BufferManager::GetLastData() { if (useV2_) { Metadata dummyMetadata; @@ -61,7 +71,7 @@ const void* BufferManager::GetLastImage() } } -const void* BufferManager::PopNextImage() +const void* BufferManager::PopNextData() { if (useV2_) { Metadata dummyMetadata; @@ -72,25 +82,7 @@ const void* BufferManager::PopNextImage() } } -bool BufferManager::Initialize(unsigned numChannels, unsigned width, unsigned height, unsigned bytesPerPixel) -{ - if (useV2_) { - // This in not required for v2 buffer because it can interleave multiple data types/image sizes - } else { - return circBuffer_->Initialize(numChannels, width, height, bytesPerPixel); - } -} - -unsigned BufferManager::GetMemorySizeMB() const -{ - if (useV2_) { - return v2Buffer_->GetMemorySizeMB(); - } else { - return circBuffer_->GetMemorySizeMB(); - } -} - -long BufferManager::GetRemainingImageCount() const +long BufferManager::GetRemainingDataCount() const { if (useV2_) { return v2Buffer_->GetActiveSlotCount(); @@ -99,37 +91,19 @@ long BufferManager::GetRemainingImageCount() const } } -void BufferManager::Clear() -{ - if (useV2_) { - // This has no effect on v2 buffer, because devices do not have authority to clear the buffer - // since higher level code may hold pointers to data in the buffer. - // It seems to be mostly used in live mode, where data is overwritten by default anyway. - } else { - circBuffer_->Clear(); - } -} - -long BufferManager::GetSize(long imageSize) const -{ +unsigned BufferManager::GetMemorySizeMB() const { if (useV2_) { - unsigned int mb = v2Buffer_->GetMemorySizeMB(); - size_t totalBytes = static_cast(mb) * 1024 * 1024; - size_t num_images = totalBytes / static_cast(imageSize); - return static_cast(num_images); + return v2Buffer_->GetMemorySizeMB(); } else { - return circBuffer_->GetSize(); + return circBuffer_->GetMemorySizeMB(); } - } -long BufferManager::GetFreeSize(long imageSize) const -{ +unsigned BufferManager::GetFreeSizeMB() const { if (useV2_) { - unsigned int mb = v2Buffer_->GetFreeMemory(); - return static_cast(mb) / imageSize; + return (unsigned) v2Buffer_->GetFreeMemory() / 1024 / 1024; } else { - return circBuffer_->GetFreeSize(); + return circBuffer_->GetFreeSize() * circBuffer_->GetImageSizeBytes() / 1024 / 1024; } } @@ -142,56 +116,23 @@ bool BufferManager::Overflow() const } } -void BufferManager::PopulateMetadata(Metadata& md, const char* deviceLabel, - unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents) { - // Add the device label (can be used to route different devices to different buffers) - md.put(MM::g_Keyword_Metadata_DataSourceDeviceLabel, deviceLabel); - - // Add essential image metadata needed for interpreting the image: - md.PutImageTag(MM::g_Keyword_Metadata_Width, width); - md.PutImageTag(MM::g_Keyword_Metadata_Height, height); - - if (byteDepth == 1) - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY8); - else if (byteDepth == 2) - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY16); - else if (byteDepth == 4) { - if (nComponents == 1) - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY32); - else - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB32); - } - else if (byteDepth == 8) - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB64); - else - md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_Unknown); -} - -bool BufferManager::InsertImage(const char* callerLabel, const unsigned char* buf, - unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd) { - return InsertMultiChannel(callerLabel, buf, 1, width, height, byteDepth, 1, pMd); -} - +/** + * @deprecated Use InsertData() instead + */ bool BufferManager::InsertImage(const char* callerLabel, const unsigned char *buf, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, Metadata *pMd) { - return InsertMultiChannel(callerLabel, buf, 1, width, height, byteDepth, nComponents, pMd); -} - - -bool BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned char *buf, - unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata *pMd) { - return InsertMultiChannel(callerLabel, buf, numChannels, width, height, byteDepth, 1, pMd); + unsigned byteDepth, Metadata *pMd) { + return InsertMultiChannel(callerLabel, buf, 1, width, height, byteDepth, pMd); } +/** + * @deprecated Use InsertData() instead + */ bool BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned char* buf, - unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, Metadata* pMd) { + unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd) { // Initialize metadata with either provided metadata or create empty Metadata md = (pMd != nullptr) ? *pMd : Metadata(); - // Add required and useful metadata. - PopulateMetadata(md, callerLabel, width, height, byteDepth, nComponents); - if (useV2_) { // All the data needed to interpret the image is in the metadata // This function will copy data and metadata into the buffer @@ -203,12 +144,26 @@ bool BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned c } } -const void* BufferManager::GetLastImageMD(Metadata& md) const throw (CMMError) +bool BufferManager::InsertData(const char* callerLabel, const unsigned char* buf, size_t dataSize, Metadata* pMd) { + + // Initialize metadata with either provided metadata or create empty + Metadata md = (pMd != nullptr) ? *pMd : Metadata(); + + if (!useV2_) { + throw CMMError("InsertData() not supported with circular buffer. Must use V2 buffer."); + } + // All the data needed to interpret the image should be in the metadata + // This function will copy data and metadata into the buffer + return v2Buffer_->InsertData(buf, dataSize, &md, callerLabel); +} + + +const void* BufferManager::GetLastDataMD(Metadata& md) const throw (CMMError) { - return GetLastImageMD(0, md); + return GetLastDataMD(0, md); } -const void* BufferManager::GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError) +const void* BufferManager::GetLastDataMD(unsigned channel, Metadata& md) const throw (CMMError) { if (useV2_) { if (channel != 0) { @@ -229,7 +184,7 @@ const void* BufferManager::GetLastImageMD(unsigned channel, Metadata& md) const } } -const void* BufferManager::GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError) +const void* BufferManager::GetNthDataMD(unsigned long n, Metadata& md) const throw (CMMError) { if (useV2_) { // NOTE: make sure calling code releases the slot after use. @@ -245,12 +200,12 @@ const void* BufferManager::GetNthImageMD(unsigned long n, Metadata& md) const th } } -const void* BufferManager::PopNextImageMD(Metadata& md) throw (CMMError) +const void* BufferManager::PopNextDataMD(Metadata& md) throw (CMMError) { - return PopNextImageMD(0, md); + return PopNextDataMD(0, md); } -const void* BufferManager::PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError) +const void* BufferManager::PopNextDataMD(unsigned channel, Metadata& md) throw (CMMError) { if (useV2_) { if (channel != 0) { @@ -278,7 +233,7 @@ bool BufferManager::EnableV2Buffer(bool enable) { } // Create new buffer of requested type with same memory size - unsigned int memorySizeMB = GetMemorySizeMB(); + unsigned memorySizeMB = GetMemorySizeMB(); try { if (enable) { @@ -287,7 +242,6 @@ bool BufferManager::EnableV2Buffer(bool enable) { delete circBuffer_; circBuffer_ = nullptr; v2Buffer_ = newBuffer; - v2Buffer_->ReinitializeBuffer(memorySizeMB); } else { // Switch to circular buffer CircularBuffer* newBuffer = new CircularBuffer(memorySizeMB); @@ -299,7 +253,6 @@ bool BufferManager::EnableV2Buffer(bool enable) { } useV2_ = enable; - Clear(); // Reset the new buffer return true; } catch (const std::exception&) { // If allocation fails, keep the existing buffer @@ -318,71 +271,7 @@ bool BufferManager::ReleaseReadAccess(const void* ptr) { return false; } -unsigned BufferManager::GetImageWidth(const void* ptr) const { - if (!useV2_) - throw CMMError("GetImageWidth(ptr) only supported with V2 buffer"); - Metadata md; - if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) - throw CMMError("Failed to extract metadata for image width"); - std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue(); - return static_cast(atoi(sVal.c_str())); -} - -unsigned BufferManager::GetImageHeight(const void* ptr) const { - if (!useV2_) - throw CMMError("GetImageHeight(ptr) only supported with V2 buffer"); - Metadata md; - if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) - throw CMMError("Failed to extract metadata for image height"); - std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue(); - return static_cast(atoi(sVal.c_str())); -} - -unsigned BufferManager::GetBytesPerPixelFromType(const std::string& pixelType) const { - if (pixelType == MM::g_Keyword_PixelType_GRAY8) - return 1; - else if (pixelType == MM::g_Keyword_PixelType_GRAY16) - return 2; - else if (pixelType == MM::g_Keyword_PixelType_GRAY32 || - pixelType == MM::g_Keyword_PixelType_RGB32) - return 4; - else if (pixelType == MM::g_Keyword_PixelType_RGB64) - return 8; - throw CMMError("Unknown pixel type for bytes per pixel"); -} - -unsigned BufferManager::GetComponentsFromType(const std::string& pixelType) const { - if (pixelType == MM::g_Keyword_PixelType_GRAY8 || - pixelType == MM::g_Keyword_PixelType_GRAY16 || - pixelType == MM::g_Keyword_PixelType_GRAY32) - return 1; - else if (pixelType == MM::g_Keyword_PixelType_RGB32 || - pixelType == MM::g_Keyword_PixelType_RGB64) - return 4; - throw CMMError("Unknown pixel type for number of components"); -} - -unsigned BufferManager::GetBytesPerPixel(const void* ptr) const { - if (!useV2_) - throw CMMError("GetBytesPerPixel(ptr) only supported with V2 buffer"); - Metadata md; - if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) - throw CMMError("Failed to extract metadata for bytes per pixel"); - std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - return GetBytesPerPixelFromType(pixelType); -} - -unsigned BufferManager::GetNumberOfComponents(const void* ptr) const { - if (!useV2_) - throw CMMError("GetNumberOfComponents(ptr) only supported with V2 buffer"); - Metadata md; - if (v2Buffer_->ExtractCorrespondingMetadata(ptr, md) != DEVICE_OK) - throw CMMError("Failed to extract metadata for number of components"); - std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - return GetComponentsFromType(pixelType); -} - -unsigned BufferManager::GetDatumSize(const void* ptr) const { +unsigned BufferManager::GetDataSize(const void* ptr) const { if (!useV2_) return circBuffer_->GetImageSizeBytes(); else @@ -398,10 +287,8 @@ bool BufferManager::SetOverwriteData(bool overwrite) { } } -bool BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, size_t additionalMetadataSize, - void** dataPointer, void** additionalMetadataPointer, - Metadata* pInitialMetadata) { +bool BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, size_t additionalMetadataSize, + void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata) { if (!useV2_) { // Not supported for circular buffer return false; @@ -410,10 +297,6 @@ bool BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, u // Initialize metadata with either provided metadata or create empty Metadata md = (pInitialMetadata != nullptr) ? *pInitialMetadata : Metadata(); - // Add in width, height, byteDepth, and nComponents to the metadata so that when - // images are retrieved from the buffer, the data can be interpreted correctly - PopulateMetadata(md, deviceLabel, width, height, byteDepth, nComponents); - std::string serializedMetadata = md.Serialize(); int ret = v2Buffer_->AcquireWriteSlot(dataSize, additionalMetadataSize, dataPointer, additionalMetadataPointer, serializedMetadata, deviceLabel); @@ -446,35 +329,51 @@ void BufferManager::ExtractMetadata(const void* dataPtr, Metadata& md) const { } } -const void* BufferManager::GetLastImageFromDevice(const std::string& deviceLabel) throw (CMMError) { +const void* BufferManager::GetLastDataFromDevice(const std::string& deviceLabel) throw (CMMError) { if (!useV2_) { - throw CMMError("V2 buffer must be enabled for device-specific image access"); + throw CMMError("V2 buffer must be enabled for device-specific data access"); } Metadata md; - return GetLastImageMDFromDevice(deviceLabel, md); + return GetLastDataMDFromDevice(deviceLabel, md); } -const void* BufferManager::GetLastImageMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError) { +const void* BufferManager::GetLastDataMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError) { if (!useV2_) { - throw CMMError("V2 buffer must be enabled for device-specific image access"); + throw CMMError("V2 buffer must be enabled for device-specific data access"); } const void* basePtr = v2Buffer_->PeekLastDataReadPointerFromDevice(deviceLabel, md); if (basePtr == nullptr) { - throw CMMError("No image found for device: " + deviceLabel, MMERR_InvalidContents); + throw CMMError("No data found for device: " + deviceLabel, MMERR_InvalidContents); } return basePtr; } -bool BufferManager::IsPointerInBuffer(const void* ptr) const throw (CMMError) { +bool BufferManager::IsPointerInV2Buffer(const void* ptr) const throw (CMMError) { if (!useV2_) { - throw CMMError("IsPointerInBuffer is only supported with V2 buffer enabled"); + return false; } if (v2Buffer_ == nullptr) { - throw CMMError("V2 buffer is null"); + return false; } return v2Buffer_->IsPointerInBuffer(ptr); } +bool BufferManager::GetOverwriteData() const { + if (useV2_) { + return v2Buffer_->GetOverwriteData(); + } else { + return circBuffer_->GetOverwriteData(); + } +} + +void BufferManager::Reset() { + if (useV2_) { + v2Buffer_->Reset(); + } else { + circBuffer_->Clear(); + } +} + diff --git a/MMCore/BufferManager.h b/MMCore/BufferManager.h index 4367b6043..42bac2a76 100644 --- a/MMCore/BufferManager.h +++ b/MMCore/BufferManager.h @@ -33,8 +33,21 @@ #include #include -// BufferManager provides a common interface for buffer operations -// used by MMCore. It currently supports only a minimal set of functions. +/** + * BufferManager provides a generic interface for managing data buffers in MMCore. + * + * This class is designed to handle arbitrary data in a format-agnostic way, with + * metadata for interpretation handled separately. Data can be stored and retrieved + * with associated metadata that describes how to interpret the raw bytes. + * + * The implementation supports two buffer types: + * - The newer V2 buffer that handles generic data + * - The legacy circular buffer (for backwards compatibility) that assumes image data + * + * While the preferred usage is through the generic data methods (InsertData, + * GetLastData, etc.), legacy image-specific methods are maintained for compatibility + * with existing code that assumes images captured on a camera. + */ class BufferManager { public: static const char* const DEFAULT_V2_BUFFER_NAME; @@ -47,6 +60,12 @@ class BufferManager { BufferManager(bool useV2Buffer, unsigned int memorySizeMB); ~BufferManager(); + /** + * Reinitialize the buffer. + * @param memorySizeMB Memory size for the buffer (in megabytes). + */ + void ReallocateBuffer(unsigned int memorySizeMB); + /** * Enable or disable v2 buffer usage. * @param enable Set to true to use v2 buffer, false to use circular buffer. @@ -58,24 +77,13 @@ class BufferManager { * Get a pointer to the top (most recent) image. * @return Pointer to image data, or nullptr if unavailable. */ - const void* GetLastImage(); + const void* GetLastData(); /** * Get a pointer to the next image from the buffer. * @return Pointer to image data, or nullptr if unavailable. */ - const void* PopNextImage(); - - - /** - * Initialize the buffer with the given parameters. - * @param numChannels Number of channels. - * @param width Image width. - * @param height Image height. - * @param bytesPerPixel Bytes per pixel. - * @return true on success, false on error. - */ - bool Initialize(unsigned numChannels, unsigned width, unsigned height, unsigned bytesPerPixel); + const void* PopNextData(); /** * Get the memory size of the buffer in megabytes. @@ -84,18 +92,19 @@ class BufferManager { unsigned GetMemorySizeMB() const; /** - * Get the remaining image count in the buffer. - * @return Number of remaining images. + * Get the free capacity of the buffer. + * @return Free capacity in MB. */ - long GetRemainingImageCount() const; + unsigned GetFreeSizeMB() const; /** - * Clear the entire image buffer. + * Get the remaining data entry (e.g. image) count in the buffer. + * @return Number of remaining data entries. */ - void Clear(); + long GetRemainingDataCount() const; /** - * Insert an image into the buffer. + * Insert an image into the buffer * @param caller The device inserting the image. * @param buf The image data. * @param width Image width. @@ -103,26 +112,17 @@ class BufferManager { * @param byteDepth Bytes per pixel. * @param pMd Metadata associated with the image. * @return true on success, false on error. + * @deprecated This method assumes specific image data format. It is provided for backwards + * compatibility with with the circular buffer, which assumes images captured on a camera. + * Use InsertData() instead, which provides format-agnostic data handling with metadata for interpretation. */ bool InsertImage(const char* deviceLabel, const unsigned char *buf, - unsigned width, unsigned height, unsigned byteDepth, Metadata *pMd); + unsigned width, unsigned height, unsigned byteDepth, + Metadata *pMd); - /** - * Insert an image into the buffer with specified number of components. - * @param caller The device inserting the image. - * @param buf The image data. - * @param width Image width. - * @param height Image height. - * @param byteDepth Bytes per pixel. - * @param nComponents Number of components in the image. - * @param pMd Metadata associated with the image. - * @return true on success, false on error. - */ - bool InsertImage(const char* deviceLabel, const unsigned char *buf, unsigned width, - unsigned height, unsigned byteDepth, unsigned nComponents, Metadata *pMd); /** - * Insert a multi-channel image into the buffer. + * Insert a multi-channel image into the buffer * @param caller The device inserting the image. * @param buf The image data. * @param numChannels Number of channels in the image. @@ -131,39 +131,27 @@ class BufferManager { * @param byteDepth Bytes per pixel. * @param pMd Metadata associated with the image. * @return true on success, false on error. + * @deprecated This method is not preferred for the V2 buffer. Use InsertData() instead. + * This method assumes specific image data format. It is provided for backwards + * compatibility with with the circular buffer, which assumes images captured on a camera. */ bool InsertMultiChannel(const char* deviceLabel, const unsigned char *buf, - unsigned numChannels, unsigned width, unsigned height, + unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata *pMd); /** - * Insert a multi-channel image into the buffer with specified number of components. - * @param caller The device inserting the image. - * @param buf The image data. - * @param numChannels Number of channels in the image. - * @param width Image width. - * @param height Image height. - * @param byteDepth Bytes per pixel. - * @param nComponents Number of components in the image. - * @param pMd Metadata associated with the image. + * Insert data into the buffer. This method is agnostic to the format of the data + * It is the caller's responsibility to ensure that appropriate metadata is provided + * for interpretation of the data (e.g. for an image: width, height, byteDepth, nComponents) + * + * @param callerLabel The label of the device inserting the data. + * @param buf The data to insert. + * @param dataSize The size of the data to insert. + * @param pMd Metadata associated with the data. * @return true on success, false on error. */ - bool InsertMultiChannel(const char* deviceLabel, const unsigned char *buf, - unsigned numChannels, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, Metadata *pMd); + bool InsertData(const char* callerLabel, const unsigned char* buf, size_t dataSize, Metadata *pMd); - /** - * Get the total capacity of the buffer. - * @return Total capacity of the buffer. - */ - long GetSize(long imageSize) const; - - /** - * Get the free capacity of the buffer. - * @param imageSize Size of a single image in bytes. - * @return Number of images that can be added without overflowing. - */ - long GetFreeSize(long imageSize) const; /** * Check if the buffer is overflowed. @@ -173,15 +161,15 @@ class BufferManager { - const void* GetNthImageMD(unsigned long n, Metadata& md) const throw (CMMError); + const void* GetNthDataMD(unsigned long n, Metadata& md) const throw (CMMError); // Channels are not directly supported in v2 buffer, these are for backwards compatibility // with circular buffer - const void* GetLastImageMD(unsigned channel, Metadata& md) const throw (CMMError); - const void* PopNextImageMD(unsigned channel, Metadata& md) throw (CMMError); + const void* GetLastDataMD(unsigned channel, Metadata& md) const throw (CMMError); + const void* PopNextDataMD(unsigned channel, Metadata& md) throw (CMMError); - const void* GetLastImageMD(Metadata& md) const throw (CMMError); - const void* PopNextImageMD(Metadata& md) throw (CMMError); + const void* GetLastDataMD(Metadata& md) const throw (CMMError); + const void* PopNextDataMD(Metadata& md) throw (CMMError); /** * Check if this manager is using the V2 buffer implementation. @@ -197,12 +185,8 @@ class BufferManager { */ bool ReleaseReadAccess(const void* ptr); - // Methods for the v2 buffer where width and heigh must be gotton on a per-image basis - unsigned GetImageWidth(const void* ptr) const; - unsigned GetImageHeight(const void* ptr) const; - unsigned GetBytesPerPixel(const void* ptr) const; - unsigned GetNumberOfComponents(const void* ptr) const; - unsigned GetDatumSize(const void* ptr) const; + // Get the size of just the data in this slot + unsigned GetDataSize(const void* ptr) const; /** * Configure whether to overwrite old data when buffer is full. @@ -212,35 +196,29 @@ class BufferManager { bool SetOverwriteData(bool overwrite); /** - * Acquires a write slot large enough to hold the image data and metadata. + * Acquires a write slot large enough to hold the data and metadata. * @param deviceLabel The label of the device requesting the write slot * @param dataSize The number of bytes reserved for image or other primary data. - * @param width Image width. - * @param height Image height. - * @param byteDepth Bytes per pixel. - * @param nComponents Number of components in the image. * @param additionalMetadataSize The maximum number of bytes reserved for metadata. * @param dataPointer On success, receives a pointer to the image data region. * @param additionalMetadataPointer On success, receives a pointer to the metadata region. * @param pInitialMetadata Optionally, a pointer to a metadata object whose contents should be pre‐written. Defaults to nullptr. * @return true on success, false on error. */ - bool AcquireWriteSlot(const char* deviceLabel, size_t dataSize, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, size_t additionalMetadataSize, - void** dataPointer, void** additionalMetadataPointer, - Metadata* pInitialMetadata = nullptr); + bool AcquireWriteSlot(const char* deviceLabel, size_t dataSize, size_t additionalMetadataSize, + void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata = nullptr); /** * Finalizes (releases) a write slot after data has been written. - * @param imageDataPointer Pointer previously obtained from AcquireWriteSlot. + * @param dataPointer Pointer previously obtained from AcquireWriteSlot. * @param actualMetadataBytes The actual number of metadata bytes written. * @return true on success, false on error. */ - bool FinalizeWriteSlot(void* imageDataPointer, size_t actualMetadataBytes); + bool FinalizeWriteSlot(void* dataPointer, size_t actualMetadataBytes); /** - * Extracts metadata for a given image data pointer. - * @param dataPtr Pointer to the image data. + * Extracts metadata for a given data pointer. + * @param dataPtr Pointer to the data. * @param md Metadata object to populate. * @throws CMMError if V2 buffer is not enabled or extraction fails. */ @@ -255,50 +233,54 @@ class BufferManager { Metadata AddDeviceLabel(const char* deviceLabel, const Metadata* pMd); /** - * Get the last image inserted by a specific device. - * @param deviceLabel The label of the device to get the image from. - * @return Pointer to the image data. - * @throws CMMError if no image is found or V2 buffer is not enabled. + * Get the last data inserted by a specific device. + * @param deviceLabel The label of the device to get the data from. + * @return Pointer to the data. + * @throws CMMError if no data is found or V2 buffer is not enabled. */ - const void* GetLastImageFromDevice(const std::string& deviceLabel) throw (CMMError); + const void* GetLastDataFromDevice(const std::string& deviceLabel) throw (CMMError); /** - * Get the last image and metadata inserted by a specific device. - * @param deviceLabel The label of the device to get the image from. + * Get the last data and metadata inserted by a specific device. + * @param deviceLabel The label of the device to get the data from. * @param md Metadata object to populate. - * @return Pointer to the image data. - * @throws CMMError if no image is found or V2 buffer is not enabled. + * @return Pointer to the data. + * @throws CMMError if no data is found or V2 buffer is not enabled. */ - const void* GetLastImageMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError); + const void* GetLastDataMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError); /** * Check if a pointer is currently managed by the buffer. * @param ptr The pointer to check. * @return true if the pointer is in the buffer, false otherwise. - * @throws CMMError if V2 buffer is not enabled. */ - bool IsPointerInBuffer(const void* ptr) const throw (CMMError); + bool IsPointerInV2Buffer(const void* ptr) const; + /** + * Get whether the buffer is in overwrite mode. + * @return true if buffer overwrites old data when full, false otherwise. + */ + bool GetOverwriteData() const; + + /** + * Get the underlying CircularBuffer pointer. + * This method is provided for backwards compatibility only. + * @return Pointer to CircularBuffer if using legacy buffer, nullptr if using V2 buffer + * @deprecated This method exposes implementation details and should be avoided in new code + */ + CircularBuffer* GetCircularBuffer() { return circBuffer_; } + + /** + * Reset the buffer, discarding all data that is not currently held externally. + */ + void Reset(); private: - unsigned GetBytesPerPixelFromType(const std::string& pixelType) const; - unsigned GetComponentsFromType(const std::string& pixelType) const; bool useV2_; CircularBuffer* circBuffer_; DataBuffer* v2Buffer_; - /** - * Add essential metadata tags required for interpreting stored data and routing it - * if multiple buffers are used. Only minimal parameters (width, height, pixel type) - * are added. - * - * Future data-producer devices (ie those that dont produce conventional images) may - * need alternative versions of this function. - */ - void PopulateMetadata(Metadata& md, const char* deviceLabel, - unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents); - /** * Get the metadata tags attached to device caller, and merge them with metadata diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index b87a53688..7d810f0c4 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -114,7 +114,7 @@ DataBuffer::DataBuffer(unsigned int memorySizeMB) unusedSlots_.push_back(bs); } - AllocateBuffer(memorySizeMB); + ReinitializeBuffer(memorySizeMB); } DataBuffer::~DataBuffer() { @@ -233,6 +233,21 @@ int DataBuffer::SetOverwriteData(bool overwrite) { return DEVICE_OK; } +/** + * Get whether the buffer should overwrite old data when full. + * @return True if overwriting is enabled, false otherwise. + */ +bool DataBuffer::GetOverwriteData() const { + return overwriteWhenFull_; +} + +/** + * Reset the buffer, discarding all data that is not currently held externally. + */ +void DataBuffer::Reset() { + // Reuse ReinitializeBuffer with current size + ReinitializeBuffer(GetMemorySizeMB()); +} /** * Get a pointer to the next available data slot in the buffer for writing. diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 30443dc26..88fa345a8 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -242,6 +242,12 @@ class DataBuffer { */ int SetOverwriteData(bool overwrite); + /** + * Returns whether the buffer should overwrite old data when full. + * @return True if overwriting is enabled, false otherwise. + */ + bool GetOverwriteData() const; + /** * Acquires a write slot large enough to hold the image data and metadata. * On success, returns pointers for the image data and metadata regions. @@ -345,13 +351,6 @@ class DataBuffer { */ long GetActiveSlotCount() const; - /** - * Reinitializes the DataBuffer, clearing its structures and allocating a new buffer. - * @param memorySizeMB New buffer size (in MB). - * @return DEVICE_OK on success. - * @throws std::runtime_error if any slot is still in use. - */ - int ReinitializeBuffer(unsigned int memorySizeMB); /** * Extracts metadata for a given image data pointer. @@ -377,6 +376,11 @@ class DataBuffer { */ bool IsPointerInBuffer(const void* ptr); + /** + * Reset the buffer, discarding all data that is not currently held externally. + */ + void Reset(); + private: /** * Internal helper function that finds the slot for a given pointer. @@ -441,6 +445,15 @@ class DataBuffer { size_t dataSize, size_t initialMetadataSize, size_t additionalMetadataSize, const std::string& deviceLabel); + /** + * Reinitializes the DataBuffer, clearing its structures and allocating a new buffer. + * @param memorySizeMB New buffer size (in MB). + * @return DEVICE_OK on success. + * @throws std::runtime_error if any slot is still in use. + */ + int ReinitializeBuffer(unsigned int memorySizeMB); + + /** * Creates a new slot with the specified parameters. * Caller must hold slotManagementMutex_. diff --git a/MMCore/CircularBuffer.cpp b/MMCore/CircularBuffer.cpp index 19cc79662..a51c5d527 100644 --- a/MMCore/CircularBuffer.cpp +++ b/MMCore/CircularBuffer.cpp @@ -62,6 +62,7 @@ CircularBuffer::CircularBuffer(unsigned int memorySizeMB) : saveIndex_(0), memorySizeMB_(memorySizeMB), overflow_(false), + overwriteData_(false), threadPool_(std::make_shared()), tasksMemCopy_(std::make_shared(threadPool_)) { @@ -72,7 +73,6 @@ CircularBuffer::~CircularBuffer() {} bool CircularBuffer::Initialize(unsigned channels, unsigned int w, unsigned int h, unsigned int pixDepth) { MMThreadGuard guard(g_bufferLock); - startTime_ = std::chrono::steady_clock::now(); bool ret = true; try @@ -137,7 +137,6 @@ void CircularBuffer::Clear() insertIndex_=0; saveIndex_=0; overflow_ = false; - startTime_ = std::chrono::steady_clock::now(); } unsigned long CircularBuffer::GetSize() const @@ -183,8 +182,12 @@ bool CircularBuffer::InsertMultiChannel(const unsigned char* pixArray, unsigned bool overflowed = (insertIndex_ - saveIndex_) >= static_cast(frameArray_.size()); if (overflowed) { - overflow_ = true; - return false; + if (!overwriteData_) { + Clear(); + } else { + overflow_ = true; + return false; + } } } diff --git a/MMCore/CircularBuffer.h b/MMCore/CircularBuffer.h index c412711c9..b9d5411f4 100644 --- a/MMCore/CircularBuffer.h +++ b/MMCore/CircularBuffer.h @@ -82,6 +82,8 @@ class CircularBuffer return width_ * height_ * pixDepth_ * numChannels_; } + bool GetOverwriteData() const { return overwriteData_; } + mutable MMThreadLock g_bufferLock; mutable MMThreadLock g_insertLock; @@ -90,7 +92,6 @@ class CircularBuffer unsigned int height_; unsigned int pixDepth_; long imageCounter_; - std::chrono::time_point startTime_; // Invariants: // 0 <= saveIndex_ <= insertIndex_ @@ -101,10 +102,12 @@ class CircularBuffer unsigned long memorySizeMB_; unsigned int numChannels_; bool overflow_; + bool overwriteData_; std::vector frameArray_; std::shared_ptr threadPool_; std::shared_ptr tasksMemCopy_; + }; #if defined(__GNUC__) && !defined(__clang__) diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index ad395c571..c40c66b8e 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -41,34 +41,6 @@ #include -static std::string FormatLocalTime(std::chrono::time_point tp) { - using namespace std::chrono; - auto us = duration_cast(tp.time_since_epoch()); - auto secs = duration_cast(us); - auto whole = duration_cast(secs); - auto frac = static_cast((us - whole).count()); - - // As of C++14/17, it is simpler (and probably faster) to use C functions for - // date-time formatting - - std::time_t t(secs.count()); // time_t is seconds on platforms we support - std::tm *ptm; -#ifdef _WIN32 // Windows localtime() is documented thread-safe - ptm = std::localtime(&t); -#else // POSIX has localtime_r() - std::tm tmstruct; - ptm = localtime_r(&t, &tmstruct); -#endif - - // Format as "yyyy-mm-dd hh:mm:ss.uuuuuu" (26 chars) - const char *timeFmt = "%Y-%m-%d %H:%M:%S"; - char buf[32]; - std::size_t len = std::strftime(buf, sizeof(buf), timeFmt, ptm); - std::snprintf(buf + len, sizeof(buf) - len, ".%06d", frac); - return buf; -} - - CoreCallback::CoreCallback(CMMCore* c) : core_(c), pValueChangeLock_(NULL) @@ -76,8 +48,7 @@ CoreCallback::CoreCallback(CMMCore* c) : assert(core_); pValueChangeLock_ = new MMThreadLock(); - // Initialize the start time for time-stamp calculations - startTime_ = std::chrono::steady_clock::now(); + } @@ -238,73 +209,6 @@ CoreCallback::Sleep(const MM::Device*, double intervalMs) } -/** - * Get the metadata tags attached to device caller, and merge them with metadata - * in pMd (if not null). Returns a metadata object. - */ -Metadata -CoreCallback::AddCameraMetadata(const MM::Device* caller, const Metadata* pMd) -{ - Metadata newMD; - if (pMd) - { - newMD = *pMd; - } - - std::shared_ptr device = core_->deviceManager_->GetDevice(caller); - - if (device->GetType() == MM::CameraDevice) - { - std::shared_ptr camera = - std::static_pointer_cast(device); - - // Ensure the camera label is present - if (!newMD.HasTag(MM::g_Keyword_Metadata_CameraLabel)) { - newMD.put(MM::g_Keyword_Metadata_CameraLabel, camera->GetLabel()); - } - - // Add image number metadata - { - std::lock_guard lock(imageNumbersMutex_); - std::string cameraName = newMD.GetSingleTag(MM::g_Keyword_Metadata_CameraLabel).GetValue(); - if (imageNumbers_.find(cameraName) == imageNumbers_.end()) - { - imageNumbers_[cameraName] = 0; - } - newMD.put(MM::g_Keyword_Metadata_ImageNumber, CDeviceUtils::ConvertToString(imageNumbers_[cameraName])); - ++imageNumbers_[cameraName]; - } - - // Add elapsed time metadata if not already set - if (!newMD.HasTag(MM::g_Keyword_Elapsed_Time_ms)) - { - using namespace std::chrono; - auto elapsed = steady_clock::now() - startTime_; - newMD.PutImageTag(MM::g_Keyword_Elapsed_Time_ms, - std::to_string(duration_cast(elapsed).count())); - } - - // Add current system time (as formatted local time) - auto now = std::chrono::system_clock::now(); - newMD.PutImageTag(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); - - // Merge any additional camera-specific tags - try - { - std::string serializedMD = camera->GetTags(); - Metadata devMD; - devMD.Restore(serializedMD.c_str()); - newMD.Merge(devMD); - } - catch (const CMMError&) - { - // Ignore errors getting tags - } - } - - return newMD; -} - int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, const char* serializedMetadata, const bool doProcess) { Metadata md; @@ -314,30 +218,7 @@ 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) { - try - { - Metadata md = AddCameraMetadata(caller, pMd); - - if(doProcess) - { - MM::ImageProcessor* ip = GetImageProcessor(caller); - if( NULL != ip) - { - ip->Process(const_cast(buf), width, height, byteDepth); - } - } - char labelBuffer[MM::MaxStrLength]; - caller->GetLabel(labelBuffer); - std::string callerLabel(labelBuffer); - if (core_->bufferManager_->InsertImage(callerLabel.c_str(), buf, width, height, byteDepth, &md)) - return DEVICE_OK; - else - return DEVICE_BUFFER_OVERFLOW; - } - catch (CMMError& /*e*/) - { - return DEVICE_INCOMPATIBLE_IMAGE; - } + return InsertImage(caller, buf, width, height, byteDepth, pMd, doProcess); } int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, const char* serializedMetadata, const bool doProcess) @@ -351,7 +232,18 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf { try { - Metadata md = AddCameraMetadata(caller, pMd); + Metadata newMD; + if (pMd) + { + newMD = *pMd; + } + std::shared_ptr device = core_->deviceManager_->GetDevice(caller); + if (device->GetType() == MM::CameraDevice) + { + // convert device to camera + std::shared_ptr camera = std::dynamic_pointer_cast(device); + core_->AddCameraMetadata(camera, newMD, width, height, byteDepth, nComponents, true); + } if(doProcess) { @@ -364,10 +256,10 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf char labelBuffer[MM::MaxStrLength]; caller->GetLabel(labelBuffer); std::string callerLabel(labelBuffer); - if (core_->bufferManager_->InsertImage(callerLabel.c_str(), buf, width, height, byteDepth, nComponents, &md)) - return DEVICE_OK; - else + int ret = core_->bufferManager_->InsertImage(callerLabel.c_str(), buf, width, height, byteDepth, &newMD); + if (ret != DEVICE_OK) return DEVICE_BUFFER_OVERFLOW; + return DEVICE_OK; } catch (CMMError& /*e*/) { @@ -394,50 +286,54 @@ int CoreCallback::InsertImage(const MM::Device* caller, const ImgBuffer & imgBuf // This method is explicitly for camera devices. It requires width, height, byteDepth, and nComponents // to be passed in, so that higher level code retrieving data from the buffer knows how to interpret the data -// For other data types, analogous methods could be added in the future - -/////// TODO: uncomment to activate these methods once tested with camera - -// int CoreCallback::AcquireImageWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, -// unsigned char** dataPointer, unsigned char** metadataPointer, -// unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents) -// { -// if (core_->deviceManager_->GetDevice(caller)->GetType() == MM::CameraDevice) -// { -// Metadata md = AddCallerMetadata(caller, nullptr); - -// if (!core_->bufferAdapter_->AcquireWriteSlot(dataSize, width, height, -// byteDepth, nComponents, metadataSize, -// dataPointer, metadataPointer, &md)) -// { -// return DEVICE_ERR; -// } -// return DEVICE_OK; -// } +// Generic data insertion is handled by AcquireDataWriteSlot +int CoreCallback::AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSize, size_t metadataSize, + unsigned char** dataPointer, unsigned char** metadataPointer, + unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents) +{ + if (core_->deviceManager_->GetDevice(caller)->GetType() == MM::CameraDevice) + { + + Metadata md; + std::shared_ptr camera = std::dynamic_pointer_cast(core_->deviceManager_->GetDevice(caller)); + // Add the metadata needed for interpreting camera images + core_->AddCameraMetadata(camera, md, width, height, byteDepth, nComponents, true); + + char label[MM::MaxStrLength]; + caller->GetLabel(label); + if (!core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, (void**)dataPointer, (void**)metadataPointer, &md)) + { + return DEVICE_ERR; + } + return DEVICE_OK; + } -// return DEVICE_ERR; -// } - -// int CoreCallback::FinalizeWriteSlot(unsigned char* dataPointer, -// size_t actualMetadataBytes) -// { -// if (core_->bufferAdapter_->FinalizeWriteSlot(dataPointer, actualMetadataBytes)) -// { -// return DEVICE_OK; -// } -// return DEVICE_ERR; -// } - -void CoreCallback::ClearImageBuffer(const MM::Device* /*caller*/) + return DEVICE_ERR; +} + +// This method is for generic data insertion. It will not add any metadata for interpretting the data +int CoreCallback::AcquireDataWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, + unsigned char** dataPointer, unsigned char** metadataPointer) { - // This has no effect on v2 buffer, because devices do not have authority to clear the buffer - // since higher level code may hold pointers to data in the buffer. - core_->bufferManager_->Clear(); - // Reset image counters when buffer is cleared - imageNumbers_.clear(); + char label[MM::MaxStrLength]; + caller->GetLabel(label); + if (core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, (void**)dataPointer, (void**)metadataPointer)) + { + return DEVICE_OK; + } + return DEVICE_ERR; +} + +int CoreCallback::FinalizeWriteSlot(unsigned char* dataPointer, + size_t actualMetadataBytes) +{ + if (core_->bufferManager_->FinalizeWriteSlot(dataPointer, actualMetadataBytes)) + { + return DEVICE_OK; + } + return DEVICE_ERR; } -// Note: this not required and has not effect on v2 buffer bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth) { @@ -445,23 +341,35 @@ bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, if (slices != 1) return false; - startTime_ = std::chrono::steady_clock::now(); // Initialize start time - imageNumbers_.clear(); - return core_->bufferManager_->Initialize(channels, w, h, pixDepth); + // For backwards compatibility with the circular buffer, but really this should be + // (or is?) handled by higher level code. Something this certainly cannot be applied + // to the v2 buffer, because devices do not have the authority to clear the buffer, + // when application code may hold pointers into the buffer. + if (!core_->bufferManager_->IsUsingV2Buffer()) { + return core_->bufferManager_->GetCircularBuffer()->Initialize(channels, w, h, pixDepth); + } + return true; } -int CoreCallback::InsertMultiChannel(const MM::Device* caller, - const unsigned char* buf, - unsigned numChannels, - unsigned width, - unsigned height, - unsigned byteDepth, - Metadata* pMd) +int CoreCallback:: InsertMultiChannel(const MM::Device* caller, const unsigned char* buf, + unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd) { try { - Metadata md = AddCameraMetadata(caller, pMd); + std::shared_ptr device = core_->deviceManager_->GetDevice(caller); + + Metadata newMD; + if (pMd) + { + newMD = *pMd; + } + if (device->GetType() == MM::CameraDevice) + { + // convert device to camera + std::shared_ptr camera = std::dynamic_pointer_cast(device); + core_->AddCameraMetadata(camera, newMD, width, height, byteDepth, 1, true); + } MM::ImageProcessor* ip = GetImageProcessor(caller); if( NULL != ip) @@ -471,7 +379,7 @@ int CoreCallback::InsertMultiChannel(const MM::Device* caller, char labelBuffer[MM::MaxStrLength]; caller->GetLabel(labelBuffer); std::string callerLabel(labelBuffer); - if (core_->bufferManager_->InsertMultiChannel(callerLabel.c_str(), buf, numChannels, width, height, byteDepth, &md)) + if (core_->bufferManager_->InsertMultiChannel(callerLabel.c_str(), buf, numChannels, width, height, byteDepth, &newMD)) return DEVICE_OK; else return DEVICE_BUFFER_OVERFLOW; diff --git a/MMCore/CoreCallback.h b/MMCore/CoreCallback.h index 524770fed..5d8d4316a 100644 --- a/MMCore/CoreCallback.h +++ b/MMCore/CoreCallback.h @@ -92,18 +92,54 @@ class CoreCallback : public MM::Core /*Deprecated*/ int InsertMultiChannel(const MM::Device* caller, const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd = 0); - // Direct writing into V2 buffer instead of using InsertImage (which has to copy the data again) - ///////// TODO: uncomment to activate these methods once tested with camera -// int AcquireImageWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, -// unsigned char** dataPointer, unsigned char** metadataPointer, -// unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents); -// int FinalizeWriteSlot(unsigned char* imageDataPointer, -// size_t actualMetadataBytes); - - // Note: these are not required and have no effect on v2 buffer, - // because devices do not have authority to clear the buffer since higher level - //code may hold pointers to data in the buffer. - void ClearImageBuffer(const MM::Device* caller); + /** + * Direct writing into V2 buffer instead of using InsertImage (which has to copy the data again). + * Version with essential parameters for camera devices. + * @param caller Camera device making the call + * @param dataSize Size of the image data in bytes + * @param metadataSize Size of metadata in bytes + * @param dataPointer Pointer that will be set to allocated image buffer. The caller should write + * data to this buffer. + * @param metadataPointer Pointer that will be set to allocated metadata buffer. The caller should write + * serialized metadata to this buffer. + * @param width Width of the image in pixels + * @param height Height of the image in pixels + * @param byteDepth Number of bytes per pixel + * @param nComponents Number of components per pixel + * @return DEVICE_OK on success + */ + int AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSize, size_t metadataSize, + unsigned char** dataPointer, unsigned char** metadataPointer, + unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents); + + /** + * Generic version for inserting data into the buffer. + * @param caller Device making the call + * @param dataSize Size of the data in bytes + * @param metadataSize Size of metadata in bytes + * @param dataPointer Pointer that will be set to allocated data buffer. The caller should write + * data to this buffer. + * @param metadataPointer Pointer that will be set to allocated metadata buffer. The caller should write + * serialized metadata to this buffer. + * @return DEVICE_OK on success + */ + int AcquireDataWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, + unsigned char** dataPointer, unsigned char** metadataPointer); + + /** + * Finalizes a write slot after data has been written. + * @param dataPointer Pointer to the data buffer that was written to + * @param actualMetadataBytes Actual number of metadata bytes written + * @return DEVICE_OK on success + */ + int FinalizeWriteSlot(unsigned char* dataPointer, size_t actualMetadataBytes); + + // @deprecated This method was previously used by many camera adapters to enforce an overwrite mode on the circular buffer + // -- making it wrap around when running a continuous sequence acquisition. Now we've added an option to do this into the + // circular buffer itself, so this method is no longer required and is not used. higher level code will control when this + // option is activated, making it simpler to develop camera adatpers and giving more consistent behavior. + void ClearImageBuffer(const MM::Device* /*caller*/) {}; + // @deprecated This method is not required for the V2 buffer and is called by higher level code for circular buffer bool InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth); int AcqFinished(const MM::Device* caller, int statusCode); @@ -154,15 +190,6 @@ class CoreCallback : public MM::Core CMMCore* core_; MMThreadLock* pValueChangeLock_; - /** - * Add camera-specific metadata tags including image numbering and timestamps, - * and merge them with any existing metadata. - */ - Metadata AddCameraMetadata(const MM::Device* caller, const Metadata* pMd); - std::map imageNumbers_; // Track image numbers per camera - std::mutex imageNumbersMutex_; - std::chrono::steady_clock::time_point startTime_; // Start time for elapsed time calculations - int OnConfigGroupChanged(const char* groupName, const char* newConfigName); int OnPixelSizeChanged(double newPixelSizeUm); int OnPixelSizeAffineChanged(std::vector newPixelSizeAffine); diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 44c8877a6..91a2e91e2 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -78,6 +78,33 @@ #pragma GCC diagnostic ignored "-Wdeprecated" #endif +static std::string FormatLocalTime(std::chrono::time_point tp) { + using namespace std::chrono; + auto us = duration_cast(tp.time_since_epoch()); + auto secs = duration_cast(us); + auto whole = duration_cast(secs); + auto frac = static_cast((us - whole).count()); + + // As of C++14/17, it is simpler (and probably faster) to use C functions for + // date-time formatting + + std::time_t t(secs.count()); // time_t is seconds on platforms we support + std::tm *ptm; +#ifdef _WIN32 // Windows localtime() is documented thread-safe + ptm = std::localtime(&t); +#else // POSIX has localtime_r() + std::tm tmstruct; + ptm = localtime_r(&t, &tmstruct); +#endif + + // Format as "yyyy-mm-dd hh:mm:ss.uuuuuu" (26 chars) + const char *timeFmt = "%Y-%m-%d %H:%M:%S"; + char buf[32]; + std::size_t len = std::strftime(buf, sizeof(buf), timeFmt, ptm); + std::snprintf(buf + len, sizeof(buf) - len, ".%06d", frac); + return buf; +} + /* * Important! Read this before changing this file: * @@ -141,7 +168,8 @@ CMMCore::CMMCore() : bufferManager_(nullptr), pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), - pPostedErrorsLock_(NULL) + pPostedErrorsLock_(NULL), + includeSystemStateCache_(true) { configGroups_ = new ConfigGroupCollection(); pixelSizeGroup_ = new PixelSizeConfigGroup(); @@ -161,6 +189,9 @@ CMMCore::CMMCore() : } CreateCoreProperties(); + + // Initialize the start time for time-stamp calculations + startTime_ = std::chrono::steady_clock::now(); } /** @@ -2660,6 +2691,178 @@ bool CMMCore::getShutterOpen() throw (CMMError) return getShutterOpen(shutterLabel.c_str()); } +/** + * A centralized function that adds all the metadata for camera devices + * + * This was previously spread among the circular buffer, corecallback.h, and + * the SWIG wrapper. + * + * Get the metadata tags attached to device caller, and merge them with metadata + * in pMd (if not null). Returns a metadata object. + */ +void CMMCore::AddCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata) +{ + // Essential metadata for interpreting the image: Width, height, and pixel type + md.PutImageTag(MM::g_Keyword_Metadata_Width, width); + md.PutImageTag(MM::g_Keyword_Metadata_Height, height); + + if (byteDepth == 1) + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY8); + else if (byteDepth == 2) + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY16); + else if (byteDepth == 4) { + if (nComponents == 1) + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY32); + else + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB32); + } else if (byteDepth == 8) + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_RGB64); + else + md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_Unknown); + + + // Needed for higher level code that needs to sort images by camera + if (!md.HasTag(MM::g_Keyword_Metadata_CameraLabel)) { + md.put(MM::g_Keyword_Metadata_CameraLabel, pCam->GetLabel()); + } + + // Additional, non-essential, but nice to have metadata + md.put(MM::g_Keyword_Metadata_BitDepth, CDeviceUtils::ConvertToString((long) pCam->GetBitDepth())); + + // Add ROI metadata + { + unsigned x, y, xSize, ySize; + pCam->GetROI(x, y, xSize, ySize); + std::string roiTag = std::to_string(x) + "-" + std::to_string(y) + "-" + + std::to_string(xSize) + "-" + std::to_string(ySize); + md.put("ROI", roiTag); + } + + try { + std::string binning = pCam->GetProperty("Binning"); + md.put("Binning", binning); + } + catch (const CMMError&) { + // Ignore errors getting binning property. The Java SWIG layer where this was copied from + // had a try catch, so keep it here because its not clear (to me) when this will fail + } + + // Add image number metadata + { + std::lock_guard lock(imageNumbersMutex_); + std::string cameraName = md.GetSingleTag(MM::g_Keyword_Metadata_CameraLabel).GetValue(); + if (imageNumbers_.find(cameraName) == imageNumbers_.end()) + { + imageNumbers_[cameraName] = 0; + } + md.put(MM::g_Keyword_Metadata_ImageNumber, CDeviceUtils::ConvertToString(imageNumbers_[cameraName])); + ++imageNumbers_[cameraName]; + } + + // Add elapsed time metadata if not already set + if (!md.HasTag(MM::g_Keyword_Elapsed_Time_ms)) + { + using namespace std::chrono; + auto elapsed = steady_clock::now() - startTime_; + md.put(MM::g_Keyword_Elapsed_Time_ms, + std::to_string(duration_cast(elapsed).count())); + } + + // Add current system time (as formatted local time) + auto now = std::chrono::system_clock::now(); + md.put(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); + + // Merge any additional camera-specific tags + try + { + std::string serializedMD = pCam->GetTags(); + Metadata devMD; + devMD.Restore(serializedMD.c_str()); + md.Merge(devMD); + } + catch (const CMMError&) + { + // Ignore errors getting tags + } + + // I'm unsure about these. They seem like they should be specific to a camera, but + // they seem to only be defined globally. Leave as is for now. + try { + md.put("PixelSizeUm", CDeviceUtils::ConvertToString(getPixelSizeUm(true))); + + // Get the affine matrix as a string. This is used by the acquistion engine + std::string pixelSizeAffine = ""; + std::vector aff = getPixelSizeAffine(true); + if (aff.size() != 6) + pixelSizeAffine = ""; + else { + std::ostringstream oss; + for (size_t i = 0; i < 5; i++) { + oss << aff[i] << ";"; + } + oss << aff[5]; + pixelSizeAffine = oss.str(); + } + md.put("PixelSizeAffine", pixelSizeAffine); + } + catch (const CMMError&) { + // Ignore errors getting pixel size properties + } + + // Metadata that previously was in the Java SWIG layer and I think may be used by the application + // and/or acquisition engine. However, it doesn't really make sense that this would exist in this + // since channel, slice, position, etc. higher level concepts associated with an acquisition engine + if (addLegacyMetadata) { + md.put("Frame", "0"); + md.put("FrameIndex", "0"); + md.put("Position", "Default"); + md.put("PositionIndex", "0"); + md.put("Slice", "0"); + md.put("SliceIndex", "0"); + + // Add channel metadata + try { + std::string channel = getCurrentConfigFromCache( + getPropertyFromCache(MM::g_Keyword_CoreDevice, MM::g_Keyword_CoreChannelGroup).c_str()); + if (channel.empty()) { + channel = "Default"; + } + md.put("Channel", channel); + md.put("ChannelIndex", "0"); + } + catch (const CMMError&) { + // If channel group not set, use defaults + md.put("Channel", "Default"); + md.put("ChannelIndex", "0"); + } + } + + // Add system state cache if enabled + if (includeSystemStateCache_) { + try { + // MMThreadGuard scg(stateCacheLock_); // Needed? + Configuration state = getSystemStateCache(); + for (size_t i = 0; i < state.size(); ++i) { + PropertySetting setting = state.getSetting(i); + std::string key = setting.getDeviceLabel() + "-" + setting.getPropertyName(); + std::string value = setting.getPropertyValue(); + md.put(key, value); + } + } + catch (const CMMError&) { + // Ignore errors getting system state cache + } + } +} + +void CMMCore::AddCameraMetadata(std::shared_ptr pCam, Metadata& md, bool addLegacyMetadata) +{ + AddCameraMetadata(pCam, md, pCam->GetImageWidth(), pCam->GetImageHeight(), + pCam->GetImageBytesPerPixel(), pCam->GetNumberOfComponents(), addLegacyMetadata); +} + + /** * Exposes the internal image buffer. * @@ -2782,6 +2985,39 @@ void* CMMCore::getImage(unsigned channelNr) throw (CMMError) } } +// Version of snap that also return metadata, so that metadata generation can be centralized in the Core and migrated +// out of the SWIG wrapper. +void* CMMCore::getImage(Metadata& md) throw (CMMError) +{ + void* pBuf = getImage(); + + std::shared_ptr camera = currentCameraDevice_.lock(); + if (!camera) + throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); + + mm::DeviceModuleLockGuard guard(camera); + // Need to do this becuase this data was never inserted into the circular buffer or V2 buffer where this + // metadata is added for other images + AddCameraMetadata(camera, md, true); + + return pBuf; +} + +void* CMMCore::getImage(unsigned channelNr, Metadata& md) throw (CMMError) +{ + void* pBuf = getImage(channelNr); + std::shared_ptr camera = currentCameraDevice_.lock(); + if (!camera) + throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); + + mm::DeviceModuleLockGuard guard(camera); + // Need to do this becuase this data was never inserted into the circular buffer or V2 buffer where this + // metadata is added for other images + AddCameraMetadata(camera, md, true); + return pBuf; +} + + /** * For use with V2 buffer * @@ -2836,18 +3072,23 @@ BufferDataPointer* CMMCore::getImagePointer() throw (CMMError) imageProcessor->Process((unsigned char*)pBuf, camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel() ); } - // Unlike the other getImage() methods, this one copies the image - // into the v2 buffer. This is faster than the copying that would otherwise - // occure in the SWIG wrapper (at least in Java) because it can use multi-threaded, - // low-level copying. - bufferManager_->InsertImage(camera->GetLabel().c_str(), (unsigned char*)pBuf, - camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel(), - camera->GetNumberOfComponents(), nullptr); - - // TODO: could add a check here that there there is one and only one image to pop - // because otherwise the user may be using this incorrectly. - const void* ptr = bufferManager_->PopNextImage(); - return new BufferDataPointer(bufferManager_, ptr); + + Metadata md; + AddCameraMetadata(camera, md, false); + bufferManager_->InsertData(camera->GetLabel().c_str(), (unsigned char*)pBuf, + camera->GetImageWidth() * camera->GetImageHeight() * camera->GetImageBytesPerPixel(), &md); + + if (bufferManager_->GetOverwriteData()) { + // If in overwrite mode (e.g. live mode), peek the last data + const void* ptr = bufferManager_->GetLastData(); + return new BufferDataPointer(bufferManager_, ptr); + } else { + // If not in overwrite mode, pop the next data + // TODO: could add a check here that there there is one and only one image to pop + // because otherwise the user may be using this incorrectly. + const void* ptr = bufferManager_->PopNextData(); + return new BufferDataPointer(bufferManager_, ptr); + } } catch( CMMError& e){ throw e; @@ -2908,16 +3149,24 @@ void CMMCore::startSequenceAcquisition(long numImages, double intervalMs, bool s try { - if (!bufferManager_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) - { - logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + { + logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } + } + if (!bufferManager_->IsUsingV2Buffer()) { + // V2 buffer does not support this, because its design is such that data + // could still be read out even when a new sequence is started. + bufferManager_->GetCircularBuffer()->Clear(); } - bufferManager_->Clear(); // Disable overwriting for finite sequence acquisition bufferManager_->SetOverwriteData(false); mm::DeviceModuleLockGuard guard(camera); + startTime_ = std::chrono::steady_clock::now(); + imageNumbers_.clear(); LOG_DEBUG(coreLogger_) << "Will start sequence acquisition from default camera"; int nRet = camera->StartSequenceAcquisition(numImages, intervalMs, stopOnOverflow); if (nRet != DEVICE_OK) @@ -2954,15 +3203,19 @@ void CMMCore::startSequenceAcquisition(const char* label, long numImages, double throw CMMError(getCoreErrorText(MMERR_NotAllowedDuringSequenceAcquisition).c_str(), MMERR_NotAllowedDuringSequenceAcquisition); - if (!bufferManager_->Initialize(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), pCam->GetImageHeight(), pCam->GetImageBytesPerPixel())) - { - logError(getDeviceName(pCam).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->GetCircularBuffer()->Initialize(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), pCam->GetImageHeight(), pCam->GetImageBytesPerPixel())) + { + logError(getDeviceName(pCam).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } + bufferManager_->GetCircularBuffer()->Clear(); } - bufferManager_->Clear(); // Disable overwriting for finite sequence acquisition bufferManager_->SetOverwriteData(false); + startTime_ = std::chrono::steady_clock::now(); + imageNumbers_.clear(); LOG_DEBUG(coreLogger_) << "Will start sequence acquisition from camera " << label; int nRet = pCam->StartSequenceAcquisition(numImages, intervalMs, stopOnOverflow); @@ -3003,22 +3256,24 @@ void CMMCore::prepareSequenceAcquisition(const char* label) throw (CMMError) */ void CMMCore::initializeCircularBuffer() throw (CMMError) { - std::shared_ptr camera = currentCameraDevice_.lock(); - if (camera) - { - mm::DeviceModuleLockGuard guard(camera); - if (!bufferManager_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferManager_->IsUsingV2Buffer()) { + std::shared_ptr camera = currentCameraDevice_.lock(); + if (camera) { - logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + mm::DeviceModuleLockGuard guard(camera); + if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + { + logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } + bufferManager_->GetCircularBuffer()->Clear(); } - bufferManager_->Clear(); - } - else - { - throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); + else + { + throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); + } + LOG_DEBUG(coreLogger_) << "Circular buffer initialized based on current camera"; } - LOG_DEBUG(coreLogger_) << "Circular buffer initialized based on current camera"; } /** @@ -3059,15 +3314,20 @@ void CMMCore::startContinuousSequenceAcquisition(double intervalMs) throw (CMMEr ,MMERR_NotAllowedDuringSequenceAcquisition); } - if (!bufferManager_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) - { - logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + // Legacy calls for circular buffer + if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + { + logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } + bufferManager_->GetCircularBuffer()->Clear(); } - bufferManager_->Clear(); - // Enable overwriting for continuous sequence acquisition. This only applies to the v2 buffer. - // and has no effect on the circular buffer. + + // Enable overwriting for continuous sequence acquisition bufferManager_->SetOverwriteData(true); + startTime_ = std::chrono::steady_clock::now(); + imageNumbers_.clear(); LOG_DEBUG(coreLogger_) << "Will start continuous sequence acquisition from current camera"; int nRet = camera->StartSequenceAcquisition(intervalMs); if (nRet != DEVICE_OK) @@ -3163,7 +3423,7 @@ void* CMMCore::getLastImage() throw (CMMError) } } - const void* pBuf = bufferManager_->GetLastImage(); + const void* pBuf = bufferManager_->GetLastData(); if (pBuf != 0) return const_cast(pBuf); else @@ -3179,7 +3439,7 @@ void* CMMCore::getLastImageMD(unsigned channel, unsigned slice, Metadata& md) co if (slice != 0) throw CMMError("Slice must be 0"); - return const_cast(bufferManager_->GetLastImageMD(channel, md)); + return const_cast(bufferManager_->GetLastDataMD(channel, md)); } /** @@ -3213,7 +3473,7 @@ void* CMMCore::getLastImageMD(Metadata& md) const throw (CMMError) */ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw (CMMError) { - return const_cast(bufferManager_->GetNthImageMD(n, md)); + return const_cast(bufferManager_->GetNthDataMD(n, md)); } /** @@ -3230,7 +3490,7 @@ void* CMMCore::getNBeforeLastImageMD(unsigned long n, Metadata& md) const throw */ void* CMMCore::popNextImage() throw (CMMError) { - const void* pBuf = bufferManager_->PopNextImage(); + const void* pBuf = bufferManager_->PopNextData(); if (pBuf != 0) return const_cast(pBuf); else @@ -3248,7 +3508,7 @@ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) th if (slice != 0) throw CMMError("Slice must be 0"); - return const_cast(bufferManager_->PopNextImageMD(channel, md)); + return const_cast(bufferManager_->PopNextDataMD(channel, md)); } /** @@ -3260,51 +3520,51 @@ void* CMMCore::popNextImageMD(Metadata& md) throw (CMMError) } //// Data pointer access for v2 Buffer -BufferDataPointer* CMMCore::getLastImagePointer() throw (CMMError) { +BufferDataPointer* CMMCore::getLastDataPointer() throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - const void* rawPtr = bufferManager_->GetLastImage(); + const void* rawPtr = bufferManager_->GetLastData(); return new BufferDataPointer(bufferManager_, rawPtr); } -BufferDataPointer* CMMCore::popNextImagePointer() throw (CMMError) { +BufferDataPointer* CMMCore::popNextDataPointer() throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - const void* rawPtr = bufferManager_->PopNextImage(); + const void* rawPtr = bufferManager_->PopNextData(); return new BufferDataPointer(bufferManager_, rawPtr); } -BufferDataPointer* CMMCore::getLastImageMDPointer(Metadata& md) const throw (CMMError) { +BufferDataPointer* CMMCore::getLastDataMDPointer(Metadata& md) const throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - const void* rawPtr = bufferManager_->GetLastImageMD(md); + const void* rawPtr = bufferManager_->GetLastDataMD(md); return new BufferDataPointer(bufferManager_, rawPtr); } -BufferDataPointer* CMMCore::popNextImageMDPointer(Metadata& md) throw (CMMError) { +BufferDataPointer* CMMCore::popNextDataMDPointer(Metadata& md) throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - const void* rawPtr = bufferManager_->PopNextImageMD(md); + const void* rawPtr = bufferManager_->PopNextDataMD(md); return new BufferDataPointer(bufferManager_, rawPtr); } -BufferDataPointer* CMMCore::getLastImageFromDevicePointer(std::string deviceLabel) throw (CMMError) { +BufferDataPointer* CMMCore::getLastDataFromDevicePointer(std::string deviceLabel) throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - const void* rawPtr = bufferManager_->GetLastImageFromDevice(deviceLabel); + const void* rawPtr = bufferManager_->GetLastDataFromDevice(deviceLabel); return new BufferDataPointer(bufferManager_, rawPtr); } -BufferDataPointer* CMMCore::getLastImageMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError) { +BufferDataPointer* CMMCore::getLastDataMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } - const void* rawPtr = bufferManager_->GetLastImageMDFromDevice(deviceLabel, md); + const void* rawPtr = bufferManager_->GetLastDataMDFromDevice(deviceLabel, md); return new BufferDataPointer(bufferManager_, rawPtr); } @@ -3316,7 +3576,24 @@ BufferDataPointer* CMMCore::getLastImageMDFromDevicePointer(std::string deviceLa */ void CMMCore::clearCircularBuffer() throw (CMMError) { - bufferManager_->Clear(); + if (!bufferManager_->IsUsingV2Buffer()) { + resetBuffer(); + } + // No effect on v2 because Reset should be used more carefully +} + +/** + * This method applies to both the circular buffer and the v2 buffer. + * A difference between the circular buffer and v2 buffer is that the v2 buffer + * does require to be empty of images before a new sequence is started. In other + * words, producers can be adding data to it that is asynchronously consumed. + * + * Thus, reset should be used carefully, because it will discard data that a consumer + * may sill asynchronously be waiting to consume + */ +void CMMCore::resetBuffer() throw (CMMError) +{ + bufferManager_->Reset(); } /** @@ -3332,6 +3609,11 @@ void CMMCore::enableV2Buffer(bool enable) throw (CMMError) */ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes ) throw (CMMError) +{ + setBufferMemoryFootprint(sizeMB); +} + +void CMMCore::setBufferMemoryFootprint(unsigned sizeMB) throw (CMMError) { if (isSequenceRunning()) { stopSequenceAcquisition(); @@ -3361,8 +3643,13 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes if (camera) { mm::DeviceModuleLockGuard guard(camera); - if (!bufferManager_->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + if (!bufferManager_->IsUsingV2Buffer()) { + // Circular buffer requires initialization specific to the camera + if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } else { + bufferManager_->ReallocateBuffer(sizeMB); + } } LOG_DEBUG(coreLogger_) << "Did set circular buffer size to " << @@ -3382,6 +3669,12 @@ void CMMCore::setCircularBufferMemoryFootprint(unsigned sizeMB ///< n megabytes * Returns the size of the Circular Buffer in MB */ unsigned CMMCore::getCircularBufferMemoryFootprint() +{ +return getBufferMemoryFootprint(); +} + + +unsigned CMMCore::getBufferMemoryFootprint() const { if (bufferManager_) { @@ -3397,7 +3690,7 @@ long CMMCore::getRemainingImageCount() { if (bufferManager_) { - return bufferManager_->GetRemainingImageCount(); + return bufferManager_->GetRemainingDataCount(); } return 0; } @@ -3412,7 +3705,8 @@ long CMMCore::getBufferTotalCapacity() // Compute image size from the current camera parameters. long imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel(); // Pass the computed image size as an argument to the adapter. - return bufferManager_->GetSize(imageSize); + unsigned int sizeMB = bufferManager_->GetMemorySizeMB(); + return sizeMB / imageSize; } return 0; } @@ -3428,7 +3722,8 @@ long CMMCore::getBufferFreeCapacity() { // Compute image size from the current camera parameters. long imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel(); - return bufferManager_->GetFreeSize(imageSize); + unsigned int sizeMB = bufferManager_->GetFreeSizeMB(); + return sizeMB / imageSize; } return 0; } @@ -4355,24 +4650,6 @@ unsigned CMMCore::getImageBitDepth() return 0; } -unsigned CMMCore::getImageBitDepth(const char* cameraLabel) -{ - std::shared_ptr camera = deviceManager_->GetDeviceOfType(cameraLabel); - if (camera) - { - try - { - mm::DeviceModuleLockGuard guard(camera); - return camera->GetBitDepth(); - } - catch (const CMMError&) // Possibly uninitialized camera - { - // Fall through - } - } - return 0; -} - /** * Returns the number of components the default camera is returning. * For example color camera will return 4 components (RGBA) on each snap. @@ -4405,18 +4682,24 @@ unsigned CMMCore::getImageWidth(DataPtr ptr) { if (!bufferManager_->IsUsingV2Buffer()) { return getImageWidth(); } - if (bufferManager_->IsPointerInBuffer(ptr)) { - return bufferManager_->GetImageWidth(ptr); + if (bufferManager_->IsPointerInV2Buffer(ptr)) { + Metadata md; + bufferManager_->ExtractMetadata(ptr, md); + std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue(); + return static_cast(atoi(sVal.c_str())); } return getImageWidth(); } unsigned CMMCore::getImageHeight(DataPtr ptr) { - if (!bufferManager_->IsUsingV2Buffer()) { - return getImageWidth(); + if (!bufferManager_->IsUsingV2Buffer()) { + return getImageHeight(); } - if (bufferManager_->IsPointerInBuffer(ptr)) { - return bufferManager_->GetImageHeight(ptr); + if (bufferManager_->IsPointerInV2Buffer(ptr)) { + Metadata md; + bufferManager_->ExtractMetadata(ptr, md); + std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue(); + return static_cast(atoi(sVal.c_str())); } return getImageHeight(); } @@ -4425,8 +4708,19 @@ unsigned CMMCore::getBytesPerPixel(DataPtr ptr) { if (!bufferManager_->IsUsingV2Buffer()) { return getBytesPerPixel(); } - if (bufferManager_->IsPointerInBuffer(ptr)) { - return bufferManager_->GetBytesPerPixel(ptr); + if (bufferManager_->IsPointerInV2Buffer(ptr)) { + Metadata md; + bufferManager_->ExtractMetadata(ptr, md); + std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + if (pixelType == MM::g_Keyword_PixelType_GRAY8) + return 1; + else if (pixelType == MM::g_Keyword_PixelType_GRAY16) + return 2; + else if (pixelType == MM::g_Keyword_PixelType_GRAY32 || + pixelType == MM::g_Keyword_PixelType_RGB32) + return 4; + else if (pixelType == MM::g_Keyword_PixelType_RGB64) + return 8; } return getBytesPerPixel(); } @@ -4435,8 +4729,17 @@ unsigned CMMCore::getNumberOfComponents(DataPtr ptr) { if (!bufferManager_->IsUsingV2Buffer()) { return getNumberOfComponents(); } - if (bufferManager_->IsPointerInBuffer(ptr)) { - return bufferManager_->GetNumberOfComponents(ptr); + if (bufferManager_->IsPointerInV2Buffer(ptr)) { + Metadata md; + bufferManager_->ExtractMetadata(ptr, md); + std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + if (pixelType == MM::g_Keyword_PixelType_GRAY8 || + pixelType == MM::g_Keyword_PixelType_GRAY16 || + pixelType == MM::g_Keyword_PixelType_GRAY32) + return 1; + else if (pixelType == MM::g_Keyword_PixelType_RGB32 || + pixelType == MM::g_Keyword_PixelType_RGB64) + return 4; } return getNumberOfComponents(); } @@ -4445,8 +4748,8 @@ unsigned CMMCore::getSizeBytes(DataPtr ptr) { if (!bufferManager_->IsUsingV2Buffer()) { return getImageWidth() * getImageHeight() * getBytesPerPixel(); } - if (bufferManager_->IsPointerInBuffer(ptr)) { - return bufferManager_->GetDatumSize(ptr); + if (bufferManager_->IsPointerInV2Buffer(ptr)) { + return bufferManager_->GetDataSize(ptr); } return getImageBufferSize(); } @@ -4457,6 +4760,10 @@ void CMMCore::releaseReadAccess(DataPtr ptr) { } } +bool CMMCore::IsPointerInV2Buffer(DataPtr ptr) { + return bufferManager_->IsPointerInV2Buffer(ptr); +} + /** * Returns the number of simultaneous channels the default camera is returning. */ @@ -4617,11 +4924,13 @@ void CMMCore::setROI(int x, int y, int xSize, int ySize) throw (CMMError) if (nRet != DEVICE_OK) throw CMMError(getDeviceErrorText(nRet, camera).c_str(), MMERR_DEVICE_GENERIC); - // Any images left over in the sequence buffer may have sizes - // inconsistent with the current image size. There is no way to "fix" - // popNextImage() to handle this correctly, so we need to make sure we - // discard such images. - bufferManager_->Clear(); + // When using the circularBuffer: Any images left over in the sequence + // buffer may have sizes inconsistent with the current image size. + // There is no way to "fix" popNextImage() to handle this correctly, + // so we need to make sure we discard such images. + if (! bufferManager_->IsUsingV2Buffer()) { + bufferManager_->GetCircularBuffer()->Clear(); + } } else throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); @@ -4700,7 +5009,9 @@ void CMMCore::setROI(const char* label, int x, int y, int xSize, int ySize) thro // inconsistent with the current image size. There is no way to "fix" // popNextImage() to handle this correctly, so we need to make sure we // discard such images. - bufferManager_->Clear(); + if (!bufferManager_->IsUsingV2Buffer()) { + bufferManager_->GetCircularBuffer()->Clear(); + } } else throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); @@ -4755,11 +5066,13 @@ void CMMCore::clearROI() throw (CMMError) if (nRet != DEVICE_OK) throw CMMError(getDeviceErrorText(nRet, camera).c_str(), MMERR_DEVICE_GENERIC); - // Any images left over in the sequence buffer may have sizes - // inconsistent with the current image size. There is no way to "fix" - // popNextImage() to handle this correctly, so we need to make sure we - // discard such images. - bufferManager_->Clear(); + // When using the circularBuffer: Any images left over in the sequence + // buffer may have sizes inconsistent with the current image size. + // There is no way to "fix" popNextImage() to handle this correctly, + // so we need to make sure we discard such images. + if (!bufferManager_->IsUsingV2Buffer()) { + bufferManager_->GetCircularBuffer()->Clear(); + } } } diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 62e75a3a7..824982242 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -384,13 +384,13 @@ class CMMCore void snapImage() throw (CMMError); void* getImage() throw (CMMError); void* getImage(unsigned numChannel) throw (CMMError); - + void* getImage(Metadata& md) throw (CMMError); + void* getImage(unsigned numChannel, Metadata& md) throw (CMMError); unsigned getImageWidth(); unsigned getImageHeight(); unsigned getBytesPerPixel(); unsigned getImageBitDepth(); - unsigned getImageBitDepth(const char* cameraLabel); unsigned getNumberOfComponents(); unsigned getNumberOfCameraChannels(); std::string getCameraChannelName(unsigned int channelNr); @@ -430,11 +430,27 @@ class CMMCore long getBufferTotalCapacity(); long getBufferFreeCapacity(); bool isBufferOverflowed() const; + + /** + * @deprecated Use setBufferMemoryFootprint() instead + */ void setCircularBufferMemoryFootprint(unsigned sizeMB) throw (CMMError); + /** + * @deprecated Use getBufferMemoryFootprint() instead + */ unsigned getCircularBufferMemoryFootprint(); + + // This is only needed for the circular buffer, because it needs to match the camera settings. Should it be deprecated? void initializeCircularBuffer() throw (CMMError); + /** + * @deprecated Use resetBuffer() instead + */ void clearCircularBuffer() throw (CMMError); + void setBufferMemoryFootprint(unsigned sizeMB) throw (CMMError); + unsigned getBufferMemoryFootprint() const; + void resetBuffer() throw (CMMError); + ///@} /** \name v2 buffer control. */ @@ -455,14 +471,20 @@ class CMMCore // Same functionality as non pointer versions above, but // enables alternative wrappers in SWIG for pointer-based access to the image data. + + // This one is "Image" not "Data" because it corresponds to SnapImage()/GetImage() BufferDataPointer* getImagePointer() throw (CMMError); - BufferDataPointer* getLastImagePointer() throw (CMMError); - BufferDataPointer* popNextImagePointer() throw (CMMError); - BufferDataPointer* getLastImageMDPointer(Metadata& md) const throw (CMMError); - BufferDataPointer* popNextImageMDPointer(Metadata& md) throw (CMMError); - BufferDataPointer* getLastImageFromDevicePointer(std::string deviceLabel) throw (CMMError); - BufferDataPointer* getLastImageMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError); + // These are "Data" not "Image" because they can be used generically for any data type + // Higher level wrapping code can read their associated metadata to determine their data type + BufferDataPointer* getLastDataPointer() throw (CMMError); + BufferDataPointer* popNextDataPointer() throw (CMMError); + BufferDataPointer* getLastDataMDPointer(Metadata& md) const throw (CMMError); + BufferDataPointer* popNextDataMDPointer(Metadata& md) throw (CMMError); + BufferDataPointer* getLastDataFromDevicePointer(std::string deviceLabel) throw (CMMError); + BufferDataPointer* getLastDataMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError); + + bool IsPointerInV2Buffer(DataPtr ptr); ///@} @@ -666,6 +688,15 @@ class CMMCore std::vector getLoadedPeripheralDevices(const char* hubLabel) throw (CMMError); ///@} + /** \name Image metadata. */ + ///@{ + bool getIncludeSystemStateCache() { + return includeSystemStateCache_; + } + void setIncludeSystemStateCache(bool state) { + includeSystemStateCache_ = state; + } + ///@} private: // make object non-copyable @@ -715,6 +746,12 @@ class CMMCore MMThreadLock* pPostedErrorsLock_; mutable std::deque > postedErrors_; + // For adding camera metadata + std::map imageNumbers_; // Track image numbers per camera + std::mutex imageNumbersMutex_; + std::chrono::steady_clock::time_point startTime_; // Start time for elapsed time calculations in seuqence acquisition + bool includeSystemStateCache_; + private: void InitializeErrorMessages(); void CreateCoreProperties(); @@ -742,6 +779,10 @@ class CMMCore void initializeAllDevicesSerial() throw (CMMError); void initializeAllDevicesParallel() throw (CMMError); int initializeVectorOfDevices(std::vector, std::string> > pDevices); + + void AddCameraMetadata(std::shared_ptr pCam, Metadata& md, bool addLegacyMetadata); + void AddCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata); }; #if defined(__GNUC__) && !defined(__clang__) diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index e7eb7d711..e6aeaafd3 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -856,13 +856,12 @@ %typemap(javacode) CMMCore %{ private boolean includeSystemStateCache_ = true; - public boolean getIncludeSystemStateCache() { - return includeSystemStateCache_; - } - public void setIncludeSystemStateCache(boolean state) { - includeSystemStateCache_ = state; - } - + // public boolean getIncludeSystemStateCache() { + // return includeSystemStateCache_; + // } + // public void setIncludeSystemStateCache(boolean state) { + // includeSystemStateCache_ = state; + // } private JSONObject metadataToMap(Metadata md) { JSONObject tags = new JSONObject(); @@ -874,21 +873,38 @@ return tags; } - private String getROITag() throws java.lang.Exception { - return getROITag(getCameraDevice()); - } + // Its probably a good idea to minimize the complexity of this SWIG layer, + // including the amount of metadata added here. However, all the metadata + // added for SnapImage is generated here, and the current Core API doesn't + // give metadata along with returning the snapimage buffer + + . In the future, routing everything + // through the bufferManager, and a new camera API can solve this so that + // all images go through a common route and can have consistent metadata added. + + + // TODO: deal with this + private JSONObject createSnapImageTags() throws java.lang.Exception { + JSONObject tags = new JSONObject(); + tags.put("Width", getImageWidth()); + tags.put("Height", getImageHeight()); + int bytesPerPixel = (int) getBytesPerPixel(); + int numComponents = (int) getNumberOfComponents(); + tags.put("PixelType", getPixelType(bytesPerPixel, numComponents)); - private String getROITag(String cameraLabel) throws java.lang.Exception { - String roi = ""; - int [] x = new int[1]; - int [] y = new int[1]; - int [] xSize = new int[1]; - int [] ySize = new int[1]; - getROI(cameraLabel, x, y, xSize, ySize); - roi += x[0] + "-" + y[0] + "-" + xSize[0] + "-" + ySize[0]; - return roi; + // These get added in corecallback.cpp + tags.put("BitDepth", getImageBitDepth()); + tags.put("ROI", getROITag()); + try { + tags.put("Binning", getProperty(camera, "Binning")); + } catch (Exception ex) {} + + return tags; } + + + // TODO add to global private String getPixelType(int depth, int numComponents) throws java.lang.Exception { switch (depth) { case 1: @@ -944,44 +960,28 @@ return image; } - private TaggedImage createTaggedImage(Object pixels, Metadata md) throws java.lang.Exception { - JSONObject tags = metadataToMap(md); - PropertySetting setting; - if (includeSystemStateCache_) { - Configuration config = getSystemStateCache(); - for (int i = 0; i < config.size(); ++i) { - setting = config.getSetting(i); - String key = setting.getDeviceLabel() + "-" + setting.getPropertyName(); - String value = setting.getPropertyValue(); - tags.put(key, value); - } - } - tags.put("BitDepth", getImageBitDepth()); - tags.put("PixelSizeUm", getPixelSizeUm(true)); - tags.put("PixelSizeAffine", getPixelSizeAffineAsString()); - tags.put("ROI", getROITag()); - tags.put("Width", getImageWidth()); - tags.put("Height", getImageHeight()); - int bytesPerPixel = (int) getBytesPerPixel(); - int numComponents = (int) getNumberOfComponents(); - tags.put("PixelType", getPixelType(bytesPerPixel, numComponents)); - tags.put("Frame", 0); - tags.put("FrameIndex", 0); - tags.put("Position", "Default"); - tags.put("PositionIndex", 0); - tags.put("Slice", 0); - tags.put("SliceIndex", 0); - String channel = getCurrentConfigFromCache(getPropertyFromCache("Core","ChannelGroup")); - if ((channel == null) || (channel.length() == 0)) { - channel = "Default"; - } - tags.put("Channel", channel); - tags.put("ChannelIndex", 0); + private TaggedImagePointer createTaggedImagePointer(BufferDataPointer pointer) throws java.lang.Exception { + // This only ever gets called by the V2 buffer, is called by the Pointer functions that have a different signature + // Thus, we do not need to maintain backwards compatibility for higher level code that calls the other functions. + // So we we only add the metadata that actually makes sense for the current abstraction. - try { - tags.put("Binning", getProperty(getCameraDevice(), "Binning")); - } catch (Exception ex) {} + // Some metadata is added here, the rest is added lazily when the image is loaded + JSONObject tagsToAdd = new JSONObject(); + + addSystemStateCacheTags(tagsToAdd); + + + TaggedImagePointer imagePointer = new TaggedImagePointer(pointer); + } + + private TaggedImage createTaggedImage(Object pixels, Metadata md, JSONObject moreTags) throws java.lang.Exception { + return createTaggedImage(pixels, md, null, tags); + } + + private TaggedImage createTaggedImage(Object pixels, Metadata md, Long pointer, JSONObject moreTags) throws java.lang.Exception { + JSONObject tags = metadataToMap(md); + tags.putAll(moreTags); return new TaggedImage(pixels, tags); } @@ -990,7 +990,8 @@ public TaggedImage getTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); Object pixels = getImage(cameraChannelIndex); - return createTaggedImage(pixels, md, cameraChannelIndex); + JSONObject tags = createSnapImageTags(); + return createTaggedImage(pixels, md, cameraChannelIndex, tags); } public TaggedImage getTaggedImage() throws java.lang.Exception { @@ -1085,9 +1086,7 @@ } /** - * Convenience function. Retuns affine transform as a String - * Used in this class and by the acquisition engine - * (rather than duplicating this code there + * Used the acquisition engine, also in AddCameraMetada in core */ public String getPixelSizeAffineAsString() throws java.lang.Exception { String pa = ""; diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/LazyJSONObject.java b/MMCoreJ_wrap/src/main/java/mmcorej/LazyJSONObject.java new file mode 100644 index 000000000..5491f9635 --- /dev/null +++ b/MMCoreJ_wrap/src/main/java/mmcorej/LazyJSONObject.java @@ -0,0 +1,97 @@ +package mmcorej.org.json; + +/** + * A JSONObject that lazily initializes its contents from a BufferDataPointer. + */ +class LazyJSONObject extends JSONObject { + private final BufferDataPointer dataPointer_; + private boolean initialized_ = false; + + + public LazyJSONObject(BufferDataPointer dataPointer) { + this.dataPointer_ = dataPointer; + } + + synchronized void initializeIfNeeded() throws JSONException { + if (!initialized_) { + try { + Metadata md = new Metadata(); + dataPointer_.getMetadata(md); + + for (String key : md.GetKeys()) { + try { + put(key, md.GetSingleTag(key).GetValue()); + } catch (Exception e) { + throw new JSONException("Failed to get value for key: " + key, e); + } + } + initialized_ = true; + } catch (Exception e) { + throw new JSONException("Failed to initialize metadata", e); + } + } + } + + @Override + public Object get(String key) throws JSONException { + initializeIfNeeded(); + return super.get(key); + } + + @Override + public JSONObject put(String key, Object value) throws JSONException { + initializeIfNeeded(); + return super.put(key, value); + } + + @Override + public Object opt(String key) { + try { + initializeIfNeeded(); + } catch (JSONException e) { + return null; // matches parent class behavior for missing keys + } + return super.opt(key); + } + + @Override + public boolean has(String key) { + try { + initializeIfNeeded(); + } catch (JSONException e) { + return false; + } + return super.has(key); + } + + @Override + public Iterator keys() { + try { + initializeIfNeeded(); + } catch (JSONException e) { + // Return empty iterator if initialization fails + return Collections.emptyList().iterator(); + } + return super.keys(); + } + + @Override + public int length() { + try { + initializeIfNeeded(); + } catch (JSONException e) { + return 0; + } + return super.length(); + } + + @Override + public Object remove(String key) { + try { + initializeIfNeeded(); + } catch (JSONException e) { + return null; + } + return super.remove(key); + } +} diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java index 4d248b51a..37a79c342 100644 --- a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java +++ b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java @@ -2,6 +2,8 @@ import mmcorej.org.json.JSONObject; import mmcorej.org.json.JSONException; +import java.util.Iterator; +import java.util.Collections; /** * TaggedImagePointer is a wrapper around a pointer to an image in the v2 buffer. @@ -17,22 +19,18 @@ public class TaggedImagePointer extends TaggedImage { public LazyJSONObject tags; - private final long address_; - private final CMMCore core_; + private final BufferDataPointer dataPointer_; private boolean released_ = false; /** * Constructs a new TaggedImagePointer. * - * @param address Memory address of the image data in native code - * @param tags JSONObject containing metadata associated with the image - * @param core Reference to the CMMCore instance managing the native resources + * @param dataPointer BufferDataPointer to the image data */ - public TaggedImagePointer(long address, CMMCore core) { + public TaggedImagePointer(BufferDataPointer dataPointer) { super(null, null); // Initialize parent with null pix - this.address_ = address; - this.core_ = core; - this.tags = new LazyJSONObject(address, core); + this.dataPointer_ = dataPointer; + this.tags = new LazyJSONObject(dataPointer); } /** @@ -51,12 +49,11 @@ public synchronized void loadData() throws IllegalStateException { if (this.pix == null) { try { - this.pix = core_.copyDataAtPointer(address_); + this.pix = dataPointer_.getData(); tags.initializeIfNeeded(); } catch (Exception e) { - throw new IllegalStateException("Failed to copy data at pointer", e); + throw new IllegalStateException("Failed to get pixel data", e); } - // Now that we have the data, release the pointer release(); } } @@ -72,11 +69,7 @@ public synchronized void loadData() throws IllegalStateException { */ public synchronized void release() { if (!released_) { - try { - core_.releaseReadAccess(address_); - } catch (Exception e) { - throw new IllegalStateException("Failed to release read access to image buffer", e); - } + dataPointer_.release(); released_ = true; } } @@ -93,39 +86,3 @@ protected void finalize() throws Throwable { } } - -class LazyJSONObject extends JSONObject { - private final long metadataPtr_; - private final CMMCore core_; - private boolean initialized_ = false; - - - public LazyJSONObject(long metadataPtr, CMMCore core) { - this.metadataPtr_ = metadataPtr; - this.core_ = core; - } - - synchronized void initializeIfNeeded() throws Exception { - if (!initialized_) { - Metadata md = new Metadata(); - this.core_.copyMetadataAtPointer(metadataPtr_, md); - - for (String key:md.GetKeys()) { - try { - this.put(key, md.GetSingleTag(key).GetValue()); - } catch (Exception e) {} - } - initialized_ = true; - } - } - - @Override - public Object get(String key) throws JSONException { - try { - initializeIfNeeded(); - } catch (Exception e) { - throw new RuntimeException("Failed to initialize metadata", e); - } - return super.get(key); - } -} diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 84cef7177..2106d1639 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1542,7 +1542,6 @@ class CCameraBase : public CDeviceBase char label[MM::MaxStrLength]; this->GetLabel(label); Metadata md; - md.put(MM::g_Keyword_Metadata_CameraLabel, label); int ret = GetCoreCallback()->InsertImage(this, GetImageBuffer(), GetImageWidth(), GetImageHeight(), GetImageBytesPerPixel(), md.Serialize().c_str()); diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 690d4e9a2..9807089c8 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -165,7 +165,7 @@ namespace MM { const char* const g_Keyword_Metadata_Width = "Width"; const char* const g_Keyword_Metadata_Height = "Height"; const char* const g_Keyword_Metadata_CameraLabel = "Camera"; - const char* const g_Keyword_Metadata_DataSourceDeviceLabel = "DataSourceDevice"; + const char* const g_Keyword_Metadata_BitDepth = "BitDepth"; const char* const g_Keyword_Metadata_Exposure = "Exposure-ms"; MM_DEPRECATED(const char* const g_Keyword_Meatdata_Exposure) = "Exposure-ms"; // Typo const char* const g_Keyword_Metadata_Score = "Score"; From 475e9c3fe59254b0d998b63ea6498178f9f6f49a Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 19 Feb 2025 13:35:21 -0800 Subject: [PATCH 30/46] fix mmcorej compilation bugs --- MMCore/BufferDataPointer.h | 38 +--- MMCore/CoreCallback.cpp | 6 +- MMCore/MMCore.cpp | 185 +++++++++-------- MMCore/MMCore.h | 32 +-- MMCoreJ_wrap/MMCoreJ.i | 392 ++++++++++++++----------------------- 5 files changed, 271 insertions(+), 382 deletions(-) diff --git a/MMCore/BufferDataPointer.h b/MMCore/BufferDataPointer.h index e6767abef..92711897d 100644 --- a/MMCore/BufferDataPointer.h +++ b/MMCore/BufferDataPointer.h @@ -58,6 +58,15 @@ class BufferDataPointer { return ptr_; } + void getImageProperties(int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { + if (!bufferManager_ || !ptr_) { + throw CMMError("Invalid buffer manager or pointer"); + } + Metadata md; + bufferManager_->ExtractMetadata(ptr_, md); + CMMCore::parseImageMetadata(md, width, height, byteDepth, nComponents); + } + // Fills the provided Metadata object with metadata extracted from the pointer. // It encapsulates calling the core API function that copies metadata from the buffer. void getMetadata(Metadata &md) const { @@ -85,35 +94,6 @@ class BufferDataPointer { } } - // TODO if an when these are needed, make them a call a single functions that reads width and height together - // unsigned getImageWidth() const { - // if (bufferManager_ && ptr_) { - // return bufferManager_->GetImageWidth(ptr_); - // } - // return 0; - // } - - // unsigned getImageHeight() const { - // if (bufferManager_ && ptr_) { - // return bufferManager_->GetImageHeight(ptr_); - // } - // return 0; - // } - - // unsigned getBytesPerPixel() const { - // if (bufferManager_ && ptr_) { - // return bufferManager_->GetBytesPerPixel(ptr_); - // } - // return 0; - // } - // - // unsigned getNumberOfComponents() const { - // if (bufferManager_ && ptr_) { - // return bufferManager_->GetNumberOfComponents(ptr_); - // } - // return 0; - // } - unsigned getSizeBytes() const { if (bufferManager_ && ptr_) { return bufferManager_->GetDataSize(ptr_); diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index c40c66b8e..244b79f1a 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -242,7 +242,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf { // convert device to camera std::shared_ptr camera = std::dynamic_pointer_cast(device); - core_->AddCameraMetadata(camera, newMD, width, height, byteDepth, nComponents, true); + core_->addCameraMetadata(camera, newMD, width, height, byteDepth, nComponents, true); } if(doProcess) @@ -297,7 +297,7 @@ int CoreCallback::AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSiz Metadata md; std::shared_ptr camera = std::dynamic_pointer_cast(core_->deviceManager_->GetDevice(caller)); // Add the metadata needed for interpreting camera images - core_->AddCameraMetadata(camera, md, width, height, byteDepth, nComponents, true); + core_->addCameraMetadata(camera, md, width, height, byteDepth, nComponents, true); char label[MM::MaxStrLength]; caller->GetLabel(label); @@ -368,7 +368,7 @@ int CoreCallback:: InsertMultiChannel(const MM::Device* caller, const unsigned c { // convert device to camera std::shared_ptr camera = std::dynamic_pointer_cast(device); - core_->AddCameraMetadata(camera, newMD, width, height, byteDepth, 1, true); + core_->addCameraMetadata(camera, newMD, width, height, byteDepth, 1, true); } MM::ImageProcessor* ip = GetImageProcessor(caller); diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 91a2e91e2..81cfed165 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -2700,8 +2700,8 @@ bool CMMCore::getShutterOpen() throw (CMMError) * Get the metadata tags attached to device caller, and merge them with metadata * in pMd (if not null). Returns a metadata object. */ -void CMMCore::AddCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata) +void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, + unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata) throw (CMMError) { // Essential metadata for interpreting the image: Width, height, and pixel type md.PutImageTag(MM::g_Keyword_Metadata_Width, width); @@ -2821,7 +2821,7 @@ void CMMCore::AddCameraMetadata(std::shared_ptr pCam, Metadata& md.put("Slice", "0"); md.put("SliceIndex", "0"); - // Add channel metadata + // Add channel metadata if not already set try { std::string channel = getCurrentConfigFromCache( getPropertyFromCache(MM::g_Keyword_CoreDevice, MM::g_Keyword_CoreChannelGroup).c_str()); @@ -2836,6 +2836,7 @@ void CMMCore::AddCameraMetadata(std::shared_ptr pCam, Metadata& md.put("Channel", "Default"); md.put("ChannelIndex", "0"); } + } // Add system state cache if enabled @@ -2856,13 +2857,37 @@ void CMMCore::AddCameraMetadata(std::shared_ptr pCam, Metadata& } } -void CMMCore::AddCameraMetadata(std::shared_ptr pCam, Metadata& md, bool addLegacyMetadata) +void CMMCore::addMultiCameraMetadata(Metadata& md, int cameraChannelIndex = 0) const { - AddCameraMetadata(pCam, md, pCam->GetImageWidth(), pCam->GetImageHeight(), - pCam->GetImageBytesPerPixel(), pCam->GetNumberOfComponents(), addLegacyMetadata); + // Multi-camera device adapter metadata. This is considered legacy since the v2 buffer + // make the multi-camera adapter unnecessary. + + if (!md.HasTag("CameraChannelIndex")) { + md.put("CameraChannelIndex", ToString(cameraChannelIndex)); + md.put("ChannelIndex", ToString(cameraChannelIndex)); + } + + if (!md.HasTag("Camera")) { + // Get the core camera name + std::string coreCamera; + try { + coreCamera = md.GetSingleTag("Core-Camera").GetValue(); + // Construct physical camera key (e.g. "Camera-Physical Camera 1") + std::string physCamKey = coreCamera + "-Physical Camera " + ToString(cameraChannelIndex + 1); + + // Check if physical camera metadata exists + if (md.HasTag(physCamKey.c_str())) { + std::string physicalCamera = md.GetSingleTag(physCamKey.c_str()).GetValue(); + md.put("Camera", physicalCamera); + md.put("Channel", physicalCamera); + } + } + catch (const CMMError&) { + // If core camera metadata not found, ignore + } + } } - /** * Exposes the internal image buffer. * @@ -2987,7 +3012,7 @@ void* CMMCore::getImage(unsigned channelNr) throw (CMMError) // Version of snap that also return metadata, so that metadata generation can be centralized in the Core and migrated // out of the SWIG wrapper. -void* CMMCore::getImage(Metadata& md) throw (CMMError) +void* CMMCore::getImageMD(Metadata& md) throw (CMMError) { void* pBuf = getImage(); @@ -2997,13 +3022,14 @@ void* CMMCore::getImage(Metadata& md) throw (CMMError) mm::DeviceModuleLockGuard guard(camera); // Need to do this becuase this data was never inserted into the circular buffer or V2 buffer where this - // metadata is added for other images - AddCameraMetadata(camera, md, true); + // metadata is added for other images + addCameraMetadata(camera, md, camera->GetImageWidth(), camera->GetImageHeight(), + camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents(), true); return pBuf; } -void* CMMCore::getImage(unsigned channelNr, Metadata& md) throw (CMMError) +void* CMMCore::getImageMD(unsigned channelNr, Metadata& md) throw (CMMError) { void* pBuf = getImage(channelNr); std::shared_ptr camera = currentCameraDevice_.lock(); @@ -3013,7 +3039,9 @@ void* CMMCore::getImage(unsigned channelNr, Metadata& md) throw (CMMError) mm::DeviceModuleLockGuard guard(camera); // Need to do this becuase this data was never inserted into the circular buffer or V2 buffer where this // metadata is added for other images - AddCameraMetadata(camera, md, true); + addCameraMetadata(camera, md, camera->GetImageWidth(), camera->GetImageHeight(), + camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents(), true); + addMultiCameraMetadata(md, channelNr); return pBuf; } @@ -3074,9 +3102,11 @@ BufferDataPointer* CMMCore::getImagePointer() throw (CMMError) Metadata md; - AddCameraMetadata(camera, md, false); - bufferManager_->InsertData(camera->GetLabel().c_str(), (unsigned char*)pBuf, - camera->GetImageWidth() * camera->GetImageHeight() * camera->GetImageBytesPerPixel(), &md); + // Add metadata to know how to interpret the data added to v2 buffer + addCameraMetadata(camera, md, camera->GetImageWidth(), camera->GetImageHeight(), + camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents(), true); + unsigned imageSize = camera->GetImageWidth() * camera->GetImageHeight() * camera->GetImageBytesPerPixel(); + bufferManager_->InsertData(camera->GetLabel().c_str(), (unsigned char*)pBuf, imageSize, &md); if (bufferManager_->GetOverwriteData()) { // If in overwrite mode (e.g. live mode), peek the last data @@ -3439,7 +3469,9 @@ void* CMMCore::getLastImageMD(unsigned channel, unsigned slice, Metadata& md) co if (slice != 0) throw CMMError("Slice must be 0"); - return const_cast(bufferManager_->GetLastDataMD(channel, md)); + void* pixels = const_cast(bufferManager_->GetLastDataMD(channel, md)); + addMultiCameraMetadata(md, channel); + return pixels; } /** @@ -3508,7 +3540,9 @@ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) th if (slice != 0) throw CMMError("Slice must be 0"); - return const_cast(bufferManager_->PopNextDataMD(channel, md)); + void* pixels = const_cast(bufferManager_->PopNextDataMD(channel, md)); + addMultiCameraMetadata(md, channel); + return pixels; } /** @@ -4672,91 +4706,20 @@ unsigned CMMCore::getNumberOfComponents() return 0; } -// For the below functions that get properties of the image based on a pointer, -// Cant assume that this a v2 buffer pointer, because Java SWIG wrapper -// will call this after copying from a snap buffer. So we'll check if the -// buffer knows about this pointer. If not, it's a snap buffer pointer. -// We don't want want to compare to the snap buffer pointer directly because -// its unclear what the device adapter might do when this is called. -unsigned CMMCore::getImageWidth(DataPtr ptr) { - if (!bufferManager_->IsUsingV2Buffer()) { - return getImageWidth(); - } - if (bufferManager_->IsPointerInV2Buffer(ptr)) { - Metadata md; - bufferManager_->ExtractMetadata(ptr, md); - std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue(); - return static_cast(atoi(sVal.c_str())); - } - return getImageWidth(); -} - -unsigned CMMCore::getImageHeight(DataPtr ptr) { - if (!bufferManager_->IsUsingV2Buffer()) { - return getImageHeight(); - } - if (bufferManager_->IsPointerInV2Buffer(ptr)) { - Metadata md; - bufferManager_->ExtractMetadata(ptr, md); - std::string sVal = md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue(); - return static_cast(atoi(sVal.c_str())); - } - return getImageHeight(); -} - -unsigned CMMCore::getBytesPerPixel(DataPtr ptr) { - if (!bufferManager_->IsUsingV2Buffer()) { - return getBytesPerPixel(); - } - if (bufferManager_->IsPointerInV2Buffer(ptr)) { - Metadata md; - bufferManager_->ExtractMetadata(ptr, md); - std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - if (pixelType == MM::g_Keyword_PixelType_GRAY8) - return 1; - else if (pixelType == MM::g_Keyword_PixelType_GRAY16) - return 2; - else if (pixelType == MM::g_Keyword_PixelType_GRAY32 || - pixelType == MM::g_Keyword_PixelType_RGB32) - return 4; - else if (pixelType == MM::g_Keyword_PixelType_RGB64) - return 8; +void CMMCore::releaseReadAccess(DataPtr ptr) { + if (bufferManager_->IsUsingV2Buffer()) { + bufferManager_->ReleaseReadAccess(ptr); } - return getBytesPerPixel(); } -unsigned CMMCore::getNumberOfComponents(DataPtr ptr) { +void CMMCore::getImageProperties(DataPtr ptr, int& width, int& height, + int& byteDepth, int& nComponents) throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { - return getNumberOfComponents(); - } - if (bufferManager_->IsPointerInV2Buffer(ptr)) { + throw CMMError("Cannot get image properties when not using V2 buffer"); + } else { Metadata md; bufferManager_->ExtractMetadata(ptr, md); - std::string pixelType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); - if (pixelType == MM::g_Keyword_PixelType_GRAY8 || - pixelType == MM::g_Keyword_PixelType_GRAY16 || - pixelType == MM::g_Keyword_PixelType_GRAY32) - return 1; - else if (pixelType == MM::g_Keyword_PixelType_RGB32 || - pixelType == MM::g_Keyword_PixelType_RGB64) - return 4; - } - return getNumberOfComponents(); -} - -unsigned CMMCore::getSizeBytes(DataPtr ptr) { - if (!bufferManager_->IsUsingV2Buffer()) { - return getImageWidth() * getImageHeight() * getBytesPerPixel(); - } - if (bufferManager_->IsPointerInV2Buffer(ptr)) { - return bufferManager_->GetDataSize(ptr); - } - return getImageBufferSize(); -} - -void CMMCore::releaseReadAccess(DataPtr ptr) { - if (bufferManager_->IsUsingV2Buffer()) { - bufferManager_->ReleaseReadAccess(ptr); + parseImageMetadata(md, width, height, byteDepth, nComponents); } } @@ -8229,3 +8192,33 @@ std::string CMMCore::getInstalledDeviceDescription(const char* hubLabel, const c } return description.empty() ? "N/A" : description; } + + +/** + * Get the essential metadata for interpretting image data stored in the buffer. + */ +void CMMCore::parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents) +{ + width = std::stoul(md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue()); + height = std::stoul(md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue()); + nComponents = 1; // Default to 1 component + std::string pixType = md.GetSingleTag(MM::g_Keyword_PixelType).GetValue(); + + if (pixType == MM::g_Keyword_PixelType_GRAY8) + byteDepth = 1; + else if (pixType == MM::g_Keyword_PixelType_GRAY16) + byteDepth = 2; + else if (pixType == MM::g_Keyword_PixelType_GRAY32) + byteDepth = 4; + else if (pixType == MM::g_Keyword_PixelType_RGB32) { + byteDepth = 4; + nComponents = 4; // ARGB format uses 4 components + } + else if (pixType == MM::g_Keyword_PixelType_RGB64) { + byteDepth = 8; + nComponents = 4; // ARGB format uses 4 components + } else { + byteDepth = 1; // Default to 1 byte depth for unknown types + nComponents = 1; + } +} \ No newline at end of file diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 824982242..5125b9eb1 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -384,8 +384,8 @@ class CMMCore void snapImage() throw (CMMError); void* getImage() throw (CMMError); void* getImage(unsigned numChannel) throw (CMMError); - void* getImage(Metadata& md) throw (CMMError); - void* getImage(unsigned numChannel, Metadata& md) throw (CMMError); + void* getImageMD(Metadata& md) throw (CMMError); + void* getImageMD(unsigned numChannel, Metadata& md) throw (CMMError); unsigned getImageWidth(); unsigned getImageHeight(); @@ -457,22 +457,20 @@ class CMMCore ///@{ void enableV2Buffer(bool enable) throw (CMMError); bool usesV2Buffer() const { return bufferManager_->IsUsingV2Buffer(); } - + // These functions are used by the Java SWIG wrapper to get properties of the image // based on a pointer. The DataPtr alias to void* is so they don't get converted to // Object in the Java SWIG wrapper. - unsigned getImageWidth(DataPtr ptr) throw (CMMError); - unsigned getImageHeight(DataPtr ptr) throw (CMMError); - unsigned getBytesPerPixel(DataPtr ptr) throw (CMMError); - unsigned getNumberOfComponents(DataPtr ptr) throw (CMMError); - unsigned getSizeBytes(DataPtr ptr) throw (CMMError); - + void getImageProperties(DataPtr ptr, int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError); void releaseReadAccess(DataPtr ptr) throw (CMMError); + // Same functionality as non pointer versions above, but // enables alternative wrappers in SWIG for pointer-based access to the image data. - // This one is "Image" not "Data" because it corresponds to SnapImage()/GetImage() + // This one is "Image" not "Data" because it corresponds to SnapImage()/GetImage(), + // Which does only goes through the v2 buffer after coming from the camera device buffer, + // so it is guarenteed to be an image, not a more generic piece of data. BufferDataPointer* getImagePointer() throw (CMMError); // These are "Data" not "Image" because they can be used generically for any data type @@ -698,6 +696,8 @@ class CMMCore } ///@} + static void parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents); + private: // make object non-copyable CMMCore(const CMMCore&); @@ -780,9 +780,17 @@ class CMMCore void initializeAllDevicesParallel() throw (CMMError); int initializeVectorOfDevices(std::vector, std::string> > pDevices); - void AddCameraMetadata(std::shared_ptr pCam, Metadata& md, bool addLegacyMetadata); - void AddCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, + + // Centralized functions for adding and parsing metadata. This includes required metadta + // for interpretting the data like width, height, pixel type, as well as option nice-to-have + // metadata like elapsed time that may be relied on by higher level code. + // If support for other types of data is added in the future, alternative versions of these functions + // should be added. + void addCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata); + // Additional metadata for the multi-camera device adapter + void addMultiCameraMetadata(Metadata& md, int cameraChannelIndex) const; + }; #if defined(__GNUC__) && !defined(__clang__) diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index e6aeaafd3..91a382b1f 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -288,6 +288,10 @@ %apply int &OUTPUT { int &xSize }; %apply int &OUTPUT { int &ySize }; +%apply int &OUTPUT { int &width }; +%apply int &OUTPUT { int &height }; +%apply int &OUTPUT { int &byteDepth }; +%apply int &OUTPUT { int &nComponents }; // Java typemap // change default SWIG mapping of unsigned char* return values @@ -519,6 +523,9 @@ } } +// This is conceptually similar to the void* typemap above, +// but requires slightly different calls because BufferDataPointer +// is different from the data-returning void* methods of the Core. %typemap(jni) BufferDataPointerVoidStar "jobject" %typemap(jtype) BufferDataPointerVoidStar "Object" %typemap(jstype) BufferDataPointerVoidStar "Object" @@ -534,9 +541,9 @@ $result = 0; return $result; } - unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); - unsigned numPixels = numBytes / bytesPerPixel; - unsigned numComponents = (arg1)->getNumberOfComponents(); + int width, height, bytesPerPixel, numComponents; + (arg1)->getImageProperties(width, height, bytesPerPixel, numComponents); + unsigned numPixels = width * height; if (bytesPerPixel == 1) { @@ -638,126 +645,124 @@ } -// This is conceptually similar to the void* typemap above, -// but requires slightly different calls because BufferDataPointer -// is different from the data-returning void* methods of the Core. -%typemap(jni) BufferDataPointer "jobject" -%typemap(jtype) BufferDataPointer "Object" -%typemap(jstype) BufferDataPointer "Object" -%typemap(javaout) BufferDataPointer { - return $jnicall; -} -%typemap(out) BufferDataPointer -{ +// What was this suppossed to do? +// %typemap(jni) BufferDataPointer "jobject" +// %typemap(jtype) BufferDataPointer "Object" +// %typemap(jstype) BufferDataPointer "Object" +// %typemap(javaout) BufferDataPointer { +// return $jnicall; +// } +// %typemap(out) BufferDataPointer +// { - unsigned numBytes = (arg1)->getSizeBytes(); - unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); - unsigned numPixels = numBytes / bytesPerPixel; - unsigned numComponents = (arg1)->getNumberOfComponents(); +// unsigned numBytes = (arg1)->getSizeBytes(); +// unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); +// unsigned numPixels = numBytes / bytesPerPixel; +// unsigned numComponents = (arg1)->getNumberOfComponents(); - if (bytesPerPixel == 1) - { - // create a new byte[] object in Java - jbyteArray data = JCALL1(NewByteArray, jenv, numPixels); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - - $result = 0; - return $result; - } - // copy pixels from the image buffer - JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels, (jbyte*)result); - - $result = data; - } - else if (bytesPerPixel == 2) - { - // create a new short[] object in Java - jshortArray data = JCALL1(NewShortArray, jenv, numPixels); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - $result = 0; - return $result; - } +// if (bytesPerPixel == 1) +// { +// // create a new byte[] object in Java +// jbyteArray data = JCALL1(NewByteArray, jenv, numPixels); +// if (data == 0) +// { +// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); +// if (excep) +// jenv->ThrowNew(excep, "The system ran out of memory!"); + +// $result = 0; +// return $result; +// } +// // copy pixels from the image buffer +// JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels, (jbyte*)result); + +// $result = data; +// } +// else if (bytesPerPixel == 2) +// { +// // create a new short[] object in Java +// jshortArray data = JCALL1(NewShortArray, jenv, numPixels); +// if (data == 0) +// { +// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); +// if (excep) +// jenv->ThrowNew(excep, "The system ran out of memory!"); +// $result = 0; +// return $result; +// } - // copy pixels from the image buffer - JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels, (jshort*)result); - - $result = data; - } - else if (bytesPerPixel == 4) - { - if (numComponents == 1) - { - // create a new float[] object in Java - jfloatArray data = JCALL1(NewFloatArray, jenv, numPixels); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - - $result = 0; - return $result; - } - - // copy pixels from the image buffer - JCALL4(SetFloatArrayRegion, jenv, data, 0, numPixels, (jfloat*)result); - - $result = data; - } - else - { - // create a new byte[] object in Java - jbyteArray data = JCALL1(NewByteArray, jenv, numPixels * 4); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - - $result = 0; - return $result; - } - - // copy pixels from the image buffer - JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels * 4, (jbyte*)result); - - $result = data; - } - } - else if (bytesPerPixel == 8) - { - // create a new short[] object in Java - jshortArray data = JCALL1(NewShortArray, jenv, numPixels * 4); - if (data == 0) - { - jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); - if (excep) - jenv->ThrowNew(excep, "The system ran out of memory!"); - $result = 0; - return $result; - } +// // copy pixels from the image buffer +// JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels, (jshort*)result); + +// $result = data; +// } +// else if (bytesPerPixel == 4) +// { +// if (numComponents == 1) +// { +// // create a new float[] object in Java +// jfloatArray data = JCALL1(NewFloatArray, jenv, numPixels); +// if (data == 0) +// { +// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); +// if (excep) +// jenv->ThrowNew(excep, "The system ran out of memory!"); + +// $result = 0; +// return $result; +// } + +// // copy pixels from the image buffer +// JCALL4(SetFloatArrayRegion, jenv, data, 0, numPixels, (jfloat*)result); + +// $result = data; +// } +// else +// { +// // create a new byte[] object in Java +// jbyteArray data = JCALL1(NewByteArray, jenv, numPixels * 4); +// if (data == 0) +// { +// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); +// if (excep) +// jenv->ThrowNew(excep, "The system ran out of memory!"); + +// $result = 0; +// return $result; +// } + +// // copy pixels from the image buffer +// JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels * 4, (jbyte*)result); + +// $result = data; +// } +// } +// else if (bytesPerPixel == 8) +// { +// // create a new short[] object in Java +// jshortArray data = JCALL1(NewShortArray, jenv, numPixels * 4); +// if (data == 0) +// { +// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); +// if (excep) +// jenv->ThrowNew(excep, "The system ran out of memory!"); +// $result = 0; +// return $result; +// } - // copy pixels from the image buffer - JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels * 4, (jshort*)result); +// // copy pixels from the image buffer +// JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels * 4, (jshort*)result); - $result = data; - } - else - { - // don't know how to map - // TODO: throw exception? - $result = 0; - } -} +// $result = data; +// } +// else +// { +// // don't know how to map +// // TODO: throw exception? +// $result = 0; +// } +// } // Define typemaps for DataPtr @@ -854,14 +859,6 @@ %} %typemap(javacode) CMMCore %{ - private boolean includeSystemStateCache_ = true; - - // public boolean getIncludeSystemStateCache() { - // return includeSystemStateCache_; - // } - // public void setIncludeSystemStateCache(boolean state) { - // includeSystemStateCache_ = state; - // } private JSONObject metadataToMap(Metadata md) { JSONObject tags = new JSONObject(); @@ -873,156 +870,61 @@ return tags; } - // Its probably a good idea to minimize the complexity of this SWIG layer, - // including the amount of metadata added here. However, all the metadata - // added for SnapImage is generated here, and the current Core API doesn't - // give metadata along with returning the snapimage buffer - - . In the future, routing everything - // through the bufferManager, and a new camera API can solve this so that - // all images go through a common route and can have consistent metadata added. - - - // TODO: deal with this - private JSONObject createSnapImageTags() throws java.lang.Exception { - JSONObject tags = new JSONObject(); - tags.put("Width", getImageWidth()); - tags.put("Height", getImageHeight()); - int bytesPerPixel = (int) getBytesPerPixel(); - int numComponents = (int) getNumberOfComponents(); - tags.put("PixelType", getPixelType(bytesPerPixel, numComponents)); - - // These get added in corecallback.cpp - tags.put("BitDepth", getImageBitDepth()); - tags.put("ROI", getROITag()); - try { - tags.put("Binning", getProperty(camera, "Binning")); - } catch (Exception ex) {} - - return tags; - } - - - - // TODO add to global - private String getPixelType(int depth, int numComponents) throws java.lang.Exception { - switch (depth) { - case 1: - return "GRAY8"; - case 2: - return "GRAY16"; - case 4: { - if (numComponents == 1) - return "GRAY32"; - else - return "RGB32"; - } - case 8: - return "RGB64"; - } - return ""; - } - - private String getMultiCameraChannel(JSONObject tags, int cameraChannelIndex) { - try { - String camera = tags.getString("Core-Camera"); - String physCamKey = camera + "-Physical Camera " + (1 + cameraChannelIndex); - if (tags.has(physCamKey)) { - try { - return tags.getString(physCamKey); - } catch (Exception e2) { - return null; - } - } else { - return null; - } - } catch (Exception e) { - return null; - } - - } - - private TaggedImage createTaggedImage(Object pixels, Metadata md, int cameraChannelIndex) throws java.lang.Exception { - TaggedImage image = createTaggedImage(pixels, md); - JSONObject tags = image.tags; - - if (!tags.has("CameraChannelIndex")) { - tags.put("CameraChannelIndex", cameraChannelIndex); - tags.put("ChannelIndex", cameraChannelIndex); - } - if (!tags.has("Camera")) { - String physicalCamera = getMultiCameraChannel(tags, cameraChannelIndex); - if (physicalCamera != null) { - tags.put("Camera", physicalCamera); - tags.put("Channel",physicalCamera); - } - } - return image; - } - - - private TaggedImagePointer createTaggedImagePointer(BufferDataPointer pointer) throws java.lang.Exception { - // This only ever gets called by the V2 buffer, is called by the Pointer functions that have a different signature - // Thus, we do not need to maintain backwards compatibility for higher level code that calls the other functions. - // So we we only add the metadata that actually makes sense for the current abstraction. - // Some metadata is added here, the rest is added lazily when the image is loaded - JSONObject tagsToAdd = new JSONObject(); + // private TaggedImagePointer createTaggedImagePointer(BufferDataPointer pointer) throws java.lang.Exception { + // // This only ever gets called by the V2 buffer, is called by the Pointer functions that have a different signature + // // Thus, we do not need to maintain backwards compatibility for higher level code that calls the other functions. + // // So we we only add the metadata that actually makes sense for the current abstraction. - addSystemStateCacheTags(tagsToAdd); + // // Some metadata is added here, the rest is added lazily when the image is loaded + // JSONObject tagsToAdd = new JSONObject(); - TaggedImagePointer imagePointer = new TaggedImagePointer(pointer); - } - - private TaggedImage createTaggedImage(Object pixels, Metadata md, JSONObject moreTags) throws java.lang.Exception { - return createTaggedImage(pixels, md, null, tags); - } - - private TaggedImage createTaggedImage(Object pixels, Metadata md, Long pointer, JSONObject moreTags) throws java.lang.Exception { - JSONObject tags = metadataToMap(md); - tags.putAll(moreTags); - - return new TaggedImage(pixels, tags); - } + // TaggedImagePointer imagePointer = new TaggedImagePointer(pointer); + // } // Snap image functions public TaggedImage getTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); - Object pixels = getImage(cameraChannelIndex); - JSONObject tags = createSnapImageTags(); - return createTaggedImage(pixels, md, cameraChannelIndex, tags); + Object pixels = getImageMD(cameraChannelIndex, md); + return new TaggedImage(pixels, metadataToMap(md)); } public TaggedImage getTaggedImage() throws java.lang.Exception { - return getTaggedImage(0); + Metadata md = new Metadata(); + Object pixels = getImageMD(md); + return new TaggedImage(pixels, metadataToMap(md)); } // sequence acq functions public TaggedImage getLastTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); Object pixels = getLastImageMD(cameraChannelIndex, 0, md); - return createTaggedImage(pixels, md, cameraChannelIndex); + return new TaggedImage(pixels, metadataToMap(md)); } public TaggedImage getLastTaggedImage() throws java.lang.Exception { - return getLastTaggedImage(0); + Metadata md = new Metadata(); + Object pixels = getLastImageMD(md); + return new TaggedImage(pixels, metadataToMap(md)); } public TaggedImage getNBeforeLastTaggedImage(long n) throws java.lang.Exception { Metadata md = new Metadata(); Object pixels = getNBeforeLastImageMD(n, md); - return createTaggedImage(pixels, md); + return new TaggedImage(pixels, metadataToMap(md)); } public TaggedImage popNextTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); Object pixels = popNextImageMD(cameraChannelIndex, 0, md); - return createTaggedImage(pixels, md, cameraChannelIndex); + return new TaggedImage(pixels, metadataToMap(md)); } public TaggedImage popNextTaggedImage() throws java.lang.Exception { - return popNextTaggedImage(0); + Metadata md = new Metadata(); + Object pixels = popNextImageMD(md); + return new TaggedImage(pixels, metadataToMap(md)); } @@ -1453,6 +1355,12 @@ namespace std { } +// These are needed by the void* typemaps to copy pixels and then +// release them, but they shouldn't be needed by the Java wrap +// because their functionality is handled by the BufferDataPointer class +// %ignore CMMCore::getImageProperties(DataPtr, int&, int&, int&, int&); +// %ignore CMMCore::releaseReadAccess(DataPtr); + %include "../MMDevice/MMDeviceConstants.h" %include "../MMCore/Configuration.h" %include "../MMCore/MMCore.h" From cd947e3b1cbb0c709a142b41551a8d4c770b965f Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 21 Feb 2025 17:25:35 -0800 Subject: [PATCH 31/46] Many big fixes and got pointer-based image handling working --- MMCore/BufferDataPointer.h | 24 +- MMCore/BufferManager.cpp | 145 +++---- MMCore/BufferManager.h | 50 ++- MMCore/Buffer_v2.cpp | 57 ++- MMCore/Buffer_v2.h | 117 +++--- MMCore/CircularBuffer.cpp | 8 +- MMCore/CircularBuffer.h | 2 + MMCore/CoreCallback.cpp | 26 +- MMCore/MMCore.cpp | 369 ++++++++++-------- MMCore/MMCore.h | 20 +- MMCoreJ_wrap/MMCoreJ.i | 203 +++------- .../src/main/java/mmcorej/LazyJSONObject.java | 34 +- .../src/main/java/mmcorej/TaggedImage.java | 8 + .../main/java/mmcorej/TaggedImagePointer.java | 53 ++- 14 files changed, 589 insertions(+), 527 deletions(-) diff --git a/MMCore/BufferDataPointer.h b/MMCore/BufferDataPointer.h index 92711897d..76814d053 100644 --- a/MMCore/BufferDataPointer.h +++ b/MMCore/BufferDataPointer.h @@ -30,6 +30,7 @@ #include "MMCore.h" #include "../MMDevice/ImageMetadata.h" #include +#include // This is needed for SWIG Java wrapping to differentiate its void* // from the void* that MMCore uses for returning data @@ -42,12 +43,20 @@ class BufferDataPointer { public: BufferDataPointer(BufferManager* bufferManager, DataPtr ptr) - : bufferManager_(bufferManager), ptr_(ptr) + : bufferManager_(bufferManager), ptr_(ptr), mutex_() { + // check for null pointer + if (!ptr_) { + throw CMMError("Pointer is null"); + } // check for v2 buffer use if (!bufferManager_->IsUsingV2Buffer()) { throw CMMError("V2 buffer must be enabled for BufferDataPointer"); } + // throw an error if the pointer is not in the buffer + if (!bufferManager_->IsPointerInV2Buffer(ptr_)) { + throw CMMError("Pointer is not in the buffer"); + } } // Returns a pointer to the pixel data (read-only) @@ -85,12 +94,13 @@ class BufferDataPointer { void release() { std::lock_guard lock(mutex_); if (bufferManager_ && ptr_) { - try { - bufferManager_->ReleaseReadAccess(ptr_); - ptr_ = nullptr; // Mark as released - } catch (...) { - // Release must not throw + + int ret = bufferManager_->ReleaseReadAccess(ptr_); + if (ret != DEVICE_OK) { + throw CMMError("Failed to release read access to buffer"); } + ptr_ = nullptr; // Mark as released + } } @@ -109,4 +119,4 @@ class BufferDataPointer { BufferManager* bufferManager_; const void* ptr_; mutable std::mutex mutex_; -}; \ No newline at end of file +}; diff --git a/MMCore/BufferManager.cpp b/MMCore/BufferManager.cpp index 7bbbae470..2bbe41c2f 100644 --- a/MMCore/BufferManager.cpp +++ b/MMCore/BufferManager.cpp @@ -30,7 +30,7 @@ BufferManager::BufferManager(bool useV2Buffer, unsigned int memorySizeMB) : useV2_(useV2Buffer), circBuffer_(nullptr), v2Buffer_(nullptr) { - if (useV2_) { + if (useV2_.load()) { v2Buffer_ = new DataBuffer(memorySizeMB); } else { circBuffer_ = new CircularBuffer(memorySizeMB); @@ -39,7 +39,7 @@ BufferManager::BufferManager(bool useV2Buffer, unsigned int memorySizeMB) BufferManager::~BufferManager() { - if (useV2_) { + if (useV2_.load()) { if (v2Buffer_) { delete v2Buffer_; } @@ -51,7 +51,11 @@ BufferManager::~BufferManager() } void BufferManager::ReallocateBuffer(unsigned int memorySizeMB) { - if (useV2_) { + if (useV2_.load()) { + int numOutstanding = v2Buffer_->NumOutstandingSlots(); + if (numOutstanding > 0) { + throw CMMError("Cannot reallocate V2 buffer: " + std::to_string(numOutstanding) + " outstanding active slot(s) detected."); + } delete v2Buffer_; v2Buffer_ = new DataBuffer(memorySizeMB); } else { @@ -62,7 +66,7 @@ void BufferManager::ReallocateBuffer(unsigned int memorySizeMB) { const void* BufferManager::GetLastData() { - if (useV2_) { + if (useV2_.load()) { Metadata dummyMetadata; // NOTE: ensure calling code releases the slot after use return v2Buffer_->PeekDataReadPointerAtIndex(0, dummyMetadata); @@ -73,7 +77,7 @@ const void* BufferManager::GetLastData() const void* BufferManager::PopNextData() { - if (useV2_) { + if (useV2_.load()) { Metadata dummyMetadata; // NOTE: ensure calling code releases the slot after use return v2Buffer_->PopNextDataReadPointer(dummyMetadata, false); @@ -84,7 +88,7 @@ const void* BufferManager::PopNextData() long BufferManager::GetRemainingDataCount() const { - if (useV2_) { + if (useV2_.load()) { return v2Buffer_->GetActiveSlotCount(); } else { return circBuffer_->GetRemainingImageCount(); @@ -92,7 +96,7 @@ long BufferManager::GetRemainingDataCount() const } unsigned BufferManager::GetMemorySizeMB() const { - if (useV2_) { + if (useV2_.load()) { return v2Buffer_->GetMemorySizeMB(); } else { return circBuffer_->GetMemorySizeMB(); @@ -100,7 +104,7 @@ unsigned BufferManager::GetMemorySizeMB() const { } unsigned BufferManager::GetFreeSizeMB() const { - if (useV2_) { + if (useV2_.load()) { return (unsigned) v2Buffer_->GetFreeMemory() / 1024 / 1024; } else { return circBuffer_->GetFreeSize() * circBuffer_->GetImageSizeBytes() / 1024 / 1024; @@ -109,7 +113,7 @@ unsigned BufferManager::GetFreeSizeMB() const { bool BufferManager::Overflow() const { - if (useV2_) { + if (useV2_.load()) { return v2Buffer_->Overflow(); } else { return circBuffer_->Overflow(); @@ -119,37 +123,35 @@ bool BufferManager::Overflow() const /** * @deprecated Use InsertData() instead */ -bool BufferManager::InsertImage(const char* callerLabel, const unsigned char *buf, unsigned width, unsigned height, - unsigned byteDepth, Metadata *pMd) { - return InsertMultiChannel(callerLabel, buf, 1, width, height, byteDepth, pMd); +int BufferManager::InsertImage(const char* callerLabel, const unsigned char* buf, unsigned width, unsigned height, + unsigned byteDepth, Metadata* pMd) { + return InsertMultiChannel(callerLabel, buf, 1, width, height, byteDepth, pMd); } /** * @deprecated Use InsertData() instead */ -bool BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned char* buf, +int BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd) { // Initialize metadata with either provided metadata or create empty Metadata md = (pMd != nullptr) ? *pMd : Metadata(); - if (useV2_) { + if (useV2_.load()) { // All the data needed to interpret the image is in the metadata // This function will copy data and metadata into the buffer - int ret = v2Buffer_->InsertData(buf, width * height * byteDepth * numChannels, &md, callerLabel); - return ret == DEVICE_OK; + return v2Buffer_->InsertData(buf, width * height * byteDepth * numChannels, &md, callerLabel); } else { return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, - byteDepth, &md); + byteDepth, &md) ? DEVICE_OK : DEVICE_BUFFER_OVERFLOW; } } -bool BufferManager::InsertData(const char* callerLabel, const unsigned char* buf, size_t dataSize, Metadata* pMd) { - +int BufferManager::InsertData(const char* callerLabel, const unsigned char* buf, size_t dataSize, Metadata* pMd) { // Initialize metadata with either provided metadata or create empty Metadata md = (pMd != nullptr) ? *pMd : Metadata(); - if (!useV2_) { + if (!useV2_.load()) { throw CMMError("InsertData() not supported with circular buffer. Must use V2 buffer."); } // All the data needed to interpret the image should be in the metadata @@ -160,18 +162,17 @@ bool BufferManager::InsertData(const char* callerLabel, const unsigned char* buf const void* BufferManager::GetLastDataMD(Metadata& md) const throw (CMMError) { - return GetLastDataMD(0, md); + return GetLastDataMD(0, 0, md); // single channel size doesnt matter here } -const void* BufferManager::GetLastDataMD(unsigned channel, Metadata& md) const throw (CMMError) +const void* BufferManager::GetLastDataMD(unsigned channel, unsigned singleChannelSizeBytes, Metadata& md) const throw (CMMError) { - if (useV2_) { - if (channel != 0) { - throw CMMError("V2 buffer does not support channels.", MMERR_InvalidContents); - } + if (useV2_.load()) { const void* basePtr = v2Buffer_->PeekLastDataReadPointer(md); if (basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); + // Add multiples of the number of bytes to get the channel pointer + basePtr = static_cast(basePtr) + channel * singleChannelSizeBytes; return basePtr; } else { const mm::ImgBuffer* pBuf = circBuffer_->GetTopImageBuffer(channel); @@ -186,7 +187,7 @@ const void* BufferManager::GetLastDataMD(unsigned channel, Metadata& md) const t const void* BufferManager::GetNthDataMD(unsigned long n, Metadata& md) const throw (CMMError) { - if (useV2_) { + if (useV2_.load()) { // NOTE: make sure calling code releases the slot after use. return v2Buffer_->PeekDataReadPointerAtIndex(n, md); } else { @@ -202,18 +203,24 @@ const void* BufferManager::GetNthDataMD(unsigned long n, Metadata& md) const thr const void* BufferManager::PopNextDataMD(Metadata& md) throw (CMMError) { - return PopNextDataMD(0, md); + return PopNextDataMD(0, 0, md); } -const void* BufferManager::PopNextDataMD(unsigned channel, Metadata& md) throw (CMMError) +/** + * @deprecated Use PopNextDataMD() without channel parameter instead. + * The V2 buffer is data type agnostic + */ +const void* BufferManager::PopNextDataMD(unsigned channel, + unsigned singleChannelSizeBytes, Metadata& md) throw (CMMError) { - if (useV2_) { - if (channel != 0) { - throw CMMError("V2 buffer does not support channels.", MMERR_InvalidContents); - } + if (useV2_.load()) { const void* basePtr = v2Buffer_->PopNextDataReadPointer(md, false); if (basePtr == nullptr) throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); + + // Add multiples of the number of bytes to get the channel pointer + basePtr = static_cast(basePtr) + channel * singleChannelSizeBytes; + return basePtr; } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNextImageBuffer(channel); @@ -226,10 +233,10 @@ const void* BufferManager::PopNextDataMD(unsigned channel, Metadata& md) throw ( } } -bool BufferManager::EnableV2Buffer(bool enable) { +int BufferManager::EnableV2Buffer(bool enable) { // Don't do anything if we're already in the requested state - if (enable == useV2_) { - return true; + if (enable == useV2_.load()) { + return DEVICE_OK; } // Create new buffer of requested type with same memory size @@ -244,54 +251,55 @@ bool BufferManager::EnableV2Buffer(bool enable) { v2Buffer_ = newBuffer; } else { // Switch to circular buffer + int numOutstanding = v2Buffer_->NumOutstandingSlots(); + if (numOutstanding > 0) { + throw CMMError("Cannot switch to circular buffer: " + std::to_string(numOutstanding) + " outstanding active slot(s) detected."); + } CircularBuffer* newBuffer = new CircularBuffer(memorySizeMB); delete v2Buffer_; v2Buffer_ = nullptr; circBuffer_ = newBuffer; - // Require it to be initialized manually, which doesn't actually matter - // because it gets initialized before sequence acquisition starts anyway. } - useV2_ = enable; - return true; + useV2_.store(enable); + return DEVICE_OK; } catch (const std::exception&) { // If allocation fails, keep the existing buffer - return false; + return DEVICE_ERR; } } bool BufferManager::IsUsingV2Buffer() const { - return useV2_; + return useV2_.load(); } -bool BufferManager::ReleaseReadAccess(const void* ptr) { - if (useV2_ && ptr) { - return v2Buffer_->ReleaseDataReadPointer(ptr) == DEVICE_OK; - } - return false; +int BufferManager::ReleaseReadAccess(const void* ptr) { + if (useV2_.load() && ptr) { + return v2Buffer_->ReleaseDataReadPointer(ptr); + } + return DEVICE_ERR; } unsigned BufferManager::GetDataSize(const void* ptr) const { - if (!useV2_) + if (!useV2_.load()) return circBuffer_->GetImageSizeBytes(); else return static_cast(v2Buffer_->GetDatumSize(ptr)); } -bool BufferManager::SetOverwriteData(bool overwrite) { - if (useV2_) { - return v2Buffer_->SetOverwriteData(overwrite) == DEVICE_OK; +int BufferManager::SetOverwriteData(bool overwrite) { + if (useV2_.load()) { + return v2Buffer_->SetOverwriteData(overwrite); } else { - // CircularBuffer doesn't have this functionality - return false; + return circBuffer_->SetOverwriteData(overwrite); } } -bool BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, size_t additionalMetadataSize, +int BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, size_t additionalMetadataSize, void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata) { - if (!useV2_) { + if (!useV2_.load()) { // Not supported for circular buffer - return false; + return DEVICE_ERR; } // Initialize metadata with either provided metadata or create empty @@ -300,22 +308,19 @@ bool BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, s std::string serializedMetadata = md.Serialize(); int ret = v2Buffer_->AcquireWriteSlot(dataSize, additionalMetadataSize, dataPointer, additionalMetadataPointer, serializedMetadata, deviceLabel); - return ret == DEVICE_OK; + return ret; } -bool BufferManager::FinalizeWriteSlot(void* imageDataPointer, size_t actualMetadataBytes) -{ - if (!useV2_) { +int BufferManager::FinalizeWriteSlot(const void* imageDataPointer, size_t actualMetadataBytes) { + if (!useV2_.load()) { // Not supported for circular buffer - return false; + return DEVICE_ERR; } - - int ret = v2Buffer_->FinalizeWriteSlot(imageDataPointer, actualMetadataBytes); - return ret == DEVICE_OK; + return v2Buffer_->FinalizeWriteSlot(imageDataPointer, actualMetadataBytes); } void BufferManager::ExtractMetadata(const void* dataPtr, Metadata& md) const { - if (!useV2_) { + if (!useV2_.load()) { throw CMMError("ExtractMetadata is only supported with V2 buffer enabled"); } @@ -330,7 +335,7 @@ void BufferManager::ExtractMetadata(const void* dataPtr, Metadata& md) const { } const void* BufferManager::GetLastDataFromDevice(const std::string& deviceLabel) throw (CMMError) { - if (!useV2_) { + if (!useV2_.load()) { throw CMMError("V2 buffer must be enabled for device-specific data access"); } Metadata md; @@ -338,7 +343,7 @@ const void* BufferManager::GetLastDataFromDevice(const std::string& deviceLabel) } const void* BufferManager::GetLastDataMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError) { - if (!useV2_) { + if (!useV2_.load()) { throw CMMError("V2 buffer must be enabled for device-specific data access"); } @@ -350,7 +355,7 @@ const void* BufferManager::GetLastDataMDFromDevice(const std::string& deviceLabe } bool BufferManager::IsPointerInV2Buffer(const void* ptr) const throw (CMMError) { - if (!useV2_) { + if (!useV2_.load()) { return false; } @@ -362,7 +367,7 @@ bool BufferManager::IsPointerInV2Buffer(const void* ptr) const throw (CMMError) } bool BufferManager::GetOverwriteData() const { - if (useV2_) { + if (useV2_.load()) { return v2Buffer_->GetOverwriteData(); } else { return circBuffer_->GetOverwriteData(); @@ -370,7 +375,7 @@ bool BufferManager::GetOverwriteData() const { } void BufferManager::Reset() { - if (useV2_) { + if (useV2_.load()) { v2Buffer_->Reset(); } else { circBuffer_->Clear(); diff --git a/MMCore/BufferManager.h b/MMCore/BufferManager.h index 42bac2a76..245262e74 100644 --- a/MMCore/BufferManager.h +++ b/MMCore/BufferManager.h @@ -32,6 +32,7 @@ #include #include #include +#include /** * BufferManager provides a generic interface for managing data buffers in MMCore. @@ -71,7 +72,7 @@ class BufferManager { * @param enable Set to true to use v2 buffer, false to use circular buffer. * @return true if the switch was successful, false otherwise. */ - bool EnableV2Buffer(bool enable); + int EnableV2Buffer(bool enable); /** * Get a pointer to the top (most recent) image. @@ -111,12 +112,12 @@ class BufferManager { * @param height Image height. * @param byteDepth Bytes per pixel. * @param pMd Metadata associated with the image. - * @return true on success, false on error. + * @return DEVICE_OK on success, DEVICE_ERR on error. * @deprecated This method assumes specific image data format. It is provided for backwards * compatibility with with the circular buffer, which assumes images captured on a camera. * Use InsertData() instead, which provides format-agnostic data handling with metadata for interpretation. */ - bool InsertImage(const char* deviceLabel, const unsigned char *buf, + int InsertImage(const char* deviceLabel, const unsigned char *buf, unsigned width, unsigned height, unsigned byteDepth, Metadata *pMd); @@ -130,12 +131,12 @@ class BufferManager { * @param height Image height. * @param byteDepth Bytes per pixel. * @param pMd Metadata associated with the image. - * @return true on success, false on error. + * @return DEVICE_OK on success, DEVICE_ERR on error. * @deprecated This method is not preferred for the V2 buffer. Use InsertData() instead. * This method assumes specific image data format. It is provided for backwards * compatibility with with the circular buffer, which assumes images captured on a camera. */ - bool InsertMultiChannel(const char* deviceLabel, const unsigned char *buf, + int InsertMultiChannel(const char* deviceLabel, const unsigned char *buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata *pMd); @@ -148,9 +149,9 @@ class BufferManager { * @param buf The data to insert. * @param dataSize The size of the data to insert. * @param pMd Metadata associated with the data. - * @return true on success, false on error. + * @return DEVICE_OK on success, DEVICE_ERR on error. */ - bool InsertData(const char* callerLabel, const unsigned char* buf, size_t dataSize, Metadata *pMd); + int InsertData(const char* callerLabel, const unsigned char* buf, size_t dataSize, Metadata *pMd); /** @@ -165,8 +166,17 @@ class BufferManager { // Channels are not directly supported in v2 buffer, these are for backwards compatibility // with circular buffer - const void* GetLastDataMD(unsigned channel, Metadata& md) const throw (CMMError); - const void* PopNextDataMD(unsigned channel, Metadata& md) throw (CMMError); + + /** + * @deprecated This method is not preferred for the V2 buffer. Use GetLastDataMD() without channel parameter instead. + * The V2 is data type agnostic + */ + const void* GetLastDataMD(unsigned channel, unsigned singleChannelSizeBytes, Metadata& md) const throw (CMMError); + /** + * @deprecated This method is not preferred for the V2 buffer. Use PopNextDataMD() without channel parameter instead. + * The V2 buffer is data type agnostic + */ + const void* PopNextDataMD(unsigned channel, unsigned singleChannelSizeBytes,Metadata& md) throw (CMMError); const void* GetLastDataMD(Metadata& md) const throw (CMMError); const void* PopNextDataMD(Metadata& md) throw (CMMError); @@ -181,9 +191,9 @@ class BufferManager { * Release a pointer obtained from the buffer. * This is required when using the V2 buffer implementation. * @param ptr The pointer to release. - * @return true on success, false on error. + * @return DEVICE_OK on success, DEVICE_ERR on error. */ - bool ReleaseReadAccess(const void* ptr); + int ReleaseReadAccess(const void* ptr); // Get the size of just the data in this slot unsigned GetDataSize(const void* ptr) const; @@ -191,9 +201,9 @@ class BufferManager { /** * Configure whether to overwrite old data when buffer is full. * @param overwrite If true, overwrite old data when buffer is full. - * @return true on success, false on error. + * @return DEVICE_OK on success, DEVICE_ERR on error. */ - bool SetOverwriteData(bool overwrite); + int SetOverwriteData(bool overwrite); /** * Acquires a write slot large enough to hold the data and metadata. @@ -202,19 +212,19 @@ class BufferManager { * @param additionalMetadataSize The maximum number of bytes reserved for metadata. * @param dataPointer On success, receives a pointer to the image data region. * @param additionalMetadataPointer On success, receives a pointer to the metadata region. - * @param pInitialMetadata Optionally, a pointer to a metadata object whose contents should be pre‐written. Defaults to nullptr. - * @return true on success, false on error. + * @param pInitialMetadata Optionally, a pointer to a metadata object whose contents should be pre‐written + * @return DEVICE_OK on success, DEVICE_ERR on error. */ - bool AcquireWriteSlot(const char* deviceLabel, size_t dataSize, size_t additionalMetadataSize, - void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata = nullptr); + int AcquireWriteSlot(const char* deviceLabel, size_t dataSize, size_t additionalMetadataSize, + void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata); /** * Finalizes (releases) a write slot after data has been written. * @param dataPointer Pointer previously obtained from AcquireWriteSlot. * @param actualMetadataBytes The actual number of metadata bytes written. - * @return true on success, false on error. + * @return DEVICE_OK on success, DEVICE_ERR on error. */ - bool FinalizeWriteSlot(void* dataPointer, size_t actualMetadataBytes); + int FinalizeWriteSlot(const void* imageDataPointer, size_t actualMetadataBytes); /** * Extracts metadata for a given data pointer. @@ -277,7 +287,7 @@ class BufferManager { private: - bool useV2_; + std::atomic useV2_; CircularBuffer* circBuffer_; DataBuffer* v2Buffer_; diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index 7d810f0c4..ed01dd254 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -126,6 +126,8 @@ DataBuffer::~DataBuffer() { #endif buffer_ = nullptr; } + + std::lock_guard lock(slotManagementMutex_); for (BufferSlot* bs : slotPool_) { delete bs; } @@ -205,8 +207,9 @@ int DataBuffer::InsertData(const void* data, size_t dataSize, const Metadata* pM // Initial metadata is all metadata because the image and metadata are already complete int result = AcquireWriteSlot(dataSize, 0, &dataPointer, &additionalMetadataPointer, serializedMetadata, deviceLabel); - if (result != DEVICE_OK) - return result; + if (result != DEVICE_OK) { + return result; + } tasksMemCopy_->MemCopy((void*)dataPointer, data, dataSize); @@ -375,7 +378,8 @@ int DataBuffer::ReleaseDataReadPointer(const void* dataPointer) { if (!slot) return DEVICE_ERR; } - const size_t offset = static_cast(dataPointer) - buffer_; + const size_t offset = static_cast(dataPointer) - + static_cast(buffer_); // Release the read access outside the global lock slot->ReleaseReadAccess(); @@ -383,7 +387,7 @@ int DataBuffer::ReleaseDataReadPointer(const void* dataPointer) { if (!overwriteWhenFull_) { std::lock_guard lock(slotManagementMutex_); // Now check if the slot is not being accessed - if (slot->IsAvailableForWriting() && slot->IsAvailableForReading()) { + if (slot->IsFree()) { auto it = activeSlotsByStart_.find(offset); DeleteSlot(offset, it); } @@ -394,6 +398,10 @@ int DataBuffer::ReleaseDataReadPointer(const void* dataPointer) { const void* DataBuffer::PopNextDataReadPointer(Metadata &md, bool waitForData) { + if (overwriteWhenFull_) { + throw std::runtime_error("PopNextDataReadPointer is not available in overwrite mode"); + } + BufferSlot* slot = nullptr; size_t slotStart = 0; @@ -415,16 +423,18 @@ const void* DataBuffer::PopNextDataReadPointer(Metadata &md, bool waitForData) slot->AcquireReadAccess(); const unsigned char* dataPointer = static_cast(buffer_) + slotStart; - - const unsigned char* metadataPtr = dataPointer + slot->GetDataSize(); this->ExtractMetadata(dataPointer, slot, md); return dataPointer; } const void* DataBuffer::PeekLastDataReadPointer(Metadata &md) { + if (!overwriteWhenFull_) { + throw std::runtime_error("PeekLastDataReadPointer is only available in overwrite mode"); + } + BufferSlot* currentSlot = nullptr; - { + { std::unique_lock lock(slotManagementMutex_); if (activeSlotsVector_.empty()) { return nullptr; @@ -447,6 +457,10 @@ const void* DataBuffer::PeekLastDataReadPointer(Metadata &md) { } const void* DataBuffer::PeekDataReadPointerAtIndex(size_t n, Metadata &md) { + if (!overwriteWhenFull_) { + throw std::runtime_error("PeekDataReadPointerAtIndex is only available in overwrite mode"); + } + BufferSlot* currentSlot = nullptr; { // Lock the global slot management mutex to safely access the active slots. @@ -464,14 +478,16 @@ const void* DataBuffer::PeekDataReadPointerAtIndex(size_t n, Metadata &md) { currentSlot->AcquireReadAccess(); const unsigned char* dataPointer = static_cast(buffer_) + currentSlot->GetStart(); - - const unsigned char* metadataPtr = dataPointer + currentSlot->GetDataSize(); this->ExtractMetadata(dataPointer, currentSlot, md); return dataPointer; } const void* DataBuffer::PeekLastDataReadPointerFromDevice(const std::string& deviceLabel, Metadata& md) { + if (!overwriteWhenFull_) { + throw std::runtime_error("PeekLastDataReadPointerFromDevice is only available in overwrite mode"); + } + BufferSlot* matchingSlot = nullptr; { std::unique_lock lock(slotManagementMutex_); @@ -547,10 +563,10 @@ bool DataBuffer::Overflow() const { */ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { std::lock_guard lock(slotManagementMutex_); - + // Ensure no active readers/writers exist. for (BufferSlot* slot : activeSlotsVector_) { - if (!slot->IsAvailableForReading() || !slot->IsAvailableForWriting()) { + if (!slot->IsFree()) { throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); } } @@ -621,7 +637,8 @@ BufferSlot* DataBuffer::FindSlotForPointer(const void* dataPointer) { assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); if (buffer_ == nullptr) return nullptr; - std::size_t offset = static_cast(dataPointer) - buffer_; + std::size_t offset = static_cast(dataPointer) - + static_cast(buffer_); auto it = activeSlotsByStart_.find(offset); return (it != activeSlotsByStart_.end()) ? it->second : nullptr; } @@ -781,11 +798,6 @@ int DataBuffer::ExtractCorrespondingMetadata(const void* dataPointer, Metadata & return DEVICE_ERR; } } - // Get metadata pointer and size while under lock - const void* initialMetadataPtr = static_cast(dataPointer) + slot->GetDataSize(); - size_t initialMetadataSize = slot->GetInitialMetadataSize(); - const void* additionalMetadataPtr = static_cast(initialMetadataPtr) + initialMetadataSize; - size_t additionalMetadataSize = slot->GetAdditionalMetadataSize(); // Extract metadata (internal method doesn't need lock) return ExtractMetadata(dataPointer, slot, md); @@ -810,3 +822,14 @@ bool DataBuffer::IsPointerInBuffer(const void* ptr) { BufferSlot* slot = FindSlotForPointer(ptr); return slot != nullptr; } + +int DataBuffer::NumOutstandingSlots() const { + std::lock_guard lock(slotManagementMutex_); + int numOutstanding = 0; + for (const BufferSlot* slot : activeSlotsVector_) { + if (!slot->IsFree()) { + numOutstanding++; + } + } + return numOutstanding; +} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h index 88fa345a8..6bface371 100644 --- a/MMCore/Buffer_v2.h +++ b/MMCore/Buffer_v2.h @@ -62,7 +62,16 @@ class BufferSlot { * @param imageSize The exact number of bytes for the image data. * @param metadataSize The exact number of bytes for the metadata. */ - BufferSlot() : start_(0), length_(0), imageSize_(0), initialMetadataSize_(0), additionalMetadataSize_(0), deviceLabel_(), rwMutex_() {} + BufferSlot() + : start_(0), + length_(0), + imageSize_(0), + initialMetadataSize_(0), + additionalMetadataSize_(0), + deviceLabel_(), + rwMutex_() + { + } /** * Destructor. @@ -83,104 +92,108 @@ class BufferSlot { std::size_t GetLength() const { return length_; } /** - * Attempts to acquire exclusive write access without blocking. - * @return True if the write lock was acquired; false otherwise. - */ - bool TryAcquireWriteAccess() { return rwMutex_.try_lock(); } - - /** - * Acquires exclusive write access (blocking call). - * This method will block until exclusive access is granted. + * Acquires exclusive (write) access (blocking). */ - void AcquireWriteAccess() { rwMutex_.lock(); } + void AcquireWriteAccess() { + rwMutex_.lock(); + } /** * Releases exclusive write access. */ - void ReleaseWriteAccess() { rwMutex_.unlock(); } + void ReleaseWriteAccess() { + rwMutex_.unlock(); + } /** * Acquires shared read access (blocking). */ - void AcquireReadAccess() { rwMutex_.lock_shared(); } + void AcquireReadAccess() { + rwMutex_.lock_shared(); + } /** * Releases shared read access. */ - void ReleaseReadAccess() { rwMutex_.unlock_shared(); } + void ReleaseReadAccess() { + rwMutex_.unlock_shared(); + } /** - * Checks if the slot is currently available for writing. - * A slot is available if no thread holds either a write lock or any read lock. - * @return True if available for writing. + * Checks if the slot is completely free (no readers, no writers). + * We do this by attempting to lock it exclusively: + * if we succeed, there are no concurrent locks. */ - bool IsAvailableForWriting() const { - if (rwMutex_.try_lock()) { - rwMutex_.unlock(); - return true; - } - return false; + bool IsFree() const { + std::unique_lock lk(rwMutex_, std::try_to_lock); + return lk.owns_lock(); } /** - * Checks if the slot is available for acquiring read access. - * @return True if available for reading. + * Resets the slot with new parameters. + * The assertion uses IsFree() as a best-effort check before reinitializing. */ - bool IsAvailableForReading() const { - if (rwMutex_.try_lock_shared()) { - rwMutex_.unlock_shared(); - return true; - } - return false; - } - - void Reset(size_t start, size_t length, size_t imageSize, size_t initialMetadataSize, - size_t additionalMetadataSize, const std::string& deviceLabel) { - // Assert that the mutex is available before recycling - assert(IsAvailableForWriting() && IsAvailableForReading() && + void Reset(size_t start, + size_t length, + size_t imageSize, + size_t initialMetadataSize, + size_t additionalMetadataSize, + const std::string& deviceLabel) + { + // If this fails, there's likely an active read or write lock on the slot. + assert(IsFree() && "BufferSlot mutex still locked during Reset - indicates a bug!"); - - // Set the new values + start_ = start; length_ = length; imageSize_ = imageSize; initialMetadataSize_ = initialMetadataSize; additionalMetadataSize_ = initialMetadataSize + additionalMetadataSize; - deviceLabel_ = deviceLabel; // Reset the device label - - // The caller should explicitly acquire write access when needed + deviceLabel_ = deviceLabel; } /** * Updates the metadata size after writing is complete. * @param newSize The actual size of the written metadata. */ - void UpdateAdditionalMetadataSize(size_t newSize) { additionalMetadataSize_ = newSize; } + void UpdateAdditionalMetadataSize(size_t newSize) { + additionalMetadataSize_ = newSize; + } /** * Record the number of bytes of the initial metadata that have been written to this slot. */ - void SetInitialMetadataSize(size_t initialSize) { initialMetadataSize_ = initialSize; } + void SetInitialMetadataSize(size_t initialSize) { + initialMetadataSize_ = initialSize; + } /** * Returns the size of the image data in bytes. * @return The image data size. */ - std::size_t GetDataSize() const { return imageSize_; } + std::size_t GetDataSize() const { + return imageSize_; + } /** * Returns the size of the initial metadata in bytes. * @return The initial metadata size. */ - std::size_t GetInitialMetadataSize() const { return initialMetadataSize_; } + std::size_t GetInitialMetadataSize() const { + return initialMetadataSize_; + } /** * Returns the size of the additional metadata in bytes. * @return The additional metadata size. */ - std::size_t GetAdditionalMetadataSize() const { return additionalMetadataSize_; } + std::size_t GetAdditionalMetadataSize() const { + return additionalMetadataSize_; + } - const std::string& GetDeviceLabel() const { return deviceLabel_; } + const std::string& GetDeviceLabel() const { + return deviceLabel_; + } private: std::size_t start_; @@ -188,7 +201,8 @@ class BufferSlot { size_t imageSize_; size_t initialMetadataSize_; size_t additionalMetadataSize_; - std::string deviceLabel_; // New member + std::string deviceLabel_; + mutable std::shared_timed_mutex rwMutex_; }; @@ -381,6 +395,13 @@ class DataBuffer { */ void Reset(); + /** + * Checks if there are any outstanding slots in the buffer. If so, it + * is unsafe to destroy the buffer. + * @return true if there are outstanding slots, false otherwise. + */ + int NumOutstandingSlots() const; + private: /** * Internal helper function that finds the slot for a given pointer. diff --git a/MMCore/CircularBuffer.cpp b/MMCore/CircularBuffer.cpp index a51c5d527..6e9d107ff 100644 --- a/MMCore/CircularBuffer.cpp +++ b/MMCore/CircularBuffer.cpp @@ -70,6 +70,12 @@ CircularBuffer::CircularBuffer(unsigned int memorySizeMB) : CircularBuffer::~CircularBuffer() {} +int CircularBuffer::SetOverwriteData(bool overwrite) { + MMThreadGuard guard(g_bufferLock); + overwriteData_ = overwrite; + return DEVICE_OK; +} + bool CircularBuffer::Initialize(unsigned channels, unsigned int w, unsigned int h, unsigned int pixDepth) { MMThreadGuard guard(g_bufferLock); @@ -182,7 +188,7 @@ bool CircularBuffer::InsertMultiChannel(const unsigned char* pixArray, unsigned bool overflowed = (insertIndex_ - saveIndex_) >= static_cast(frameArray_.size()); if (overflowed) { - if (!overwriteData_) { + if (overwriteData_) { Clear(); } else { overflow_ = true; diff --git a/MMCore/CircularBuffer.h b/MMCore/CircularBuffer.h index b9d5411f4..6c952f6d2 100644 --- a/MMCore/CircularBuffer.h +++ b/MMCore/CircularBuffer.h @@ -55,6 +55,8 @@ class CircularBuffer CircularBuffer(unsigned int memorySizeMB); ~CircularBuffer(); + int SetOverwriteData(bool overwrite); + unsigned GetMemorySizeMB() const { return memorySizeMB_; } bool Initialize(unsigned channels, unsigned int xSize, unsigned int ySize, unsigned int pixDepth); diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 244b79f1a..cf70767c6 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -238,6 +238,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf newMD = *pMd; } std::shared_ptr device = core_->deviceManager_->GetDevice(caller); + if (device->GetType() == MM::CameraDevice) { // convert device to camera @@ -245,6 +246,10 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf core_->addCameraMetadata(camera, newMD, width, height, byteDepth, nComponents, true); } + char labelBuffer[MM::MaxStrLength]; + caller->GetLabel(labelBuffer); + std::string callerLabel(labelBuffer); + if(doProcess) { MM::ImageProcessor* ip = GetImageProcessor(caller); @@ -253,9 +258,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf ip->Process(const_cast(buf), width, height, byteDepth); } } - char labelBuffer[MM::MaxStrLength]; - caller->GetLabel(labelBuffer); - std::string callerLabel(labelBuffer); + int ret = core_->bufferManager_->InsertImage(callerLabel.c_str(), buf, width, height, byteDepth, &newMD); if (ret != DEVICE_OK) return DEVICE_BUFFER_OVERFLOW; @@ -301,13 +304,10 @@ int CoreCallback::AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSiz char label[MM::MaxStrLength]; caller->GetLabel(label); - if (!core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, (void**)dataPointer, (void**)metadataPointer, &md)) - { - return DEVICE_ERR; - } - return DEVICE_OK; + return core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, + (void**)dataPointer, (void**)metadataPointer, &md); } - + // This is only for camera devices, other devices should call AcquireDataWriteSlot return DEVICE_ERR; } @@ -317,11 +317,9 @@ int CoreCallback::AcquireDataWriteSlot(const MM::Device* caller, size_t dataSize { char label[MM::MaxStrLength]; caller->GetLabel(label); - if (core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, (void**)dataPointer, (void**)metadataPointer)) - { - return DEVICE_OK; - } - return DEVICE_ERR; + Metadata md; + return core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, + (void**)dataPointer, (void**)metadataPointer, &md); } int CoreCallback::FinalizeWriteSlot(unsigned char* dataPointer, diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 81cfed165..a8e9c0dbf 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -169,7 +169,8 @@ CMMCore::CMMCore() : pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), pPostedErrorsLock_(NULL), - includeSystemStateCache_(true) + includeSystemStateCache_(true), + metadataProfileFlag_(0) { configGroups_ = new ConfigGroupCollection(); pixelSizeGroup_ = new PixelSizeConfigGroup(); @@ -180,8 +181,7 @@ CMMCore::CMMCore() : callback_ = new CoreCallback(this); const unsigned seqBufMegabytes = (sizeof(void*) > 4) ? 250 : 25; - // TODO: change to v1 buffer before PR - bufferManager_ = new BufferManager(true, seqBufMegabytes); // Default to v2 buffer + bufferManager_ = new BufferManager(false, seqBufMegabytes); nullAffine_ = new std::vector(6); for (int i = 0; i < 6; i++) { @@ -2692,20 +2692,23 @@ bool CMMCore::getShutterOpen() throw (CMMError) } /** - * A centralized function that adds all the metadata for camera devices + * A centralized function that adds all the metadata for camera devices. * * This was previously spread among the circular buffer, corecallback.h, and * the SWIG wrapper. * * Get the metadata tags attached to device caller, and merge them with metadata * in pMd (if not null). Returns a metadata object. + * + * The caller should have locked the camera device, or be calling from a thread + * in the camera (e.g. CoreCallback::InsertImage) */ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata) throw (CMMError) { // Essential metadata for interpreting the image: Width, height, and pixel type - md.PutImageTag(MM::g_Keyword_Metadata_Width, width); - md.PutImageTag(MM::g_Keyword_Metadata_Height, height); + md.PutImageTag(MM::g_Keyword_Metadata_Width, (unsigned int) width); + md.PutImageTag(MM::g_Keyword_Metadata_Height, (unsigned int) height); if (byteDepth == 1) md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_GRAY8); @@ -2721,106 +2724,103 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& else md.PutImageTag(MM::g_Keyword_PixelType, MM::g_Keyword_PixelType_Unknown); - - // Needed for higher level code that needs to sort images by camera + // Needed for acquisition engine if (!md.HasTag(MM::g_Keyword_Metadata_CameraLabel)) { - md.put(MM::g_Keyword_Metadata_CameraLabel, pCam->GetLabel()); + md.put(MM::g_Keyword_Metadata_CameraLabel, pCam->GetLabel()); } - // Additional, non-essential, but nice to have metadata - md.put(MM::g_Keyword_Metadata_BitDepth, CDeviceUtils::ConvertToString((long) pCam->GetBitDepth())); + // Return if no additional metadata requested + if (metadataProfileFlag_ == 0) { + return; + } + + // Group 2: Bit depth + if (metadataProfileFlag_ >= 2) { + md.put(MM::g_Keyword_Metadata_BitDepth, CDeviceUtils::ConvertToString((long) pCam->GetBitDepth())); + } - // Add ROI metadata - { + // Group 3: Camera settings (ROI, binning) + if (metadataProfileFlag_ >= 3) { unsigned x, y, xSize, ySize; pCam->GetROI(x, y, xSize, ySize); std::string roiTag = std::to_string(x) + "-" + std::to_string(y) + "-" + std::to_string(xSize) + "-" + std::to_string(ySize); md.put("ROI", roiTag); + + try { + std::string binning = pCam->GetProperty("Binning"); + md.put("Binning", binning); + } + catch (const CMMError&) { } } - - try { - std::string binning = pCam->GetProperty("Binning"); - md.put("Binning", binning); - } - catch (const CMMError&) { - // Ignore errors getting binning property. The Java SWIG layer where this was copied from - // had a try catch, so keep it here because its not clear (to me) when this will fail - } - - // Add image number metadata - { + + // Group 4: Timing metadata + if (metadataProfileFlag_ >= 4) { std::lock_guard lock(imageNumbersMutex_); std::string cameraName = md.GetSingleTag(MM::g_Keyword_Metadata_CameraLabel).GetValue(); - if (imageNumbers_.find(cameraName) == imageNumbers_.end()) - { + if (imageNumbers_.find(cameraName) == imageNumbers_.end()) { imageNumbers_[cameraName] = 0; } md.put(MM::g_Keyword_Metadata_ImageNumber, CDeviceUtils::ConvertToString(imageNumbers_[cameraName])); ++imageNumbers_[cameraName]; + + if (!md.HasTag(MM::g_Keyword_Elapsed_Time_ms)) { + using namespace std::chrono; + auto elapsed = steady_clock::now() - startTime_; + md.put(MM::g_Keyword_Elapsed_Time_ms, + std::to_string(duration_cast(elapsed).count())); + } + + auto now = std::chrono::system_clock::now(); + md.put(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); } - - // Add elapsed time metadata if not already set - if (!md.HasTag(MM::g_Keyword_Elapsed_Time_ms)) - { - using namespace std::chrono; - auto elapsed = steady_clock::now() - startTime_; - md.put(MM::g_Keyword_Elapsed_Time_ms, - std::to_string(duration_cast(elapsed).count())); - } - - // Add current system time (as formatted local time) - auto now = std::chrono::system_clock::now(); - md.put(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); - - // Merge any additional camera-specific tags - try - { - std::string serializedMD = pCam->GetTags(); - Metadata devMD; - devMD.Restore(serializedMD.c_str()); - md.Merge(devMD); - } - catch (const CMMError&) - { - // Ignore errors getting tags + + // Group 5: Camera-specific tags + if (metadataProfileFlag_ >= 5) { + try { + std::string serializedMD = pCam->GetTags(); + Metadata devMD; + devMD.Restore(serializedMD.c_str()); + md.Merge(devMD); + } + catch (const CMMError&) { } } - + + // Group 6: Pixel size metadata // I'm unsure about these. They seem like they should be specific to a camera, but // they seem to only be defined globally. Leave as is for now. - try { - md.put("PixelSizeUm", CDeviceUtils::ConvertToString(getPixelSizeUm(true))); - - // Get the affine matrix as a string. This is used by the acquistion engine - std::string pixelSizeAffine = ""; - std::vector aff = getPixelSizeAffine(true); - if (aff.size() != 6) - pixelSizeAffine = ""; - else { - std::ostringstream oss; - for (size_t i = 0; i < 5; i++) { - oss << aff[i] << ";"; + if (metadataProfileFlag_ >= 6) { + try { + int binning = pCam->GetBinning(); + md.put("PixelSizeUm", CDeviceUtils::ConvertToString(getPixelSizeUm(true, binning))); + + std::string pixelSizeAffine = ""; + std::vector aff = getPixelSizeAffine(true, binning); + if (aff.size() == 6) { + std::ostringstream oss; + for (size_t i = 0; i < 5; i++) { + oss << aff[i] << ";"; + } + oss << aff[5]; + pixelSizeAffine = oss.str(); } - oss << aff[5]; - pixelSizeAffine = oss.str(); + md.put("PixelSizeAffine", pixelSizeAffine); } - md.put("PixelSizeAffine", pixelSizeAffine); - } - catch (const CMMError&) { - // Ignore errors getting pixel size properties + catch (const CMMError&) { } } - - // Metadata that previously was in the Java SWIG layer and I think may be used by the application + + // Metadata that previously was in the Java SWIG layer and I think may be used by the application // and/or acquisition engine. However, it doesn't really make sense that this would exist in this // since channel, slice, position, etc. higher level concepts associated with an acquisition engine - if (addLegacyMetadata) { + // Group 7: Legacy metadata + if (metadataProfileFlag_ >= 7 && addLegacyMetadata) { md.put("Frame", "0"); md.put("FrameIndex", "0"); md.put("Position", "Default"); md.put("PositionIndex", "0"); md.put("Slice", "0"); md.put("SliceIndex", "0"); - + // Add channel metadata if not already set try { std::string channel = getCurrentConfigFromCache( @@ -2832,15 +2832,13 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& md.put("ChannelIndex", "0"); } catch (const CMMError&) { - // If channel group not set, use defaults md.put("Channel", "Default"); md.put("ChannelIndex", "0"); } - } - - // Add system state cache if enabled - if (includeSystemStateCache_) { + + // Group 8: System state cache + if (metadataProfileFlag_ >= 8 && includeSystemStateCache_) { try { // MMThreadGuard scg(stateCacheLock_); // Needed? Configuration state = getSystemStateCache(); @@ -2851,9 +2849,7 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& md.put(key, value); } } - catch (const CMMError&) { - // Ignore errors getting system state cache - } + catch (const CMMError&) { } // ignore } } @@ -2867,25 +2863,29 @@ void CMMCore::addMultiCameraMetadata(Metadata& md, int cameraChannelIndex = 0) md.put("ChannelIndex", ToString(cameraChannelIndex)); } - if (!md.HasTag("Camera")) { - // Get the core camera name - std::string coreCamera; - try { - coreCamera = md.GetSingleTag("Core-Camera").GetValue(); - // Construct physical camera key (e.g. "Camera-Physical Camera 1") - std::string physCamKey = coreCamera + "-Physical Camera " + ToString(cameraChannelIndex + 1); - - // Check if physical camera metadata exists - if (md.HasTag(physCamKey.c_str())) { - std::string physicalCamera = md.GetSingleTag(physCamKey.c_str()).GetValue(); - md.put("Camera", physicalCamera); - md.put("Channel", physicalCamera); - } - } - catch (const CMMError&) { - // If core camera metadata not found, ignore - } - } + // This whole block seems superfluos now since we always add the "Camera" tag + // in the block above. Also, the V2 buffer eliminates the need for the multi-camera + // adapter. Leaving here and commented out for now, because I'm unsure what the upstream + // effects are + // if (!md.HasTag("Camera")) { + // // Get the core camera name + // std::string coreCamera; + // try { + // coreCamera = md.GetSingleTag("Core-Camera").GetValue(); + // // Construct physical camera key (e.g. "Camera-Physical Camera 1") + // std::string physCamKey = coreCamera + "-Physical Camera " + ToString(cameraChannelIndex + 1); + + // // Check if physical camera metadata exists + // if (md.HasTag(physCamKey.c_str())) { + // std::string physicalCamera = md.GetSingleTag(physCamKey.c_str()).GetValue(); + // md.put("Camera", physicalCamera); + // md.put("Channel", physicalCamera); + // } + // } + // catch (const CMMError&) { + // // If core camera metadata not found, ignore + // } + // } } /** @@ -3106,7 +3106,10 @@ BufferDataPointer* CMMCore::getImagePointer() throw (CMMError) addCameraMetadata(camera, md, camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents(), true); unsigned imageSize = camera->GetImageWidth() * camera->GetImageHeight() * camera->GetImageBytesPerPixel(); - bufferManager_->InsertData(camera->GetLabel().c_str(), (unsigned char*)pBuf, imageSize, &md); + int ret = bufferManager_->InsertData(camera->GetLabel().c_str(), (unsigned char*)pBuf, imageSize, &md); + if (ret != DEVICE_OK) { + throw CMMError("v2 buffer overflow"); + } if (bufferManager_->GetOverwriteData()) { // If in overwrite mode (e.g. live mode), peek the last data @@ -3334,6 +3337,14 @@ void CMMCore::stopSequenceAcquisition(const char* label) throw (CMMError) void CMMCore::startContinuousSequenceAcquisition(double intervalMs) throw (CMMError) { std::shared_ptr camera = currentCameraDevice_.lock(); + if (camera) { + startContinuousSequenceAcquisition(camera->GetLabel().c_str(), intervalMs); + } +} + +void CMMCore::startContinuousSequenceAcquisition(const char* cameraLabel, double intervalMs) throw (CMMError) +{ + std::shared_ptr camera = deviceManager_->GetDeviceOfType(cameraLabel); if (camera) { mm::DeviceModuleLockGuard guard(camera); @@ -3469,7 +3480,10 @@ void* CMMCore::getLastImageMD(unsigned channel, unsigned slice, Metadata& md) co if (slice != 0) throw CMMError("Slice must be 0"); - void* pixels = const_cast(bufferManager_->GetLastDataMD(channel, md)); + unsigned bytesPerSingleChannel = const_cast(this)->getImageWidth() * + const_cast(this)->getImageHeight() * + const_cast(this)->getBytesPerPixel(); + void* pixels = const_cast(bufferManager_->GetLastDataMD(channel, bytesPerSingleChannel, md)); addMultiCameraMetadata(md, channel); return pixels; } @@ -3487,8 +3501,8 @@ void* CMMCore::getLastImageMD(unsigned channel, unsigned slice, Metadata& md) co * (see: https://en.wikipedia.org/wiki/RGBA_color_model). */ void* CMMCore::getLastImageMD(Metadata& md) const throw (CMMError) -{ - return getLastImageMD(0, 0, md); +{ + return const_cast(bufferManager_->GetLastDataMD(md)); } /** @@ -3533,6 +3547,8 @@ void* CMMCore::popNextImage() throw (CMMError) * Gets and removes the next image (and metadata) from the circular buffer * channel indicates which cameraChannel image should be retrieved. * slice has not been implement and should always be 0 + * + * @deprecated Use popNextImageMD() without channel parameter instead. */ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) throw (CMMError) { @@ -3540,7 +3556,10 @@ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) th if (slice != 0) throw CMMError("Slice must be 0"); - void* pixels = const_cast(bufferManager_->PopNextDataMD(channel, md)); + unsigned bytesPerSingleChannel = const_cast(this)->getImageWidth() * + const_cast(this)->getImageHeight() * + const_cast(this)->getBytesPerPixel(); + void* pixels = const_cast(bufferManager_->PopNextDataMD(channel, bytesPerSingleChannel, md)); addMultiCameraMetadata(md, channel); return pixels; } @@ -3550,7 +3569,9 @@ void* CMMCore::popNextImageMD(unsigned channel, unsigned slice, Metadata& md) th */ void* CMMCore::popNextImageMD(Metadata& md) throw (CMMError) { - return popNextImageMD(0, 0, md); + // Unlike version above, don't add multi-camera metadata + void* pixels = const_cast(bufferManager_->PopNextDataMD(md)); + return pixels; } //// Data pointer access for v2 Buffer @@ -3559,6 +3580,9 @@ BufferDataPointer* CMMCore::getLastDataPointer() throw (CMMError) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->GetLastData(); + if (rawPtr == nullptr) { + throw CMMError("Buffer is empty"); + } return new BufferDataPointer(bufferManager_, rawPtr); } @@ -3567,22 +3591,9 @@ BufferDataPointer* CMMCore::popNextDataPointer() throw (CMMError) { throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->PopNextData(); - return new BufferDataPointer(bufferManager_, rawPtr); -} - -BufferDataPointer* CMMCore::getLastDataMDPointer(Metadata& md) const throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); - } - const void* rawPtr = bufferManager_->GetLastDataMD(md); - return new BufferDataPointer(bufferManager_, rawPtr); -} - -BufferDataPointer* CMMCore::popNextDataMDPointer(Metadata& md) throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); + if (rawPtr == nullptr) { + throw CMMError("Buffer is empty"); } - const void* rawPtr = bufferManager_->PopNextDataMD(md); return new BufferDataPointer(bufferManager_, rawPtr); } @@ -3591,17 +3602,12 @@ BufferDataPointer* CMMCore::getLastDataFromDevicePointer(std::string deviceLabel throw CMMError("V2 buffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->GetLastDataFromDevice(deviceLabel); + if (rawPtr == nullptr) { + throw CMMError("Buffer is empty"); + } return new BufferDataPointer(bufferManager_, rawPtr); } -BufferDataPointer* CMMCore::getLastDataMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); - } - const void* rawPtr = bufferManager_->GetLastDataMDFromDevice(deviceLabel, md); - return new BufferDataPointer(bufferManager_, rawPtr); -} - /** * Removes all images from the circular buffer. * @@ -3611,7 +3617,7 @@ BufferDataPointer* CMMCore::getLastDataMDFromDevicePointer(std::string deviceLab void CMMCore::clearCircularBuffer() throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { - resetBuffer(); + clearBuffer(); } // No effect on v2 because Reset should be used more carefully } @@ -3625,7 +3631,7 @@ void CMMCore::clearCircularBuffer() throw (CMMError) * Thus, reset should be used carefully, because it will discard data that a consumer * may sill asynchronously be waiting to consume */ -void CMMCore::resetBuffer() throw (CMMError) +void CMMCore::clearBuffer() throw (CMMError) { bufferManager_->Reset(); } @@ -3635,7 +3641,9 @@ void CMMCore::resetBuffer() throw (CMMError) */ void CMMCore::enableV2Buffer(bool enable) throw (CMMError) { - bufferManager_->EnableV2Buffer(enable); + int ret = bufferManager_->EnableV2Buffer(enable); + if (ret != DEVICE_OK) + throw CMMError("Failed to enable V2 buffer", ret); } /** @@ -3652,12 +3660,11 @@ void CMMCore::setBufferMemoryFootprint(unsigned sizeMB) throw (CMMError) if (isSequenceRunning()) { stopSequenceAcquisition(); } - delete bufferManager_; // discard old buffer LOG_DEBUG(coreLogger_) << "Will set circular buffer size to " << sizeMB << " MB"; try { - bufferManager_ = new BufferManager(bufferManager_->IsUsingV2Buffer(), sizeMB); + bufferManager_->ReallocateBuffer(sizeMB); } catch (std::bad_alloc& ex) { @@ -3737,10 +3744,13 @@ long CMMCore::getBufferTotalCapacity() if (bufferManager_) { // Compute image size from the current camera parameters. - long imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel(); + double imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel() / 1024.0 / 1024.0; // Pass the computed image size as an argument to the adapter. unsigned int sizeMB = bufferManager_->GetMemorySizeMB(); - return sizeMB / imageSize; + if (imageSize > 0) + { + return static_cast(sizeMB / imageSize); + } } return 0; } @@ -3755,9 +3765,12 @@ long CMMCore::getBufferFreeCapacity() if (bufferManager_) { // Compute image size from the current camera parameters. - long imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel(); + double imageSize = getImageWidth() * getImageHeight() * getBytesPerPixel() / 1024.0 / 1024.0; unsigned int sizeMB = bufferManager_->GetFreeSizeMB(); - return sizeMB / imageSize; + if (imageSize > 0) + { + return static_cast(sizeMB / imageSize); + } } return 0; } @@ -4712,14 +4725,32 @@ void CMMCore::releaseReadAccess(DataPtr ptr) { } } + +// For the below function that gets properties of the image based on a pointer, +// Cant assume that this a v2 buffer pointer, because Java SWIG wrapper +// will call this after copying from a snap buffer. So we'll check if the +// buffer knows about this pointer. If not, it's a snap buffer pointer. +// We don't want want to compare to the snap buffer pointer directly because +// its unclear what the device adapter might do when this is called. void CMMCore::getImageProperties(DataPtr ptr, int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { if (!bufferManager_->IsUsingV2Buffer()) { - throw CMMError("Cannot get image properties when not using V2 buffer"); - } else { + // Could be snap or circular buffer pointer + width = getImageWidth(); + height = getImageHeight(); + byteDepth = getBytesPerPixel(); + nComponents = getNumberOfComponents(); + } else if (bufferManager_->IsPointerInV2Buffer(ptr)) { + // V2 buffer pointer Metadata md; bufferManager_->ExtractMetadata(ptr, md); parseImageMetadata(md, width, height, byteDepth, nComponents); + } else { + // Snap buffer pointer with v2 buffer on + width = getImageWidth(); + height = getImageHeight(); + byteDepth = getBytesPerPixel(); + nComponents = getNumberOfComponents(); } } @@ -6016,6 +6047,25 @@ double CMMCore::getPixelSizeUm() * pixel size preset matches the property values. */ double CMMCore::getPixelSizeUm(bool cached) +{ + int binning = 1; + std::shared_ptr camera = currentCameraDevice_.lock(); + if (camera) + { + try + { + mm::DeviceModuleLockGuard guard(camera); + binning = camera->GetBinning(); + } + catch (const CMMError&) // Possibly uninitialized camera + { + // Assume no binning + } + } + return getPixelSizeUm(cached, binning); +} + +double CMMCore::getPixelSizeUm(bool cached, int binning) { std::string resolutionID; try @@ -6035,20 +6085,7 @@ double CMMCore::getPixelSizeUm(bool cached) double pixSize = pCfg->getPixelSizeUm(); - std::shared_ptr camera = currentCameraDevice_.lock(); - if (camera) - { - try - { - mm::DeviceModuleLockGuard guard(camera); - pixSize *= camera->GetBinning(); - } - catch (const CMMError&) // Possibly uninitialized camera - { - // Assume no binning - } - } - + pixSize *= binning; pixSize /= getMagnificationFactor(); return pixSize; @@ -6085,6 +6122,18 @@ std::vector CMMCore::getPixelSizeAffine() throw (CMMError) * and known magnification devices */ std::vector CMMCore::getPixelSizeAffine(bool cached) throw (CMMError) +{ + std::shared_ptr camera = currentCameraDevice_.lock(); + int binning = 1; + if (camera) + { + mm::DeviceModuleLockGuard guard(camera); + binning = camera->GetBinning(); + } + return getPixelSizeAffine(cached, binning); +} + +std::vector CMMCore::getPixelSizeAffine(bool cached, int binning) throw (CMMError) { std::string resolutionID = getCurrentPixelSizeConfig(cached); if (resolutionID.length() > 0) @@ -6093,14 +6142,6 @@ std::vector CMMCore::getPixelSizeAffine(bool cached) throw (CMMError) PixelSizeConfiguration* pCfg = pixelSizeGroup_->Find(resolutionID.c_str()); std::vector af = pCfg->getPixelConfigAffineMatrix(); - std::shared_ptr camera = currentCameraDevice_.lock(); - int binning = 1; - if (camera) - { - mm::DeviceModuleLockGuard guard(camera); - binning = camera->GetBinning(); - } - double factor = binning / getMagnificationFactor(); if (factor != 1.0) diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 5125b9eb1..59222c73e 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -409,6 +409,7 @@ class CMMCore double intervalMs, bool stopOnOverflow) throw (CMMError); void prepareSequenceAcquisition(const char* cameraLabel) throw (CMMError); void startContinuousSequenceAcquisition(double intervalMs) throw (CMMError); + void startContinuousSequenceAcquisition(const char* cameraLabel, double intervalMs) throw (CMMError); void stopSequenceAcquisition() throw (CMMError); void stopSequenceAcquisition(const char* cameraLabel) throw (CMMError); bool isSequenceRunning() throw (); @@ -449,7 +450,7 @@ class CMMCore void setBufferMemoryFootprint(unsigned sizeMB) throw (CMMError); unsigned getBufferMemoryFootprint() const; - void resetBuffer() throw (CMMError); + void clearBuffer() throw (CMMError); ///@} @@ -474,13 +475,12 @@ class CMMCore BufferDataPointer* getImagePointer() throw (CMMError); // These are "Data" not "Image" because they can be used generically for any data type - // Higher level wrapping code can read their associated metadata to determine their data type + // Higher level wrapping code can read their associated metadata to determine their data typ + // These ones don't need the metadata versions (e.g. getLastDataMDPointer) because the metadata + // is accessed through the buffer data pointer. BufferDataPointer* getLastDataPointer() throw (CMMError); BufferDataPointer* popNextDataPointer() throw (CMMError); - BufferDataPointer* getLastDataMDPointer(Metadata& md) const throw (CMMError); - BufferDataPointer* popNextDataMDPointer(Metadata& md) throw (CMMError); BufferDataPointer* getLastDataFromDevicePointer(std::string deviceLabel) throw (CMMError); - BufferDataPointer* getLastDataMDFromDevicePointer(std::string deviceLabel, Metadata& md) throw (CMMError); bool IsPointerInV2Buffer(DataPtr ptr); @@ -694,6 +694,9 @@ class CMMCore void setIncludeSystemStateCache(bool state) { includeSystemStateCache_ = state; } + void setMetadataProfile(int level) { + metadataProfileFlag_ = level; + } ///@} static void parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents); @@ -751,6 +754,7 @@ class CMMCore std::mutex imageNumbersMutex_; std::chrono::steady_clock::time_point startTime_; // Start time for elapsed time calculations in seuqence acquisition bool includeSystemStateCache_; + int metadataProfileFlag_; private: void InitializeErrorMessages(); @@ -790,6 +794,12 @@ class CMMCore unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata); // Additional metadata for the multi-camera device adapter void addMultiCameraMetadata(Metadata& md, int cameraChannelIndex) const; + // Want to be able to pass in binning so camera doesn't have to be locked and this can + // be called on a camera thread + double getPixelSizeUm(bool cached, int binning); + std::vector getPixelSizeAffine(bool cached, int binning) throw (CMMError); + + }; diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index 91a382b1f..b4681c9f9 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -408,11 +408,15 @@ } %typemap(out) void* { - - unsigned numBytes = (arg1)->getImageBufferSize(); - unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); - unsigned numPixels = numBytes / bytesPerPixel; - unsigned numComponents = (arg1)->getNumberOfComponents(); + if (result == NULL) { + $result = 0; + return $result; + } + + int width, height, bytesPerPixel, numComponents; + (arg1)->getImageProperties(result, width, height, bytesPerPixel, numComponents); + unsigned numPixels = width * height; + if (bytesPerPixel == 1) { @@ -534,7 +538,11 @@ } %typemap(out) BufferDataPointerVoidStar { - + if (result == NULL) { + $result = 0; + return $result; + } + unsigned numBytes = (arg1)->getSizeBytes(); // Return null if no bytes if (numBytes == 0) { @@ -644,128 +652,15 @@ } } +%extend BufferDataPointer { + // Trigger immediate release instead of waiting for garbage collection + void dispose() { + $self->release(); + } +} -// What was this suppossed to do? -// %typemap(jni) BufferDataPointer "jobject" -// %typemap(jtype) BufferDataPointer "Object" -// %typemap(jstype) BufferDataPointer "Object" -// %typemap(javaout) BufferDataPointer { -// return $jnicall; -// } -// %typemap(out) BufferDataPointer -// { - -// unsigned numBytes = (arg1)->getSizeBytes(); -// unsigned bytesPerPixel = (arg1)->getBytesPerPixel(); -// unsigned numPixels = numBytes / bytesPerPixel; -// unsigned numComponents = (arg1)->getNumberOfComponents(); - - -// if (bytesPerPixel == 1) -// { -// // create a new byte[] object in Java -// jbyteArray data = JCALL1(NewByteArray, jenv, numPixels); -// if (data == 0) -// { -// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); -// if (excep) -// jenv->ThrowNew(excep, "The system ran out of memory!"); - -// $result = 0; -// return $result; -// } -// // copy pixels from the image buffer -// JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels, (jbyte*)result); - -// $result = data; -// } -// else if (bytesPerPixel == 2) -// { -// // create a new short[] object in Java -// jshortArray data = JCALL1(NewShortArray, jenv, numPixels); -// if (data == 0) -// { -// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); -// if (excep) -// jenv->ThrowNew(excep, "The system ran out of memory!"); -// $result = 0; -// return $result; -// } - -// // copy pixels from the image buffer -// JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels, (jshort*)result); - -// $result = data; -// } -// else if (bytesPerPixel == 4) -// { -// if (numComponents == 1) -// { -// // create a new float[] object in Java -// jfloatArray data = JCALL1(NewFloatArray, jenv, numPixels); -// if (data == 0) -// { -// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); -// if (excep) -// jenv->ThrowNew(excep, "The system ran out of memory!"); - -// $result = 0; -// return $result; -// } - -// // copy pixels from the image buffer -// JCALL4(SetFloatArrayRegion, jenv, data, 0, numPixels, (jfloat*)result); - -// $result = data; -// } -// else -// { -// // create a new byte[] object in Java -// jbyteArray data = JCALL1(NewByteArray, jenv, numPixels * 4); -// if (data == 0) -// { -// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); -// if (excep) -// jenv->ThrowNew(excep, "The system ran out of memory!"); - -// $result = 0; -// return $result; -// } - -// // copy pixels from the image buffer -// JCALL4(SetByteArrayRegion, jenv, data, 0, numPixels * 4, (jbyte*)result); - -// $result = data; -// } -// } -// else if (bytesPerPixel == 8) -// { -// // create a new short[] object in Java -// jshortArray data = JCALL1(NewShortArray, jenv, numPixels * 4); -// if (data == 0) -// { -// jclass excep = jenv->FindClass("java/lang/OutOfMemoryError"); -// if (excep) -// jenv->ThrowNew(excep, "The system ran out of memory!"); -// $result = 0; -// return $result; -// } - -// // copy pixels from the image buffer -// JCALL4(SetShortArrayRegion, jenv, data, 0, numPixels * 4, (jshort*)result); - -// $result = data; -// } -// else -// { -// // don't know how to map -// // TODO: throw exception? -// $result = 0; -// } -// } - - -// Define typemaps for DataPtr +// Unlike void* above, this alias to void* is mapped to long so it can be used as a pointer +// address instead of having the data it points to copied %typemap(jni) DataPtr "jlong" %typemap(jtype) DataPtr "long" %typemap(jstype) DataPtr "long" @@ -860,29 +755,32 @@ %typemap(javacode) CMMCore %{ - private JSONObject metadataToMap(Metadata md) { + static JSONObject metadataToMap(Metadata md) { JSONObject tags = new JSONObject(); for (String key:md.GetKeys()) { try { - tags.put(key, md.GetSingleTag(key).GetValue()); + String value = md.GetSingleTag(key).GetValue(); + // Try to convert these to the appropriate type + // since the metadata tags coming from the core + // are all strings + try { + // Try parsing as integer first + tags.put(key, Integer.parseInt(value)); + } catch (NumberFormatException e1) { + try { + // If not integer, try as double/float + tags.put(key, Double.parseDouble(value)); + } catch (NumberFormatException e2) { + // If not a number, keep as string + tags.put(key, value); + } + } } catch (Exception e) {} } return tags; } - // private TaggedImagePointer createTaggedImagePointer(BufferDataPointer pointer) throws java.lang.Exception { - // // This only ever gets called by the V2 buffer, is called by the Pointer functions that have a different signature - // // Thus, we do not need to maintain backwards compatibility for higher level code that calls the other functions. - // // So we we only add the metadata that actually makes sense for the current abstraction. - - // // Some metadata is added here, the rest is added lazily when the image is loaded - // JSONObject tagsToAdd = new JSONObject(); - - - // TaggedImagePointer imagePointer = new TaggedImagePointer(pointer); - // } - // Snap image functions public TaggedImage getTaggedImage(int cameraChannelIndex) throws java.lang.Exception { Metadata md = new Metadata(); @@ -927,6 +825,25 @@ return new TaggedImage(pixels, metadataToMap(md)); } + // BufferDataPointer wrappers + // snap image + public TaggedImagePointer getTaggedImagePointer() throws java.lang.Exception { + return new TaggedImagePointer(getImagePointer()); + } + + // sequence acq + public TaggedImagePointer getLastTaggedImagePointer() throws java.lang.Exception { + return new TaggedImagePointer(getLastDataPointer()); + } + + public TaggedImagePointer popNextTaggedImagePointer() throws java.lang.Exception { + return new TaggedImagePointer(popNextDataPointer()); + } + + public TaggedImagePointer getLastTaggedImagePointerFromDevice(String deviceLabel) throws java.lang.Exception { + return new TaggedImagePointer(getLastDataFromDevicePointer(deviceLabel)); + } + // convenience functions follow @@ -1358,8 +1275,8 @@ namespace std { // These are needed by the void* typemaps to copy pixels and then // release them, but they shouldn't be needed by the Java wrap // because their functionality is handled by the BufferDataPointer class -// %ignore CMMCore::getImageProperties(DataPtr, int&, int&, int&, int&); -// %ignore CMMCore::releaseReadAccess(DataPtr); +%ignore CMMCore::getImageProperties(DataPtr, int&, int&, int&, int&); +%ignore CMMCore::releaseReadAccess(DataPtr); %include "../MMDevice/MMDeviceConstants.h" %include "../MMCore/Configuration.h" diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/LazyJSONObject.java b/MMCoreJ_wrap/src/main/java/mmcorej/LazyJSONObject.java index 5491f9635..26c9f7c50 100644 --- a/MMCoreJ_wrap/src/main/java/mmcorej/LazyJSONObject.java +++ b/MMCoreJ_wrap/src/main/java/mmcorej/LazyJSONObject.java @@ -1,10 +1,18 @@ -package mmcorej.org.json; +package mmcorej; + +import java.util.Iterator; +import java.util.Collections; +import mmcorej.BufferDataPointer; +import mmcorej.Metadata; +import mmcorej.org.json.JSONException; +import mmcorej.org.json.JSONObject; +import mmcorej.CMMCore; /** * A JSONObject that lazily initializes its contents from a BufferDataPointer. */ class LazyJSONObject extends JSONObject { - private final BufferDataPointer dataPointer_; + private BufferDataPointer dataPointer_; private boolean initialized_ = false; @@ -12,22 +20,30 @@ public LazyJSONObject(BufferDataPointer dataPointer) { this.dataPointer_ = dataPointer; } + /** + * Releases the BufferDataPointer associated with this LazyJSONObject. + + */ + public void releasePointer() { + dataPointer_ = null; + } + synchronized void initializeIfNeeded() throws JSONException { if (!initialized_) { try { Metadata md = new Metadata(); dataPointer_.getMetadata(md); - for (String key : md.GetKeys()) { - try { - put(key, md.GetSingleTag(key).GetValue()); - } catch (Exception e) { - throw new JSONException("Failed to get value for key: " + key, e); - } + // This handles some type conversions + JSONObject tags = CMMCore.metadataToMap(md); + Iterator keyIter = tags.keys(); + while (keyIter.hasNext()) { + String key = keyIter.next(); + super.put(key, tags.get(key)); } initialized_ = true; } catch (Exception e) { - throw new JSONException("Failed to initialize metadata", e); + throw new JSONException("Failed to initialize metadata"); } } } diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java index dd43a4666..7cdc103d5 100644 --- a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java +++ b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImage.java @@ -14,4 +14,12 @@ public TaggedImage(Object pix, JSONObject tags) { this.pix = pix; this.tags = tags; } + + // This is so that this method can be callled on the + // TaggedImagePointer subclass, so pixels are loaded lazily. + // For regular TaggedImage objects, pixels are already loaded. + public Object getPixels() { + return pix; + } + } diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java index 37a79c342..9ea927047 100644 --- a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java +++ b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java @@ -6,10 +6,10 @@ import java.util.Collections; /** - * TaggedImagePointer is a wrapper around a pointer to an image in the v2 buffer. - * It provides copy-free access to data in the C++ layer until the data is actually - * needed. This class implements lazy loading of image data to optimize memory usage - * and performance. + * TaggedImagePointer is a wrapper around a pointer to an image in the v2 buffer + * (a BufferDataPointer object). It provides copy-free access to data in the + * C++ layer until the data is actually needed. This class implements lazy loading of + * image data to optimize memory usage and performance. * *

This class extends TaggedImage and manages the lifecycle of image data stored * in native memory. It ensures proper release of resources when the image data is @@ -19,7 +19,7 @@ public class TaggedImagePointer extends TaggedImage { public LazyJSONObject tags; - private final BufferDataPointer dataPointer_; + private BufferDataPointer dataPointer_; private boolean released_ = false; /** @@ -33,6 +33,11 @@ public TaggedImagePointer(BufferDataPointer dataPointer) { this.tags = new LazyJSONObject(dataPointer); } + public Object getPixels() { + loadData(); + return pix; + } + /** * Retrieves the pixels and metadata associated with this image. * @@ -42,19 +47,17 @@ public TaggedImagePointer(BufferDataPointer dataPointer) { * * @throws IllegalStateException if te image has already been released */ - public synchronized void loadData() throws IllegalStateException { - if (released_) { - throw new IllegalStateException("Image has been released"); - } - - if (this.pix == null) { - try { - this.pix = dataPointer_.getData(); - tags.initializeIfNeeded(); - } catch (Exception e) { - throw new IllegalStateException("Failed to get pixel data", e); - } - release(); + private synchronized void loadData() throws IllegalStateException { + if (!released_) { + if (this.pix == null) { + try { + this.pix = dataPointer_.getData(); + tags.initializeIfNeeded(); + } catch (Exception e) { + throw new IllegalStateException("Failed to get pixel data", e); + } + release(); + } } } @@ -69,20 +72,12 @@ public synchronized void loadData() throws IllegalStateException { */ public synchronized void release() { if (!released_) { - dataPointer_.release(); + dataPointer_.dispose(); + tags.releasePointer(); released_ = true; + dataPointer_ = null; } } - /** - * Ensures proper cleanup of native resources when this object is garbage collected. - * - * @throws Throwable if an error occurs during finalization - */ - @Override - protected void finalize() throws Throwable { - release(); - super.finalize(); - } } From ca88de6a40b5f291d5b4151cc134877fb1ecfca6 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 22 Feb 2025 09:24:58 -0800 Subject: [PATCH 32/46] remove unused method --- MMCore/MMCore.cpp | 4 ---- MMCore/MMCore.h | 2 -- 2 files changed, 6 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index a8e9c0dbf..e3d37b577 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -4754,10 +4754,6 @@ void CMMCore::getImageProperties(DataPtr ptr, int& width, int& height, } } -bool CMMCore::IsPointerInV2Buffer(DataPtr ptr) { - return bufferManager_->IsPointerInV2Buffer(ptr); -} - /** * Returns the number of simultaneous channels the default camera is returning. */ diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 59222c73e..f1f591205 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -482,8 +482,6 @@ class CMMCore BufferDataPointer* popNextDataPointer() throw (CMMError); BufferDataPointer* getLastDataFromDevicePointer(std::string deviceLabel) throw (CMMError); - bool IsPointerInV2Buffer(DataPtr ptr); - ///@} /** \name Exposure sequence control. */ From 02cb17e5a5ce28fa6b888315168c031f785be150 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 22 Feb 2025 11:41:05 -0800 Subject: [PATCH 33/46] add method for getting raw pointer address --- MMCore/BufferDataPointer.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MMCore/BufferDataPointer.h b/MMCore/BufferDataPointer.h index 76814d053..d58bfba71 100644 --- a/MMCore/BufferDataPointer.h +++ b/MMCore/BufferDataPointer.h @@ -67,6 +67,15 @@ class BufferDataPointer { return ptr_; } + // Same as the above method, but this get wrapped by SWIG differently + // to return the actual + DataPtr getDataPointer() const { + if (!ptr_) { + return nullptr; + } + return ptr_; + } + void getImageProperties(int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { if (!bufferManager_ || !ptr_) { throw CMMError("Invalid buffer manager or pointer"); From 6a3e8d9f54e242056845a5a86f554cfbf2a9797c Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 10:10:04 -0800 Subject: [PATCH 34/46] remove CMMError signature from internal buffermanager functions --- MMCore/BufferManager.cpp | 4 ++-- MMCore/BufferManager.h | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/MMCore/BufferManager.cpp b/MMCore/BufferManager.cpp index 2bbe41c2f..8ca7a6097 100644 --- a/MMCore/BufferManager.cpp +++ b/MMCore/BufferManager.cpp @@ -160,7 +160,7 @@ int BufferManager::InsertData(const char* callerLabel, const unsigned char* buf, } -const void* BufferManager::GetLastDataMD(Metadata& md) const throw (CMMError) +const void* BufferManager::GetLastDataMD(Metadata& md) const { return GetLastDataMD(0, 0, md); // single channel size doesnt matter here } @@ -354,7 +354,7 @@ const void* BufferManager::GetLastDataMDFromDevice(const std::string& deviceLabe return basePtr; } -bool BufferManager::IsPointerInV2Buffer(const void* ptr) const throw (CMMError) { +bool BufferManager::IsPointerInV2Buffer(const void* ptr) { if (!useV2_.load()) { return false; } diff --git a/MMCore/BufferManager.h b/MMCore/BufferManager.h index 245262e74..d3807f1d4 100644 --- a/MMCore/BufferManager.h +++ b/MMCore/BufferManager.h @@ -162,7 +162,7 @@ class BufferManager { - const void* GetNthDataMD(unsigned long n, Metadata& md) const throw (CMMError); + const void* GetNthDataMD(unsigned long n, Metadata& md) const; // Channels are not directly supported in v2 buffer, these are for backwards compatibility // with circular buffer @@ -171,15 +171,15 @@ class BufferManager { * @deprecated This method is not preferred for the V2 buffer. Use GetLastDataMD() without channel parameter instead. * The V2 is data type agnostic */ - const void* GetLastDataMD(unsigned channel, unsigned singleChannelSizeBytes, Metadata& md) const throw (CMMError); + const void* GetLastDataMD(unsigned channel, unsigned singleChannelSizeBytes, Metadata& md) const; /** * @deprecated This method is not preferred for the V2 buffer. Use PopNextDataMD() without channel parameter instead. * The V2 buffer is data type agnostic */ - const void* PopNextDataMD(unsigned channel, unsigned singleChannelSizeBytes,Metadata& md) throw (CMMError); + const void* PopNextDataMD(unsigned channel, unsigned singleChannelSizeBytes,Metadata& md); - const void* GetLastDataMD(Metadata& md) const throw (CMMError); - const void* PopNextDataMD(Metadata& md) throw (CMMError); + const void* GetLastDataMD(Metadata& md) const; + const void* PopNextDataMD(Metadata& md); /** * Check if this manager is using the V2 buffer implementation. @@ -248,7 +248,7 @@ class BufferManager { * @return Pointer to the data. * @throws CMMError if no data is found or V2 buffer is not enabled. */ - const void* GetLastDataFromDevice(const std::string& deviceLabel) throw (CMMError); + const void* GetLastDataFromDevice(const std::string& deviceLabel); /** * Get the last data and metadata inserted by a specific device. @@ -257,7 +257,7 @@ class BufferManager { * @return Pointer to the data. * @throws CMMError if no data is found or V2 buffer is not enabled. */ - const void* GetLastDataMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError); + const void* GetLastDataMDFromDevice(const std::string& deviceLabel, Metadata& md); /** * Check if a pointer is currently managed by the buffer. From 3814ea99103b21c91ff1a2e7f860b731b3bcc1cd Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 10:12:03 -0800 Subject: [PATCH 35/46] remove errant bracket --- MMCore/Buffer_v2.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp index ed01dd254..3c5f8a356 100644 --- a/MMCore/Buffer_v2.cpp +++ b/MMCore/Buffer_v2.cpp @@ -163,7 +163,6 @@ int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { // Advise the kernel that we will need this memory soon. madvise(buffer_, numBytes, MADV_WILLNEED); - } #endif bufferSize_ = numBytes; From bc1a9f18951f215d253b81d9d537f5c863bb97d5 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:04:14 -0800 Subject: [PATCH 36/46] fine-grained handling metadata categories and correct default behavior --- MMCore/CoreFeatures.h | 1 + MMCore/MMCore.cpp | 101 ++++++++++++++++++++++++++--------- MMCore/MMCore.h | 28 +++++++--- MMDevice/MMDeviceConstants.h | 9 ++++ 4 files changed, 105 insertions(+), 34 deletions(-) diff --git a/MMCore/CoreFeatures.h b/MMCore/CoreFeatures.h index f1c180ca5..7cca430e9 100644 --- a/MMCore/CoreFeatures.h +++ b/MMCore/CoreFeatures.h @@ -27,6 +27,7 @@ namespace features { struct Flags { bool strictInitializationChecks = false; bool ParallelDeviceInitialization = true; + bool NewDataBuffer = false; // How to add a new Core feature: see the comment in the .cpp file. }; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index e3d37b577..5c1070a04 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -169,8 +169,13 @@ CMMCore::CMMCore() : pluginManager_(new CPluginManager()), deviceManager_(new mm::DeviceManager()), pPostedErrorsLock_(NULL), - includeSystemStateCache_(true), - metadataProfileFlag_(0) + imageMDIncludeSystemStateCache_(true), + imageMDIncludeBitDepth_(true), + imageMDIncludeCameraParams_(true), + imageMDIncludeCameraTags_(true), + imageMDIncludeTiming_(true), + imageMDIncludeLegacyCalibration_(true), + imageMDIncludeAdditionalLegacy_(false) { configGroups_ = new ConfigGroupCollection(); pixelSizeGroup_ = new PixelSizeConfigGroup(); @@ -2691,6 +2696,55 @@ bool CMMCore::getShutterOpen() throw (CMMError) return getShutterOpen(shutterLabel.c_str()); } +/** + * Set whether to include a specific category of metadata in the image metadata. + * + * Valid categories are: + * - "BitDepth" + * - "CameraParams" + * - "CameraTags" + * - "Timing" + * - "SystemStateCache" + * - "LegacyCalibration" + * - "Legacy" + * + * @param category The category of metadata to include. + * @param include Whether to include the metadata. + */ +void CMMCore::setIncludeImageMetadata(std::string & category, bool include) throw (CMMError) { + if (category == MM::g_Keyword_Metadata_BitDepth) { + imageMDIncludeBitDepth_ = include; + } + else if (category == MM::g_Keyword_Metadata_CameraParams) { + imageMDIncludeCameraParams_ = include; + } + else if (category == MM::g_Keyword_Metadata_CameraTags) { + imageMDIncludeCameraTags_ = include; + } + else if (category == MM::g_Keyword_Metadata_Timing) { + imageMDIncludeTiming_ = include; + } + else if (category == MM::g_Keyword_Metadata_SystemStateCache) { + imageMDIncludeSystemStateCache_ = include; + } + else if (category == MM::g_Keyword_Metadata_LegacyCalibration) { + imageMDIncludeLegacyCalibration_ = include; + } + else if (category == MM::g_Keyword_Metadata_Legacy) { + imageMDIncludeAdditionalLegacy_ = include; + } + else { + throw CMMError("Invalid metadata category. Valid options are: " + + std::string(MM::g_Keyword_Metadata_BitDepth) + ", " + + std::string(MM::g_Keyword_Metadata_CameraParams) + ", " + + std::string(MM::g_Keyword_Metadata_CameraTags) + ", " + + std::string(MM::g_Keyword_Metadata_Timing) + ", " + + std::string(MM::g_Keyword_Metadata_SystemStateCache) + ", " + + std::string(MM::g_Keyword_Metadata_LegacyCalibration) + ", " + + std::string(MM::g_Keyword_Metadata_Legacy)); + } +} + /** * A centralized function that adds all the metadata for camera devices. * @@ -2703,8 +2757,9 @@ bool CMMCore::getShutterOpen() throw (CMMError) * The caller should have locked the camera device, or be calling from a thread * in the camera (e.g. CoreCallback::InsertImage) */ + void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata) throw (CMMError) + unsigned byteDepth, unsigned nComponents) { // Essential metadata for interpreting the image: Width, height, and pixel type md.PutImageTag(MM::g_Keyword_Metadata_Width, (unsigned int) width); @@ -2728,19 +2783,13 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& if (!md.HasTag(MM::g_Keyword_Metadata_CameraLabel)) { md.put(MM::g_Keyword_Metadata_CameraLabel, pCam->GetLabel()); } - - // Return if no additional metadata requested - if (metadataProfileFlag_ == 0) { - return; - } - // Group 2: Bit depth - if (metadataProfileFlag_ >= 2) { + // Optional metadata + if (imageMDIncludeBitDepth_) { md.put(MM::g_Keyword_Metadata_BitDepth, CDeviceUtils::ConvertToString((long) pCam->GetBitDepth())); } - // Group 3: Camera settings (ROI, binning) - if (metadataProfileFlag_ >= 3) { + if (imageMDIncludeCameraParams_) { unsigned x, y, xSize, ySize; pCam->GetROI(x, y, xSize, ySize); std::string roiTag = std::to_string(x) + "-" + std::to_string(y) + "-" + @@ -2754,8 +2803,7 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& catch (const CMMError&) { } } - // Group 4: Timing metadata - if (metadataProfileFlag_ >= 4) { + if (imageMDIncludeTiming_) { std::lock_guard lock(imageNumbersMutex_); std::string cameraName = md.GetSingleTag(MM::g_Keyword_Metadata_CameraLabel).GetValue(); if (imageNumbers_.find(cameraName) == imageNumbers_.end()) { @@ -2775,8 +2823,7 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& md.put(MM::g_Keyword_Metadata_TimeInCore, FormatLocalTime(now)); } - // Group 5: Camera-specific tags - if (metadataProfileFlag_ >= 5) { + if (imageMDIncludeCameraTags_) { try { std::string serializedMD = pCam->GetTags(); Metadata devMD; @@ -2786,10 +2833,7 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& catch (const CMMError&) { } } - // Group 6: Pixel size metadata - // I'm unsure about these. They seem like they should be specific to a camera, but - // they seem to only be defined globally. Leave as is for now. - if (metadataProfileFlag_ >= 6) { + if (imageMDIncludeLegacyCalibration_) { try { int binning = pCam->GetBinning(); md.put("PixelSizeUm", CDeviceUtils::ConvertToString(getPixelSizeUm(true, binning))); @@ -2809,11 +2853,10 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& catch (const CMMError&) { } } - // Metadata that previously was in the Java SWIG layer and I think may be used by the application + // Metadata that previously was in the Java SWIG layer and I think may be used by the application // and/or acquisition engine. However, it doesn't really make sense that this would exist in this // since channel, slice, position, etc. higher level concepts associated with an acquisition engine - // Group 7: Legacy metadata - if (metadataProfileFlag_ >= 7 && addLegacyMetadata) { + if (imageMDIncludeAdditionalLegacy_) { md.put("Frame", "0"); md.put("FrameIndex", "0"); md.put("Position", "Default"); @@ -2837,8 +2880,7 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& } } - // Group 8: System state cache - if (metadataProfileFlag_ >= 8 && includeSystemStateCache_) { + if (imageMDIncludeSystemStateCache_) { try { // MMThreadGuard scg(stateCacheLock_); // Needed? Configuration state = getSystemStateCache(); @@ -3643,7 +3685,14 @@ void CMMCore::enableV2Buffer(bool enable) throw (CMMError) { int ret = bufferManager_->EnableV2Buffer(enable); if (ret != DEVICE_OK) - throw CMMError("Failed to enable V2 buffer", ret); + throw CMMError("Failed to enable New Data Buffer", ret); + + // Default include circular buffer, exclude new buffer + imageMDIncludeLegacyCalibration_ = !enable; + imageMDIncludeSystemStateCache_ = !enable; + imageMDIncludeCameraTags_ = !enable; + + } /** diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index f1f591205..bc0eed9a6 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -686,15 +686,22 @@ class CMMCore /** \name Image metadata. */ ///@{ + + /** + * Unclear why this is needed, deprecate? + */ bool getIncludeSystemStateCache() { - return includeSystemStateCache_; + return imageMDIncludeSystemStateCache_; } + + /** + * @deprecated Use setIncludeImageMetadata("SystemStateCache", state) instead + */ void setIncludeSystemStateCache(bool state) { - includeSystemStateCache_ = state; - } - void setMetadataProfile(int level) { - metadataProfileFlag_ = level; + imageMDIncludeSystemStateCache_ = state; } + + void setIncludeImageMetadata(std::string & category, bool include) throw (CMMError); ///@} static void parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents); @@ -751,8 +758,13 @@ class CMMCore std::map imageNumbers_; // Track image numbers per camera std::mutex imageNumbersMutex_; std::chrono::steady_clock::time_point startTime_; // Start time for elapsed time calculations in seuqence acquisition - bool includeSystemStateCache_; - int metadataProfileFlag_; + bool imageMDIncludeSystemStateCache_; + bool imageMDIncludeBitDepth_; + bool imageMDIncludeCameraParams_; + bool imageMDIncludeCameraTags_; + bool imageMDIncludeTiming_; + bool imageMDIncludeLegacyCalibration_; + bool imageMDIncludeAdditionalLegacy_; private: void InitializeErrorMessages(); @@ -789,7 +801,7 @@ class CMMCore // If support for other types of data is added in the future, alternative versions of these functions // should be added. void addCameraMetadata(std::shared_ptr pCam, Metadata& md, unsigned width, unsigned height, - unsigned byteDepth, unsigned nComponents, bool addLegacyMetadata); + unsigned byteDepth, unsigned nComponents); // Additional metadata for the multi-camera device adapter void addMultiCameraMetadata(Metadata& md, int cameraChannelIndex) const; // Want to be able to pass in binning so camera doesn't have to be locked and this can diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 9807089c8..30620b927 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -175,6 +175,15 @@ namespace MM { const char* const g_Keyword_Metadata_ROI_Y = "ROI-Y-start"; const char* const g_Keyword_Metadata_TimeInCore = "TimeReceivedByCore"; + // Image metadata categories + const char* const g_Keyword_Metadata_BitDepth = "BitDepth"; + const char* const g_Keyword_Metadata_CameraParams = "CameraParams"; + const char* const g_Keyword_Metadata_CameraTags = "CameraTags"; + const char* const g_Keyword_Metadata_Timing = "Timing"; + const char* const g_Keyword_Metadata_SystemStateCache = "SystemStateCache"; + const char* const g_Keyword_Metadata_LegacyCalibration = "LegacyCalibration"; + const char* const g_Keyword_Metadata_Legacy = "Legacy"; + // configuration file format constants const char* const g_FieldDelimiters = ","; const char* const g_CFGCommand_Device = "Device"; From 6cc217efa1a6829e44e5b38a549b00dda0439524 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:10:07 -0800 Subject: [PATCH 37/46] fix metadata keyword --- MMCore/BufferDataPointer.h | 131 ------ MMCore/Buffer_v2.cpp | 834 ---------------------------------- MMCore/Buffer_v2.h | 497 -------------------- MMCore/MMCore.cpp | 28 +- MMCore/MMCore.vcxproj | 6 +- MMCore/MMCore.vcxproj.filters | 6 +- MMDevice/MMDeviceConstants.h | 14 +- 7 files changed, 27 insertions(+), 1489 deletions(-) delete mode 100644 MMCore/BufferDataPointer.h delete mode 100644 MMCore/Buffer_v2.cpp delete mode 100644 MMCore/Buffer_v2.h diff --git a/MMCore/BufferDataPointer.h b/MMCore/BufferDataPointer.h deleted file mode 100644 index d58bfba71..000000000 --- a/MMCore/BufferDataPointer.h +++ /dev/null @@ -1,131 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// FILE: DataPointer.h -// PROJECT: Micro-Manager -// SUBSYSTEM: MMCore -//----------------------------------------------------------------------------- -// DESCRIPTION: A read-only wrapper class for accessing image data and metadata -// from a buffer slot. Provides safe access to image data by -// automatically releasing read access when the object is destroyed. -// Includes methods for retrieving pixel data and associated -// metadata.. -// -// COPYRIGHT: Henry Pinkard, 2025 -// -// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. -// License text is included with the source distribution. -// -// This file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// -// AUTHOR: Henry Pinkard, 2/16/2025 -/////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "MMCore.h" -#include "../MMDevice/ImageMetadata.h" -#include -#include - -// This is needed for SWIG Java wrapping to differentiate its void* -// from the void* that MMCore uses for returning data -typedef const void* BufferDataPointerVoidStar; - -/// A read-only wrapper for accessing image data and metadata from a buffer slot. -/// Automatically releases the read access when destroyed. -class BufferDataPointer { - -public: - - BufferDataPointer(BufferManager* bufferManager, DataPtr ptr) - : bufferManager_(bufferManager), ptr_(ptr), mutex_() - { - // check for null pointer - if (!ptr_) { - throw CMMError("Pointer is null"); - } - // check for v2 buffer use - if (!bufferManager_->IsUsingV2Buffer()) { - throw CMMError("V2 buffer must be enabled for BufferDataPointer"); - } - // throw an error if the pointer is not in the buffer - if (!bufferManager_->IsPointerInV2Buffer(ptr_)) { - throw CMMError("Pointer is not in the buffer"); - } - } - - // Returns a pointer to the pixel data (read-only) - BufferDataPointerVoidStar getData() const { - if (!ptr_) { - return nullptr; - } - return ptr_; - } - - // Same as the above method, but this get wrapped by SWIG differently - // to return the actual - DataPtr getDataPointer() const { - if (!ptr_) { - return nullptr; - } - return ptr_; - } - - void getImageProperties(int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { - if (!bufferManager_ || !ptr_) { - throw CMMError("Invalid buffer manager or pointer"); - } - Metadata md; - bufferManager_->ExtractMetadata(ptr_, md); - CMMCore::parseImageMetadata(md, width, height, byteDepth, nComponents); - } - - // Fills the provided Metadata object with metadata extracted from the pointer. - // It encapsulates calling the core API function that copies metadata from the buffer. - void getMetadata(Metadata &md) const { - if (bufferManager_ && ptr_) { - bufferManager_->ExtractMetadata(ptr_, md); - } - } - - // Destructor: releases the read access to the pointer if not already released - ~BufferDataPointer() { - release(); - } - - - // Explicitly release the pointer before destruction if needed - void release() { - std::lock_guard lock(mutex_); - if (bufferManager_ && ptr_) { - - int ret = bufferManager_->ReleaseReadAccess(ptr_); - if (ret != DEVICE_OK) { - throw CMMError("Failed to release read access to buffer"); - } - ptr_ = nullptr; // Mark as released - - } - } - - unsigned getSizeBytes() const { - if (bufferManager_ && ptr_) { - return bufferManager_->GetDataSize(ptr_); - } - return 0; - } - -private: - // Disable copy semantics to avoid double releasing the pointer - BufferDataPointer(const BufferDataPointer&); - BufferDataPointer& operator=(const BufferDataPointer&); - - BufferManager* bufferManager_; - const void* ptr_; - mutable std::mutex mutex_; -}; diff --git a/MMCore/Buffer_v2.cpp b/MMCore/Buffer_v2.cpp deleted file mode 100644 index 3c5f8a356..000000000 --- a/MMCore/Buffer_v2.cpp +++ /dev/null @@ -1,834 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// FILE: Buffer_v2.cpp -// PROJECT: Micro-Manager -// SUBSYSTEM: MMCore -//----------------------------------------------------------------------------- -// DESCRIPTION: Generic implementation of a buffer for storing image data and -// metadata. Provides thread-safe access for reading and writing -// with configurable overflow behavior. -//// -// COPYRIGHT: Henry Pinkard, 2025 -// -// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. -// License text is included with the source distribution. -// -// This file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// -// AUTHOR: Henry Pinkard, 01/31/2025 - - -/* -Design Overview: - -The buffer is designed as a flexible data structure for storing image data and metadata: - -Buffer Structure: -- A large block of contiguous memory divided into slots - -Slots: -- Contiguous sections within the buffer that can vary in size -- Support exclusive write access with shared read access -- Memory management through reference counting: - - Writers get exclusive ownership during writes - - Readers can get shared read-only access - - Slots are recycled when all references are released (In non-overwriting mode) - -Data Access: -- Two access patterns supported: - 1. Copy-based access - 2. Direct pointer access with explicit release -- Reference counting ensures safe memory management -- Slots become available for recycling when: - - Writing is complete (via Insert or GetDataWriteSlot+Release) - - All readers have released their references - -Metadata Handling: -- Devices must specify PixelType when adding data -- Device-specific metadata requirements (e.g. image dimensions) are handled at the - device API level rather than in the buffer API to maintain clean separation -*/ - - -#include "Buffer_v2.h" -#include -#include // for std::this_thread::yield if needed -#include -#include -#include -#include -#include -#include -#include "TaskSet_CopyMemory.h" -#include - - -/////////////////////////////////////////////////////////////////////////////// -// DataBuffer Implementation -/////////////////////////////////////////////////////////////////////////////// - -namespace { - // Get system page size at runtime - inline size_t GetPageSize() { - #ifdef _WIN32 - SYSTEM_INFO si; - GetSystemInfo(&si); - return si.dwPageSize; - #else - return sysconf(_SC_PAGESIZE); - #endif - } - - // Cache the page size. - const size_t PAGE_SIZE = GetPageSize(); - - // Inline alignment function using bitwise operations. - // For a power-of-two alignment, this computes the smallest multiple - // of 'alignment' that is at least as large as 'value'. - inline size_t Align(size_t value) { - // Use PAGE_SIZE if value is large enough; otherwise use the sizeof(max_align_t) - size_t alignment = (value >= PAGE_SIZE) ? PAGE_SIZE : alignof(std::max_align_t); - return (value + alignment - 1) & ~(alignment - 1); - } -} - -DataBuffer::DataBuffer(unsigned int memorySizeMB) - : buffer_(nullptr), - bufferSize_(0), - overwriteWhenFull_(false), - nextAllocOffset_(0), - currentSlotIndex_(0), - overflow_(false), - threadPool_(std::make_shared()), - tasksMemCopy_(std::make_shared(threadPool_)) -{ - // Pre-allocate slots (one per MB) and store in both slotPool_ and unusedSlots_ - for (unsigned int i = 0; i < memorySizeMB; i++) { - BufferSlot* bs = new BufferSlot(); - slotPool_.push_back(bs); - unusedSlots_.push_back(bs); - } - - ReinitializeBuffer(memorySizeMB); -} - -DataBuffer::~DataBuffer() { - if (buffer_) { - #ifdef _WIN32 - VirtualFree(buffer_, 0, MEM_RELEASE); - #else - munmap(buffer_, bufferSize_); - #endif - buffer_ = nullptr; - } - - std::lock_guard lock(slotManagementMutex_); - for (BufferSlot* bs : slotPool_) { - delete bs; - } -} - -/** - * Allocate a character buffer - * @param memorySizeMB The size (in MB) of the buffer to allocate. - * @return Error code (0 on success). - */ -int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { - // Convert MB to bytes (1 MB = 1048576 bytes) - size_t numBytes = static_cast(memorySizeMB) * (1ULL << 20); - - #ifdef _WIN32 - buffer_ = (unsigned char*)VirtualAlloc(nullptr, numBytes, - MEM_RESERVE | MEM_COMMIT, - PAGE_READWRITE); - if (!buffer_) { - return DEVICE_ERR; - } - - #else - buffer_ = (unsigned char*)mmap(nullptr, numBytes, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, - -1, 0); - if (buffer_ == MAP_FAILED) { - buffer_ = nullptr; - return DEVICE_ERR; - } - - // Advise the kernel that we will need this memory soon. - madvise(buffer_, numBytes, MADV_WILLNEED); - - #endif - - bufferSize_ = numBytes; - overflow_ = false; - freeRegions_.clear(); - freeRegions_[0] = bufferSize_; - return DEVICE_OK; -} - -/** - * Release the buffer. - * @return Error code (0 on success, error if buffer not found or already released). - */ -int DataBuffer::ReleaseBuffer() { - if (buffer_ != nullptr) { - #ifdef _WIN32 - VirtualFree(buffer_, 0, MEM_RELEASE); - #else - munmap(buffer_, bufferSize_); - #endif - buffer_ = nullptr; - return DEVICE_OK; - } - // TODO: Handle errors if other parts of the system still hold pointers. - return DEVICE_ERR; -} - -/** - * Pack the data as [BufferSlotRecord][image data][serialized metadata] - */ -int DataBuffer::InsertData(const void* data, size_t dataSize, const Metadata* pMd, const std::string& deviceLabel) { - - void* dataPointer = nullptr; - void* additionalMetadataPointer = nullptr; - - // Convert metadata to serialized string if provided - std::string serializedMetadata; - if (pMd != nullptr) { - serializedMetadata = pMd->Serialize(); - } - // Initial metadata is all metadata because the image and metadata are already complete - int result = AcquireWriteSlot(dataSize, 0, &dataPointer, &additionalMetadataPointer, - serializedMetadata, deviceLabel); - if (result != DEVICE_OK) { - return result; - } - - tasksMemCopy_->MemCopy((void*)dataPointer, data, dataSize); - - // Finalize the write slot. - return FinalizeWriteSlot(dataPointer, 0); -} - -/** - * Configure whether to overwrite old data when buffer is full. - * - * If true, when there are no more slots available for writing because - * images haven't been read fast enough, then automatically recycle the - * oldest slot(s) in the buffer as needed in order to make space for new images. - * This is suitable for situations when its okay to drop frames, like live - * view when data is not being saved. - * - * If false, then throw an exception if the buffer becomes full. - * - * @param overwrite Whether to enable overwriting of old data - * @return Error code (0 on success) - */ -int DataBuffer::SetOverwriteData(bool overwrite) { - overwriteWhenFull_ = overwrite; - return DEVICE_OK; -} - -/** - * Get whether the buffer should overwrite old data when full. - * @return True if overwriting is enabled, false otherwise. - */ -bool DataBuffer::GetOverwriteData() const { - return overwriteWhenFull_; -} - -/** - * Reset the buffer, discarding all data that is not currently held externally. - */ -void DataBuffer::Reset() { - // Reuse ReinitializeBuffer with current size - ReinitializeBuffer(GetMemorySizeMB()); -} - -/** - * Get a pointer to the next available data slot in the buffer for writing. - * - * The caller must release the slot using ReleaseDataSlot after writing is complete. - */ -int DataBuffer::AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, - void** dataPointer, - void** additionalMetadataPointer, - const std::string& serializedInitialMetadata, - const std::string& deviceLabel) -{ - if (buffer_ == nullptr) { - return DEVICE_ERR; - } - - // Total size includes data, initial metadata, and any additional metadata space - size_t rawTotalSize = dataSize + serializedInitialMetadata.size() + additionalMetadataSize; - size_t totalSlotSize = Align(rawTotalSize); - size_t candidateStart = 0; - - if (!overwriteWhenFull_) { - std::lock_guard lock(slotManagementMutex_); - // Look in the free-region list as fallback using a cached cursor. - { - bool found = false; - size_t newCandidate = 0; - // Start search from freeRegionCursor_ - auto it = freeRegions_.lower_bound(freeRegionCursor_); - // Loop over free regions at most once (wrapping around if necessary). - for (size_t count = 0, sz = freeRegions_.size(); count < sz; count++) { - if (it == freeRegions_.end()) - it = freeRegions_.begin(); - size_t alignedCandidate = Align(it->first); - if (it->first + it->second >= alignedCandidate + totalSlotSize) { - newCandidate = alignedCandidate; - found = true; - break; - } - ++it; - } - if (found) { - candidateStart = newCandidate; - // Update the cursor so that next search can start here. - freeRegionCursor_ = candidateStart + totalSlotSize; - return CreateSlot(candidateStart, totalSlotSize, dataSize, additionalMetadataSize, - dataPointer, additionalMetadataPointer, - true, serializedInitialMetadata, deviceLabel); - } - } - - // No recycled slot or free region can satisfy the allocation. - overflow_ = true; - *dataPointer = nullptr; - *additionalMetadataPointer = nullptr; - return DEVICE_ERR; - } else { - // Overwrite mode - size_t prevOffset, newOffset; - do { - prevOffset = nextAllocOffset_.load(std::memory_order_relaxed); - candidateStart = Align(prevOffset); - if (candidateStart + totalSlotSize > bufferSize_) - candidateStart = 0; // Wrap around if needed. - newOffset = candidateStart + totalSlotSize; - } while (!nextAllocOffset_.compare_exchange_weak(prevOffset, newOffset)); - - // Only now grab the lock to register the new slot. - { - std::lock_guard lock(slotManagementMutex_); - return CreateSlot(candidateStart, totalSlotSize, dataSize, additionalMetadataSize, - dataPointer, additionalMetadataPointer, false, serializedInitialMetadata, deviceLabel); - } - } -} - -/** - * @brief Release a data slot after writing is complete. - * - * @param caller The device calling this function. - * @param buffer The buffer to be released. - * @return Error code (0 on success). - */ -int DataBuffer::FinalizeWriteSlot(const void* dataPointer, size_t actualMetadataBytes) { - if (dataPointer == nullptr) - return DEVICE_ERR; - - BufferSlot* slot = nullptr; - { - std::lock_guard lock(slotManagementMutex_); - slot = FindSlotForPointer(dataPointer); - if (!slot) - return DEVICE_ERR; - - // Update the slot with actual metadata size - slot->UpdateAdditionalMetadataSize(actualMetadataBytes); - } - - slot->ReleaseWriteAccess(); - - // Notify waiting threads under a brief lock - { - std::lock_guard lock(slotManagementMutex_); - dataCV_.notify_all(); - } - - return DEVICE_OK; -} - -/** - * ReleaseSlot is called after a slot's content has been fully read. - * - * This implementation pushes only the start of the released slot onto the FILO - * (releasedSlots_) and removes the slot from the active slot map and activeSlots_. - */ -int DataBuffer::ReleaseDataReadPointer(const void* dataPointer) { - if (dataPointer == nullptr) - return DEVICE_ERR; - - // First find the slot without the global lock - BufferSlot* slot = nullptr; - { - std::lock_guard lock(slotManagementMutex_); - slot = FindSlotForPointer(dataPointer); - if (!slot) - return DEVICE_ERR; - } - const size_t offset = static_cast(dataPointer) - - static_cast(buffer_); - - // Release the read access outside the global lock - slot->ReleaseReadAccess(); - - if (!overwriteWhenFull_) { - std::lock_guard lock(slotManagementMutex_); - // Now check if the slot is not being accessed - if (slot->IsFree()) { - auto it = activeSlotsByStart_.find(offset); - DeleteSlot(offset, it); - } - } - - return DEVICE_OK; -} - -const void* DataBuffer::PopNextDataReadPointer(Metadata &md, bool waitForData) -{ - if (overwriteWhenFull_) { - throw std::runtime_error("PopNextDataReadPointer is not available in overwrite mode"); - } - - BufferSlot* slot = nullptr; - size_t slotStart = 0; - - // First, get the slot under the global lock - { - std::unique_lock lock(slotManagementMutex_); - while (activeSlotsVector_.empty()) { - if (!waitForData) - return nullptr; - dataCV_.wait(lock); - } - // Atomically take the slot and advance the index - slot = activeSlotsVector_[currentSlotIndex_]; - slotStart = slot->GetStart(); - currentSlotIndex_ = (currentSlotIndex_ + 1) % activeSlotsVector_.size(); - } // Release global lock - - // Now acquire read access outside the global lock - slot->AcquireReadAccess(); - - const unsigned char* dataPointer = static_cast(buffer_) + slotStart; - this->ExtractMetadata(dataPointer, slot, md); - - return dataPointer; -} - -const void* DataBuffer::PeekLastDataReadPointer(Metadata &md) { - if (!overwriteWhenFull_) { - throw std::runtime_error("PeekLastDataReadPointer is only available in overwrite mode"); - } - - BufferSlot* currentSlot = nullptr; - { - std::unique_lock lock(slotManagementMutex_); - if (activeSlotsVector_.empty()) { - return nullptr; - } - - // Get the most recent slot (last in vector) - currentSlot = activeSlotsVector_.back(); - } - - currentSlot->AcquireReadAccess(); - - const void* result = static_cast(buffer_) + currentSlot->GetStart(); - - if (ExtractMetadata(result, currentSlot, md) != DEVICE_OK) { - currentSlot->ReleaseReadAccess(); - return nullptr; - } - - return result; -} - -const void* DataBuffer::PeekDataReadPointerAtIndex(size_t n, Metadata &md) { - if (!overwriteWhenFull_) { - throw std::runtime_error("PeekDataReadPointerAtIndex is only available in overwrite mode"); - } - - BufferSlot* currentSlot = nullptr; - { - // Lock the global slot management mutex to safely access the active slots. - std::unique_lock lock(slotManagementMutex_); - if (activeSlotsVector_.empty() || n >= activeSlotsVector_.size()) { - return nullptr; - } - - // Instead of looking ahead from currentSlotIndex_, we look back from the end. - // For n==0, return the most recent slot; for n==1, the one before it; etc. - size_t index = activeSlotsVector_.size() - n - 1; - currentSlot = activeSlotsVector_[index]; - } - - currentSlot->AcquireReadAccess(); - - const unsigned char* dataPointer = static_cast(buffer_) + currentSlot->GetStart(); - this->ExtractMetadata(dataPointer, currentSlot, md); - - return dataPointer; -} - -const void* DataBuffer::PeekLastDataReadPointerFromDevice(const std::string& deviceLabel, Metadata& md) { - if (!overwriteWhenFull_) { - throw std::runtime_error("PeekLastDataReadPointerFromDevice is only available in overwrite mode"); - } - - BufferSlot* matchingSlot = nullptr; - { - std::unique_lock lock(slotManagementMutex_); - - // Search backwards through activeSlotsVector_ to find most recent matching slot - for (auto it = activeSlotsVector_.rbegin(); it != activeSlotsVector_.rend(); ++it) { - if ((*it)->GetDeviceLabel() == deviceLabel) { - matchingSlot = *it; - break; - } - } - - if (!matchingSlot) { - return nullptr; - } - } - - // Acquire read access and get data pointer - matchingSlot->AcquireReadAccess(); - - const void* result = static_cast(buffer_) + matchingSlot->GetStart(); - - if (ExtractMetadata(result, matchingSlot, md) != DEVICE_OK) { - matchingSlot->ReleaseReadAccess(); - return nullptr; - } - - return result; -} - -unsigned int DataBuffer::GetMemorySizeMB() const { - // Convert bytes to MB (1 MB = 1048576 bytes) - return static_cast(bufferSize_ >> 20); -} - -size_t DataBuffer::GetOccupiedSlotCount() const { - std::lock_guard lock(slotManagementMutex_); - return activeSlotsVector_.size(); -} - -size_t DataBuffer::GetOccupiedMemory() const { - std::lock_guard lock(slotManagementMutex_); - size_t usedMemory = 0; - for (const auto& slot : activeSlotsVector_) { - usedMemory += slot->GetLength(); - } - return usedMemory; -} - -size_t DataBuffer::GetFreeMemory() const { - std::lock_guard lock(slotManagementMutex_); - // Free memory is the total buffer size minus the sum of all occupied memory. - size_t usedMemory = 0; - for (const auto& slot : activeSlotsVector_) { - usedMemory += slot->GetLength(); - } - return (bufferSize_ > usedMemory) ? (bufferSize_ - usedMemory) : 0; -} - -bool DataBuffer::Overflow() const { - std::lock_guard lock(slotManagementMutex_); - return overflow_; -} - -/** - * Reinitialize the DataBuffer by clearing all internal data structures, - * releasing the current buffer, and reallocating a new one. - * This method uses the existing slotManagementMutex_ to ensure thread-safety. - * - * @param memorySizeMB New size (in MB) for the buffer. - * @return DEVICE_OK on success. - * @throws std::runtime_error if any slot is still actively being read or written. - */ -int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { - std::lock_guard lock(slotManagementMutex_); - - // Ensure no active readers/writers exist. - for (BufferSlot* slot : activeSlotsVector_) { - if (!slot->IsFree()) { - throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); - } - } - - // Clear internal data structures - activeSlotsVector_.clear(); - activeSlotsByStart_.clear(); - currentSlotIndex_ = 0; - nextAllocOffset_ = 0; - overflow_ = false; - - // Reset the slot pool - unusedSlots_.clear(); - for (BufferSlot* bs : slotPool_) { - unusedSlots_.push_back(bs); - } - - // Release and reallocate the buffer - if (buffer_ != nullptr) { - #ifdef _WIN32 - VirtualFree(buffer_, 0, MEM_RELEASE); - #else - munmap(buffer_, bufferSize_); - #endif - buffer_ = nullptr; - } - - return AllocateBuffer(memorySizeMB); -} - -long DataBuffer::GetActiveSlotCount() const { - return static_cast(activeSlotsVector_.size()); -} - -int DataBuffer::ExtractMetadata(const void* dataPointer, BufferSlot* slot, Metadata &md) { - // No lock is required here because we assume the slot is already locked - - if (!dataPointer || !slot) - return DEVICE_ERR; // Invalid pointer - - // Calculate metadata pointers and sizes from the slot - const unsigned char* initialMetadataPtr = static_cast(dataPointer) + slot->GetDataSize(); - size_t initialMetadataSize = slot->GetInitialMetadataSize(); - const unsigned char* additionalMetadataPtr = initialMetadataPtr + initialMetadataSize; - size_t additionalMetadataSize = slot->GetAdditionalMetadataSize(); - - // Handle initial metadata if present - if (initialMetadataSize > 0) { - Metadata initialMd; - std::string initialMetaStr(reinterpret_cast(initialMetadataPtr), initialMetadataSize); - initialMd.Restore(initialMetaStr.c_str()); - md.Merge(initialMd); - } - - // Handle additional metadata if present - if (additionalMetadataSize > 0) { - Metadata additionalMd; - std::string additionalMetaStr(reinterpret_cast(additionalMetadataPtr), additionalMetadataSize); - additionalMd.Restore(additionalMetaStr.c_str()); - md.Merge(additionalMd); - } - - return DEVICE_OK; -} - -// NOTE: Caller must hold slotManagementMutex_ for thread safety. -BufferSlot* DataBuffer::FindSlotForPointer(const void* dataPointer) { - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - if (buffer_ == nullptr) - return nullptr; - std::size_t offset = static_cast(dataPointer) - - static_cast(buffer_); - auto it = activeSlotsByStart_.find(offset); - return (it != activeSlotsByStart_.end()) ? it->second : nullptr; -} - -void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd) { - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - // Find the free region that starts at or after newEnd - auto right = freeRegions_.lower_bound(newRegionEnd); - - // Check if there is a free region immediately preceding the new region - auto left = freeRegions_.lower_bound(newRegionStart); - if (left != freeRegions_.begin()) { - auto prev = std::prev(left); - // If the previous region's end matches the new region's start... - if (prev->first + prev->second == newRegionStart) { - newRegionStart = prev->first; - freeRegions_.erase(prev); - } - } - - // Check if the region immediately to the right can be merged - if (right != freeRegions_.end() && right->first == newRegionEnd) { - newRegionEnd = right->first + right->second; - freeRegions_.erase(right); - } - - // Insert the merged (or standalone) free region - size_t newRegionSize = (newRegionEnd > newRegionStart ? newRegionEnd - newRegionStart : 0); - if (newRegionSize > 0) { - freeRegions_[newRegionStart] = newRegionSize; - } -} - -void DataBuffer::RemoveFromActiveTracking(size_t offset, std::unordered_map::iterator it) { - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - activeSlotsByStart_.erase(it); - for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { - if ((*vecIt)->GetStart() == offset) { - // Determine the index being removed. - size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); - activeSlotsVector_.erase(vecIt); - // Adjust the currentSlotIndex_; if the deleted slot was before it, decrement. - if (currentSlotIndex_ > indexDeleted) - currentSlotIndex_--; - break; - } - } -} - -void DataBuffer::DeleteSlot(size_t offset, std::unordered_map::iterator it) { - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - - size_t newRegionStart = offset; - size_t newRegionEnd = offset + it->second->GetLength(); - - // Return the slot to the pool before removing from active tracking - ReturnSlotToPool(it->second); - - MergeFreeRegions(newRegionStart, newRegionEnd); - RemoveFromActiveTracking(offset, it); -} - -void DataBuffer::UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize) { - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - auto it = freeRegions_.upper_bound(candidateStart); - if (it != freeRegions_.begin()) { - --it; - size_t freeRegionStart = it->first; - size_t freeRegionSize = it->second; - size_t freeRegionEnd = freeRegionStart + freeRegionSize; - - if (candidateStart >= freeRegionStart && - candidateStart + totalSlotSize <= freeRegionEnd) { - freeRegions_.erase(it); - - if (candidateStart > freeRegionStart) { - size_t gap = candidateStart - freeRegionStart; - if (gap > 0) - freeRegions_.insert({freeRegionStart, gap}); - } - - if (freeRegionEnd > candidateStart + totalSlotSize) { - size_t gap = freeRegionEnd - (candidateStart + totalSlotSize); - if (gap > 0) - freeRegions_.insert({candidateStart + totalSlotSize, gap}); - } - } - } -} - -int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, - size_t dataSize, size_t additionalMetadataSize, - void** dataPointer, void** additionalMetadataPointer, - bool fromFreeRegion, const std::string& serializedInitialMetadata, - const std::string& deviceLabel) -{ - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, - dataSize, serializedInitialMetadata.size(), - additionalMetadataSize, deviceLabel); - - newSlot->AcquireWriteAccess(); - - // Initialize the data pointer before using it. - *dataPointer = static_cast(buffer_) + newSlot->GetStart(); - - if (!serializedInitialMetadata.empty()) { - std::memcpy(static_cast(*dataPointer) + dataSize, serializedInitialMetadata.data(), - serializedInitialMetadata.size()); - newSlot->SetInitialMetadataSize(serializedInitialMetadata.size()); - } - - *additionalMetadataPointer = static_cast(*dataPointer) + newSlot->GetDataSize() + newSlot->GetInitialMetadataSize(); - - if (fromFreeRegion) { - UpdateFreeRegions(candidateStart, totalSlotSize); - } - - return DEVICE_OK; -} - -BufferSlot* DataBuffer::GetSlotFromPool(size_t start, size_t totalLength, - size_t dataSize, size_t initialMetadataSize, - size_t additionalMetadataSize, - const std::string& deviceLabel) { - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - - // Grow the pool if needed. - if (unusedSlots_.empty()) { - BufferSlot* newSlot = new BufferSlot(); - slotPool_.push_back(newSlot); - unusedSlots_.push_back(newSlot); - } - - // Get a slot from the front of the deque. - BufferSlot* slot = unusedSlots_.front(); - unusedSlots_.pop_front(); - slot->Reset(start, totalLength, dataSize, initialMetadataSize, additionalMetadataSize, deviceLabel); - - // Add to active tracking. - activeSlotsVector_.push_back(slot); - activeSlotsByStart_[start] = slot; - return slot; -} - -void DataBuffer::ReturnSlotToPool(BufferSlot* slot) { - assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); - unusedSlots_.push_back(slot); -} - -int DataBuffer::ExtractCorrespondingMetadata(const void* dataPointer, Metadata &md) { - BufferSlot* slot = nullptr; - { - std::lock_guard lock(slotManagementMutex_); - slot = FindSlotForPointer(dataPointer); - if (!slot) { - return DEVICE_ERR; - } - } - - // Extract metadata (internal method doesn't need lock) - return ExtractMetadata(dataPointer, slot, md); -} - -size_t DataBuffer::GetDatumSize(const void* dataPointer) { - std::lock_guard lock(slotManagementMutex_); - BufferSlot* slot = FindSlotForPointer(dataPointer); - if (!slot) { - throw std::runtime_error("DataBuffer::GetDatumSize: pointer not found in buffer"); - } - return slot->GetDataSize(); -} - -bool DataBuffer::IsPointerInBuffer(const void* ptr) { - if (buffer_ == nullptr || ptr == nullptr) { - return false; - } - // get the mutex - std::lock_guard lock(slotManagementMutex_); - // find the slot - BufferSlot* slot = FindSlotForPointer(ptr); - return slot != nullptr; -} - -int DataBuffer::NumOutstandingSlots() const { - std::lock_guard lock(slotManagementMutex_); - int numOutstanding = 0; - for (const BufferSlot* slot : activeSlotsVector_) { - if (!slot->IsFree()) { - numOutstanding++; - } - } - return numOutstanding; -} diff --git a/MMCore/Buffer_v2.h b/MMCore/Buffer_v2.h deleted file mode 100644 index 6bface371..000000000 --- a/MMCore/Buffer_v2.h +++ /dev/null @@ -1,497 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// FILE: Buffer_v2.h -// PROJECT: Micro-Manager -// SUBSYSTEM: MMCore -//----------------------------------------------------------------------------- -// DESCRIPTION: Generic implementation of a buffer for storing image data and -// metadata. Provides thread-safe access for reading and writing -// with configurable overflow behavior. -// -// The buffer is organized into slots (BufferSlot objects), each of which -// supports exclusive write access and shared read access. Read access is -// delivered using const pointers and is tracked via RAII-based synchronization. -// Write access is protected via an exclusive lock. This ensures that once a -// read pointer is given out it cannot be misused for writing. -// -// COPYRIGHT: Henry Pinkard, 2025 -// -// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. -// License text is included with the source distribution. -// -// This file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. -// -// AUTHOR: Henry Pinkard, 01/31/2025 -/////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "../MMDevice/ImageMetadata.h" -#include "../MMDevice/MMDevice.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "TaskSet_CopyMemory.h" -#include -#include - -/** - * BufferSlot represents a contiguous slot in the DataBuffer that holds image - * data and metadata. It uses RAII-based locking with std::shared_timed_mutex to - * support exclusive write access and concurrent shared read access. - */ -class BufferSlot { -public: - /** - * Constructs a BufferSlot with all sizes specified up front. - * - * @param start The starting offset (in bytes) within the buffer. - * @param totalLength The total length (in bytes) reserved for this slot, typically - * an aligned size (which includes image data and metadata). - * @param imageSize The exact number of bytes for the image data. - * @param metadataSize The exact number of bytes for the metadata. - */ - BufferSlot() - : start_(0), - length_(0), - imageSize_(0), - initialMetadataSize_(0), - additionalMetadataSize_(0), - deviceLabel_(), - rwMutex_() - { - } - - /** - * Destructor. - */ - ~BufferSlot() {}; - - /** - * Returns the starting offset (in bytes) of the slot. - * @return The slot's start offset. - */ - std::size_t GetStart() const { return start_; } - - /** - * Returns the length (in bytes) of the slot. - * - * @return The slot's length. - */ - std::size_t GetLength() const { return length_; } - - /** - * Acquires exclusive (write) access (blocking). - */ - void AcquireWriteAccess() { - rwMutex_.lock(); - } - - /** - * Releases exclusive write access. - */ - void ReleaseWriteAccess() { - rwMutex_.unlock(); - } - - /** - * Acquires shared read access (blocking). - */ - void AcquireReadAccess() { - rwMutex_.lock_shared(); - } - - /** - * Releases shared read access. - */ - void ReleaseReadAccess() { - rwMutex_.unlock_shared(); - } - - /** - * Checks if the slot is completely free (no readers, no writers). - * We do this by attempting to lock it exclusively: - * if we succeed, there are no concurrent locks. - */ - bool IsFree() const { - std::unique_lock lk(rwMutex_, std::try_to_lock); - return lk.owns_lock(); - } - - /** - * Resets the slot with new parameters. - * The assertion uses IsFree() as a best-effort check before reinitializing. - */ - void Reset(size_t start, - size_t length, - size_t imageSize, - size_t initialMetadataSize, - size_t additionalMetadataSize, - const std::string& deviceLabel) - { - // If this fails, there's likely an active read or write lock on the slot. - assert(IsFree() && - "BufferSlot mutex still locked during Reset - indicates a bug!"); - - start_ = start; - length_ = length; - imageSize_ = imageSize; - initialMetadataSize_ = initialMetadataSize; - additionalMetadataSize_ = initialMetadataSize + additionalMetadataSize; - deviceLabel_ = deviceLabel; - } - - /** - * Updates the metadata size after writing is complete. - * @param newSize The actual size of the written metadata. - */ - void UpdateAdditionalMetadataSize(size_t newSize) { - additionalMetadataSize_ = newSize; - } - - /** - * Record the number of bytes of the initial metadata that have been written to this slot. - */ - void SetInitialMetadataSize(size_t initialSize) { - initialMetadataSize_ = initialSize; - } - - /** - * Returns the size of the image data in bytes. - * @return The image data size. - */ - std::size_t GetDataSize() const { - return imageSize_; - } - - /** - * Returns the size of the initial metadata in bytes. - * @return The initial metadata size. - */ - std::size_t GetInitialMetadataSize() const { - return initialMetadataSize_; - } - - /** - * Returns the size of the additional metadata in bytes. - * @return The additional metadata size. - */ - std::size_t GetAdditionalMetadataSize() const { - return additionalMetadataSize_; - } - - const std::string& GetDeviceLabel() const { - return deviceLabel_; - } - -private: - std::size_t start_; - std::size_t length_; - size_t imageSize_; - size_t initialMetadataSize_; - size_t additionalMetadataSize_; - std::string deviceLabel_; - - mutable std::shared_timed_mutex rwMutex_; -}; - -/** - * DataBuffer manages a contiguous block of memory divided into BufferSlot objects - * for storing image data and metadata. Each slot in memory holds - * only the image data (followed immediately by metadata), while header information - * is maintained in the BufferSlot objects. - */ -class DataBuffer { -public: - - /** - * Constructor. - * @param memorySizeMB The size (in megabytes) of the buffer. - */ - DataBuffer(unsigned int memorySizeMB); - - /** - * Destructor. - */ - ~DataBuffer(); - - /** - * Allocates the memory buffer. - * @param memorySizeMB Size in megabytes. - * @return DEVICE_OK on success. - */ - int AllocateBuffer(unsigned int memorySizeMB); - - /** - * Releases the memory buffer. - * @return DEVICE_OK on success. - */ - int ReleaseBuffer(); - - /** - * Inserts image data along with metadata into the buffer. - * @param data Pointer to the raw image data. - * @param dataSize The image data size in bytes. - * @param pMd Pointer to the metadata (can be null if not applicable). - * @param deviceLabel The label of the device that is the source of the data. - * @return DEVICE_OK on success. - */ - int InsertData(const void* data, size_t dataSize, const Metadata* pMd, const std::string& deviceLabel); - - /** - * Sets whether the buffer should overwrite old data when full. - * @param overwrite True to enable overwriting. - * @return DEVICE_OK on success. - */ - int SetOverwriteData(bool overwrite); - - /** - * Returns whether the buffer should overwrite old data when full. - * @return True if overwriting is enabled, false otherwise. - */ - bool GetOverwriteData() const; - - /** - * Acquires a write slot large enough to hold the image data and metadata. - * On success, returns pointers for the image data and metadata regions. - * - * @param dataSize The number of bytes reserved for data. - * @param additionalMetadataSize The maximum number of bytes reserved for additional metadata. - * @param dataPointer On success, receives a pointer to the data region. - * @param additionalMetadataPointer On success, receives a pointer to the additional metadata region. - * @param serializedInitialMetadata Optional string containing initial metadata to write. - * @return DEVICE_OK on success. - */ - int AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, - void** dataPointer, - void** additionalMetadataPointer, - const std::string& serializedInitialMetadata, - const std::string& deviceLabel); - - /** - * Finalizes (releases) a write slot after data has been written. - * Requires the actual number of metadata bytes written. - * - * @param imageDataPointer Pointer previously obtained from AcquireWriteSlot. - * @param actualMetadataBytes The actual number of metadata bytes written. - * @return DEVICE_OK on success. - */ - int FinalizeWriteSlot(const void* dataPointer, size_t actualMetadataBytes); - - /** - * Releases read access for the image data after reading. - * @param imageDataPointer Pointer previously obtained from reading routines. - * @return DEVICE_OK on success. - */ - int ReleaseDataReadPointer(const void* dataPointer); - - /** - * Retrieves and consumes the next available data entry for reading, - * populating the provided Metadata object. - * @param md Metadata object to populate. - * @param waitForData If true, blocks until data is available. - * @return Pointer to the image data region, or nullptr if none available. - */ - const void* PopNextDataReadPointer(Metadata &md, bool waitForData); - - /** - * Peeks at the most recently added data entry. - * @param md Metadata object populated from the stored metadata. - * @return Pointer to the start of the data region. - */ - const void* PeekLastDataReadPointer(Metadata &md); - - /** - * Peeks at the nth unread data entry without consuming it. - * (n = 0 is equivalent to PeekNextDataReadPointer). - * @param n Index of the data entry to peek at (0 for next available). - * @param md Metadata object populated from the stored metadata. - * @return Pointer to the start of the data region. - */ - const void* PeekDataReadPointerAtIndex(size_t n, Metadata &md); - - /** - * Get the last image inserted by a specific device. - * @param deviceLabel The label of the device to get the image from. - * @param md Metadata object to populate. - * @return Pointer to the image data, or nullptr if not found. - */ - const void* PeekLastDataReadPointerFromDevice(const std::string& deviceLabel, Metadata& md); - - /** - * Returns the total buffer memory size (in MB). - * @return Buffer size in MB. - */ - unsigned int GetMemorySizeMB() const; - - /** - * Returns the number of occupied buffer slots. - * @return Occupied slot count. - */ - size_t GetOccupiedSlotCount() const; - - /** - * Returns the total occupied memory in bytes. - * @return Sum of active slot lengths. - */ - size_t GetOccupiedMemory() const; - - /** - * Returns the amount of free memory remaining in bytes. - * @return Free byte count. - */ - size_t GetFreeMemory() const; - - /** - * Indicates whether a buffer overflow has occurred. - * @return True if an insert failed (buffer full), false otherwise. - */ - bool Overflow() const; - - /** - * Returns the number of unread slots in the buffer. - * @return Unread slot count. - */ - long GetActiveSlotCount() const; - - - /** - * Extracts metadata for a given image data pointer. - * Thread-safe method that acquires necessary locks to lookup metadata location. - * - * @param dataPtr Pointer to the (usuallyimage data. - * @param md Metadata object to populate. - * @return DEVICE_OK on success, or an error code if extraction fails. - */ - int ExtractCorrespondingMetadata(const void* dataPtr, Metadata &md); - - /** - * Returns the size of the data portion of the slot in bytes. - * @param dataPointer Pointer to the data portion of a slot. - * @return Size in bytes of the data portion, or 0 if pointer is invalid. - */ - size_t GetDatumSize(const void* dataPointer); - - /** - * Check if a pointer is within the buffer's memory range. - * @param ptr The pointer to check. - * @return true if the pointer is within the buffer, false otherwise. - */ - bool IsPointerInBuffer(const void* ptr); - - /** - * Reset the buffer, discarding all data that is not currently held externally. - */ - void Reset(); - - /** - * Checks if there are any outstanding slots in the buffer. If so, it - * is unsafe to destroy the buffer. - * @return true if there are outstanding slots, false otherwise. - */ - int NumOutstandingSlots() const; - -private: - /** - * Internal helper function that finds the slot for a given pointer. - * Returns non-const pointer since slots need to be modified for locking. - * - * @param dataPtr Pointer to the data. - * @return Pointer to the corresponding BufferSlot, or nullptr if not found. - */ - BufferSlot* FindSlotForPointer(const void* dataPtr); - - // Memory managed by the DataBuffer. - void* buffer_; - size_t bufferSize_; - - // Whether to overwrite old data when full. - bool overwriteWhenFull_; - - // Overflow flag (set if insert fails due to full buffer). - bool overflow_; - - // Active slots and their mapping. - std::vector activeSlotsVector_; - std::unordered_map activeSlotsByStart_; - - // Free region list for non-overwrite mode. - // Map from starting offset -> region size (in bytes). - std::map freeRegions_; - - // Cached cursor for scanning free regions in non-overwrite mode. - size_t freeRegionCursor_; - - // Instead of ownership via unique_ptr, store raw pointers - // Note: unusedSlots_ is now a deque of raw pointers. - std::deque unusedSlots_; - - // This container holds the ownership; they live for the lifetime of the buffer. - std::vector slotPool_; - - // Next free offset within the buffer. - // In overwrite mode, new allocations will come from this pointer. - std::atomic nextAllocOffset_; - - // Index tracking the next slot for read. - size_t currentSlotIndex_; - - // Synchronization for slot management. - std::condition_variable dataCV_; - mutable std::mutex slotManagementMutex_; - - // Members for multithreaded copying. - std::shared_ptr threadPool_; - std::shared_ptr tasksMemCopy_; - - void DeleteSlot(size_t offset, std::unordered_map::iterator it); - - void MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd); - void RemoveFromActiveTracking(size_t offset, std::unordered_map::iterator it); - - void UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize); - - BufferSlot* GetSlotFromPool(size_t start, size_t totalLength, - size_t dataSize, size_t initialMetadataSize, - size_t additionalMetadataSize, const std::string& deviceLabel); - - /** - * Reinitializes the DataBuffer, clearing its structures and allocating a new buffer. - * @param memorySizeMB New buffer size (in MB). - * @return DEVICE_OK on success. - * @throws std::runtime_error if any slot is still in use. - */ - int ReinitializeBuffer(unsigned int memorySizeMB); - - - /** - * Creates a new slot with the specified parameters. - * Caller must hold slotManagementMutex_. - */ - int CreateSlot(size_t candidateStart, size_t totalSlotSize, - size_t dataSize, size_t additionalMetadataSize, - void** dataPointer, - void** subsequentMetadataPointer, - bool fromFreeRegion, - const std::string& serializedInitialMetadata, - const std::string& deviceLabel); - - - void ReturnSlotToPool(BufferSlot* slot); - - int ExtractMetadata(const void* dataPointer, - BufferSlot* slot, - Metadata &md); - -}; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 5c1070a04..867d77c89 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -2712,36 +2712,36 @@ bool CMMCore::getShutterOpen() throw (CMMError) * @param include Whether to include the metadata. */ void CMMCore::setIncludeImageMetadata(std::string & category, bool include) throw (CMMError) { - if (category == MM::g_Keyword_Metadata_BitDepth) { + if (category == MM::g_Keyword_Include_Metadata_BitDepth) { imageMDIncludeBitDepth_ = include; } - else if (category == MM::g_Keyword_Metadata_CameraParams) { + else if (category == MM::g_Keyword_Include_Metadata_CameraParams) { imageMDIncludeCameraParams_ = include; } - else if (category == MM::g_Keyword_Metadata_CameraTags) { + else if (category == MM::g_Keyword_Include_Metadata_CameraTags) { imageMDIncludeCameraTags_ = include; } - else if (category == MM::g_Keyword_Metadata_Timing) { + else if (category == MM::g_Keyword_Include_Metadata_Timing) { imageMDIncludeTiming_ = include; } - else if (category == MM::g_Keyword_Metadata_SystemStateCache) { + else if (category == MM::g_Keyword_Include_Metadata_SystemStateCache) { imageMDIncludeSystemStateCache_ = include; } - else if (category == MM::g_Keyword_Metadata_LegacyCalibration) { + else if (category == MM::g_Keyword_Include_Metadata_LegacyCalibration) { imageMDIncludeLegacyCalibration_ = include; } - else if (category == MM::g_Keyword_Metadata_Legacy) { + else if (category == MM::g_Keyword_Include_Metadata_Legacy) { imageMDIncludeAdditionalLegacy_ = include; } else { throw CMMError("Invalid metadata category. Valid options are: " + - std::string(MM::g_Keyword_Metadata_BitDepth) + ", " + - std::string(MM::g_Keyword_Metadata_CameraParams) + ", " + - std::string(MM::g_Keyword_Metadata_CameraTags) + ", " + - std::string(MM::g_Keyword_Metadata_Timing) + ", " + - std::string(MM::g_Keyword_Metadata_SystemStateCache) + ", " + - std::string(MM::g_Keyword_Metadata_LegacyCalibration) + ", " + - std::string(MM::g_Keyword_Metadata_Legacy)); + std::string(MM::g_Keyword_Include_Metadata_BitDepth) + ", " + + std::string(MM::g_Keyword_Include_Metadata_CameraParams) + ", " + + std::string(MM::g_Keyword_Include_Metadata_CameraTags) + ", " + + std::string(MM::g_Keyword_Include_Metadata_Timing) + ", " + + std::string(MM::g_Keyword_Include_Metadata_SystemStateCache) + ", " + + std::string(MM::g_Keyword_Include_Metadata_LegacyCalibration) + ", " + + std::string(MM::g_Keyword_Include_Metadata_Legacy)); } } diff --git a/MMCore/MMCore.vcxproj b/MMCore/MMCore.vcxproj index 4e7583a17..44c28d7c7 100644 --- a/MMCore/MMCore.vcxproj +++ b/MMCore/MMCore.vcxproj @@ -76,7 +76,7 @@ - + @@ -115,9 +115,9 @@ - + - + diff --git a/MMCore/MMCore.vcxproj.filters b/MMCore/MMCore.vcxproj.filters index bb7c9cece..7a8d7e1e3 100644 --- a/MMCore/MMCore.vcxproj.filters +++ b/MMCore/MMCore.vcxproj.filters @@ -141,7 +141,7 @@ Source Files - + Source Files @@ -311,10 +311,10 @@ Header Files - + Header Files - + Header Files diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 30620b927..54f2c6219 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -176,13 +176,13 @@ namespace MM { const char* const g_Keyword_Metadata_TimeInCore = "TimeReceivedByCore"; // Image metadata categories - const char* const g_Keyword_Metadata_BitDepth = "BitDepth"; - const char* const g_Keyword_Metadata_CameraParams = "CameraParams"; - const char* const g_Keyword_Metadata_CameraTags = "CameraTags"; - const char* const g_Keyword_Metadata_Timing = "Timing"; - const char* const g_Keyword_Metadata_SystemStateCache = "SystemStateCache"; - const char* const g_Keyword_Metadata_LegacyCalibration = "LegacyCalibration"; - const char* const g_Keyword_Metadata_Legacy = "Legacy"; + const char* const g_Keyword_Include_Metadata_BitDepth = "BitDepth"; + const char* const g_Keyword_Include_Metadata_CameraParams = "CameraParams"; + const char* const g_Keyword_Include_Metadata_CameraTags = "CameraTags"; + const char* const g_Keyword_Include_Metadata_Timing = "Timing"; + const char* const g_Keyword_Include_Metadata_SystemStateCache = "SystemStateCache"; + const char* const g_Keyword_Include_Metadata_LegacyCalibration = "LegacyCalibration"; + const char* const g_Keyword_Include_Metadata_Legacy = "Legacy"; // configuration file format constants const char* const g_FieldDelimiters = ","; From b15d252ca5b83e2370ec3ce828567257d19af466 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:26:12 -0800 Subject: [PATCH 38/46] fix bugs and rename from v2 to newdatabuffer --- MMCore/BufferManager.cpp | 151 +++--- MMCore/BufferManager.h | 50 +- MMCore/CoreCallback.cpp | 8 +- MMCore/MMCore.cpp | 88 ++-- MMCore/MMCore.h | 10 +- MMCore/NewDataBuffer.cpp | 834 ++++++++++++++++++++++++++++++++++ MMCore/NewDataBuffer.h | 497 ++++++++++++++++++++ MMCore/NewDataBufferPointer.h | 131 ++++++ MMCoreJ_wrap/MMCoreJ.i | 4 +- 9 files changed, 1618 insertions(+), 155 deletions(-) create mode 100644 MMCore/NewDataBuffer.cpp create mode 100644 MMCore/NewDataBuffer.h create mode 100644 MMCore/NewDataBufferPointer.h diff --git a/MMCore/BufferManager.cpp b/MMCore/BufferManager.cpp index 8ca7a6097..8e77c63cb 100644 --- a/MMCore/BufferManager.cpp +++ b/MMCore/BufferManager.cpp @@ -27,11 +27,11 @@ #include -BufferManager::BufferManager(bool useV2Buffer, unsigned int memorySizeMB) - : useV2_(useV2Buffer), circBuffer_(nullptr), v2Buffer_(nullptr) +BufferManager::BufferManager(bool useNewDataBuffer, unsigned int memorySizeMB) + : useNewDataBuffer_(useNewDataBuffer), circBuffer_(nullptr), newDataBuffer_(nullptr) { - if (useV2_.load()) { - v2Buffer_ = new DataBuffer(memorySizeMB); + if (useNewDataBuffer_.load()) { + newDataBuffer_ = new DataBuffer(memorySizeMB); } else { circBuffer_ = new CircularBuffer(memorySizeMB); } @@ -39,9 +39,9 @@ BufferManager::BufferManager(bool useV2Buffer, unsigned int memorySizeMB) BufferManager::~BufferManager() { - if (useV2_.load()) { - if (v2Buffer_) { - delete v2Buffer_; + if (useNewDataBuffer_.load()) { + if (newDataBuffer_) { + delete newDataBuffer_; } } else { if (circBuffer_) { @@ -51,13 +51,13 @@ BufferManager::~BufferManager() } void BufferManager::ReallocateBuffer(unsigned int memorySizeMB) { - if (useV2_.load()) { - int numOutstanding = v2Buffer_->NumOutstandingSlots(); + if (useNewDataBuffer_.load()) { + int numOutstanding = newDataBuffer_->NumOutstandingSlots(); if (numOutstanding > 0) { - throw CMMError("Cannot reallocate V2 buffer: " + std::to_string(numOutstanding) + " outstanding active slot(s) detected."); + throw CMMError("Cannot reallocate NewDataBuffer: " + std::to_string(numOutstanding) + " outstanding active slot(s) detected."); } - delete v2Buffer_; - v2Buffer_ = new DataBuffer(memorySizeMB); + delete newDataBuffer_; + newDataBuffer_ = new DataBuffer(memorySizeMB); } else { delete circBuffer_; circBuffer_ = new CircularBuffer(memorySizeMB); @@ -66,10 +66,10 @@ void BufferManager::ReallocateBuffer(unsigned int memorySizeMB) { const void* BufferManager::GetLastData() { - if (useV2_.load()) { + if (useNewDataBuffer_.load()) { Metadata dummyMetadata; // NOTE: ensure calling code releases the slot after use - return v2Buffer_->PeekDataReadPointerAtIndex(0, dummyMetadata); + return newDataBuffer_->PeekDataReadPointerAtIndex(0, dummyMetadata); } else { return circBuffer_->GetTopImage(); } @@ -77,10 +77,10 @@ const void* BufferManager::GetLastData() const void* BufferManager::PopNextData() { - if (useV2_.load()) { + if (useNewDataBuffer_.load()) { Metadata dummyMetadata; // NOTE: ensure calling code releases the slot after use - return v2Buffer_->PopNextDataReadPointer(dummyMetadata, false); + return newDataBuffer_->PopNextDataReadPointer(dummyMetadata, false); } else { return circBuffer_->PopNextImage(); } @@ -88,24 +88,24 @@ const void* BufferManager::PopNextData() long BufferManager::GetRemainingDataCount() const { - if (useV2_.load()) { - return v2Buffer_->GetActiveSlotCount(); + if (useNewDataBuffer_.load()) { + return newDataBuffer_->GetActiveSlotCount(); } else { return circBuffer_->GetRemainingImageCount(); } } unsigned BufferManager::GetMemorySizeMB() const { - if (useV2_.load()) { - return v2Buffer_->GetMemorySizeMB(); + if (useNewDataBuffer_.load()) { + return newDataBuffer_->GetMemorySizeMB(); } else { return circBuffer_->GetMemorySizeMB(); } } unsigned BufferManager::GetFreeSizeMB() const { - if (useV2_.load()) { - return (unsigned) v2Buffer_->GetFreeMemory() / 1024 / 1024; + if (useNewDataBuffer_.load()) { + return (unsigned) newDataBuffer_->GetFreeMemory() / 1024 / 1024; } else { return circBuffer_->GetFreeSize() * circBuffer_->GetImageSizeBytes() / 1024 / 1024; } @@ -113,8 +113,8 @@ unsigned BufferManager::GetFreeSizeMB() const { bool BufferManager::Overflow() const { - if (useV2_.load()) { - return v2Buffer_->Overflow(); + if (useNewDataBuffer_.load()) { + return newDataBuffer_->Overflow(); } else { return circBuffer_->Overflow(); } @@ -137,10 +137,10 @@ int BufferManager::InsertMultiChannel(const char* callerLabel, const unsigned ch // Initialize metadata with either provided metadata or create empty Metadata md = (pMd != nullptr) ? *pMd : Metadata(); - if (useV2_.load()) { + if (useNewDataBuffer_.load()) { // All the data needed to interpret the image is in the metadata // This function will copy data and metadata into the buffer - return v2Buffer_->InsertData(buf, width * height * byteDepth * numChannels, &md, callerLabel); + return newDataBuffer_->InsertData(buf, width * height * byteDepth * numChannels, &md, callerLabel); } else { return circBuffer_->InsertMultiChannel(buf, numChannels, width, height, byteDepth, &md) ? DEVICE_OK : DEVICE_BUFFER_OVERFLOW; @@ -151,12 +151,12 @@ int BufferManager::InsertData(const char* callerLabel, const unsigned char* buf, // Initialize metadata with either provided metadata or create empty Metadata md = (pMd != nullptr) ? *pMd : Metadata(); - if (!useV2_.load()) { - throw CMMError("InsertData() not supported with circular buffer. Must use V2 buffer."); + if (!useNewDataBuffer_.load()) { + throw CMMError("InsertData() not supported with circular buffer. Must use NewDataBuffer."); } // All the data needed to interpret the image should be in the metadata // This function will copy data and metadata into the buffer - return v2Buffer_->InsertData(buf, dataSize, &md, callerLabel); + return newDataBuffer_->InsertData(buf, dataSize, &md, callerLabel); } @@ -167,10 +167,10 @@ const void* BufferManager::GetLastDataMD(Metadata& md) const const void* BufferManager::GetLastDataMD(unsigned channel, unsigned singleChannelSizeBytes, Metadata& md) const throw (CMMError) { - if (useV2_.load()) { - const void* basePtr = v2Buffer_->PeekLastDataReadPointer(md); + if (useNewDataBuffer_.load()) { + const void* basePtr = newDataBuffer_->PeekLastDataReadPointer(md); if (basePtr == nullptr) - throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); + throw CMMError("NewDataBuffer is empty.", MMERR_CircularBufferEmpty); // Add multiples of the number of bytes to get the channel pointer basePtr = static_cast(basePtr) + channel * singleChannelSizeBytes; return basePtr; @@ -187,9 +187,9 @@ const void* BufferManager::GetLastDataMD(unsigned channel, unsigned singleChanne const void* BufferManager::GetNthDataMD(unsigned long n, Metadata& md) const throw (CMMError) { - if (useV2_.load()) { + if (useNewDataBuffer_.load()) { // NOTE: make sure calling code releases the slot after use. - return v2Buffer_->PeekDataReadPointerAtIndex(n, md); + return newDataBuffer_->PeekDataReadPointerAtIndex(n, md); } else { const mm::ImgBuffer* pBuf = circBuffer_->GetNthFromTopImageBuffer(n); if (pBuf != nullptr) { @@ -208,15 +208,15 @@ const void* BufferManager::PopNextDataMD(Metadata& md) throw (CMMError) /** * @deprecated Use PopNextDataMD() without channel parameter instead. - * The V2 buffer is data type agnostic + * The NewDataBuffer is data type agnostic */ const void* BufferManager::PopNextDataMD(unsigned channel, unsigned singleChannelSizeBytes, Metadata& md) throw (CMMError) { - if (useV2_.load()) { - const void* basePtr = v2Buffer_->PopNextDataReadPointer(md, false); + if (useNewDataBuffer_.load()) { + const void* basePtr = newDataBuffer_->PopNextDataReadPointer(md, false); if (basePtr == nullptr) - throw CMMError("V2 buffer is empty.", MMERR_CircularBufferEmpty); + throw CMMError("NewDataBuffer is empty.", MMERR_CircularBufferEmpty); // Add multiples of the number of bytes to get the channel pointer basePtr = static_cast(basePtr) + channel * singleChannelSizeBytes; @@ -233,9 +233,9 @@ const void* BufferManager::PopNextDataMD(unsigned channel, } } -int BufferManager::EnableV2Buffer(bool enable) { +int BufferManager::EnableNewDataBuffer(bool enable) { // Don't do anything if we're already in the requested state - if (enable == useV2_.load()) { + if (enable == useNewDataBuffer_.load()) { return DEVICE_OK; } @@ -248,20 +248,21 @@ int BufferManager::EnableV2Buffer(bool enable) { DataBuffer* newBuffer = new DataBuffer(memorySizeMB); delete circBuffer_; circBuffer_ = nullptr; - v2Buffer_ = newBuffer; + newDataBuffer_ = newBuffer; } else { // Switch to circular buffer - int numOutstanding = v2Buffer_->NumOutstandingSlots(); + int numOutstanding = newDataBuffer_->NumOutstandingSlots(); if (numOutstanding > 0) { throw CMMError("Cannot switch to circular buffer: " + std::to_string(numOutstanding) + " outstanding active slot(s) detected."); } CircularBuffer* newBuffer = new CircularBuffer(memorySizeMB); - delete v2Buffer_; - v2Buffer_ = nullptr; + delete newDataBuffer_; + newDataBuffer_ = nullptr; circBuffer_ = newBuffer; } - useV2_.store(enable); + + useNewDataBuffer_.store(enable); return DEVICE_OK; } catch (const std::exception&) { // If allocation fails, keep the existing buffer @@ -269,27 +270,27 @@ int BufferManager::EnableV2Buffer(bool enable) { } } -bool BufferManager::IsUsingV2Buffer() const { - return useV2_.load(); +bool BufferManager::IsUsingNewDataBuffer() const { + return useNewDataBuffer_.load(); } int BufferManager::ReleaseReadAccess(const void* ptr) { - if (useV2_.load() && ptr) { - return v2Buffer_->ReleaseDataReadPointer(ptr); + if (useNewDataBuffer_.load() && ptr) { + return newDataBuffer_->ReleaseDataReadPointer(ptr); } return DEVICE_ERR; } unsigned BufferManager::GetDataSize(const void* ptr) const { - if (!useV2_.load()) + if (!useNewDataBuffer_.load()) return circBuffer_->GetImageSizeBytes(); else - return static_cast(v2Buffer_->GetDatumSize(ptr)); + return static_cast(newDataBuffer_->GetDatumSize(ptr)); } int BufferManager::SetOverwriteData(bool overwrite) { - if (useV2_.load()) { - return v2Buffer_->SetOverwriteData(overwrite); + if (useNewDataBuffer_.load()) { + return newDataBuffer_->SetOverwriteData(overwrite); } else { return circBuffer_->SetOverwriteData(overwrite); } @@ -297,7 +298,7 @@ int BufferManager::SetOverwriteData(bool overwrite) { int BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, size_t additionalMetadataSize, void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata) { - if (!useV2_.load()) { + if (!useNewDataBuffer_.load()) { // Not supported for circular buffer return DEVICE_ERR; } @@ -306,77 +307,77 @@ int BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, si Metadata md = (pInitialMetadata != nullptr) ? *pInitialMetadata : Metadata(); std::string serializedMetadata = md.Serialize(); - int ret = v2Buffer_->AcquireWriteSlot(dataSize, additionalMetadataSize, + int ret = newDataBuffer_->AcquireWriteSlot(dataSize, additionalMetadataSize, dataPointer, additionalMetadataPointer, serializedMetadata, deviceLabel); return ret; } int BufferManager::FinalizeWriteSlot(const void* imageDataPointer, size_t actualMetadataBytes) { - if (!useV2_.load()) { + if (!useNewDataBuffer_.load()) { // Not supported for circular buffer return DEVICE_ERR; } - return v2Buffer_->FinalizeWriteSlot(imageDataPointer, actualMetadataBytes); + return newDataBuffer_->FinalizeWriteSlot(imageDataPointer, actualMetadataBytes); } void BufferManager::ExtractMetadata(const void* dataPtr, Metadata& md) const { - if (!useV2_.load()) { - throw CMMError("ExtractMetadata is only supported with V2 buffer enabled"); + if (!useNewDataBuffer_.load()) { + throw CMMError("ExtractMetadata is only supported with NewDataBuffer enabled"); } - if (v2Buffer_ == nullptr) { - throw CMMError("V2 buffer is null"); + if (newDataBuffer_ == nullptr) { + throw CMMError("NewDataBuffer is null"); } - int result = v2Buffer_->ExtractCorrespondingMetadata(dataPtr, md); + int result = newDataBuffer_->ExtractCorrespondingMetadata(dataPtr, md); if (result != DEVICE_OK) { throw CMMError("Failed to extract metadata"); } } const void* BufferManager::GetLastDataFromDevice(const std::string& deviceLabel) throw (CMMError) { - if (!useV2_.load()) { - throw CMMError("V2 buffer must be enabled for device-specific data access"); + if (!useNewDataBuffer_.load()) { + throw CMMError("NewDataBuffer must be enabled for device-specific data access"); } Metadata md; return GetLastDataMDFromDevice(deviceLabel, md); } const void* BufferManager::GetLastDataMDFromDevice(const std::string& deviceLabel, Metadata& md) throw (CMMError) { - if (!useV2_.load()) { - throw CMMError("V2 buffer must be enabled for device-specific data access"); + if (!useNewDataBuffer_.load()) { + throw CMMError("NewDataBuffer must be enabled for device-specific data access"); } - const void* basePtr = v2Buffer_->PeekLastDataReadPointerFromDevice(deviceLabel, md); + const void* basePtr = newDataBuffer_->PeekLastDataReadPointerFromDevice(deviceLabel, md); if (basePtr == nullptr) { throw CMMError("No data found for device: " + deviceLabel, MMERR_InvalidContents); } return basePtr; } -bool BufferManager::IsPointerInV2Buffer(const void* ptr) { - if (!useV2_.load()) { +bool BufferManager::IsPointerInNewDataBuffer(const void* ptr) const { + if (!useNewDataBuffer_.load()) { return false; } - if (v2Buffer_ == nullptr) { + if (newDataBuffer_ == nullptr) { return false; } - return v2Buffer_->IsPointerInBuffer(ptr); + return newDataBuffer_->IsPointerInBuffer(ptr); } bool BufferManager::GetOverwriteData() const { - if (useV2_.load()) { - return v2Buffer_->GetOverwriteData(); + if (useNewDataBuffer_.load()) { + return newDataBuffer_->GetOverwriteData(); } else { return circBuffer_->GetOverwriteData(); } } void BufferManager::Reset() { - if (useV2_.load()) { - v2Buffer_->Reset(); + if (useNewDataBuffer_.load()) { + newDataBuffer_->Reset(); } else { circBuffer_->Clear(); } diff --git a/MMCore/BufferManager.h b/MMCore/BufferManager.h index d3807f1d4..55ad8e26c 100644 --- a/MMCore/BufferManager.h +++ b/MMCore/BufferManager.h @@ -27,7 +27,7 @@ #define BUFFERMANAGER_H #include "CircularBuffer.h" -#include "Buffer_v2.h" +#include "NewDataBuffer.h" #include "../MMDevice/MMDevice.h" #include #include @@ -42,7 +42,7 @@ * with associated metadata that describes how to interpret the raw bytes. * * The implementation supports two buffer types: - * - The newer V2 buffer that handles generic data + * - The newer NewDataBuffer that handles generic data * - The legacy circular buffer (for backwards compatibility) that assumes image data * * While the preferred usage is through the generic data methods (InsertData, @@ -51,14 +51,14 @@ */ class BufferManager { public: - static const char* const DEFAULT_V2_BUFFER_NAME; + static const char* const DEFAULT_NEW_DATA_BUFFER_NAME; /** * Constructor. - * @param useV2Buffer Set to true to use the new DataBuffer (v2); false to use CircularBuffer. + * @param useNewDataBuffer Set to true to use the new DataBuffer (NewDataBuffer); false to use CircularBuffer. * @param memorySizeMB Memory size for the buffer (in megabytes). */ - BufferManager(bool useV2Buffer, unsigned int memorySizeMB); + BufferManager(bool useNewDataBuffer, unsigned int memorySizeMB); ~BufferManager(); /** @@ -68,11 +68,11 @@ class BufferManager { void ReallocateBuffer(unsigned int memorySizeMB); /** - * Enable or disable v2 buffer usage. - * @param enable Set to true to use v2 buffer, false to use circular buffer. + * Enable or disable NewDataBuffer usage. + * @param enable Set to true to use NewDataBuffer, false to use CircularBuffer. * @return true if the switch was successful, false otherwise. */ - int EnableV2Buffer(bool enable); + int EnableNewDataBuffer(bool enable); /** * Get a pointer to the top (most recent) image. @@ -132,7 +132,7 @@ class BufferManager { * @param byteDepth Bytes per pixel. * @param pMd Metadata associated with the image. * @return DEVICE_OK on success, DEVICE_ERR on error. - * @deprecated This method is not preferred for the V2 buffer. Use InsertData() instead. + * @deprecated This method is not preferred for the NewDataBuffer. Use InsertData() instead. * This method assumes specific image data format. It is provided for backwards * compatibility with with the circular buffer, which assumes images captured on a camera. */ @@ -164,17 +164,17 @@ class BufferManager { const void* GetNthDataMD(unsigned long n, Metadata& md) const; - // Channels are not directly supported in v2 buffer, these are for backwards compatibility + // Channels are not directly supported in NewDataBuffer, these are for backwards compatibility // with circular buffer /** - * @deprecated This method is not preferred for the V2 buffer. Use GetLastDataMD() without channel parameter instead. - * The V2 is data type agnostic + * @deprecated This method is not preferred for the NewDataBuffer. Use GetLastDataMD() without channel parameter instead. + * The NewDataBuffer is data type agnostic */ const void* GetLastDataMD(unsigned channel, unsigned singleChannelSizeBytes, Metadata& md) const; /** - * @deprecated This method is not preferred for the V2 buffer. Use PopNextDataMD() without channel parameter instead. - * The V2 buffer is data type agnostic + * @deprecated This method is not preferred for the NewDataBuffer. Use PopNextDataMD() without channel parameter instead. + * The NewDataBuffer is data type agnostic */ const void* PopNextDataMD(unsigned channel, unsigned singleChannelSizeBytes,Metadata& md); @@ -182,14 +182,14 @@ class BufferManager { const void* PopNextDataMD(Metadata& md); /** - * Check if this manager is using the V2 buffer implementation. - * @return true if using V2 buffer, false if using circular buffer. + * Check if this manager is using the NewDataBuffer implementation. + * @return true if using NewDataBuffer, false if using circular buffer. */ - bool IsUsingV2Buffer() const; + bool IsUsingNewDataBuffer() const; /** * Release a pointer obtained from the buffer. - * This is required when using the V2 buffer implementation. + * This is required when using the NewDataBuffer implementation. * @param ptr The pointer to release. * @return DEVICE_OK on success, DEVICE_ERR on error. */ @@ -230,7 +230,7 @@ class BufferManager { * Extracts metadata for a given data pointer. * @param dataPtr Pointer to the data. * @param md Metadata object to populate. - * @throws CMMError if V2 buffer is not enabled or extraction fails. + * @throws CMMError if NewDataBuffer is not enabled or extraction fails. */ void ExtractMetadata(const void* dataPtr, Metadata& md) const; @@ -246,7 +246,7 @@ class BufferManager { * Get the last data inserted by a specific device. * @param deviceLabel The label of the device to get the data from. * @return Pointer to the data. - * @throws CMMError if no data is found or V2 buffer is not enabled. + * @throws CMMError if no data is found or NewDataBuffer is not enabled. */ const void* GetLastDataFromDevice(const std::string& deviceLabel); @@ -255,7 +255,7 @@ class BufferManager { * @param deviceLabel The label of the device to get the data from. * @param md Metadata object to populate. * @return Pointer to the data. - * @throws CMMError if no data is found or V2 buffer is not enabled. + * @throws CMMError if no data is found or NewDataBuffer is not enabled. */ const void* GetLastDataMDFromDevice(const std::string& deviceLabel, Metadata& md); @@ -264,7 +264,7 @@ class BufferManager { * @param ptr The pointer to check. * @return true if the pointer is in the buffer, false otherwise. */ - bool IsPointerInV2Buffer(const void* ptr) const; + bool IsPointerInNewDataBuffer(const void* ptr) const; /** * Get whether the buffer is in overwrite mode. @@ -275,7 +275,7 @@ class BufferManager { /** * Get the underlying CircularBuffer pointer. * This method is provided for backwards compatibility only. - * @return Pointer to CircularBuffer if using legacy buffer, nullptr if using V2 buffer + * @return Pointer to CircularBuffer if using legacy buffer, nullptr if using NewDataBuffer * @deprecated This method exposes implementation details and should be avoided in new code */ CircularBuffer* GetCircularBuffer() { return circBuffer_; } @@ -287,9 +287,9 @@ class BufferManager { private: - std::atomic useV2_; + std::atomic useNewDataBuffer_; CircularBuffer* circBuffer_; - DataBuffer* v2Buffer_; + DataBuffer* newDataBuffer_; /** diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index cf70767c6..7862289a5 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -243,7 +243,7 @@ int CoreCallback::InsertImage(const MM::Device* caller, const unsigned char* buf { // convert device to camera std::shared_ptr camera = std::dynamic_pointer_cast(device); - core_->addCameraMetadata(camera, newMD, width, height, byteDepth, nComponents, true); + core_->addCameraMetadata(camera, newMD, width, height, byteDepth, nComponents); } char labelBuffer[MM::MaxStrLength]; @@ -300,7 +300,7 @@ int CoreCallback::AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSiz Metadata md; std::shared_ptr camera = std::dynamic_pointer_cast(core_->deviceManager_->GetDevice(caller)); // Add the metadata needed for interpreting camera images - core_->addCameraMetadata(camera, md, width, height, byteDepth, nComponents, true); + core_->addCameraMetadata(camera, md, width, height, byteDepth, nComponents); char label[MM::MaxStrLength]; caller->GetLabel(label); @@ -344,7 +344,7 @@ bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, // (or is?) handled by higher level code. Something this certainly cannot be applied // to the v2 buffer, because devices do not have the authority to clear the buffer, // when application code may hold pointers into the buffer. - if (!core_->bufferManager_->IsUsingV2Buffer()) { + if (!core_->bufferManager_->IsUsingNewDataBuffer()) { return core_->bufferManager_->GetCircularBuffer()->Initialize(channels, w, h, pixDepth); } return true; @@ -366,7 +366,7 @@ int CoreCallback:: InsertMultiChannel(const MM::Device* caller, const unsigned c { // convert device to camera std::shared_ptr camera = std::dynamic_pointer_cast(device); - core_->addCameraMetadata(camera, newMD, width, height, byteDepth, 1, true); + core_->addCameraMetadata(camera, newMD, width, height, byteDepth, 1); } MM::ImageProcessor* ip = GetImageProcessor(caller); diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 867d77c89..17d16dcaf 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -54,7 +54,7 @@ #include "MMCore.h" #include "MMEventCallback.h" #include "PluginManager.h" -#include "BufferDataPointer.h" +#include "NewDataBufferPointer.h" #include #include @@ -2897,7 +2897,7 @@ void CMMCore::addCameraMetadata(std::shared_ptr pCam, Metadata& void CMMCore::addMultiCameraMetadata(Metadata& md, int cameraChannelIndex = 0) const { - // Multi-camera device adapter metadata. This is considered legacy since the v2 buffer + // Multi-camera device adapter metadata. This is considered legacy since the NewDataBuffer // make the multi-camera adapter unnecessary. if (!md.HasTag("CameraChannelIndex")) { @@ -2906,7 +2906,7 @@ void CMMCore::addMultiCameraMetadata(Metadata& md, int cameraChannelIndex = 0) } // This whole block seems superfluos now since we always add the "Camera" tag - // in the block above. Also, the V2 buffer eliminates the need for the multi-camera + // in the block above. Also, the NewDataBuffer eliminates the need for the multi-camera // adapter. Leaving here and commented out for now, because I'm unsure what the upstream // effects are // if (!md.HasTag("Camera")) { @@ -3063,10 +3063,10 @@ void* CMMCore::getImageMD(Metadata& md) throw (CMMError) throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); mm::DeviceModuleLockGuard guard(camera); - // Need to do this becuase this data was never inserted into the circular buffer or V2 buffer where this + // Need to do this becuase this data was never inserted into the circular buffer or NewDataBuffer where this // metadata is added for other images addCameraMetadata(camera, md, camera->GetImageWidth(), camera->GetImageHeight(), - camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents(), true); + camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents()); return pBuf; } @@ -3079,20 +3079,20 @@ void* CMMCore::getImageMD(unsigned channelNr, Metadata& md) throw (CMMError) throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable); mm::DeviceModuleLockGuard guard(camera); - // Need to do this becuase this data was never inserted into the circular buffer or V2 buffer where this + // Need to do this becuase this data was never inserted into the circular buffer or NewDataBuffer where this // metadata is added for other images addCameraMetadata(camera, md, camera->GetImageWidth(), camera->GetImageHeight(), - camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents(), true); + camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents()); addMultiCameraMetadata(md, channelNr); return pBuf; } /** - * For use with V2 buffer + * For use with NewDataBuffer * * Get a pointer to the image acquired by snapImage. This will copy the image - * from the camera buffer into the V2 buffer so that it can be persistently + * from the camera buffer into the NewDataBuffer so that it can be persistently * accessed (i.e. subsequent calls snapImage will not overwrite the pointer * returned by this function). * @@ -3104,8 +3104,8 @@ void* CMMCore::getImageMD(unsigned channelNr, Metadata& md) throw (CMMError) */ BufferDataPointer* CMMCore::getImagePointer() throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer() ) - throw CMMError("Only valid when V2 buffer in use"); + if (!bufferManager_->IsUsingNewDataBuffer() ) + throw CMMError("Only valid when NewDataBuffer in use"); std::shared_ptr camera = currentCameraDevice_.lock(); if (!camera) @@ -3144,13 +3144,13 @@ BufferDataPointer* CMMCore::getImagePointer() throw (CMMError) Metadata md; - // Add metadata to know how to interpret the data added to v2 buffer + // Add metadata to know how to interpret the data added to NewDataBuffer addCameraMetadata(camera, md, camera->GetImageWidth(), camera->GetImageHeight(), - camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents(), true); + camera->GetImageBytesPerPixel(), camera->GetNumberOfComponents()); unsigned imageSize = camera->GetImageWidth() * camera->GetImageHeight() * camera->GetImageBytesPerPixel(); int ret = bufferManager_->InsertData(camera->GetLabel().c_str(), (unsigned char*)pBuf, imageSize, &md); if (ret != DEVICE_OK) { - throw CMMError("v2 buffer overflow"); + throw CMMError("NewDataBuffer overflow"); } if (bufferManager_->GetOverwriteData()) { @@ -3224,15 +3224,15 @@ void CMMCore::startSequenceAcquisition(long numImages, double intervalMs, bool s try { - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } } - if (!bufferManager_->IsUsingV2Buffer()) { - // V2 buffer does not support this, because its design is such that data + if (!bufferManager_->IsUsingNewDataBuffer()) { + // NewDataBuffer does not support this, because its design is such that data // could still be read out even when a new sequence is started. bufferManager_->GetCircularBuffer()->Clear(); } @@ -3278,7 +3278,7 @@ void CMMCore::startSequenceAcquisition(const char* label, long numImages, double throw CMMError(getCoreErrorText(MMERR_NotAllowedDuringSequenceAcquisition).c_str(), MMERR_NotAllowedDuringSequenceAcquisition); - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { if (!bufferManager_->GetCircularBuffer()->Initialize(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), pCam->GetImageHeight(), pCam->GetImageBytesPerPixel())) { logError(getDeviceName(pCam).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); @@ -3331,7 +3331,7 @@ void CMMCore::prepareSequenceAcquisition(const char* label) throw (CMMError) */ void CMMCore::initializeCircularBuffer() throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { std::shared_ptr camera = currentCameraDevice_.lock(); if (camera) { @@ -3398,7 +3398,7 @@ void CMMCore::startContinuousSequenceAcquisition(const char* cameraLabel, double } // Legacy calls for circular buffer - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); @@ -3616,10 +3616,10 @@ void* CMMCore::popNextImageMD(Metadata& md) throw (CMMError) return pixels; } -//// Data pointer access for v2 Buffer +//// Data pointer access for NewDataBuffer BufferDataPointer* CMMCore::getLastDataPointer() throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); + if (!bufferManager_->IsUsingNewDataBuffer()) { + throw CMMError("NewDataBuffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->GetLastData(); if (rawPtr == nullptr) { @@ -3629,8 +3629,8 @@ BufferDataPointer* CMMCore::getLastDataPointer() throw (CMMError) { } BufferDataPointer* CMMCore::popNextDataPointer() throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); + if (!bufferManager_->IsUsingNewDataBuffer()) { + throw CMMError("NewDataBuffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->PopNextData(); if (rawPtr == nullptr) { @@ -3640,8 +3640,8 @@ BufferDataPointer* CMMCore::popNextDataPointer() throw (CMMError) { } BufferDataPointer* CMMCore::getLastDataFromDevicePointer(std::string deviceLabel) throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { - throw CMMError("V2 buffer must be enabled for pointer-based image access"); + if (!bufferManager_->IsUsingNewDataBuffer()) { + throw CMMError("NewDataBuffer must be enabled for pointer-based image access"); } const void* rawPtr = bufferManager_->GetLastDataFromDevice(deviceLabel); if (rawPtr == nullptr) { @@ -3658,15 +3658,15 @@ BufferDataPointer* CMMCore::getLastDataFromDevicePointer(std::string deviceLabel */ void CMMCore::clearCircularBuffer() throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { clearBuffer(); } - // No effect on v2 because Reset should be used more carefully + // No effect on NewDataBuffer because Reset should be used more carefully } /** - * This method applies to both the circular buffer and the v2 buffer. - * A difference between the circular buffer and v2 buffer is that the v2 buffer + * This method applies to both the circular buffer and the NewDataBuffer. + * A difference between the circular buffer and NewDataBuffer is that the NewDataBuffer * does require to be empty of images before a new sequence is started. In other * words, producers can be adding data to it that is asynchronously consumed. * @@ -3679,11 +3679,11 @@ void CMMCore::clearBuffer() throw (CMMError) } /** - * Enables or disables the v2 buffer. + * Enables or disables the NewDataBuffer. */ -void CMMCore::enableV2Buffer(bool enable) throw (CMMError) +void CMMCore::enableNewDataBuffer(bool enable) throw (CMMError) { - int ret = bufferManager_->EnableV2Buffer(enable); + int ret = bufferManager_->EnableNewDataBuffer(enable); if (ret != DEVICE_OK) throw CMMError("Failed to enable New Data Buffer", ret); @@ -3733,7 +3733,7 @@ void CMMCore::setBufferMemoryFootprint(unsigned sizeMB) throw (CMMError) if (camera) { mm::DeviceModuleLockGuard guard(camera); - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { // Circular buffer requires initialization specific to the camera if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); @@ -4769,33 +4769,33 @@ unsigned CMMCore::getNumberOfComponents() } void CMMCore::releaseReadAccess(DataPtr ptr) { - if (bufferManager_->IsUsingV2Buffer()) { + if (bufferManager_->IsUsingNewDataBuffer()) { bufferManager_->ReleaseReadAccess(ptr); } } // For the below function that gets properties of the image based on a pointer, -// Cant assume that this a v2 buffer pointer, because Java SWIG wrapper +// Cant assume that this a NewDataBuffer pointer, because Java SWIG wrapper // will call this after copying from a snap buffer. So we'll check if the // buffer knows about this pointer. If not, it's a snap buffer pointer. // We don't want want to compare to the snap buffer pointer directly because // its unclear what the device adapter might do when this is called. void CMMCore::getImageProperties(DataPtr ptr, int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { // Could be snap or circular buffer pointer width = getImageWidth(); height = getImageHeight(); byteDepth = getBytesPerPixel(); nComponents = getNumberOfComponents(); - } else if (bufferManager_->IsPointerInV2Buffer(ptr)) { - // V2 buffer pointer + } else if (bufferManager_->IsPointerInNewDataBuffer(ptr)) { + // NewDataBuffer pointer Metadata md; bufferManager_->ExtractMetadata(ptr, md); parseImageMetadata(md, width, height, byteDepth, nComponents); } else { - // Snap buffer pointer with v2 buffer on + // Snap buffer pointer with NewDataBuffer on width = getImageWidth(); height = getImageHeight(); byteDepth = getBytesPerPixel(); @@ -4967,7 +4967,7 @@ void CMMCore::setROI(int x, int y, int xSize, int ySize) throw (CMMError) // buffer may have sizes inconsistent with the current image size. // There is no way to "fix" popNextImage() to handle this correctly, // so we need to make sure we discard such images. - if (! bufferManager_->IsUsingV2Buffer()) { + if (! bufferManager_->IsUsingNewDataBuffer()) { bufferManager_->GetCircularBuffer()->Clear(); } } @@ -5048,7 +5048,7 @@ void CMMCore::setROI(const char* label, int x, int y, int xSize, int ySize) thro // inconsistent with the current image size. There is no way to "fix" // popNextImage() to handle this correctly, so we need to make sure we // discard such images. - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { bufferManager_->GetCircularBuffer()->Clear(); } } @@ -5109,7 +5109,7 @@ void CMMCore::clearROI() throw (CMMError) // buffer may have sizes inconsistent with the current image size. // There is no way to "fix" popNextImage() to handle this correctly, // so we need to make sure we discard such images. - if (!bufferManager_->IsUsingV2Buffer()) { + if (!bufferManager_->IsUsingNewDataBuffer()) { bufferManager_->GetCircularBuffer()->Clear(); } } diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index bc0eed9a6..13fa214be 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -454,10 +454,10 @@ class CMMCore ///@} - /** \name v2 buffer control. */ + /** \name NewDataBuffer control. */ ///@{ - void enableV2Buffer(bool enable) throw (CMMError); - bool usesV2Buffer() const { return bufferManager_->IsUsingV2Buffer(); } + void enableNewDataBuffer(bool enable) throw (CMMError); + bool usesNewDataBuffer() const { return bufferManager_->IsUsingNewDataBuffer(); } // These functions are used by the Java SWIG wrapper to get properties of the image // based on a pointer. The DataPtr alias to void* is so they don't get converted to @@ -470,7 +470,7 @@ class CMMCore // enables alternative wrappers in SWIG for pointer-based access to the image data. // This one is "Image" not "Data" because it corresponds to SnapImage()/GetImage(), - // Which does only goes through the v2 buffer after coming from the camera device buffer, + // Which does only goes through the NewDataBuffer after coming from the camera device buffer, // so it is guarenteed to be an image, not a more generic piece of data. BufferDataPointer* getImagePointer() throw (CMMError); @@ -739,7 +739,7 @@ class CMMCore CorePropertyCollection* properties_; MMEventCallback* externalCallback_; // notification hook to the higher layer (e.g. GUI) PixelSizeConfigGroup* pixelSizeGroup_; - // New adapter to wrap either the circular buffer or the DataBuffer (v2) + // New adapter to wrap either the circular buffer or the NewDataBuffer BufferManager* bufferManager_; std::shared_ptr pluginManager_; diff --git a/MMCore/NewDataBuffer.cpp b/MMCore/NewDataBuffer.cpp new file mode 100644 index 000000000..90302de35 --- /dev/null +++ b/MMCore/NewDataBuffer.cpp @@ -0,0 +1,834 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: NewDataBuffer.cpp +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +//----------------------------------------------------------------------------- +// DESCRIPTION: Generic implementation of a buffer for storing image data and +// metadata. Provides thread-safe access for reading and writing +// with configurable overflow behavior. +//// +// COPYRIGHT: Henry Pinkard, 2025 +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Henry Pinkard, 01/31/2025 + + +/* +Design Overview: + +The buffer is designed as a flexible data structure for storing image data and metadata: + +Buffer Structure: +- A large block of contiguous memory divided into slots + +Slots: +- Contiguous sections within the buffer that can vary in size +- Support exclusive write access with shared read access +- Memory management through reference counting: + - Writers get exclusive ownership during writes + - Readers can get shared read-only access + - Slots are recycled when all references are released (In non-overwriting mode) + +Data Access: +- Two access patterns supported: + 1. Copy-based access + 2. Direct pointer access with explicit release +- Reference counting ensures safe memory management +- Slots become available for recycling when: + - Writing is complete (via Insert or GetDataWriteSlot+Release) + - All readers have released their references + +Metadata Handling: +- Devices must specify PixelType when adding data +- Device-specific metadata requirements (e.g. image dimensions) are handled at the + device API level rather than in the buffer API to maintain clean separation +*/ + + +#include "NewDataBuffer.h" +#include +#include // for std::this_thread::yield if needed +#include +#include +#include +#include +#include +#include +#include "TaskSet_CopyMemory.h" +#include + + +/////////////////////////////////////////////////////////////////////////////// +// DataBuffer Implementation +/////////////////////////////////////////////////////////////////////////////// + +namespace { + // Get system page size at runtime + inline size_t GetPageSize() { + #ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; + #else + return sysconf(_SC_PAGESIZE); + #endif + } + + // Cache the page size. + const size_t PAGE_SIZE = GetPageSize(); + + // Inline alignment function using bitwise operations. + // For a power-of-two alignment, this computes the smallest multiple + // of 'alignment' that is at least as large as 'value'. + inline size_t Align(size_t value) { + // Use PAGE_SIZE if value is large enough; otherwise use the sizeof(max_align_t) + size_t alignment = (value >= PAGE_SIZE) ? PAGE_SIZE : alignof(std::max_align_t); + return (value + alignment - 1) & ~(alignment - 1); + } +} + +DataBuffer::DataBuffer(unsigned int memorySizeMB) + : buffer_(nullptr), + bufferSize_(0), + overwriteWhenFull_(false), + nextAllocOffset_(0), + currentSlotIndex_(0), + overflow_(false), + threadPool_(std::make_shared()), + tasksMemCopy_(std::make_shared(threadPool_)) +{ + // Pre-allocate slots (one per MB) and store in both slotPool_ and unusedSlots_ + for (unsigned int i = 0; i < memorySizeMB; i++) { + BufferSlot* bs = new BufferSlot(); + slotPool_.push_back(bs); + unusedSlots_.push_back(bs); + } + + ReinitializeBuffer(memorySizeMB); +} + +DataBuffer::~DataBuffer() { + if (buffer_) { + #ifdef _WIN32 + VirtualFree(buffer_, 0, MEM_RELEASE); + #else + munmap(buffer_, bufferSize_); + #endif + buffer_ = nullptr; + } + + std::lock_guard lock(slotManagementMutex_); + for (BufferSlot* bs : slotPool_) { + delete bs; + } +} + +/** + * Allocate a character buffer + * @param memorySizeMB The size (in MB) of the buffer to allocate. + * @return Error code (0 on success). + */ +int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { + // Convert MB to bytes (1 MB = 1048576 bytes) + size_t numBytes = static_cast(memorySizeMB) * (1ULL << 20); + + #ifdef _WIN32 + buffer_ = (unsigned char*)VirtualAlloc(nullptr, numBytes, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE); + if (!buffer_) { + return DEVICE_ERR; + } + + #else + buffer_ = (unsigned char*)mmap(nullptr, numBytes, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (buffer_ == MAP_FAILED) { + buffer_ = nullptr; + return DEVICE_ERR; + } + + // Advise the kernel that we will need this memory soon. + madvise(buffer_, numBytes, MADV_WILLNEED); + + #endif + + bufferSize_ = numBytes; + overflow_ = false; + freeRegions_.clear(); + freeRegions_[0] = bufferSize_; + return DEVICE_OK; +} + +/** + * Release the buffer. + * @return Error code (0 on success, error if buffer not found or already released). + */ +int DataBuffer::ReleaseBuffer() { + if (buffer_ != nullptr) { + #ifdef _WIN32 + VirtualFree(buffer_, 0, MEM_RELEASE); + #else + munmap(buffer_, bufferSize_); + #endif + buffer_ = nullptr; + return DEVICE_OK; + } + // TODO: Handle errors if other parts of the system still hold pointers. + return DEVICE_ERR; +} + +/** + * Pack the data as [BufferSlotRecord][image data][serialized metadata] + */ +int DataBuffer::InsertData(const void* data, size_t dataSize, const Metadata* pMd, const std::string& deviceLabel) { + + void* dataPointer = nullptr; + void* additionalMetadataPointer = nullptr; + + // Convert metadata to serialized string if provided + std::string serializedMetadata; + if (pMd != nullptr) { + serializedMetadata = pMd->Serialize(); + } + // Initial metadata is all metadata because the image and metadata are already complete + int result = AcquireWriteSlot(dataSize, 0, &dataPointer, &additionalMetadataPointer, + serializedMetadata, deviceLabel); + if (result != DEVICE_OK) { + return result; + } + + tasksMemCopy_->MemCopy((void*)dataPointer, data, dataSize); + + // Finalize the write slot. + return FinalizeWriteSlot(dataPointer, 0); +} + +/** + * Configure whether to overwrite old data when buffer is full. + * + * If true, when there are no more slots available for writing because + * images haven't been read fast enough, then automatically recycle the + * oldest slot(s) in the buffer as needed in order to make space for new images. + * This is suitable for situations when its okay to drop frames, like live + * view when data is not being saved. + * + * If false, then throw an exception if the buffer becomes full. + * + * @param overwrite Whether to enable overwriting of old data + * @return Error code (0 on success) + */ +int DataBuffer::SetOverwriteData(bool overwrite) { + overwriteWhenFull_ = overwrite; + return DEVICE_OK; +} + +/** + * Get whether the buffer should overwrite old data when full. + * @return True if overwriting is enabled, false otherwise. + */ +bool DataBuffer::GetOverwriteData() const { + return overwriteWhenFull_; +} + +/** + * Reset the buffer, discarding all data that is not currently held externally. + */ +void DataBuffer::Reset() { + // Reuse ReinitializeBuffer with current size + ReinitializeBuffer(GetMemorySizeMB()); +} + +/** + * Get a pointer to the next available data slot in the buffer for writing. + * + * The caller must release the slot using ReleaseDataSlot after writing is complete. + */ +int DataBuffer::AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, + void** dataPointer, + void** additionalMetadataPointer, + const std::string& serializedInitialMetadata, + const std::string& deviceLabel) +{ + if (buffer_ == nullptr) { + return DEVICE_ERR; + } + + // Total size includes data, initial metadata, and any additional metadata space + size_t rawTotalSize = dataSize + serializedInitialMetadata.size() + additionalMetadataSize; + size_t totalSlotSize = Align(rawTotalSize); + size_t candidateStart = 0; + + if (!overwriteWhenFull_) { + std::lock_guard lock(slotManagementMutex_); + // Look in the free-region list as fallback using a cached cursor. + { + bool found = false; + size_t newCandidate = 0; + // Start search from freeRegionCursor_ + auto it = freeRegions_.lower_bound(freeRegionCursor_); + // Loop over free regions at most once (wrapping around if necessary). + for (size_t count = 0, sz = freeRegions_.size(); count < sz; count++) { + if (it == freeRegions_.end()) + it = freeRegions_.begin(); + size_t alignedCandidate = Align(it->first); + if (it->first + it->second >= alignedCandidate + totalSlotSize) { + newCandidate = alignedCandidate; + found = true; + break; + } + ++it; + } + if (found) { + candidateStart = newCandidate; + // Update the cursor so that next search can start here. + freeRegionCursor_ = candidateStart + totalSlotSize; + return CreateSlot(candidateStart, totalSlotSize, dataSize, additionalMetadataSize, + dataPointer, additionalMetadataPointer, + true, serializedInitialMetadata, deviceLabel); + } + } + + // No recycled slot or free region can satisfy the allocation. + overflow_ = true; + *dataPointer = nullptr; + *additionalMetadataPointer = nullptr; + return DEVICE_ERR; + } else { + // Overwrite mode + size_t prevOffset, newOffset; + do { + prevOffset = nextAllocOffset_.load(std::memory_order_relaxed); + candidateStart = Align(prevOffset); + if (candidateStart + totalSlotSize > bufferSize_) + candidateStart = 0; // Wrap around if needed. + newOffset = candidateStart + totalSlotSize; + } while (!nextAllocOffset_.compare_exchange_weak(prevOffset, newOffset)); + + // Only now grab the lock to register the new slot. + { + std::lock_guard lock(slotManagementMutex_); + return CreateSlot(candidateStart, totalSlotSize, dataSize, additionalMetadataSize, + dataPointer, additionalMetadataPointer, false, serializedInitialMetadata, deviceLabel); + } + } +} + +/** + * @brief Release a data slot after writing is complete. + * + * @param caller The device calling this function. + * @param buffer The buffer to be released. + * @return Error code (0 on success). + */ +int DataBuffer::FinalizeWriteSlot(const void* dataPointer, size_t actualMetadataBytes) { + if (dataPointer == nullptr) + return DEVICE_ERR; + + BufferSlot* slot = nullptr; + { + std::lock_guard lock(slotManagementMutex_); + slot = FindSlotForPointer(dataPointer); + if (!slot) + return DEVICE_ERR; + + // Update the slot with actual metadata size + slot->UpdateAdditionalMetadataSize(actualMetadataBytes); + } + + slot->ReleaseWriteAccess(); + + // Notify waiting threads under a brief lock + { + std::lock_guard lock(slotManagementMutex_); + dataCV_.notify_all(); + } + + return DEVICE_OK; +} + +/** + * ReleaseSlot is called after a slot's content has been fully read. + * + * This implementation pushes only the start of the released slot onto the FILO + * (releasedSlots_) and removes the slot from the active slot map and activeSlots_. + */ +int DataBuffer::ReleaseDataReadPointer(const void* dataPointer) { + if (dataPointer == nullptr) + return DEVICE_ERR; + + // First find the slot without the global lock + BufferSlot* slot = nullptr; + { + std::lock_guard lock(slotManagementMutex_); + slot = FindSlotForPointer(dataPointer); + if (!slot) + return DEVICE_ERR; + } + const size_t offset = static_cast(dataPointer) - + static_cast(buffer_); + + // Release the read access outside the global lock + slot->ReleaseReadAccess(); + + if (!overwriteWhenFull_) { + std::lock_guard lock(slotManagementMutex_); + // Now check if the slot is not being accessed + if (slot->IsFree()) { + auto it = activeSlotsByStart_.find(offset); + DeleteSlot(offset, it); + } + } + + return DEVICE_OK; +} + +const void* DataBuffer::PopNextDataReadPointer(Metadata &md, bool waitForData) +{ + if (overwriteWhenFull_) { + throw std::runtime_error("PopNextDataReadPointer is not available in overwrite mode"); + } + + BufferSlot* slot = nullptr; + size_t slotStart = 0; + + // First, get the slot under the global lock + { + std::unique_lock lock(slotManagementMutex_); + while (activeSlotsVector_.empty()) { + if (!waitForData) + return nullptr; + dataCV_.wait(lock); + } + // Atomically take the slot and advance the index + slot = activeSlotsVector_[currentSlotIndex_]; + slotStart = slot->GetStart(); + currentSlotIndex_ = (currentSlotIndex_ + 1) % activeSlotsVector_.size(); + } // Release global lock + + // Now acquire read access outside the global lock + slot->AcquireReadAccess(); + + const unsigned char* dataPointer = static_cast(buffer_) + slotStart; + this->ExtractMetadata(dataPointer, slot, md); + + return dataPointer; +} + +const void* DataBuffer::PeekLastDataReadPointer(Metadata &md) { + if (!overwriteWhenFull_) { + throw std::runtime_error("PeekLastDataReadPointer is only available in overwrite mode"); + } + + BufferSlot* currentSlot = nullptr; + { + std::unique_lock lock(slotManagementMutex_); + if (activeSlotsVector_.empty()) { + return nullptr; + } + + // Get the most recent slot (last in vector) + currentSlot = activeSlotsVector_.back(); + } + + currentSlot->AcquireReadAccess(); + + const void* result = static_cast(buffer_) + currentSlot->GetStart(); + + if (ExtractMetadata(result, currentSlot, md) != DEVICE_OK) { + currentSlot->ReleaseReadAccess(); + return nullptr; + } + + return result; +} + +const void* DataBuffer::PeekDataReadPointerAtIndex(size_t n, Metadata &md) { + if (!overwriteWhenFull_) { + throw std::runtime_error("PeekDataReadPointerAtIndex is only available in overwrite mode"); + } + + BufferSlot* currentSlot = nullptr; + { + // Lock the global slot management mutex to safely access the active slots. + std::unique_lock lock(slotManagementMutex_); + if (activeSlotsVector_.empty() || n >= activeSlotsVector_.size()) { + return nullptr; + } + + // Instead of looking ahead from currentSlotIndex_, we look back from the end. + // For n==0, return the most recent slot; for n==1, the one before it; etc. + size_t index = activeSlotsVector_.size() - n - 1; + currentSlot = activeSlotsVector_[index]; + } + + currentSlot->AcquireReadAccess(); + + const unsigned char* dataPointer = static_cast(buffer_) + currentSlot->GetStart(); + this->ExtractMetadata(dataPointer, currentSlot, md); + + return dataPointer; +} + +const void* DataBuffer::PeekLastDataReadPointerFromDevice(const std::string& deviceLabel, Metadata& md) { + if (!overwriteWhenFull_) { + throw std::runtime_error("PeekLastDataReadPointerFromDevice is only available in overwrite mode"); + } + + BufferSlot* matchingSlot = nullptr; + { + std::unique_lock lock(slotManagementMutex_); + + // Search backwards through activeSlotsVector_ to find most recent matching slot + for (auto it = activeSlotsVector_.rbegin(); it != activeSlotsVector_.rend(); ++it) { + if ((*it)->GetDeviceLabel() == deviceLabel) { + matchingSlot = *it; + break; + } + } + + if (!matchingSlot) { + return nullptr; + } + } + + // Acquire read access and get data pointer + matchingSlot->AcquireReadAccess(); + + const void* result = static_cast(buffer_) + matchingSlot->GetStart(); + + if (ExtractMetadata(result, matchingSlot, md) != DEVICE_OK) { + matchingSlot->ReleaseReadAccess(); + return nullptr; + } + + return result; +} + +unsigned int DataBuffer::GetMemorySizeMB() const { + // Convert bytes to MB (1 MB = 1048576 bytes) + return static_cast(bufferSize_ >> 20); +} + +size_t DataBuffer::GetOccupiedSlotCount() const { + std::lock_guard lock(slotManagementMutex_); + return activeSlotsVector_.size(); +} + +size_t DataBuffer::GetOccupiedMemory() const { + std::lock_guard lock(slotManagementMutex_); + size_t usedMemory = 0; + for (const auto& slot : activeSlotsVector_) { + usedMemory += slot->GetLength(); + } + return usedMemory; +} + +size_t DataBuffer::GetFreeMemory() const { + std::lock_guard lock(slotManagementMutex_); + // Free memory is the total buffer size minus the sum of all occupied memory. + size_t usedMemory = 0; + for (const auto& slot : activeSlotsVector_) { + usedMemory += slot->GetLength(); + } + return (bufferSize_ > usedMemory) ? (bufferSize_ - usedMemory) : 0; +} + +bool DataBuffer::Overflow() const { + std::lock_guard lock(slotManagementMutex_); + return overflow_; +} + +/** + * Reinitialize the DataBuffer by clearing all internal data structures, + * releasing the current buffer, and reallocating a new one. + * This method uses the existing slotManagementMutex_ to ensure thread-safety. + * + * @param memorySizeMB New size (in MB) for the buffer. + * @return DEVICE_OK on success. + * @throws std::runtime_error if any slot is still actively being read or written. + */ +int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { + std::lock_guard lock(slotManagementMutex_); + + // Ensure no active readers/writers exist. + for (BufferSlot* slot : activeSlotsVector_) { + if (!slot->IsFree()) { + throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); + } + } + + // Clear internal data structures + activeSlotsVector_.clear(); + activeSlotsByStart_.clear(); + currentSlotIndex_ = 0; + nextAllocOffset_ = 0; + overflow_ = false; + + // Reset the slot pool + unusedSlots_.clear(); + for (BufferSlot* bs : slotPool_) { + unusedSlots_.push_back(bs); + } + + // Release and reallocate the buffer + if (buffer_ != nullptr) { + #ifdef _WIN32 + VirtualFree(buffer_, 0, MEM_RELEASE); + #else + munmap(buffer_, bufferSize_); + #endif + buffer_ = nullptr; + } + + return AllocateBuffer(memorySizeMB); +} + +long DataBuffer::GetActiveSlotCount() const { + return static_cast(activeSlotsVector_.size()); +} + +int DataBuffer::ExtractMetadata(const void* dataPointer, BufferSlot* slot, Metadata &md) { + // No lock is required here because we assume the slot is already locked + + if (!dataPointer || !slot) + return DEVICE_ERR; // Invalid pointer + + // Calculate metadata pointers and sizes from the slot + const unsigned char* initialMetadataPtr = static_cast(dataPointer) + slot->GetDataSize(); + size_t initialMetadataSize = slot->GetInitialMetadataSize(); + const unsigned char* additionalMetadataPtr = initialMetadataPtr + initialMetadataSize; + size_t additionalMetadataSize = slot->GetAdditionalMetadataSize(); + + // Handle initial metadata if present + if (initialMetadataSize > 0) { + Metadata initialMd; + std::string initialMetaStr(reinterpret_cast(initialMetadataPtr), initialMetadataSize); + initialMd.Restore(initialMetaStr.c_str()); + md.Merge(initialMd); + } + + // Handle additional metadata if present + if (additionalMetadataSize > 0) { + Metadata additionalMd; + std::string additionalMetaStr(reinterpret_cast(additionalMetadataPtr), additionalMetadataSize); + additionalMd.Restore(additionalMetaStr.c_str()); + md.Merge(additionalMd); + } + + return DEVICE_OK; +} + +// NOTE: Caller must hold slotManagementMutex_ for thread safety. +BufferSlot* DataBuffer::FindSlotForPointer(const void* dataPointer) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + if (buffer_ == nullptr) + return nullptr; + std::size_t offset = static_cast(dataPointer) - + static_cast(buffer_); + auto it = activeSlotsByStart_.find(offset); + return (it != activeSlotsByStart_.end()) ? it->second : nullptr; +} + +void DataBuffer::MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + // Find the free region that starts at or after newEnd + auto right = freeRegions_.lower_bound(newRegionEnd); + + // Check if there is a free region immediately preceding the new region + auto left = freeRegions_.lower_bound(newRegionStart); + if (left != freeRegions_.begin()) { + auto prev = std::prev(left); + // If the previous region's end matches the new region's start... + if (prev->first + prev->second == newRegionStart) { + newRegionStart = prev->first; + freeRegions_.erase(prev); + } + } + + // Check if the region immediately to the right can be merged + if (right != freeRegions_.end() && right->first == newRegionEnd) { + newRegionEnd = right->first + right->second; + freeRegions_.erase(right); + } + + // Insert the merged (or standalone) free region + size_t newRegionSize = (newRegionEnd > newRegionStart ? newRegionEnd - newRegionStart : 0); + if (newRegionSize > 0) { + freeRegions_[newRegionStart] = newRegionSize; + } +} + +void DataBuffer::RemoveFromActiveTracking(size_t offset, std::unordered_map::iterator it) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + activeSlotsByStart_.erase(it); + for (auto vecIt = activeSlotsVector_.begin(); vecIt != activeSlotsVector_.end(); ++vecIt) { + if ((*vecIt)->GetStart() == offset) { + // Determine the index being removed. + size_t indexDeleted = std::distance(activeSlotsVector_.begin(), vecIt); + activeSlotsVector_.erase(vecIt); + // Adjust the currentSlotIndex_; if the deleted slot was before it, decrement. + if (currentSlotIndex_ > indexDeleted) + currentSlotIndex_--; + break; + } + } +} + +void DataBuffer::DeleteSlot(size_t offset, std::unordered_map::iterator it) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + + size_t newRegionStart = offset; + size_t newRegionEnd = offset + it->second->GetLength(); + + // Return the slot to the pool before removing from active tracking + ReturnSlotToPool(it->second); + + MergeFreeRegions(newRegionStart, newRegionEnd); + RemoveFromActiveTracking(offset, it); +} + +void DataBuffer::UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + auto it = freeRegions_.upper_bound(candidateStart); + if (it != freeRegions_.begin()) { + --it; + size_t freeRegionStart = it->first; + size_t freeRegionSize = it->second; + size_t freeRegionEnd = freeRegionStart + freeRegionSize; + + if (candidateStart >= freeRegionStart && + candidateStart + totalSlotSize <= freeRegionEnd) { + freeRegions_.erase(it); + + if (candidateStart > freeRegionStart) { + size_t gap = candidateStart - freeRegionStart; + if (gap > 0) + freeRegions_.insert({freeRegionStart, gap}); + } + + if (freeRegionEnd > candidateStart + totalSlotSize) { + size_t gap = freeRegionEnd - (candidateStart + totalSlotSize); + if (gap > 0) + freeRegions_.insert({candidateStart + totalSlotSize, gap}); + } + } + } +} + +int DataBuffer::CreateSlot(size_t candidateStart, size_t totalSlotSize, + size_t dataSize, size_t additionalMetadataSize, + void** dataPointer, void** additionalMetadataPointer, + bool fromFreeRegion, const std::string& serializedInitialMetadata, + const std::string& deviceLabel) +{ + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + BufferSlot* newSlot = GetSlotFromPool(candidateStart, totalSlotSize, + dataSize, serializedInitialMetadata.size(), + additionalMetadataSize, deviceLabel); + + newSlot->AcquireWriteAccess(); + + // Initialize the data pointer before using it. + *dataPointer = static_cast(buffer_) + newSlot->GetStart(); + + if (!serializedInitialMetadata.empty()) { + std::memcpy(static_cast(*dataPointer) + dataSize, serializedInitialMetadata.data(), + serializedInitialMetadata.size()); + newSlot->SetInitialMetadataSize(serializedInitialMetadata.size()); + } + + *additionalMetadataPointer = static_cast(*dataPointer) + newSlot->GetDataSize() + newSlot->GetInitialMetadataSize(); + + if (fromFreeRegion) { + UpdateFreeRegions(candidateStart, totalSlotSize); + } + + return DEVICE_OK; +} + +BufferSlot* DataBuffer::GetSlotFromPool(size_t start, size_t totalLength, + size_t dataSize, size_t initialMetadataSize, + size_t additionalMetadataSize, + const std::string& deviceLabel) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + + // Grow the pool if needed. + if (unusedSlots_.empty()) { + BufferSlot* newSlot = new BufferSlot(); + slotPool_.push_back(newSlot); + unusedSlots_.push_back(newSlot); + } + + // Get a slot from the front of the deque. + BufferSlot* slot = unusedSlots_.front(); + unusedSlots_.pop_front(); + slot->Reset(start, totalLength, dataSize, initialMetadataSize, additionalMetadataSize, deviceLabel); + + // Add to active tracking. + activeSlotsVector_.push_back(slot); + activeSlotsByStart_[start] = slot; + return slot; +} + +void DataBuffer::ReturnSlotToPool(BufferSlot* slot) { + assert(!slotManagementMutex_.try_lock() && "Caller must hold slotManagementMutex_"); + unusedSlots_.push_back(slot); +} + +int DataBuffer::ExtractCorrespondingMetadata(const void* dataPointer, Metadata &md) { + BufferSlot* slot = nullptr; + { + std::lock_guard lock(slotManagementMutex_); + slot = FindSlotForPointer(dataPointer); + if (!slot) { + return DEVICE_ERR; + } + } + + // Extract metadata (internal method doesn't need lock) + return ExtractMetadata(dataPointer, slot, md); +} + +size_t DataBuffer::GetDatumSize(const void* dataPointer) { + std::lock_guard lock(slotManagementMutex_); + BufferSlot* slot = FindSlotForPointer(dataPointer); + if (!slot) { + throw std::runtime_error("DataBuffer::GetDatumSize: pointer not found in buffer"); + } + return slot->GetDataSize(); +} + +bool DataBuffer::IsPointerInBuffer(const void* ptr) { + if (buffer_ == nullptr || ptr == nullptr) { + return false; + } + // get the mutex + std::lock_guard lock(slotManagementMutex_); + // find the slot + BufferSlot* slot = FindSlotForPointer(ptr); + return slot != nullptr; +} + +int DataBuffer::NumOutstandingSlots() const { + std::lock_guard lock(slotManagementMutex_); + int numOutstanding = 0; + for (const BufferSlot* slot : activeSlotsVector_) { + if (!slot->IsFree()) { + numOutstanding++; + } + } + return numOutstanding; +} diff --git a/MMCore/NewDataBuffer.h b/MMCore/NewDataBuffer.h new file mode 100644 index 000000000..d5177fa37 --- /dev/null +++ b/MMCore/NewDataBuffer.h @@ -0,0 +1,497 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: NewDataBuffer.h +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +//----------------------------------------------------------------------------- +// DESCRIPTION: Generic implementation of a buffer for storing image data and +// metadata. Provides thread-safe access for reading and writing +// with configurable overflow behavior. +// +// The buffer is organized into slots (BufferSlot objects), each of which +// supports exclusive write access and shared read access. Read access is +// delivered using const pointers and is tracked via RAII-based synchronization. +// Write access is protected via an exclusive lock. This ensures that once a +// read pointer is given out it cannot be misused for writing. +// +// COPYRIGHT: Henry Pinkard, 2025 +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Henry Pinkard, 01/31/2025 +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "../MMDevice/ImageMetadata.h" +#include "../MMDevice/MMDevice.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "TaskSet_CopyMemory.h" +#include +#include + +/** + * BufferSlot represents a contiguous slot in the DataBuffer that holds image + * data and metadata. It uses RAII-based locking with std::shared_timed_mutex to + * support exclusive write access and concurrent shared read access. + */ +class BufferSlot { +public: + /** + * Constructs a BufferSlot with all sizes specified up front. + * + * @param start The starting offset (in bytes) within the buffer. + * @param totalLength The total length (in bytes) reserved for this slot, typically + * an aligned size (which includes image data and metadata). + * @param imageSize The exact number of bytes for the image data. + * @param metadataSize The exact number of bytes for the metadata. + */ + BufferSlot() + : start_(0), + length_(0), + imageSize_(0), + initialMetadataSize_(0), + additionalMetadataSize_(0), + deviceLabel_(), + rwMutex_() + { + } + + /** + * Destructor. + */ + ~BufferSlot() {}; + + /** + * Returns the starting offset (in bytes) of the slot. + * @return The slot's start offset. + */ + std::size_t GetStart() const { return start_; } + + /** + * Returns the length (in bytes) of the slot. + * + * @return The slot's length. + */ + std::size_t GetLength() const { return length_; } + + /** + * Acquires exclusive (write) access (blocking). + */ + void AcquireWriteAccess() { + rwMutex_.lock(); + } + + /** + * Releases exclusive write access. + */ + void ReleaseWriteAccess() { + rwMutex_.unlock(); + } + + /** + * Acquires shared read access (blocking). + */ + void AcquireReadAccess() { + rwMutex_.lock_shared(); + } + + /** + * Releases shared read access. + */ + void ReleaseReadAccess() { + rwMutex_.unlock_shared(); + } + + /** + * Checks if the slot is completely free (no readers, no writers). + * We do this by attempting to lock it exclusively: + * if we succeed, there are no concurrent locks. + */ + bool IsFree() const { + std::unique_lock lk(rwMutex_, std::try_to_lock); + return lk.owns_lock(); + } + + /** + * Resets the slot with new parameters. + * The assertion uses IsFree() as a best-effort check before reinitializing. + */ + void Reset(size_t start, + size_t length, + size_t imageSize, + size_t initialMetadataSize, + size_t additionalMetadataSize, + const std::string& deviceLabel) + { + // If this fails, there's likely an active read or write lock on the slot. + assert(IsFree() && + "BufferSlot mutex still locked during Reset - indicates a bug!"); + + start_ = start; + length_ = length; + imageSize_ = imageSize; + initialMetadataSize_ = initialMetadataSize; + additionalMetadataSize_ = initialMetadataSize + additionalMetadataSize; + deviceLabel_ = deviceLabel; + } + + /** + * Updates the metadata size after writing is complete. + * @param newSize The actual size of the written metadata. + */ + void UpdateAdditionalMetadataSize(size_t newSize) { + additionalMetadataSize_ = newSize; + } + + /** + * Record the number of bytes of the initial metadata that have been written to this slot. + */ + void SetInitialMetadataSize(size_t initialSize) { + initialMetadataSize_ = initialSize; + } + + /** + * Returns the size of the image data in bytes. + * @return The image data size. + */ + std::size_t GetDataSize() const { + return imageSize_; + } + + /** + * Returns the size of the initial metadata in bytes. + * @return The initial metadata size. + */ + std::size_t GetInitialMetadataSize() const { + return initialMetadataSize_; + } + + /** + * Returns the size of the additional metadata in bytes. + * @return The additional metadata size. + */ + std::size_t GetAdditionalMetadataSize() const { + return additionalMetadataSize_; + } + + const std::string& GetDeviceLabel() const { + return deviceLabel_; + } + +private: + std::size_t start_; + std::size_t length_; + size_t imageSize_; + size_t initialMetadataSize_; + size_t additionalMetadataSize_; + std::string deviceLabel_; + + mutable std::shared_timed_mutex rwMutex_; +}; + +/** + * DataBuffer manages a contiguous block of memory divided into BufferSlot objects + * for storing image data and metadata. Each slot in memory holds + * only the image data (followed immediately by metadata), while header information + * is maintained in the BufferSlot objects. + */ +class DataBuffer { +public: + + /** + * Constructor. + * @param memorySizeMB The size (in megabytes) of the buffer. + */ + DataBuffer(unsigned int memorySizeMB); + + /** + * Destructor. + */ + ~DataBuffer(); + + /** + * Allocates the memory buffer. + * @param memorySizeMB Size in megabytes. + * @return DEVICE_OK on success. + */ + int AllocateBuffer(unsigned int memorySizeMB); + + /** + * Releases the memory buffer. + * @return DEVICE_OK on success. + */ + int ReleaseBuffer(); + + /** + * Inserts image data along with metadata into the buffer. + * @param data Pointer to the raw image data. + * @param dataSize The image data size in bytes. + * @param pMd Pointer to the metadata (can be null if not applicable). + * @param deviceLabel The label of the device that is the source of the data. + * @return DEVICE_OK on success. + */ + int InsertData(const void* data, size_t dataSize, const Metadata* pMd, const std::string& deviceLabel); + + /** + * Sets whether the buffer should overwrite old data when full. + * @param overwrite True to enable overwriting. + * @return DEVICE_OK on success. + */ + int SetOverwriteData(bool overwrite); + + /** + * Returns whether the buffer should overwrite old data when full. + * @return True if overwriting is enabled, false otherwise. + */ + bool GetOverwriteData() const; + + /** + * Acquires a write slot large enough to hold the image data and metadata. + * On success, returns pointers for the image data and metadata regions. + * + * @param dataSize The number of bytes reserved for data. + * @param additionalMetadataSize The maximum number of bytes reserved for additional metadata. + * @param dataPointer On success, receives a pointer to the data region. + * @param additionalMetadataPointer On success, receives a pointer to the additional metadata region. + * @param serializedInitialMetadata Optional string containing initial metadata to write. + * @return DEVICE_OK on success. + */ + int AcquireWriteSlot(size_t dataSize, size_t additionalMetadataSize, + void** dataPointer, + void** additionalMetadataPointer, + const std::string& serializedInitialMetadata, + const std::string& deviceLabel); + + /** + * Finalizes (releases) a write slot after data has been written. + * Requires the actual number of metadata bytes written. + * + * @param imageDataPointer Pointer previously obtained from AcquireWriteSlot. + * @param actualMetadataBytes The actual number of metadata bytes written. + * @return DEVICE_OK on success. + */ + int FinalizeWriteSlot(const void* dataPointer, size_t actualMetadataBytes); + + /** + * Releases read access for the image data after reading. + * @param imageDataPointer Pointer previously obtained from reading routines. + * @return DEVICE_OK on success. + */ + int ReleaseDataReadPointer(const void* dataPointer); + + /** + * Retrieves and consumes the next available data entry for reading, + * populating the provided Metadata object. + * @param md Metadata object to populate. + * @param waitForData If true, blocks until data is available. + * @return Pointer to the image data region, or nullptr if none available. + */ + const void* PopNextDataReadPointer(Metadata &md, bool waitForData); + + /** + * Peeks at the most recently added data entry. + * @param md Metadata object populated from the stored metadata. + * @return Pointer to the start of the data region. + */ + const void* PeekLastDataReadPointer(Metadata &md); + + /** + * Peeks at the nth unread data entry without consuming it. + * (n = 0 is equivalent to PeekNextDataReadPointer). + * @param n Index of the data entry to peek at (0 for next available). + * @param md Metadata object populated from the stored metadata. + * @return Pointer to the start of the data region. + */ + const void* PeekDataReadPointerAtIndex(size_t n, Metadata &md); + + /** + * Get the last image inserted by a specific device. + * @param deviceLabel The label of the device to get the image from. + * @param md Metadata object to populate. + * @return Pointer to the image data, or nullptr if not found. + */ + const void* PeekLastDataReadPointerFromDevice(const std::string& deviceLabel, Metadata& md); + + /** + * Returns the total buffer memory size (in MB). + * @return Buffer size in MB. + */ + unsigned int GetMemorySizeMB() const; + + /** + * Returns the number of occupied buffer slots. + * @return Occupied slot count. + */ + size_t GetOccupiedSlotCount() const; + + /** + * Returns the total occupied memory in bytes. + * @return Sum of active slot lengths. + */ + size_t GetOccupiedMemory() const; + + /** + * Returns the amount of free memory remaining in bytes. + * @return Free byte count. + */ + size_t GetFreeMemory() const; + + /** + * Indicates whether a buffer overflow has occurred. + * @return True if an insert failed (buffer full), false otherwise. + */ + bool Overflow() const; + + /** + * Returns the number of unread slots in the buffer. + * @return Unread slot count. + */ + long GetActiveSlotCount() const; + + + /** + * Extracts metadata for a given image data pointer. + * Thread-safe method that acquires necessary locks to lookup metadata location. + * + * @param dataPtr Pointer to the (usuallyimage data. + * @param md Metadata object to populate. + * @return DEVICE_OK on success, or an error code if extraction fails. + */ + int ExtractCorrespondingMetadata(const void* dataPtr, Metadata &md); + + /** + * Returns the size of the data portion of the slot in bytes. + * @param dataPointer Pointer to the data portion of a slot. + * @return Size in bytes of the data portion, or 0 if pointer is invalid. + */ + size_t GetDatumSize(const void* dataPointer); + + /** + * Check if a pointer is within the buffer's memory range. + * @param ptr The pointer to check. + * @return true if the pointer is within the buffer, false otherwise. + */ + bool IsPointerInBuffer(const void* ptr); + + /** + * Reset the buffer, discarding all data that is not currently held externally. + */ + void Reset(); + + /** + * Checks if there are any outstanding slots in the buffer. If so, it + * is unsafe to destroy the buffer. + * @return true if there are outstanding slots, false otherwise. + */ + int NumOutstandingSlots() const; + +private: + /** + * Internal helper function that finds the slot for a given pointer. + * Returns non-const pointer since slots need to be modified for locking. + * + * @param dataPtr Pointer to the data. + * @return Pointer to the corresponding BufferSlot, or nullptr if not found. + */ + BufferSlot* FindSlotForPointer(const void* dataPtr); + + // Memory managed by the DataBuffer. + void* buffer_; + size_t bufferSize_; + + // Whether to overwrite old data when full. + bool overwriteWhenFull_; + + // Overflow flag (set if insert fails due to full buffer). + bool overflow_; + + // Active slots and their mapping. + std::vector activeSlotsVector_; + std::unordered_map activeSlotsByStart_; + + // Free region list for non-overwrite mode. + // Map from starting offset -> region size (in bytes). + std::map freeRegions_; + + // Cached cursor for scanning free regions in non-overwrite mode. + size_t freeRegionCursor_; + + // Instead of ownership via unique_ptr, store raw pointers + // Note: unusedSlots_ is now a deque of raw pointers. + std::deque unusedSlots_; + + // This container holds the ownership; they live for the lifetime of the buffer. + std::vector slotPool_; + + // Next free offset within the buffer. + // In overwrite mode, new allocations will come from this pointer. + std::atomic nextAllocOffset_; + + // Index tracking the next slot for read. + size_t currentSlotIndex_; + + // Synchronization for slot management. + std::condition_variable dataCV_; + mutable std::mutex slotManagementMutex_; + + // Members for multithreaded copying. + std::shared_ptr threadPool_; + std::shared_ptr tasksMemCopy_; + + void DeleteSlot(size_t offset, std::unordered_map::iterator it); + + void MergeFreeRegions(size_t newRegionStart, size_t newRegionEnd); + void RemoveFromActiveTracking(size_t offset, std::unordered_map::iterator it); + + void UpdateFreeRegions(size_t candidateStart, size_t totalSlotSize); + + BufferSlot* GetSlotFromPool(size_t start, size_t totalLength, + size_t dataSize, size_t initialMetadataSize, + size_t additionalMetadataSize, const std::string& deviceLabel); + + /** + * Reinitializes the DataBuffer, clearing its structures and allocating a new buffer. + * @param memorySizeMB New buffer size (in MB). + * @return DEVICE_OK on success. + * @throws std::runtime_error if any slot is still in use. + */ + int ReinitializeBuffer(unsigned int memorySizeMB); + + + /** + * Creates a new slot with the specified parameters. + * Caller must hold slotManagementMutex_. + */ + int CreateSlot(size_t candidateStart, size_t totalSlotSize, + size_t dataSize, size_t additionalMetadataSize, + void** dataPointer, + void** subsequentMetadataPointer, + bool fromFreeRegion, + const std::string& serializedInitialMetadata, + const std::string& deviceLabel); + + + void ReturnSlotToPool(BufferSlot* slot); + + int ExtractMetadata(const void* dataPointer, + BufferSlot* slot, + Metadata &md); + +}; diff --git a/MMCore/NewDataBufferPointer.h b/MMCore/NewDataBufferPointer.h new file mode 100644 index 000000000..b4329144e --- /dev/null +++ b/MMCore/NewDataBufferPointer.h @@ -0,0 +1,131 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: NewDataBufferPointer.h +// PROJECT: Micro-Manager +// SUBSYSTEM: MMCore +//----------------------------------------------------------------------------- +// DESCRIPTION: A read-only wrapper class for accessing image data and metadata +// from a buffer slot. Provides safe access to image data by +// automatically releasing read access when the object is destroyed. +// Includes methods for retrieving pixel data and associated +// metadata.. +// +// COPYRIGHT: Henry Pinkard, 2025 +// +// LICENSE: This file is distributed under the "Lesser GPL" (LGPL) license. +// License text is included with the source distribution. +// +// This file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. +// +// AUTHOR: Henry Pinkard, 2/16/2025 +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "MMCore.h" +#include "../MMDevice/ImageMetadata.h" +#include +#include + +// This is needed for SWIG Java wrapping to differentiate its void* +// from the void* that MMCore uses for returning data +typedef const void* BufferDataPointerVoidStar; + +/// A read-only wrapper for accessing image data and metadata from a buffer slot. +/// Automatically releases the read access when destroyed. +class BufferDataPointer { + +public: + + BufferDataPointer(BufferManager* bufferManager, DataPtr ptr) + : bufferManager_(bufferManager), ptr_(ptr), mutex_() + { + // check for null pointer + if (!ptr_) { + throw CMMError("Pointer is null"); + } + // check for v2 buffer use + if (!bufferManager_->IsUsingNewDataBuffer()) { + throw CMMError("V2 buffer must be enabled for BufferDataPointer"); + } + // throw an error if the pointer is not in the buffer + if (!bufferManager_->IsPointerInNewDataBuffer(ptr_)) { + throw CMMError("Pointer is not in the buffer"); + } + } + + // Returns a pointer to the pixel data (read-only) + BufferDataPointerVoidStar getData() const { + if (!ptr_) { + return nullptr; + } + return ptr_; + } + + // Same as the above method, but this get wrapped by SWIG differently + // to return the actual + DataPtr getDataPointer() const { + if (!ptr_) { + return nullptr; + } + return ptr_; + } + + void getImageProperties(int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { + if (!bufferManager_ || !ptr_) { + throw CMMError("Invalid buffer manager or pointer"); + } + Metadata md; + bufferManager_->ExtractMetadata(ptr_, md); + CMMCore::parseImageMetadata(md, width, height, byteDepth, nComponents); + } + + // Fills the provided Metadata object with metadata extracted from the pointer. + // It encapsulates calling the core API function that copies metadata from the buffer. + void getMetadata(Metadata &md) const { + if (bufferManager_ && ptr_) { + bufferManager_->ExtractMetadata(ptr_, md); + } + } + + // Destructor: releases the read access to the pointer if not already released + ~BufferDataPointer() { + release(); + } + + + // Explicitly release the pointer before destruction if needed + void release() { + std::lock_guard lock(mutex_); + if (bufferManager_ && ptr_) { + + int ret = bufferManager_->ReleaseReadAccess(ptr_); + if (ret != DEVICE_OK) { + throw CMMError("Failed to release read access to buffer"); + } + ptr_ = nullptr; // Mark as released + + } + } + + unsigned getSizeBytes() const { + if (bufferManager_ && ptr_) { + return bufferManager_->GetDataSize(ptr_); + } + return 0; + } + +private: + // Disable copy semantics to avoid double releasing the pointer + BufferDataPointer(const BufferDataPointer&); + BufferDataPointer& operator=(const BufferDataPointer&); + + BufferManager* bufferManager_; + const void* ptr_; + mutable std::mutex mutex_; +}; diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index b4681c9f9..2c4426063 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -958,7 +958,7 @@ #include "../MMDevice/ImageMetadata.h" #include "../MMCore/MMEventCallback.h" #include "../MMCore/MMCore.h" -#include "../MMCore/BufferDataPointer.h" +#include "../MMCore/NewBufferDataPointer.h" %} @@ -1283,5 +1283,5 @@ namespace std { %include "../MMCore/MMCore.h" %include "../MMDevice/ImageMetadata.h" %include "../MMCore/MMEventCallback.h" -%include "../MMCore/BufferDataPointer.h" +%include "../MMCore/NewDataBufferPointer.h" From a9f20fad4510f84704969683a20fb981731d3930 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:51:07 -0800 Subject: [PATCH 39/46] allow retrieval of generic non image data --- MMCore/MMCore.cpp | 17 ++++++++++++++--- MMCore/MMCore.h | 4 ++-- MMCore/NewDataBufferPointer.h | 4 ++-- MMCoreJ_wrap/MMCoreJ.i | 17 ++++++++++++++--- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 17d16dcaf..6d95e89a1 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -4781,7 +4781,7 @@ void CMMCore::releaseReadAccess(DataPtr ptr) { // buffer knows about this pointer. If not, it's a snap buffer pointer. // We don't want want to compare to the snap buffer pointer directly because // its unclear what the device adapter might do when this is called. -void CMMCore::getImageProperties(DataPtr ptr, int& width, int& height, +bool CMMCore::getImageProperties(DataPtr ptr, int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { if (!bufferManager_->IsUsingNewDataBuffer()) { // Could be snap or circular buffer pointer @@ -4793,7 +4793,8 @@ void CMMCore::getImageProperties(DataPtr ptr, int& width, int& height, // NewDataBuffer pointer Metadata md; bufferManager_->ExtractMetadata(ptr, md); - parseImageMetadata(md, width, height, byteDepth, nComponents); + // If it can't return this metadata, then its not a valid image + return parseImageMetadata(md, width, height, byteDepth, nComponents); } else { // Snap buffer pointer with NewDataBuffer on width = getImageWidth(); @@ -4801,6 +4802,7 @@ void CMMCore::getImageProperties(DataPtr ptr, int& width, int& height, byteDepth = getBytesPerPixel(); nComponents = getNumberOfComponents(); } + return true; } /** @@ -8283,8 +8285,16 @@ std::string CMMCore::getInstalledDeviceDescription(const char* hubLabel, const c /** * Get the essential metadata for interpretting image data stored in the buffer. */ -void CMMCore::parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents) +bool CMMCore::parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents) { + // Check if required metadata tags exist + if (!md.HasTag(MM::g_Keyword_Metadata_Width) || + !md.HasTag(MM::g_Keyword_Metadata_Height) || + !md.HasTag(MM::g_Keyword_PixelType)) + { + return false; + } + width = std::stoul(md.GetSingleTag(MM::g_Keyword_Metadata_Width).GetValue()); height = std::stoul(md.GetSingleTag(MM::g_Keyword_Metadata_Height).GetValue()); nComponents = 1; // Default to 1 component @@ -8307,4 +8317,5 @@ void CMMCore::parseImageMetadata(Metadata& md, int& width, int& height, int& byt byteDepth = 1; // Default to 1 byte depth for unknown types nComponents = 1; } + return true; } \ No newline at end of file diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 13fa214be..530faeb11 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -462,7 +462,7 @@ class CMMCore // These functions are used by the Java SWIG wrapper to get properties of the image // based on a pointer. The DataPtr alias to void* is so they don't get converted to // Object in the Java SWIG wrapper. - void getImageProperties(DataPtr ptr, int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError); + bool getImageProperties(DataPtr ptr, int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError); void releaseReadAccess(DataPtr ptr) throw (CMMError); @@ -704,7 +704,7 @@ class CMMCore void setIncludeImageMetadata(std::string & category, bool include) throw (CMMError); ///@} - static void parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents); + static bool parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents); private: // make object non-copyable diff --git a/MMCore/NewDataBufferPointer.h b/MMCore/NewDataBufferPointer.h index b4329144e..35fc6a9ae 100644 --- a/MMCore/NewDataBufferPointer.h +++ b/MMCore/NewDataBufferPointer.h @@ -76,13 +76,13 @@ class BufferDataPointer { return ptr_; } - void getImageProperties(int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { + bool getImageProperties(int& width, int& height, int& byteDepth, int& nComponents) throw (CMMError) { if (!bufferManager_ || !ptr_) { throw CMMError("Invalid buffer manager or pointer"); } Metadata md; bufferManager_->ExtractMetadata(ptr_, md); - CMMCore::parseImageMetadata(md, width, height, byteDepth, nComponents); + return CMMCore::parseImageMetadata(md, width, height, byteDepth, nComponents); } // Fills the provided Metadata object with metadata extracted from the pointer. diff --git a/MMCoreJ_wrap/MMCoreJ.i b/MMCoreJ_wrap/MMCoreJ.i index 2c4426063..fdaabde4c 100644 --- a/MMCoreJ_wrap/MMCoreJ.i +++ b/MMCoreJ_wrap/MMCoreJ.i @@ -550,8 +550,19 @@ return $result; } int width, height, bytesPerPixel, numComponents; - (arg1)->getImageProperties(width, height, bytesPerPixel, numComponents); - unsigned numPixels = width * height; + bool propertiesOK = (arg1)->getImageProperties(width, height, bytesPerPixel, numComponents); + + unsigned numPixels; + // If getImageProperties fails, its not image data. Assume 1 byte per pixel + // If more data types are supported in the future, could add other + // checks here to return other data types. + if (!propertiesOK) { + bytesPerPixel = 1; + numComponents = 1; + numPixels = numBytes; + } else { + numPixels = width * height; + } if (bytesPerPixel == 1) { @@ -958,7 +969,7 @@ #include "../MMDevice/ImageMetadata.h" #include "../MMCore/MMEventCallback.h" #include "../MMCore/MMCore.h" -#include "../MMCore/NewBufferDataPointer.h" +#include "../MMCore/NewDataBufferPointer.h" %} From 8a798e21b70ae78bfcd2e9497762b65ff639da53 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:09:55 -0800 Subject: [PATCH 40/46] comment out acquirewriteslot mechanism for now --- MMCore/CoreCallback.cpp | 89 +++++++++++++++++++++-------------------- MMCore/CoreCallback.h | 82 +++++++++++++++++++------------------ 2 files changed, 87 insertions(+), 84 deletions(-) diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 7862289a5..31f4f8150 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -287,50 +287,51 @@ int CoreCallback::InsertImage(const MM::Device* caller, const ImgBuffer & imgBuf imgBuf.Height(), imgBuf.Depth(), &md); } -// This method is explicitly for camera devices. It requires width, height, byteDepth, and nComponents -// to be passed in, so that higher level code retrieving data from the buffer knows how to interpret the data -// Generic data insertion is handled by AcquireDataWriteSlot -int CoreCallback::AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSize, size_t metadataSize, - unsigned char** dataPointer, unsigned char** metadataPointer, - unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents) -{ - if (core_->deviceManager_->GetDevice(caller)->GetType() == MM::CameraDevice) - { - - Metadata md; - std::shared_ptr camera = std::dynamic_pointer_cast(core_->deviceManager_->GetDevice(caller)); - // Add the metadata needed for interpreting camera images - core_->addCameraMetadata(camera, md, width, height, byteDepth, nComponents); - - char label[MM::MaxStrLength]; - caller->GetLabel(label); - return core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, - (void**)dataPointer, (void**)metadataPointer, &md); - } - // This is only for camera devices, other devices should call AcquireDataWriteSlot - return DEVICE_ERR; -} - -// This method is for generic data insertion. It will not add any metadata for interpretting the data -int CoreCallback::AcquireDataWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, - unsigned char** dataPointer, unsigned char** metadataPointer) -{ - char label[MM::MaxStrLength]; - caller->GetLabel(label); - Metadata md; - return core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, - (void**)dataPointer, (void**)metadataPointer, &md); -} - -int CoreCallback::FinalizeWriteSlot(unsigned char* dataPointer, - size_t actualMetadataBytes) -{ - if (core_->bufferManager_->FinalizeWriteSlot(dataPointer, actualMetadataBytes)) - { - return DEVICE_OK; - } - return DEVICE_ERR; -} +// TODO: enable these once we know what to do about backwards compatibility with circular buffer +// // This method is explicitly for camera devices. It requires width, height, byteDepth, and nComponents +// // to be passed in, so that higher level code retrieving data from the buffer knows how to interpret the data +// // Generic data insertion is handled by AcquireDataWriteSlot +// int CoreCallback::AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSize, size_t metadataSize, +// unsigned char** dataPointer, unsigned char** metadataPointer, +// unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents) +// { +// if (core_->deviceManager_->GetDevice(caller)->GetType() == MM::CameraDevice) +// { + +// Metadata md; +// std::shared_ptr camera = std::dynamic_pointer_cast(core_->deviceManager_->GetDevice(caller)); +// // Add the metadata needed for interpreting camera images +// core_->addCameraMetadata(camera, md, width, height, byteDepth, nComponents); + +// char label[MM::MaxStrLength]; +// caller->GetLabel(label); +// return core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, +// (void**)dataPointer, (void**)metadataPointer, &md); +// } +// // This is only for camera devices, other devices should call AcquireDataWriteSlot +// return DEVICE_ERR; +// } + +// // This method is for generic data insertion. It will not add any metadata for interpretting the data +// int CoreCallback::AcquireDataWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, +// unsigned char** dataPointer, unsigned char** metadataPointer) +// { +// char label[MM::MaxStrLength]; +// caller->GetLabel(label); +// Metadata md; +// return core_->bufferManager_->AcquireWriteSlot(label, dataSize, metadataSize, +// (void**)dataPointer, (void**)metadataPointer, &md); +// } + +// int CoreCallback::FinalizeWriteSlot(unsigned char* dataPointer, +// size_t actualMetadataBytes) +// { +// if (core_->bufferManager_->FinalizeWriteSlot(dataPointer, actualMetadataBytes)) +// { +// return DEVICE_OK; +// } +// return DEVICE_ERR; +// } bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth) diff --git a/MMCore/CoreCallback.h b/MMCore/CoreCallback.h index 5d8d4316a..6cb79f5bd 100644 --- a/MMCore/CoreCallback.h +++ b/MMCore/CoreCallback.h @@ -92,47 +92,49 @@ class CoreCallback : public MM::Core /*Deprecated*/ int InsertMultiChannel(const MM::Device* caller, const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd = 0); - /** - * Direct writing into V2 buffer instead of using InsertImage (which has to copy the data again). - * Version with essential parameters for camera devices. - * @param caller Camera device making the call - * @param dataSize Size of the image data in bytes - * @param metadataSize Size of metadata in bytes - * @param dataPointer Pointer that will be set to allocated image buffer. The caller should write - * data to this buffer. - * @param metadataPointer Pointer that will be set to allocated metadata buffer. The caller should write - * serialized metadata to this buffer. - * @param width Width of the image in pixels - * @param height Height of the image in pixels - * @param byteDepth Number of bytes per pixel - * @param nComponents Number of components per pixel - * @return DEVICE_OK on success - */ - int AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSize, size_t metadataSize, - unsigned char** dataPointer, unsigned char** metadataPointer, - unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents); - /** - * Generic version for inserting data into the buffer. - * @param caller Device making the call - * @param dataSize Size of the data in bytes - * @param metadataSize Size of metadata in bytes - * @param dataPointer Pointer that will be set to allocated data buffer. The caller should write - * data to this buffer. - * @param metadataPointer Pointer that will be set to allocated metadata buffer. The caller should write - * serialized metadata to this buffer. - * @return DEVICE_OK on success - */ - int AcquireDataWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, - unsigned char** dataPointer, unsigned char** metadataPointer); - - /** - * Finalizes a write slot after data has been written. - * @param dataPointer Pointer to the data buffer that was written to - * @param actualMetadataBytes Actual number of metadata bytes written - * @return DEVICE_OK on success - */ - int FinalizeWriteSlot(unsigned char* dataPointer, size_t actualMetadataBytes); +// TODO: enable these when we know what to do about backwards compatibility for circular buffer +// /** +// * Direct writing into V2 buffer instead of using InsertImage (which has to copy the data again). +// * Version with essential parameters for camera devices. +// * @param caller Camera device making the call +// * @param dataSize Size of the image data in bytes +// * @param metadataSize Size of metadata in bytes +// * @param dataPointer Pointer that will be set to allocated image buffer. The caller should write +// * data to this buffer. +// * @param metadataPointer Pointer that will be set to allocated metadata buffer. The caller should write +// * serialized metadata to this buffer. +// * @param width Width of the image in pixels +// * @param height Height of the image in pixels +// * @param byteDepth Number of bytes per pixel +// * @param nComponents Number of components per pixel +// * @return DEVICE_OK on success +// */ +// int AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSize, size_t metadataSize, +// unsigned char** dataPointer, unsigned char** metadataPointer, +// unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents); + +// /** +// * Generic version for inserting data into the buffer. +// * @param caller Device making the call +// * @param dataSize Size of the data in bytes +// * @param metadataSize Size of metadata in bytes +// * @param dataPointer Pointer that will be set to allocated data buffer. The caller should write +// * data to this buffer. +// * @param metadataPointer Pointer that will be set to allocated metadata buffer. The caller should write +// * serialized metadata to this buffer. +// * @return DEVICE_OK on success +// */ +// int AcquireDataWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, +// unsigned char** dataPointer, unsigned char** metadataPointer); + +// /** +// * Finalizes a write slot after data has been written. +// * @param dataPointer Pointer to the data buffer that was written to +// * @param actualMetadataBytes Actual number of metadata bytes written +// * @return DEVICE_OK on success +// */ +// int FinalizeWriteSlot(unsigned char* dataPointer, size_t actualMetadataBytes); // @deprecated This method was previously used by many camera adapters to enforce an overwrite mode on the circular buffer // -- making it wrap around when running a continuous sequence acquisition. Now we've added an option to do this into the From bc3ea3b47f3702e5a4ebec7db2b8fa85e37665f4 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:12:24 -0800 Subject: [PATCH 41/46] add commented out functions to MMCore interface --- MMDevice/MMDevice.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index c674ac0b7..58de2c44f 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -1394,6 +1394,17 @@ namespace MM { /// \deprecated Use the other forms instead. virtual int InsertMultiChannel(const Device* caller, const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* md = 0) = 0; + // TODO: enable these when we know what to do about backwards compatibility for circular buffer +// int AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSize, size_t metadataSize, +// unsigned char** dataPointer, unsigned char** metadataPointer, +// unsigned width, unsigned height, unsigned byteDepth, unsigned nComponents); + +// int AcquireDataWriteSlot(const MM::Device* caller, size_t dataSize, size_t metadataSize, +// unsigned char** dataPointer, unsigned char** metadataPointer); + +// int FinalizeWriteSlot(unsigned char* dataPointer, size_t actualMetadataBytes); + + // Formerly intended for use by autofocus MM_DEPRECATED(virtual const char* GetImage()) = 0; MM_DEPRECATED(virtual int GetImageDimensions(int& width, int& height, int& depth)) = 0; From f5dec94887a03dce20ed4e19f8b0265ad1180ccf Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 4 Mar 2025 08:57:34 -0800 Subject: [PATCH 42/46] change to metadata bitmask --- MMCore/MMCore.cpp | 87 ++++++++++++++++++------------------ MMCore/MMCore.h | 12 ++++- MMDevice/MMDeviceConstants.h | 15 ++++--- 3 files changed, 63 insertions(+), 51 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 6d95e89a1..fde6028df 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -2697,52 +2697,53 @@ bool CMMCore::getShutterOpen() throw (CMMError) } /** - * Set whether to include a specific category of metadata in the image metadata. + * Set which metadata categories to include in the image metadata using a bitmask. * - * Valid categories are: - * - "BitDepth" - * - "CameraParams" - * - "CameraTags" - * - "Timing" - * - "SystemStateCache" - * - "LegacyCalibration" - * - "Legacy" + * Bitmask values: + * - MM::MetadataBitDepth (1): Include bit depth information + * - MM::MetadataCameraParams (2): Include camera parameters + * - MM::MetadataCameraTags (4): Include camera tags + * - MM::MetadataTiming (8): Include timing information + * - MM::MetadataSystemStateCache (16): Include system state cache + * - MM::MetadataLegacyCalibration (32): Include legacy calibration + * - MM::MetadataLegacy (64): Include additional legacy metadata * - * @param category The category of metadata to include. - * @param include Whether to include the metadata. + * @param categoryBits Bitmask of metadata categories to include */ -void CMMCore::setIncludeImageMetadata(std::string & category, bool include) throw (CMMError) { - if (category == MM::g_Keyword_Include_Metadata_BitDepth) { - imageMDIncludeBitDepth_ = include; - } - else if (category == MM::g_Keyword_Include_Metadata_CameraParams) { - imageMDIncludeCameraParams_ = include; - } - else if (category == MM::g_Keyword_Include_Metadata_CameraTags) { - imageMDIncludeCameraTags_ = include; - } - else if (category == MM::g_Keyword_Include_Metadata_Timing) { - imageMDIncludeTiming_ = include; - } - else if (category == MM::g_Keyword_Include_Metadata_SystemStateCache) { - imageMDIncludeSystemStateCache_ = include; - } - else if (category == MM::g_Keyword_Include_Metadata_LegacyCalibration) { - imageMDIncludeLegacyCalibration_ = include; - } - else if (category == MM::g_Keyword_Include_Metadata_Legacy) { - imageMDIncludeAdditionalLegacy_ = include; - } - else { - throw CMMError("Invalid metadata category. Valid options are: " + - std::string(MM::g_Keyword_Include_Metadata_BitDepth) + ", " + - std::string(MM::g_Keyword_Include_Metadata_CameraParams) + ", " + - std::string(MM::g_Keyword_Include_Metadata_CameraTags) + ", " + - std::string(MM::g_Keyword_Include_Metadata_Timing) + ", " + - std::string(MM::g_Keyword_Include_Metadata_SystemStateCache) + ", " + - std::string(MM::g_Keyword_Include_Metadata_LegacyCalibration) + ", " + - std::string(MM::g_Keyword_Include_Metadata_Legacy)); - } +void CMMCore::setIncludeImageMetadata(unsigned int categoryBits) throw (CMMError) { + imageMDIncludeBitDepth_ = (categoryBits & MM::g_Image_Metadata_Bitmask_BitDepth) != 0; + imageMDIncludeCameraParams_ = (categoryBits & MM::g_Image_Metadata_Bitmask_CameraParams) != 0; + imageMDIncludeCameraTags_ = (categoryBits & MM::g_Image_Metadata_Bitmask_CameraTags) != 0; + imageMDIncludeTiming_ = (categoryBits & MM::g_Image_Metadata_Bitmask_Timing) != 0; + imageMDIncludeSystemStateCache_ = (categoryBits & MM::g_Image_Metadata_Bitmask_SystemStateCache) != 0; + imageMDIncludeLegacyCalibration_ = (categoryBits & MM::g_Image_Metadata_Bitmask_LegacyCalibration) != 0; + imageMDIncludeAdditionalLegacy_ = (categoryBits & MM::g_Image_Metadata_Bitmask_Legacy) != 0; +} + +/** + * Get the current metadata inclusion bitmask. + * + * @return Bitmask indicating which metadata categories are included + */ +unsigned int CMMCore::getIncludeImageMetadata() const throw (CMMError) { + unsigned int result = 0; + + if (imageMDIncludeBitDepth_) + result |= MM::g_Image_Metadata_Bitmask_BitDepth; + if (imageMDIncludeCameraParams_) + result |= MM::g_Image_Metadata_Bitmask_CameraParams; + if (imageMDIncludeCameraTags_) + result |= MM::g_Image_Metadata_Bitmask_CameraTags; + if (imageMDIncludeTiming_) + result |= MM::g_Image_Metadata_Bitmask_Timing; + if (imageMDIncludeSystemStateCache_) + result |= MM::g_Image_Metadata_Bitmask_SystemStateCache; + if (imageMDIncludeLegacyCalibration_) + result |= MM::g_Image_Metadata_Bitmask_LegacyCalibration; + if (imageMDIncludeAdditionalLegacy_) + result |= MM::g_Image_Metadata_Bitmask_Legacy; + + return result; } /** diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 530faeb11..33839fbb0 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -701,7 +701,17 @@ class CMMCore imageMDIncludeSystemStateCache_ = state; } - void setIncludeImageMetadata(std::string & category, bool include) throw (CMMError); + /** + * Sets which metadata categories should be included with images. + * @param categoryBits Bitmask of metadata categories to include + */ + void setIncludeImageMetadata(unsigned int categoryBits) throw (CMMError); + + /** + * Gets the current metadata inclusion bitmask. + * @return Bitmask indicating which metadata categories are included + */ + unsigned int getIncludeImageMetadata() const throw (CMMError); ///@} static bool parseImageMetadata(Metadata& md, int& width, int& height, int& byteDepth, int& nComponents); diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 54f2c6219..75385eb09 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -176,13 +176,14 @@ namespace MM { const char* const g_Keyword_Metadata_TimeInCore = "TimeReceivedByCore"; // Image metadata categories - const char* const g_Keyword_Include_Metadata_BitDepth = "BitDepth"; - const char* const g_Keyword_Include_Metadata_CameraParams = "CameraParams"; - const char* const g_Keyword_Include_Metadata_CameraTags = "CameraTags"; - const char* const g_Keyword_Include_Metadata_Timing = "Timing"; - const char* const g_Keyword_Include_Metadata_SystemStateCache = "SystemStateCache"; - const char* const g_Keyword_Include_Metadata_LegacyCalibration = "LegacyCalibration"; - const char* const g_Keyword_Include_Metadata_Legacy = "Legacy"; + // Metadata category bitmasks + const unsigned int g_Image_Metadata_Bitmask_BitDepth = 1; + const unsigned int g_Image_Metadata_Bitmask_CameraParams = 2; + const unsigned int g_Image_Metadata_Bitmask_CameraTags = 4; + const unsigned int g_Image_Metadata_Bitmask_Timing = 8; + const unsigned int g_Image_Metadata_Bitmask_SystemStateCache = 16; + const unsigned int g_Image_Metadata_Bitmask_LegacyCalibration = 32; + const unsigned int g_Image_Metadata_Bitmask_Legacy = 64; // configuration file format constants const char* const g_FieldDelimiters = ","; From b2a3b69508d8948a8a9dfc5410bad55da94821b6 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:41:35 -0800 Subject: [PATCH 43/46] clarified API for force reseting vs clearing and added safety checks --- MMCore/BufferManager.cpp | 35 ++++++++--- MMCore/BufferManager.h | 27 ++++++--- MMCore/CircularBuffer.h | 1 + MMCore/CoreCallback.cpp | 2 +- MMCore/MMCore.cpp | 123 +++++++++++++++++++++++++-------------- MMCore/MMCore.h | 4 +- MMCore/NewDataBuffer.cpp | 80 +++++++++++++++---------- MMCore/NewDataBuffer.h | 62 +++++++++++--------- 8 files changed, 214 insertions(+), 120 deletions(-) diff --git a/MMCore/BufferManager.cpp b/MMCore/BufferManager.cpp index 8e77c63cb..5eb6640d0 100644 --- a/MMCore/BufferManager.cpp +++ b/MMCore/BufferManager.cpp @@ -50,6 +50,32 @@ BufferManager::~BufferManager() } } +void BufferManager::Clear() { + if (useNewDataBuffer_.load()) { + newDataBuffer_->Clear(); + } else { + circBuffer_->Clear(); + } +} + +void BufferManager::ForceReset() { + if (useNewDataBuffer_.load()) { + // This is dangerous with the NewDataBuffer because there may be pointers into the buffer's memory + newDataBuffer_->ReinitializeBuffer(GetMemorySizeMB(), true); + } else { + // This is not dangerous with the circular buffer because it does not give out pointers to its memory + circBuffer_->Initialize(circBuffer_->NumChannels(), circBuffer_->Width(), + circBuffer_->Height(), circBuffer_->Depth()); + } +} + +bool BufferManager::InitializeCircularBuffer(unsigned int numChannels, unsigned int width, unsigned int height, unsigned int depth) { + if (!useNewDataBuffer_.load()) { + return circBuffer_->Initialize(numChannels, width, height, depth); + } + return false; +} + void BufferManager::ReallocateBuffer(unsigned int memorySizeMB) { if (useNewDataBuffer_.load()) { int numOutstanding = newDataBuffer_->NumOutstandingSlots(); @@ -374,12 +400,3 @@ bool BufferManager::GetOverwriteData() const { return circBuffer_->GetOverwriteData(); } } - -void BufferManager::Reset() { - if (useNewDataBuffer_.load()) { - newDataBuffer_->Reset(); - } else { - circBuffer_->Clear(); - } -} - diff --git a/MMCore/BufferManager.h b/MMCore/BufferManager.h index 55ad8e26c..12908da1f 100644 --- a/MMCore/BufferManager.h +++ b/MMCore/BufferManager.h @@ -160,8 +160,6 @@ class BufferManager { */ bool Overflow() const; - - const void* GetNthDataMD(unsigned long n, Metadata& md) const; // Channels are not directly supported in NewDataBuffer, these are for backwards compatibility @@ -273,17 +271,30 @@ class BufferManager { bool GetOverwriteData() const; /** - * Get the underlying CircularBuffer pointer. - * This method is provided for backwards compatibility only. - * @return Pointer to CircularBuffer if using legacy buffer, nullptr if using NewDataBuffer - * @deprecated This method exposes implementation details and should be avoided in new code + * Clear the buffer, discarding all data. */ - CircularBuffer* GetCircularBuffer() { return circBuffer_; } + void Clear(); /** * Reset the buffer, discarding all data that is not currently held externally. + * When using the NewDataBuffer, this is a dangerous operation because there may be pointers + * into the buffer's memory that will not longer be valid. It can be used to reset the buffer + * without having to restart the application, but its use likely indicates a bug in the + * application or device adapter that is not properly releasing the buffer's memory. + */ + void ForceReset(); + + /** + * Initialize the circular buffer. This has no effect if NewDataBuffer is enabled. This method + * is for backwards compatibility with the circular buffer, which requires knowledge of the + * dimensions of the image to be captured. + * @param numChannels Number of channels in the image. + * @param width Image width. + * @param height Image height. + * @param depth Bytes per pixel. + * @return true if the buffer was initialized, false otherwise. */ - void Reset(); + MM_DEPRECATED(bool InitializeCircularBuffer(unsigned int numChannels, unsigned int width, unsigned int height, unsigned int depth)); private: diff --git a/MMCore/CircularBuffer.h b/MMCore/CircularBuffer.h index 6c952f6d2..9d077e55a 100644 --- a/MMCore/CircularBuffer.h +++ b/MMCore/CircularBuffer.h @@ -67,6 +67,7 @@ class CircularBuffer unsigned int Width() const {MMThreadGuard guard(g_bufferLock); return width_;} unsigned int Height() const {MMThreadGuard guard(g_bufferLock); return height_;} unsigned int Depth() const {MMThreadGuard guard(g_bufferLock); return pixDepth_;} + unsigned int NumChannels() const {MMThreadGuard guard(g_bufferLock); return numChannels_;} bool InsertMultiChannel(const unsigned char* pixArray, unsigned int numChannels, unsigned int width, unsigned int height, unsigned int byteDepth, const Metadata* pMd) throw (CMMError); const unsigned char* GetTopImage() const; diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 31f4f8150..6bd72fa54 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -346,7 +346,7 @@ bool CoreCallback::InitializeImageBuffer(unsigned channels, unsigned slices, // to the v2 buffer, because devices do not have the authority to clear the buffer, // when application code may hold pointers into the buffer. if (!core_->bufferManager_->IsUsingNewDataBuffer()) { - return core_->bufferManager_->GetCircularBuffer()->Initialize(channels, w, h, pixDepth); + return core_->bufferManager_->InitializeCircularBuffer(channels, w, h, pixDepth); } return true; } diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index fde6028df..7f50e525b 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -3226,19 +3226,24 @@ void CMMCore::startSequenceAcquisition(long numImages, double intervalMs, bool s try { if (!bufferManager_->IsUsingNewDataBuffer()) { - if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) - { - logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); - } - } - if (!bufferManager_->IsUsingNewDataBuffer()) { - // NewDataBuffer does not support this, because its design is such that data - // could still be read out even when a new sequence is started. - bufferManager_->GetCircularBuffer()->Clear(); + // Circular buffer is initialized in case of a change in camera settings + try { + bufferManager_->InitializeCircularBuffer(camera->GetNumberOfChannels(), camera->GetImageWidth(), + camera->GetImageHeight(), camera->GetImageBytesPerPixel()); + } catch (...) { + logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } } + // NewDataBuffer does not need to be initialized or cleared, becuase it does not make + // assumptions about image size and it supports asynchronous sequences from different + // cameras. + // Disable overwriting for finite sequence acquisition - bufferManager_->SetOverwriteData(false); + int ret = bufferManager_->SetOverwriteData(false); + if (ret != DEVICE_OK) { + throw CMMError("Failed to switch to non-overwriting mode in DataBuffer"); + } mm::DeviceModuleLockGuard guard(camera); startTime_ = std::chrono::steady_clock::now(); @@ -3279,16 +3284,25 @@ void CMMCore::startSequenceAcquisition(const char* label, long numImages, double throw CMMError(getCoreErrorText(MMERR_NotAllowedDuringSequenceAcquisition).c_str(), MMERR_NotAllowedDuringSequenceAcquisition); - if (!bufferManager_->IsUsingNewDataBuffer()) { - if (!bufferManager_->GetCircularBuffer()->Initialize(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), pCam->GetImageHeight(), pCam->GetImageBytesPerPixel())) - { - logError(getDeviceName(pCam).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + if (!bufferManager_->IsUsingNewDataBuffer()) { + // Circular buffer is initialized in case of a change in camera settings + try { + bufferManager_->InitializeCircularBuffer(pCam->GetNumberOfChannels(), pCam->GetImageWidth(), + pCam->GetImageHeight(), pCam->GetImageBytesPerPixel()); + } catch (...) { + logError(getDeviceName(pCam).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } } - bufferManager_->GetCircularBuffer()->Clear(); - } + // NewDataBuffer does not need to be initialized or cleared, becuase it does not make + // assumptions about image size and it supports asynchronous sequences from different + // cameras. + // Disable overwriting for finite sequence acquisition - bufferManager_->SetOverwriteData(false); + int ret = bufferManager_->SetOverwriteData(false); + if (ret != DEVICE_OK) { + throw CMMError("Failed to switch to non-overwriting mode in DataBuffer"); + } startTime_ = std::chrono::steady_clock::now(); imageNumbers_.clear(); @@ -3337,12 +3351,13 @@ void CMMCore::initializeCircularBuffer() throw (CMMError) if (camera) { mm::DeviceModuleLockGuard guard(camera); - if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) - { + try { + bufferManager_->InitializeCircularBuffer(camera->GetNumberOfChannels(), camera->GetImageWidth(), + camera->GetImageHeight(), camera->GetImageBytesPerPixel()); + } catch (...) { logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } - bufferManager_->GetCircularBuffer()->Clear(); } else { @@ -3398,18 +3413,25 @@ void CMMCore::startContinuousSequenceAcquisition(const char* cameraLabel, double ,MMERR_NotAllowedDuringSequenceAcquisition); } - // Legacy calls for circular buffer if (!bufferManager_->IsUsingNewDataBuffer()) { - if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) - { - logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); - throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); - } - bufferManager_->GetCircularBuffer()->Clear(); + // Circular buffer is initialized in case of a change in camera settings + try { + bufferManager_->InitializeCircularBuffer(camera->GetNumberOfChannels(), camera->GetImageWidth(), + camera->GetImageHeight(), camera->GetImageBytesPerPixel()); + } catch (...) { + logError(getDeviceName(camera).c_str(), getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str()); + throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); + } } + // NewDataBuffer does not need to be initialized or cleared, becuase it does not make + // assumptions about image size and it supports asynchronous sequences from different + // cameras. // Enable overwriting for continuous sequence acquisition - bufferManager_->SetOverwriteData(true); + int ret = bufferManager_->SetOverwriteData(true); + if (ret != DEVICE_OK) { + throw CMMError("Failed to switch to overwriting mode in DataBuffer"); + } startTime_ = std::chrono::steady_clock::now(); imageNumbers_.clear(); LOG_DEBUG(coreLogger_) << "Will start continuous sequence acquisition from current camera"; @@ -3662,21 +3684,38 @@ void CMMCore::clearCircularBuffer() throw (CMMError) if (!bufferManager_->IsUsingNewDataBuffer()) { clearBuffer(); } - // No effect on NewDataBuffer because Reset should be used more carefully + // No effect on NewDataBuffer, because it supports asynchronous sequences from different + // cameras and should be cleared more carefully using the clearBuffer() method. } /** * This method applies to both the circular buffer and the NewDataBuffer. * A difference between the circular buffer and NewDataBuffer is that the NewDataBuffer - * does require to be empty of images before a new sequence is started. In other - * words, producers can be adding data to it that is asynchronously consumed. + * is not required to be empty of images before a new sequence is started. In other + * words, producers can be adding data to it that is asynchronously consumed. This + * means that this method should not be substituted for clearCircularBuffer() in + * acquisition code. * - * Thus, reset should be used carefully, because it will discard data that a consumer - * may sill asynchronously be waiting to consume */ void CMMCore::clearBuffer() throw (CMMError) { - bufferManager_->Reset(); + try { + bufferManager_->Clear(); + } catch (...) { + throw CMMError("Failed to clear buffer"); + } +} + +/** + * For the circular buffer, this does a non-dangerous re-initialization + * for NewDataBuffer, this is a dangerous operation because there may be pointers into the buffer's memory + * that are not valid anymore. It can be used to reset the buffer without having to restart the + * application, but the need to use it indicates a bug in the application or device adapter that + * is not properly releasing the buffer's memory. + */ +void CMMCore::forceBufferReset() throw (CMMError) +{ + bufferManager_->ForceReset(); } /** @@ -3691,9 +3730,7 @@ void CMMCore::enableNewDataBuffer(bool enable) throw (CMMError) // Default include circular buffer, exclude new buffer imageMDIncludeLegacyCalibration_ = !enable; imageMDIncludeSystemStateCache_ = !enable; - imageMDIncludeCameraTags_ = !enable; - - + imageMDIncludeCameraTags_ = !enable; } /** @@ -3736,7 +3773,7 @@ void CMMCore::setBufferMemoryFootprint(unsigned sizeMB) throw (CMMError) mm::DeviceModuleLockGuard guard(camera); if (!bufferManager_->IsUsingNewDataBuffer()) { // Circular buffer requires initialization specific to the camera - if (!bufferManager_->GetCircularBuffer()->Initialize(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) + if (!bufferManager_->InitializeCircularBuffer(camera->GetNumberOfChannels(), camera->GetImageWidth(), camera->GetImageHeight(), camera->GetImageBytesPerPixel())) throw CMMError(getCoreErrorText(MMERR_CircularBufferFailedToInitialize).c_str(), MMERR_CircularBufferFailedToInitialize); } else { bufferManager_->ReallocateBuffer(sizeMB); @@ -4971,7 +5008,7 @@ void CMMCore::setROI(int x, int y, int xSize, int ySize) throw (CMMError) // There is no way to "fix" popNextImage() to handle this correctly, // so we need to make sure we discard such images. if (! bufferManager_->IsUsingNewDataBuffer()) { - bufferManager_->GetCircularBuffer()->Clear(); + bufferManager_->Clear(); } } else @@ -5052,7 +5089,7 @@ void CMMCore::setROI(const char* label, int x, int y, int xSize, int ySize) thro // popNextImage() to handle this correctly, so we need to make sure we // discard such images. if (!bufferManager_->IsUsingNewDataBuffer()) { - bufferManager_->GetCircularBuffer()->Clear(); + bufferManager_->Clear(); } } else @@ -5113,7 +5150,7 @@ void CMMCore::clearROI() throw (CMMError) // There is no way to "fix" popNextImage() to handle this correctly, // so we need to make sure we discard such images. if (!bufferManager_->IsUsingNewDataBuffer()) { - bufferManager_->GetCircularBuffer()->Clear(); + bufferManager_->Clear(); } } } diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 33839fbb0..bca279a5b 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -444,13 +444,15 @@ class CMMCore // This is only needed for the circular buffer, because it needs to match the camera settings. Should it be deprecated? void initializeCircularBuffer() throw (CMMError); /** - * @deprecated Use resetBuffer() instead + * @deprecated Use clearBuffer() instead, but see note in ClearBuffer() about how it does not + * need to be called as frequently as clearCircularBuffer() was */ void clearCircularBuffer() throw (CMMError); void setBufferMemoryFootprint(unsigned sizeMB) throw (CMMError); unsigned getBufferMemoryFootprint() const; void clearBuffer() throw (CMMError); + void forceBufferReset() throw (CMMError); ///@} diff --git a/MMCore/NewDataBuffer.cpp b/MMCore/NewDataBuffer.cpp index 90302de35..3a6e64a67 100644 --- a/MMCore/NewDataBuffer.cpp +++ b/MMCore/NewDataBuffer.cpp @@ -106,15 +106,8 @@ DataBuffer::DataBuffer(unsigned int memorySizeMB) overflow_(false), threadPool_(std::make_shared()), tasksMemCopy_(std::make_shared(threadPool_)) -{ - // Pre-allocate slots (one per MB) and store in both slotPool_ and unusedSlots_ - for (unsigned int i = 0; i < memorySizeMB; i++) { - BufferSlot* bs = new BufferSlot(); - slotPool_.push_back(bs); - unusedSlots_.push_back(bs); - } - - ReinitializeBuffer(memorySizeMB); +{ + ReinitializeBuffer(memorySizeMB, false); } DataBuffer::~DataBuffer() { @@ -169,6 +162,7 @@ int DataBuffer::AllocateBuffer(unsigned int memorySizeMB) { overflow_ = false; freeRegions_.clear(); freeRegions_[0] = bufferSize_; + freeRegionCursor_ = 0; return DEVICE_OK; } @@ -186,7 +180,6 @@ int DataBuffer::ReleaseBuffer() { buffer_ = nullptr; return DEVICE_OK; } - // TODO: Handle errors if other parts of the system still hold pointers. return DEVICE_ERR; } @@ -231,6 +224,15 @@ int DataBuffer::InsertData(const void* data, size_t dataSize, const Metadata* pM * @return Error code (0 on success) */ int DataBuffer::SetOverwriteData(bool overwrite) { + if (overwriteWhenFull_ == overwrite) { + return DEVICE_OK; + } + + // You can't change modes when code holds pointers into the buffer + if (GetActiveSlotCount() > 0) { + return DEVICE_ERR; + } + overwriteWhenFull_ = overwrite; return DEVICE_OK; } @@ -243,14 +245,6 @@ bool DataBuffer::GetOverwriteData() const { return overwriteWhenFull_; } -/** - * Reset the buffer, discarding all data that is not currently held externally. - */ -void DataBuffer::Reset() { - // Reuse ReinitializeBuffer with current size - ReinitializeBuffer(GetMemorySizeMB()); -} - /** * Get a pointer to the next available data slot in the buffer for writing. * @@ -557,16 +551,23 @@ bool DataBuffer::Overflow() const { * This method uses the existing slotManagementMutex_ to ensure thread-safety. * * @param memorySizeMB New size (in MB) for the buffer. + * @param forceReset If true, the buffer will be reset even if there are outstanding active slots. + * This is a dangerous operation operation becuase there may be pointers into the buffer's memory + * that are not valid anymore. It can be used to reset the buffer without having to restart the + * application, but it indicates a bug in the application or device adapter that is not properly + * releasing the buffer's memory. * @return DEVICE_OK on success. * @throws std::runtime_error if any slot is still actively being read or written. */ -int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { +int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB, bool forceReset) { std::lock_guard lock(slotManagementMutex_); // Ensure no active readers/writers exist. - for (BufferSlot* slot : activeSlotsVector_) { - if (!slot->IsFree()) { - throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); + if (!forceReset) { + for (BufferSlot* slot : activeSlotsVector_) { + if (!slot->IsFree()) { + throw std::runtime_error("Cannot reinitialize DataBuffer: outstanding active slot detected."); + } } } @@ -577,25 +578,44 @@ int DataBuffer::ReinitializeBuffer(unsigned int memorySizeMB) { nextAllocOffset_ = 0; overflow_ = false; - // Reset the slot pool + // Pre-allocate slots (one per MB) and store in both slotPool_ and unusedSlots_ + slotPool_.clear(); unusedSlots_.clear(); - for (BufferSlot* bs : slotPool_) { + for (unsigned int i = 0; i < memorySizeMB; i++) { + BufferSlot* bs = new BufferSlot(); + slotPool_.push_back(bs); unusedSlots_.push_back(bs); } + // Release and reallocate the buffer if (buffer_ != nullptr) { - #ifdef _WIN32 - VirtualFree(buffer_, 0, MEM_RELEASE); - #else - munmap(buffer_, bufferSize_); - #endif - buffer_ = nullptr; + ReleaseBuffer(); } return AllocateBuffer(memorySizeMB); } +void DataBuffer::Clear() { + if (NumOutstandingSlots() > 0) { + throw std::runtime_error("Cannot clear DataBuffer: outstanding active slot detected."); + } + std::lock_guard lock(slotManagementMutex_); + activeSlotsVector_.clear(); + activeSlotsByStart_.clear(); + currentSlotIndex_ = 0; + nextAllocOffset_ = 0; + // reset the unused slot pool + unusedSlots_.clear(); + for (BufferSlot* bs : slotPool_) { + unusedSlots_.push_back(bs); + } + // Rest freee regions to whole buffer + freeRegions_.clear(); + freeRegions_[0] = bufferSize_; + freeRegionCursor_ = 0; +} + long DataBuffer::GetActiveSlotCount() const { return static_cast(activeSlotsVector_.size()); } diff --git a/MMCore/NewDataBuffer.h b/MMCore/NewDataBuffer.h index d5177fa37..df7601c70 100644 --- a/MMCore/NewDataBuffer.h +++ b/MMCore/NewDataBuffer.h @@ -226,19 +226,6 @@ class DataBuffer { */ ~DataBuffer(); - /** - * Allocates the memory buffer. - * @param memorySizeMB Size in megabytes. - * @return DEVICE_OK on success. - */ - int AllocateBuffer(unsigned int memorySizeMB); - - /** - * Releases the memory buffer. - * @return DEVICE_OK on success. - */ - int ReleaseBuffer(); - /** * Inserts image data along with metadata into the buffer. * @param data Pointer to the raw image data. @@ -365,7 +352,6 @@ class DataBuffer { */ long GetActiveSlotCount() const; - /** * Extracts metadata for a given image data pointer. * Thread-safe method that acquires necessary locks to lookup metadata location. @@ -390,11 +376,6 @@ class DataBuffer { */ bool IsPointerInBuffer(const void* ptr); - /** - * Reset the buffer, discarding all data that is not currently held externally. - */ - void Reset(); - /** * Checks if there are any outstanding slots in the buffer. If so, it * is unsafe to destroy the buffer. @@ -402,7 +383,41 @@ class DataBuffer { */ int NumOutstandingSlots() const; + /** + * Reinitializes the DataBuffer, clearing its structures and allocating a new buffer. + * @param memorySizeMB New buffer size (in MB). + * @param forceReset If true, the buffer will be reset even if there are outstanding active slots. + * This is a dangerous operation operation becuase there may be pointers into the buffer's memory + * that are not valid anymore. It can be used to reset the buffer without having to restart the + * application, but it indicates a bug in the application or device adapter that is not properly + * releasing the buffer's memory. + * @return DEVICE_OK on success. + * @throws std::runtime_error if any slot is still in use. + */ + int ReinitializeBuffer(unsigned int memorySizeMB, bool forceReset); + + /** + * Clears the buffer, discarding all data that does not have outstanding pointers. + */ + void Clear(); + + + private: + + /** + * Allocates the memory buffer. + * @param memorySizeMB Size in megabytes. + * @return DEVICE_OK on success. + */ + int AllocateBuffer(unsigned int memorySizeMB); + + /** + * Releases the memory buffer. + * @return DEVICE_OK on success. + */ + int ReleaseBuffer(); + /** * Internal helper function that finds the slot for a given pointer. * Returns non-const pointer since slots need to be modified for locking. @@ -434,7 +449,6 @@ class DataBuffer { size_t freeRegionCursor_; // Instead of ownership via unique_ptr, store raw pointers - // Note: unusedSlots_ is now a deque of raw pointers. std::deque unusedSlots_; // This container holds the ownership; they live for the lifetime of the buffer. @@ -466,15 +480,7 @@ class DataBuffer { size_t dataSize, size_t initialMetadataSize, size_t additionalMetadataSize, const std::string& deviceLabel); - /** - * Reinitializes the DataBuffer, clearing its structures and allocating a new buffer. - * @param memorySizeMB New buffer size (in MB). - * @return DEVICE_OK on success. - * @throws std::runtime_error if any slot is still in use. - */ - int ReinitializeBuffer(unsigned int memorySizeMB); - /** * Creates a new slot with the specified parameters. * Caller must hold slotManagementMutex_. From 484ca341ff60b31fc4c37b60dc10065ae3aab211 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:52:17 -0800 Subject: [PATCH 44/46] add method for adding generic dat --- MMCore/CoreCallback.cpp | 21 +++++++++++++++++++++ MMCore/CoreCallback.h | 2 ++ MMDevice/MMDeviceConstants.h | 1 + 3 files changed, 24 insertions(+) diff --git a/MMCore/CoreCallback.cpp b/MMCore/CoreCallback.cpp index 6bd72fa54..77946b31e 100644 --- a/MMCore/CoreCallback.cpp +++ b/MMCore/CoreCallback.cpp @@ -390,6 +390,27 @@ int CoreCallback:: InsertMultiChannel(const MM::Device* caller, const unsigned c } +int CoreCallback::InsertData(const MM::Device* caller, const unsigned char* buf, size_t dataSize, Metadata* pMd) +{ + std::shared_ptr device = core_->deviceManager_->GetDevice(caller); + + if (device->GetType() == MM::CameraDevice) + { + return DEVICE_ERR; + } + + Metadata newMD; + if (pMd) + { + newMD = *pMd; + } + + // Add a metadata tag for the data-producing device + newMD.put(MM::g_Keyword_Metadata_CameraLabel, device->GetLabel()); + + return core_->bufferManager_->InsertData(device->GetLabel().c_str(), buf, dataSize, &newMD); +} + int CoreCallback::AcqFinished(const MM::Device* caller, int /*statusCode*/) { std::shared_ptr camera; diff --git a/MMCore/CoreCallback.h b/MMCore/CoreCallback.h index 6cb79f5bd..87d931f24 100644 --- a/MMCore/CoreCallback.h +++ b/MMCore/CoreCallback.h @@ -92,6 +92,8 @@ class CoreCallback : public MM::Core /*Deprecated*/ int InsertMultiChannel(const MM::Device* caller, const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* pMd = 0); + // Method for inserting generic non-image data into the buffer + int InsertData(const MM::Device* caller, const unsigned char* buf, size_t dataSize, Metadata* pMd = 0); // TODO: enable these when we know what to do about backwards compatibility for circular buffer // /** diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 75385eb09..4f9cdc0a0 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -165,6 +165,7 @@ namespace MM { const char* const g_Keyword_Metadata_Width = "Width"; const char* const g_Keyword_Metadata_Height = "Height"; const char* const g_Keyword_Metadata_CameraLabel = "Camera"; + const char* const g_Keyword_Metadata_DataProducerLabel = "DataProducer"; const char* const g_Keyword_Metadata_BitDepth = "BitDepth"; const char* const g_Keyword_Metadata_Exposure = "Exposure-ms"; MM_DEPRECATED(const char* const g_Keyword_Meatdata_Exposure) = "Exposure-ms"; // Typo From 8cd1e38a7c395dfcb42e72286fc42c1899effada Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:53:19 -0800 Subject: [PATCH 45/46] add autoclosable --- .../main/java/mmcorej/TaggedImagePointer.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java index 9ea927047..28c15d155 100644 --- a/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java +++ b/MMCoreJ_wrap/src/main/java/mmcorej/TaggedImagePointer.java @@ -14,8 +14,11 @@ *

This class extends TaggedImage and manages the lifecycle of image data stored * in native memory. It ensures proper release of resources when the image data is * no longer needed.

+ * + *

This class implements AutoCloseable, allowing it to be used with try-with-resources + * statements for automatic resource management.

*/ -public class TaggedImagePointer extends TaggedImage { +public class TaggedImagePointer extends TaggedImage implements AutoCloseable { public LazyJSONObject tags; @@ -79,5 +82,16 @@ public synchronized void release() { } } + /** + * Closes this resource, relinquishing any underlying resources. + * This method is invoked automatically when used in a try-with-resources statement. + * + *

This implementation calls {@link #release()} to free native memory.

+ */ + @Override + public void close() { + release(); + } + } From e877874171143baa58cef2f186ab357b6927b57a Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:16:16 -0700 Subject: [PATCH 46/46] clarify deprecations --- MMCore/CoreCallback.h | 3 ++- MMDevice/MMDevice.h | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/MMCore/CoreCallback.h b/MMCore/CoreCallback.h index 87d931f24..e2705e79e 100644 --- a/MMCore/CoreCallback.h +++ b/MMCore/CoreCallback.h @@ -143,7 +143,8 @@ class CoreCallback : public MM::Core // circular buffer itself, so this method is no longer required and is not used. higher level code will control when this // option is activated, making it simpler to develop camera adatpers and giving more consistent behavior. void ClearImageBuffer(const MM::Device* /*caller*/) {}; - // @deprecated This method is not required for the V2 buffer and is called by higher level code for circular buffer + // @deprecated This method is not required for the V2 buffer, and in fact should not be called because + // devices do not have the authority to throw away pointers held by other code. bool InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth); int AcqFinished(const MM::Device* caller, int statusCode); diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index 58de2c44f..c724ded5e 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -1389,11 +1389,15 @@ namespace MM { virtual int InsertImage(const Device* caller, const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, const Metadata* md = 0, const bool doProcess = true) = 0; /// \deprecated Use the other forms instead. virtual int InsertImage(const Device* caller, const unsigned char* buf, unsigned width, unsigned height, unsigned byteDepth, const char* serializedMetadata, const bool doProcess = true) = 0; - virtual void ClearImageBuffer(const Device* caller) = 0; - virtual bool InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth) = 0; + + MM_DEPRECATED(virtual void ClearImageBuffer(const Device* caller)) = 0; + MM_DEPRECATED(virtual bool InitializeImageBuffer(unsigned channels, unsigned slices, unsigned int w, unsigned int h, unsigned int pixDepth)) = 0; /// \deprecated Use the other forms instead. virtual int InsertMultiChannel(const Device* caller, const unsigned char* buf, unsigned numChannels, unsigned width, unsigned height, unsigned byteDepth, Metadata* md = 0) = 0; + // Method for inserting generic non-image data into the buffer + virtual int InsertData(const Device* caller, const unsigned char* buf, size_t dataSize, Metadata* pMd = 0) = 0; + // TODO: enable these when we know what to do about backwards compatibility for circular buffer // int AcquireImageWriteSlot(const MM::Camera* caller, size_t dataSize, size_t metadataSize, // unsigned char** dataPointer, unsigned char** metadataPointer,