Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
206 changes: 205 additions & 1 deletion MMCore/MMCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ namespace mmi = mmcore::internal;
* (Keep the 3 numbers on one line to make it easier to look at diffs when
* merging/rebasing.)
*/
const int MMCore_versionMajor = 11, MMCore_versionMinor = 11, MMCore_versionPatch = 0;
const int MMCore_versionMajor = 11, MMCore_versionMinor = 12, MMCore_versionPatch = 0;


///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -4407,6 +4407,210 @@ double CMMCore::getExposure(const char* label) MMCORE_LEGACY_THROW(CMMError)
return 0.0;
}

/**
* Returns the current binning setting of the camera.
* Binning values in "NxN" format (e.g., "2x2") are parsed to return the integer factor (e.g., 2).
* @return the binning factor (1-100)
*/
int CMMCore::getBinning() MMCORE_LEGACY_THROW(CMMError)
{
std::shared_ptr<mmi::CameraInstance> camera = currentCameraDevice_.lock();
if (!camera)
{
throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable);
}
else
{
std::string cameraName;
{
mmi::DeviceModuleLockGuard guard(camera);
cameraName = camera->GetLabel();
}
return getBinning(cameraName.c_str());
}
}

/**
* Returns the current binning setting of the specified camera.
* Binning values in "NxN" format (e.g., "2x2") are parsed to return the integer factor (e.g., 2).
* @param label the camera device label
* @return the binning factor (1-100)
*/
int CMMCore::getBinning(const char* label) MMCORE_LEGACY_THROW(CMMError)
{
std::shared_ptr<mmi::CameraInstance> pCamera =
deviceManager_->GetDeviceOfType<mmi::CameraInstance>(label);

mmi::DeviceModuleLockGuard guard(pCamera);

if (!pCamera->HasProperty(MM::g_Keyword_Binning))
{
throw CMMError("Camera does not support binning property");
}

std::string binningValue = pCamera->GetProperty(MM::g_Keyword_Binning);

if (binningValue.empty())
{
throw CMMError("Binning property returned empty value");
}

// Parse the binning value - handle both integer ("2") and NxN ("2x2") formats
int binning;
size_t xPos = binningValue.find('x');
if (xPos != std::string::npos)
{
// NxN format - parse both parts and verify they match
int binX = atoi(binningValue.c_str());
int binY = atoi(binningValue.c_str() + xPos + 1);
if (binX != binY)
{
throw CMMError("Asymmetric binning not supported: " + binningValue);
}
binning = binX;
}
else
{
// Integer format
binning = atoi(binningValue.c_str());
}

if (binning < 1 || binning > 100)
{
throw CMMError("Binning value out of valid range (1-100): " + binningValue);
}

return binning;
}

/**
* Sets the binning setting of the camera.
* Automatically handles both integer ("2") and "NxN" ("2x2") property formats.
* @param binning the binning factor (1-100)
*/
void CMMCore::setBinning(int binning) MMCORE_LEGACY_THROW(CMMError)
{
if (binning < 1 || binning > 100)
{
throw CMMError("Binning must be between 1 and 100");
}

std::shared_ptr<mmi::CameraInstance> camera = currentCameraDevice_.lock();
if (!camera)
{
throw CMMError(getCoreErrorText(MMERR_CameraNotAvailable).c_str(), MMERR_CameraNotAvailable);
}
else
{
std::string cameraName;
{
mmi::DeviceModuleLockGuard guard(camera);
cameraName = camera->GetLabel();
}
setBinning(cameraName.c_str(), binning);
}
}

/**
* Sets the binning setting of the specified camera.
* Automatically handles both integer ("2") and "NxN" ("2x2") property formats.
* @param label the camera device label
* @param binning the binning factor (1-100)
*/
void CMMCore::setBinning(const char* label, int binning) MMCORE_LEGACY_THROW(CMMError)
{
if (binning < 1 || binning > 100)
{
throw CMMError("Binning must be between 1 and 100");
}

std::shared_ptr<mmi::CameraInstance> pCamera =
deviceManager_->GetDeviceOfType<mmi::CameraInstance>(label);

{
mmi::DeviceModuleLockGuard guard(pCamera);
LOG_DEBUG(coreLogger_) << "Will set camera " << label <<
" binning to " << binning;

if (!pCamera->HasProperty(MM::g_Keyword_Binning))
{
throw CMMError("Camera does not support binning property");
}

// Try to determine the format by checking the current property value
std::string currentValue;
bool useNxNFormat = false;

try
{
currentValue = pCamera->GetProperty(MM::g_Keyword_Binning);
// If current value contains 'x' and looks like NxN format, use that
size_t xPos = currentValue.find('x');
if (!currentValue.empty() && xPos != std::string::npos)
{
// Validate that the current value has symmetric binning
int binX = atoi(currentValue.c_str());
int binY = atoi(currentValue.c_str() + xPos + 1);
if (binX != binY)
{
throw CMMError("Asymmetric binning not supported: " + currentValue);
}
useNxNFormat = true;
}
}
catch (...)
{
// If we can't get current value, we'll try integer format first
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch block catches all exceptions (catch (...)) but provides no logging or indication of what went wrong. This makes debugging difficult if GetProperty fails. Consider either catching specific exceptions and logging them at DEBUG level, or at minimum adding a debug log message indicating that the current value couldn't be retrieved and the default format will be tried.

Suggested change
// If we can't get current value, we'll try integer format first
// If we can't get current value, we'll try integer format first
LOG_DEBUG(coreLogger_) << "Could not retrieve current binning value for camera "
<< label << "; will try integer format first.";

Copilot uses AI. Check for mistakes.
}

// Build the property value string
std::string binningValue;
if (useNxNFormat)
{
// NxN format: "2x2", "4x4", etc.
std::string binStr = CDeviceUtils::ConvertToString(binning);
binningValue = binStr + "x" + binStr;
}
else
{
// Integer format: "2", "4", etc.
binningValue = CDeviceUtils::ConvertToString(binning);
}

// Try to set the property with detected format
try
{
pCamera->SetProperty(MM::g_Keyword_Binning, binningValue);
}
catch (const CMMError&)
{
// If it failed and we haven't tried the other format yet, try it
if (useNxNFormat)
{
// Tried NxN, now try integer
binningValue = CDeviceUtils::ConvertToString(binning);
}
else
{
// Tried integer, now try NxN
std::string binStr = CDeviceUtils::ConvertToString(binning);
binningValue = binStr + "x" + binStr;
}

// Try with alternate format (this will throw if it still fails)
pCamera->SetProperty(MM::g_Keyword_Binning, binningValue);
}

{
MMThreadGuard scg(stateCacheLock_);
stateCache_.addSetting(PropertySetting(label, MM::g_Keyword_Binning, binningValue.c_str()));
}
}

LOG_DEBUG(coreLogger_) << "Did set camera " << label <<
" binning to " << binning;
}

/**
* Set the hardware region of interest for the current camera.
*
Expand Down
5 changes: 5 additions & 0 deletions MMCore/MMCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,11 @@ class CMMCore
double getExposure() MMCORE_LEGACY_THROW(CMMError);
double getExposure(const char* label) MMCORE_LEGACY_THROW(CMMError);

int getBinning() MMCORE_LEGACY_THROW(CMMError);
int getBinning(const char* label) MMCORE_LEGACY_THROW(CMMError);
void setBinning(int binning) MMCORE_LEGACY_THROW(CMMError);
void setBinning(const char* label, int binning) MMCORE_LEGACY_THROW(CMMError);

void snapImage() MMCORE_LEGACY_THROW(CMMError);
void* getImage() MMCORE_LEGACY_THROW(CMMError);
void* getImage(unsigned numChannel) MMCORE_LEGACY_THROW(CMMError);
Expand Down