Skip to content
Open
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
27e4c8c
initial data buffer commit
henrypinkard Jan 31, 2025
f886b12
Factored out circularBuffer behind new API
henrypinkard Feb 1, 2025
ebbc1bd
finished concurrency for buffer slots
henrypinkard Feb 1, 2025
94125ee
most functionality in v2 buffer implemented
henrypinkard Feb 4, 2025
8efc90d
added metadata to buffer
henrypinkard Feb 5, 2025
0fd0823
fix compiler warnings
henrypinkard Feb 5, 2025
06fe9ae
cleanup
henrypinkard Feb 6, 2025
e468f0f
cleanup
henrypinkard Feb 6, 2025
93860a9
remove comment
henrypinkard Feb 6, 2025
b0aa28e
Merge branch 'data_buffer_new' of https://github.com/henrypinkard/mmC…
henrypinkard Feb 6, 2025
d87cc2e
modify SWIG to read image size dynamically for each image
henrypinkard Feb 7, 2025
c7883ee
fixed bugs, implementation seems to work just like circularbuffer
henrypinkard Feb 11, 2025
c29e114
add parallel copying and memory mapping
henrypinkard Feb 13, 2025
b7a02a0
switch to simpler mutexs
henrypinkard Feb 13, 2025
093fb1d
fix docs and clean up
henrypinkard Feb 13, 2025
f37b964
clean up and remove headers from buffer
henrypinkard Feb 13, 2025
8564b15
restored delted functions, refactor, and recycle buffer slots
henrypinkard Feb 13, 2025
9887ae9
lots of bug fixes and refactoring
henrypinkard Feb 13, 2025
23e1cc4
small perf improvements and bug fixes
henrypinkard Feb 14, 2025
37ab2cb
expose direct getting of pointers from corecallback for writing into …
henrypinkard Feb 14, 2025
8697a85
fix bug
henrypinkard Feb 14, 2025
6aae6b6
fix bug getting image without metadta and standardize internal pointe…
henrypinkard Feb 15, 2025
0f1b645
fix bit depth fn, which is not stored in v2 buffer, and map image poi…
henrypinkard Feb 15, 2025
567cb2b
remove unused typemap
henrypinkard Feb 15, 2025
a5a69b7
add ability to manipulate data pointers inside v2 buffer. Also make s…
henrypinkard Feb 15, 2025
1408c8e
fix bugs with snap. pointer-based taggedimages WIP
henrypinkard Feb 16, 2025
2847d1c
Refactor to Metadata to only maintain essential metadata in buffer
henrypinkard Feb 16, 2025
893642f
refactor and simplify to make safer pointer handling and less SWIG co…
henrypinkard Feb 17, 2025
6b2fd6d
Working bufferdatapointer class and snapimage
henrypinkard Feb 18, 2025
785e995
major refactor to make v2 buffer and buffer manager data type agnosti…
henrypinkard Feb 19, 2025
475e9c3
fix mmcorej compilation bugs
henrypinkard Feb 19, 2025
cd947e3
Many big fixes and got pointer-based image handling working
henrypinkard Feb 22, 2025
ca88de6
remove unused method
henrypinkard Feb 22, 2025
02cb17e
add method for getting raw pointer address
henrypinkard Feb 22, 2025
6a3e8d9
remove CMMError signature from internal buffermanager functions
henrypinkard Feb 28, 2025
3814ea9
remove errant bracket
henrypinkard Feb 28, 2025
bc1a9f1
fine-grained handling metadata categories and correct default behavior
henrypinkard Feb 28, 2025
6cc217e
fix metadata keyword
henrypinkard Feb 28, 2025
b15d252
fix bugs and rename from v2 to newdatabuffer
henrypinkard Feb 28, 2025
a9f20fa
allow retrieval of generic non image data
henrypinkard Feb 28, 2025
8a798e2
comment out acquirewriteslot mechanism for now
henrypinkard Feb 28, 2025
bc3ea3b
add commented out functions to MMCore interface
henrypinkard Feb 28, 2025
f5dec94
change to metadata bitmask
henrypinkard Mar 4, 2025
b2a3b69
clarified API for force reseting vs clearing and added safety checks
henrypinkard Mar 4, 2025
484ca34
add method for adding generic dat
henrypinkard Mar 4, 2025
8cd1e38
add autoclosable
henrypinkard Mar 4, 2025
e877874
clarify deprecations
henrypinkard Mar 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
385 changes: 385 additions & 0 deletions MMCore/BufferManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,385 @@
///////////////////////////////////////////////////////////////////////////////
// FILE: BufferManager.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 "BufferManager.h"
#include <mutex>


BufferManager::BufferManager(bool useNewDataBuffer, unsigned int memorySizeMB)
: useNewDataBuffer_(useNewDataBuffer), circBuffer_(nullptr), newDataBuffer_(nullptr)
{
if (useNewDataBuffer_.load()) {
newDataBuffer_ = new DataBuffer(memorySizeMB);
} else {
circBuffer_ = new CircularBuffer(memorySizeMB);
}
}

BufferManager::~BufferManager()
{
if (useNewDataBuffer_.load()) {
if (newDataBuffer_) {
delete newDataBuffer_;
}
} else {
if (circBuffer_) {
delete circBuffer_;
}
}
}

void BufferManager::ReallocateBuffer(unsigned int memorySizeMB) {
if (useNewDataBuffer_.load()) {
int numOutstanding = newDataBuffer_->NumOutstandingSlots();
if (numOutstanding > 0) {
throw CMMError("Cannot reallocate NewDataBuffer: " + std::to_string(numOutstanding) + " outstanding active slot(s) detected.");
}
delete newDataBuffer_;
newDataBuffer_ = new DataBuffer(memorySizeMB);
} else {
delete circBuffer_;
circBuffer_ = new CircularBuffer(memorySizeMB);
}
}

const void* BufferManager::GetLastData()
{
if (useNewDataBuffer_.load()) {
Metadata dummyMetadata;
// NOTE: ensure calling code releases the slot after use
return newDataBuffer_->PeekDataReadPointerAtIndex(0, dummyMetadata);
} else {
return circBuffer_->GetTopImage();
}
}

const void* BufferManager::PopNextData()
{
if (useNewDataBuffer_.load()) {
Metadata dummyMetadata;
// NOTE: ensure calling code releases the slot after use
return newDataBuffer_->PopNextDataReadPointer(dummyMetadata, false);
} else {
return circBuffer_->PopNextImage();
}
}

long BufferManager::GetRemainingDataCount() const
{
if (useNewDataBuffer_.load()) {
return newDataBuffer_->GetActiveSlotCount();
} else {
return circBuffer_->GetRemainingImageCount();
}
}

unsigned BufferManager::GetMemorySizeMB() const {
if (useNewDataBuffer_.load()) {
return newDataBuffer_->GetMemorySizeMB();
} else {
return circBuffer_->GetMemorySizeMB();
}
}

unsigned BufferManager::GetFreeSizeMB() const {
if (useNewDataBuffer_.load()) {
return (unsigned) newDataBuffer_->GetFreeMemory() / 1024 / 1024;
} else {
return circBuffer_->GetFreeSize() * circBuffer_->GetImageSizeBytes() / 1024 / 1024;
}
}

bool BufferManager::Overflow() const
{
if (useNewDataBuffer_.load()) {
return newDataBuffer_->Overflow();
} else {
return circBuffer_->Overflow();
}
}

/**
* @deprecated Use InsertData() instead
*/
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
*/
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 (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 newDataBuffer_->InsertData(buf, width * height * byteDepth * numChannels, &md, callerLabel);
} else {
return circBuffer_->InsertMultiChannel(buf, numChannels, width, height,
byteDepth, &md) ? DEVICE_OK : DEVICE_BUFFER_OVERFLOW;
}
}

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 (!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 newDataBuffer_->InsertData(buf, dataSize, &md, callerLabel);
}


const void* BufferManager::GetLastDataMD(Metadata& md) const
{
return GetLastDataMD(0, 0, md); // single channel size doesnt matter here
}

const void* BufferManager::GetLastDataMD(unsigned channel, unsigned singleChannelSizeBytes, Metadata& md) const throw (CMMError)
{
if (useNewDataBuffer_.load()) {
const void* basePtr = newDataBuffer_->PeekLastDataReadPointer(md);
if (basePtr == nullptr)
throw CMMError("NewDataBuffer is empty.", MMERR_CircularBufferEmpty);
// Add multiples of the number of bytes to get the channel pointer
basePtr = static_cast<const unsigned char*>(basePtr) + channel * singleChannelSizeBytes;
return basePtr;
} else {
const mm::ImgBuffer* pBuf = circBuffer_->GetTopImageBuffer(channel);
if (pBuf != nullptr) {
md = pBuf->GetMetadata();
return pBuf->GetPixels();
} else {
throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty);
}
}
}

const void* BufferManager::GetNthDataMD(unsigned long n, Metadata& md) const throw (CMMError)
{
if (useNewDataBuffer_.load()) {
// NOTE: make sure calling code releases the slot after use.
return newDataBuffer_->PeekDataReadPointerAtIndex(n, md);
} else {
const mm::ImgBuffer* pBuf = circBuffer_->GetNthFromTopImageBuffer(n);
if (pBuf != nullptr) {
md = pBuf->GetMetadata();
return pBuf->GetPixels();
} else {
throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty);
}
}
}

const void* BufferManager::PopNextDataMD(Metadata& md) throw (CMMError)
{
return PopNextDataMD(0, 0, md);
}

/**
* @deprecated Use PopNextDataMD() without channel parameter instead.
* The NewDataBuffer is data type agnostic
*/
const void* BufferManager::PopNextDataMD(unsigned channel,
unsigned singleChannelSizeBytes, Metadata& md) throw (CMMError)
{
if (useNewDataBuffer_.load()) {
const void* basePtr = newDataBuffer_->PopNextDataReadPointer(md, false);
if (basePtr == nullptr)
throw CMMError("NewDataBuffer is empty.", MMERR_CircularBufferEmpty);

// Add multiples of the number of bytes to get the channel pointer
basePtr = static_cast<const unsigned char*>(basePtr) + channel * singleChannelSizeBytes;

return basePtr;
} else {
const mm::ImgBuffer* pBuf = circBuffer_->GetNextImageBuffer(channel);
if (pBuf != nullptr) {
md = pBuf->GetMetadata();
return pBuf->GetPixels();
} else {
throw CMMError("Circular buffer is empty.", MMERR_CircularBufferEmpty);
}
}
}

int BufferManager::EnableNewDataBuffer(bool enable) {
// Don't do anything if we're already in the requested state
if (enable == useNewDataBuffer_.load()) {
return DEVICE_OK;
}

// Create new buffer of requested type with same memory size
unsigned memorySizeMB = GetMemorySizeMB();

try {
if (enable) {
// Switch to V2 buffer
DataBuffer* newBuffer = new DataBuffer(memorySizeMB);
delete circBuffer_;
circBuffer_ = nullptr;
newDataBuffer_ = newBuffer;
} else {
// Switch to circular buffer
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 newDataBuffer_;
newDataBuffer_ = nullptr;
circBuffer_ = newBuffer;
}


useNewDataBuffer_.store(enable);
return DEVICE_OK;
} catch (const std::exception&) {
// If allocation fails, keep the existing buffer
return DEVICE_ERR;
}
}

bool BufferManager::IsUsingNewDataBuffer() const {
return useNewDataBuffer_.load();
}

int BufferManager::ReleaseReadAccess(const void* ptr) {
if (useNewDataBuffer_.load() && ptr) {
return newDataBuffer_->ReleaseDataReadPointer(ptr);
}
return DEVICE_ERR;
}

unsigned BufferManager::GetDataSize(const void* ptr) const {
if (!useNewDataBuffer_.load())
return circBuffer_->GetImageSizeBytes();
else
return static_cast<long>(newDataBuffer_->GetDatumSize(ptr));
}

int BufferManager::SetOverwriteData(bool overwrite) {
if (useNewDataBuffer_.load()) {
return newDataBuffer_->SetOverwriteData(overwrite);
} else {
return circBuffer_->SetOverwriteData(overwrite);
}
}

int BufferManager::AcquireWriteSlot(const char* deviceLabel, size_t dataSize, size_t additionalMetadataSize,
void** dataPointer, void** additionalMetadataPointer, Metadata* pInitialMetadata) {
if (!useNewDataBuffer_.load()) {
// Not supported for circular buffer
return DEVICE_ERR;
}

// Initialize metadata with either provided metadata or create empty
Metadata md = (pInitialMetadata != nullptr) ? *pInitialMetadata : Metadata();

std::string serializedMetadata = md.Serialize();
int ret = newDataBuffer_->AcquireWriteSlot(dataSize, additionalMetadataSize,
dataPointer, additionalMetadataPointer, serializedMetadata, deviceLabel);
return ret;
}

int BufferManager::FinalizeWriteSlot(const void* imageDataPointer, size_t actualMetadataBytes) {
if (!useNewDataBuffer_.load()) {
// Not supported for circular buffer
return DEVICE_ERR;
}
return newDataBuffer_->FinalizeWriteSlot(imageDataPointer, actualMetadataBytes);
}

void BufferManager::ExtractMetadata(const void* dataPtr, Metadata& md) const {
if (!useNewDataBuffer_.load()) {
throw CMMError("ExtractMetadata is only supported with NewDataBuffer enabled");
}

if (newDataBuffer_ == nullptr) {
throw CMMError("NewDataBuffer is null");
}

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 (!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 (!useNewDataBuffer_.load()) {
throw CMMError("NewDataBuffer must be enabled for device-specific data access");
}

const void* basePtr = newDataBuffer_->PeekLastDataReadPointerFromDevice(deviceLabel, md);
if (basePtr == nullptr) {
throw CMMError("No data found for device: " + deviceLabel, MMERR_InvalidContents);
}
return basePtr;
}

bool BufferManager::IsPointerInNewDataBuffer(const void* ptr) const {
if (!useNewDataBuffer_.load()) {
return false;
}

if (newDataBuffer_ == nullptr) {
return false;
}

return newDataBuffer_->IsPointerInBuffer(ptr);
}

bool BufferManager::GetOverwriteData() const {
if (useNewDataBuffer_.load()) {
return newDataBuffer_->GetOverwriteData();
} else {
return circBuffer_->GetOverwriteData();
}
}

void BufferManager::Reset() {
if (useNewDataBuffer_.load()) {
newDataBuffer_->Reset();
} else {
circBuffer_->Clear();
}
}

Loading