Skip to content

Conversation

@nicost
Copy link
Member

@nicost nicost commented Jan 8, 2026

Implements getting and setting camera binning values (1-100) via
MM::g_Keyword_Binning property. Auto-handles integer and NxN formats.

Addresses parts of #38.

  Implements getting and setting camera binning values (1-100) via
  MM::g_Keyword_Binning property. Auto-handles integer and NxN formats.
@nicost nicost requested review from Copilot and marktsuchida January 8, 2026 21:25
@nicost nicost marked this pull request as ready for review January 8, 2026 21:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds new methods to MMCore for getting and setting camera binning values, providing a more convenient API similar to the existing exposure methods. The implementation handles binning values from 1-100 and automatically adapts to either integer format ("2") or NxN format ("2x2") based on the camera's current property format.

  • Adds four new methods: getBinning(), getBinning(const char* label), setBinning(int binning), and setBinning(const char* label, int binning)
  • Implements automatic format detection and conversion for binning property values
  • Increments the minor version from 11.11.0 to 11.12.0

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
MMCore/MMCore.h Declares four new public methods for getting and setting camera binning
MMCore/MMCore.cpp Implements the binning methods with format detection logic and updates API version to 11.12.0

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

nicost added 2 commits January 8, 2026 14:06
Checks if the property exists and throws an error if not. Handles both 1 and 1x1 formats.  Throws error if property returns empty value, if the range is outside 1-100.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4459 to +4461
// atoi() naturally handles both formats - it parses until it hits non-digit
// "2" -> 2, "2x2" -> 2, "4x4" -> 4
int binning = atoi(binningValue.c_str());
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.

Using atoi() for parsing is unsafe as it returns 0 for invalid input and doesn't provide error detection. For example, if the binning property returns "0", "abc", or other invalid values, atoi() will return 0, which will then fail the range check. However, if it returns something like "0x2", atoi() will parse it as 0, potentially missing a valid "2" value. Consider using std::stoi() with exception handling or checking if the first character is a digit before calling atoi() to ensure robust parsing.

Suggested change
// atoi() naturally handles both formats - it parses until it hits non-digit
// "2" -> 2, "2x2" -> 2, "4x4" -> 4
int binning = atoi(binningValue.c_str());
// Use std::stoi to parse leading digits and detect invalid formats
// "2" -> 2, "2x2" -> 2, "4x4" -> 4
int binning = 0;
try
{
std::size_t parsedChars = 0;
binning = std::stoi(binningValue, &parsedChars, 10);
if (parsedChars == 0)
{
throw CMMError("Invalid binning value (no leading digits): " + binningValue);
}
}
catch (const std::exception&)
{
throw CMMError("Invalid binning value: " + binningValue);
}

Copilot uses AI. Check for mistakes.
Comment on lines +4533 to +4536
if (!currentValue.empty() && currentValue.find('x') != std::string::npos)
{
useNxNFormat = true;
}
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 format detection logic only checks for the presence of 'x' in the current value. This could incorrectly detect formats if the camera returns values like "max", "mixed", or other strings containing 'x'. Consider using a more robust check, such as a regex pattern to match "NxN" format (e.g., checking for digits before and after 'x'), or checking for a pattern like std::isdigit() before 'x' and after 'x'.

Copilot uses AI. Check for mistakes.
Comment on lines +4415 to +4589
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);

// Parse the binning value - handle both integer ("2") and NxN ("2x2") formats
if (binningValue.empty())
{
throw CMMError("Binning property returned empty value");
}

// atoi() naturally handles both formats - it parses until it hits non-digit
// "2" -> 2, "2x2" -> 2, "4x4" -> 4
int 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
if (!currentValue.empty() && currentValue.find('x') != std::string::npos)
{
useNxNFormat = true;
}
}
catch (...)
{
// If we can't get current value, we'll try integer format first
}

// 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;
}
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 new getBinning and setBinning methods lack test coverage. Given that the codebase includes unit tests (e.g., PixelSize-Tests.cpp tests binning-related functionality), these new API methods should have corresponding tests to verify:

  1. Parsing of integer format ("2")
  2. Parsing of NxN format ("2x2")
  3. Setting binning in both formats
  4. Error handling for cameras without binning support
  5. Error handling for invalid binning values
  6. The fallback mechanism when one format fails

Copilot uses AI. Check for mistakes.
}
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant