From 778a438489817527b2096ae56bb8651a672991da Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Fri, 22 May 2026 13:50:55 -0700 Subject: [PATCH 1/3] =?UTF-8?q?EvidentIX85:=20attempt=20untested=20fixes?= =?UTF-8?q?=20for=20autofocus=20limits:=20=20Fix=201=20(FindFocusWithOffse?= =?UTF-8?q?t,=20line=20~4383):=20Added=20clamping=20of=20targetZPos=20to?= =?UTF-8?q?=20=20=20hardware=20limits=20(FOCUS=5FMIN=5FPOS/FOCUS=5FMAX=5FP?= =?UTF-8?q?OS)=20and=20then=20to=20the=20user-set=20AF=20=20=20search=20li?= =?UTF-8?q?mits=20(farLimit=5F/nearLimit=5F)=20before=20the=20final=20focu?= =?UTF-8?q?s=20move.=20This=20=20=20prevents=20the=20post-search=20offset?= =?UTF-8?q?=20move=20from=20driving=20the=20objective=20outside=20the=20?= =?UTF-8?q?=20=20safe=20range=20=E2=80=94=20the=20most=20likely=20cause=20?= =?UTF-8?q?of=20the=20collision.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 2 (Initialize, line ~3586): Removed the block that overwrote nearLimit_/farLimit_ from the hardware's AFL query (which returns the cleared full-range defaults after remote mode exits). Replaced with zdcInitNeeded_ = true, ensuring InitializeZDC() always re-applies the adapter's stored limits (restored from a saved config file) before the first AF operation. Fix 3 (OnNearLimit/OnFarLimit, lines ~4479, ~4517): The direct AFL send is now only attempted when afStatus_ == 0 (idle), respecting the hardware documentation constraint. The member variable is updated unconditionally and zdcInitNeeded_ = true guarantees limits are applied before the next AF run regardless. --- .../EvidentIX85Win/EvidentIX85Win.cpp | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp b/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp index bc973c425..a3dc6f55f 100644 --- a/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp +++ b/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp @@ -3583,18 +3583,12 @@ int EvidentAutofocus::Initialize() } } - // Query initial AF limits - cmd = BuildQuery(CMD_AF_LIMIT); - ret = hub->ExecuteCommand(cmd, response); - if (ret == DEVICE_OK) - { - std::vector params = ParseParameters(response); - if (params.size() >= 2 && params[0] != "X" && params[1] != "X") - { - nearLimit_ = ParseIntParameter(params[0]); - farLimit_ = ParseIntParameter(params[1]); - } - } + // The hardware clears AFL limits when remote mode is exited, so we do not + // overwrite nearLimit_/farLimit_ from the hardware here. Instead, mark + // zdcInitNeeded_ so InitializeZDC() will re-apply the adapter's stored + // limits (which include any values restored from a saved config file) + // before the first AF operation. + zdcInitNeeded_ = true; // Create AF Status property (read-only) CPropertyAction* pAct = new CPropertyAction(this, &EvidentAutofocus::OnAFStatus); @@ -4384,6 +4378,13 @@ int EvidentAutofocus::FindFocusWithOffset() long measuredZOffset = hub->GetModel()->GetMeasuredZOffset(); long targetZPos = currentZPos + measuredZOffset; + // Clamp to hardware limits, then to user-set AF search limits so the + // offset move cannot drive the objective outside the safe search range. + if (targetZPos < FOCUS_MIN_POS) targetZPos = FOCUS_MIN_POS; + if (targetZPos > FOCUS_MAX_POS) targetZPos = FOCUS_MAX_POS; + if (targetZPos < farLimit_) targetZPos = farLimit_; + if (targetZPos > nearLimit_) targetZPos = nearLimit_; + std::ostringstream logMsg; logMsg << "Applying Z-offset: " << measuredZOffset << " steps (from " << currentZPos << @@ -4472,17 +4473,16 @@ int EvidentAutofocus::OnNearLimit(MM::PropertyBase* pProp, MM::ActionType eAct) return ERR_INVALID_PARAMETER; } - std::string cmd = BuildCommand(CMD_AF_LIMIT, static_cast(newNear), static_cast(farLimit_)); - std::string response; - int ret = hub->ExecuteCommand(cmd, response); - if (ret != DEVICE_OK) - return ret; - - if (!IsPositiveAck(response, CMD_AF_LIMIT)) - return ERR_NEGATIVE_ACK; - nearLimit_ = newNear; + // AFL can only be set while AF is idle; if active, defer to InitializeZDC(). + if (afStatus_ == 0) + { + std::string cmd = BuildCommand(CMD_AF_LIMIT, static_cast(nearLimit_), static_cast(farLimit_)); + std::string response; + hub->ExecuteCommand(cmd, response); // best-effort; InitializeZDC() will re-apply + } + // Mark that ZDC needs re-initialization (deferred until next AF operation) zdcInitNeeded_ = true; } @@ -4511,17 +4511,16 @@ int EvidentAutofocus::OnFarLimit(MM::PropertyBase* pProp, MM::ActionType eAct) return ERR_INVALID_PARAMETER; } - std::string cmd = BuildCommand(CMD_AF_LIMIT, static_cast(nearLimit_), static_cast(newFar)); - std::string response; - int ret = hub->ExecuteCommand(cmd, response); - if (ret != DEVICE_OK) - return ret; - - if (!IsPositiveAck(response, CMD_AF_LIMIT)) - return ERR_NEGATIVE_ACK; - farLimit_ = newFar; + // AFL can only be set while AF is idle; if active, defer to InitializeZDC(). + if (afStatus_ == 0) + { + std::string cmd = BuildCommand(CMD_AF_LIMIT, static_cast(nearLimit_), static_cast(farLimit_)); + std::string response; + hub->ExecuteCommand(cmd, response); // best-effort; InitializeZDC() will re-apply + } + // Mark that ZDC needs re-initialization (deferred until next AF operation) zdcInitNeeded_ = true; } From 1874ea299920d5f0339fce48a7cbd05ad39940b9 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Fri, 22 May 2026 13:55:47 -0700 Subject: [PATCH 2/3] EvidentIX85Win: New property: Set-Focus-Near-Limit-um (writable float, on the IX85-Nosepiece device) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BeforeGet: reads back the stored near limit for the currently active objective, so the property always shows the current value - AfterSet: converts the typed µm value to steps, clamps to [FOCUS_MIN_POS, FOCUS_MAX_POS], updates nearLimits_[obj], and sends the NL command to the hardware - Range is enforced via SetPropertyLimits (0 to 10,500 µm) The existing Set-Focus-Near-Limit (Set/Clear) and the read-only Focus-Near-Limit-um properties are unchanged. The new property is per-objective — whatever objective is currently in position receives the new limit when the value is set. --- .../EvidentIX85Win/EvidentIX85Win.cpp | 62 +++++++++++++++++++ .../EvidentIX85Win/EvidentIX85Win.h | 1 + 2 files changed, 63 insertions(+) diff --git a/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp b/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp index a3dc6f55f..a6130fcc3 100644 --- a/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp +++ b/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp @@ -497,6 +497,13 @@ int EvidentNosepiece::Initialize() AddAllowedValue("Set-Focus-Near-Limit", "Set"); AddAllowedValue("Set-Focus-Near-Limit", "Clear"); + // Create writable float property to set the near limit by typing a value + pAct = new CPropertyAction(this, &EvidentNosepiece::OnSetNearLimitValue); + ret = CreateProperty("Set-Focus-Near-Limit-um", "0.0", MM::Float, false, pAct); + if (ret != DEVICE_OK) + return ret; + SetPropertyLimits("Set-Focus-Near-Limit-um", 0.0, FOCUS_MAX_POS * FOCUS_STEP_SIZE_UM); + // Query parfocal settings from microscope ret = QueryParfocalSettings(); if (ret != DEVICE_OK) @@ -939,6 +946,61 @@ int EvidentNosepiece::OnSetNearLimit(MM::PropertyBase* pProp, MM::ActionType eAc return DEVICE_OK; } +int EvidentNosepiece::OnSetNearLimitValue(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + EvidentHubWin* hub = GetHub(); + if (!hub) + return DEVICE_ERR; + + long nosepiecePos = hub->GetModel()->GetPosition(DeviceType_Nosepiece); + if (nosepiecePos >= 1 && nosepiecePos <= (long)nearLimits_.size()) + pProp->Set(nearLimits_[nosepiecePos - 1] * FOCUS_STEP_SIZE_UM); + } + else if (eAct == MM::AfterSet) + { + EvidentHubWin* hub = GetHub(); + if (!hub) + return DEVICE_ERR; + + long nosepiecePos = hub->GetModel()->GetPosition(DeviceType_Nosepiece); + if (nosepiecePos < 1 || nosepiecePos > (long)nearLimits_.size()) + return ERR_INVALID_PARAMETER; + + double val; + pProp->Get(val); + long newLimit = static_cast(val / FOCUS_STEP_SIZE_UM); + if (newLimit < FOCUS_MIN_POS) newLimit = FOCUS_MIN_POS; + if (newLimit > FOCUS_MAX_POS) newLimit = FOCUS_MAX_POS; + + nearLimits_[nosepiecePos - 1] = newLimit; + + std::ostringstream cmd; + cmd << CMD_FOCUS_NEAR_LIMIT << TAG_DELIMITER; + for (size_t i = 0; i < nearLimits_.size(); i++) + { + if (i > 0) + cmd << DATA_DELIMITER; + cmd << nearLimits_[i]; + } + + std::string response; + int ret = hub->ExecuteCommand(cmd.str(), response); + if (ret != DEVICE_OK) + return ret; + + if (!IsPositiveAck(response, CMD_FOCUS_NEAR_LIMIT)) + return ERR_NEGATIVE_ACK; + + std::ostringstream logMsg; + logMsg << "Set near limit for objective " << nosepiecePos + << " to " << (newLimit * FOCUS_STEP_SIZE_UM) << " um"; + LogMessage(logMsg.str().c_str()); + } + return DEVICE_OK; +} + int EvidentNosepiece::OnParfocalPosition(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) diff --git a/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.h b/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.h index 75640054a..9e05a400d 100644 --- a/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.h +++ b/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.h @@ -93,6 +93,7 @@ class EvidentNosepiece : public CStateDeviceBase int OnObjectiveWD(MM::PropertyBase* pProp, MM::ActionType eAct); int OnNearLimit(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSetNearLimit(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnSetNearLimitValue(MM::PropertyBase* pProp, MM::ActionType eAct); int OnParfocalPosition(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSetParfocalPosition(MM::PropertyBase* pProp, MM::ActionType eAct); int OnParfocalEnabled(MM::PropertyBase* pProp, MM::ActionType eAct); From e805b1509ba3dfecb17060d25d1f74ec7e990c58 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Fri, 22 May 2026 14:40:29 -0700 Subject: [PATCH 3/3] EvidentIX85WIn: Address CoPilot comments --- .../EvidentIX85Win/EvidentIX85Win.cpp | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp b/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp index a6130fcc3..dbcce95e6 100644 --- a/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp +++ b/DeviceAdapters/EvidentIX85Win/EvidentIX85Win.cpp @@ -957,6 +957,8 @@ int EvidentNosepiece::OnSetNearLimitValue(MM::PropertyBase* pProp, MM::ActionTyp long nosepiecePos = hub->GetModel()->GetPosition(DeviceType_Nosepiece); if (nosepiecePos >= 1 && nosepiecePos <= (long)nearLimits_.size()) pProp->Set(nearLimits_[nosepiecePos - 1] * FOCUS_STEP_SIZE_UM); + else + pProp->Set(0.0); } else if (eAct == MM::AfterSet) { @@ -974,6 +976,7 @@ int EvidentNosepiece::OnSetNearLimitValue(MM::PropertyBase* pProp, MM::ActionTyp if (newLimit < FOCUS_MIN_POS) newLimit = FOCUS_MIN_POS; if (newLimit > FOCUS_MAX_POS) newLimit = FOCUS_MAX_POS; + long oldLimit = nearLimits_[nosepiecePos - 1]; nearLimits_[nosepiecePos - 1] = newLimit; std::ostringstream cmd; @@ -988,10 +991,20 @@ int EvidentNosepiece::OnSetNearLimitValue(MM::PropertyBase* pProp, MM::ActionTyp std::string response; int ret = hub->ExecuteCommand(cmd.str(), response); if (ret != DEVICE_OK) + { + nearLimits_[nosepiecePos - 1] = oldLimit; + pProp->Set(oldLimit * FOCUS_STEP_SIZE_UM); return ret; + } if (!IsPositiveAck(response, CMD_FOCUS_NEAR_LIMIT)) + { + nearLimits_[nosepiecePos - 1] = oldLimit; + pProp->Set(oldLimit * FOCUS_STEP_SIZE_UM); return ERR_NEGATIVE_ACK; + } + + pProp->Set(newLimit * FOCUS_STEP_SIZE_UM); std::ostringstream logMsg; logMsg << "Set near limit for objective " << nosepiecePos @@ -4440,12 +4453,13 @@ int EvidentAutofocus::FindFocusWithOffset() long measuredZOffset = hub->GetModel()->GetMeasuredZOffset(); long targetZPos = currentZPos + measuredZOffset; - // Clamp to hardware limits, then to user-set AF search limits so the - // offset move cannot drive the objective outside the safe search range. - if (targetZPos < FOCUS_MIN_POS) targetZPos = FOCUS_MIN_POS; - if (targetZPos > FOCUS_MAX_POS) targetZPos = FOCUS_MAX_POS; + // Clamp to user-set AF search limits first, then enforce hardware bounds + // last so hardware limits always win regardless of what farLimit_/nearLimit_ + // contain. if (targetZPos < farLimit_) targetZPos = farLimit_; if (targetZPos > nearLimit_) targetZPos = nearLimit_; + if (targetZPos < FOCUS_MIN_POS) targetZPos = FOCUS_MIN_POS; + if (targetZPos > FOCUS_MAX_POS) targetZPos = FOCUS_MAX_POS; std::ostringstream logMsg; logMsg << "Applying Z-offset: " << measuredZOffset << @@ -4542,7 +4556,11 @@ int EvidentAutofocus::OnNearLimit(MM::PropertyBase* pProp, MM::ActionType eAct) { std::string cmd = BuildCommand(CMD_AF_LIMIT, static_cast(nearLimit_), static_cast(farLimit_)); std::string response; - hub->ExecuteCommand(cmd, response); // best-effort; InitializeZDC() will re-apply + int ret = hub->ExecuteCommand(cmd, response); + if (ret != DEVICE_OK) + LogMessage("Warning: AFL command failed when setting near limit; will retry via InitializeZDC()"); + else if (!IsPositiveAck(response, CMD_AF_LIMIT)) + LogMessage("Warning: AFL negative ACK when setting near limit; will retry via InitializeZDC()"); } // Mark that ZDC needs re-initialization (deferred until next AF operation) @@ -4580,7 +4598,11 @@ int EvidentAutofocus::OnFarLimit(MM::PropertyBase* pProp, MM::ActionType eAct) { std::string cmd = BuildCommand(CMD_AF_LIMIT, static_cast(nearLimit_), static_cast(farLimit_)); std::string response; - hub->ExecuteCommand(cmd, response); // best-effort; InitializeZDC() will re-apply + int ret = hub->ExecuteCommand(cmd, response); + if (ret != DEVICE_OK) + LogMessage("Warning: AFL command failed when setting far limit; will retry via InitializeZDC()"); + else if (!IsPositiveAck(response, CMD_AF_LIMIT)) + LogMessage("Warning: AFL negative ACK when setting far limit; will retry via InitializeZDC()"); } // Mark that ZDC needs re-initialization (deferred until next AF operation)