-
Notifications
You must be signed in to change notification settings - Fork 128
Add getBinning/setBinning methods to MMCore #811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Implements getting and setting camera binning values (1-100) via MM::g_Keyword_Binning property. Auto-handles integer and NxN formats.
There was a problem hiding this 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), andsetBinning(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.
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.
There was a problem hiding this 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.
| // atoi() naturally handles both formats - it parses until it hits non-digit | ||
| // "2" -> 2, "2x2" -> 2, "4x4" -> 4 | ||
| int binning = atoi(binningValue.c_str()); |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
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.
| // 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); | |
| } |
| if (!currentValue.empty() && currentValue.find('x') != std::string::npos) | ||
| { | ||
| useNxNFormat = true; | ||
| } |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
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'.
| 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; | ||
| } |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
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:
- Parsing of integer format ("2")
- Parsing of NxN format ("2x2")
- Setting binning in both formats
- Error handling for cameras without binning support
- Error handling for invalid binning values
- The fallback mechanism when one format fails
| } | ||
| catch (...) | ||
| { | ||
| // If we can't get current value, we'll try integer format first |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
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.
| // 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."; |
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.