Skip to content

Conversation

@nicost
Copy link
Member

@nicost nicost commented Dec 1, 2025

No description provided.

nicost and others added 30 commits November 5, 2025 11:29
Implements device adapter for the Evident IX85 microscope system with
support for:
- Focus drive (Z-stage) with position and speed control
- Nosepiece (objective turret) with 6 positions
- Magnification changer (1x, 1.6x, 2x)
- Light path selector
- Condenser turret
- DIA and EPI shutters
- Mirror units (filter cube turrets)
- Polarizer, DIC prism, EPI ND filter
- Correction collar

Key features:
- Hub-based architecture with automatic device discovery
- C++17 std::thread for monitoring thread (not MMDeviceThreadBase)
- Thread-safe state management using std::mutex
- Active notifications for real-time position updates
- Serial communication via USB VCOM (115200 baud, even parity)

Files added:
- DeviceAdapters/EvidentIX85/EvidentProtocol.h - Protocol definitions
- DeviceAdapters/EvidentIX85/EvidentModel.h/.cpp - State management
- DeviceAdapters/EvidentIX85/EvidentHub.h/.cpp - Hub with monitoring
- DeviceAdapters/EvidentIX85/EvidentIX85.h/.cpp - Device implementations
- DeviceAdapters/EvidentIX85/EvidentIX85.vcxproj - VS project (C++17)
- DeviceAdapters/EvidentIX85/Makefile.am - Unix build config

Build system integration:
- Added to micromanager.sln
- Added to DeviceAdapters/configure.ac

Also updated CLAUDE.md with repository documentation.

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix include: change <lock_guard> to <mutex> in EvidentModel.cpp
- Add missing error codes: ERR_PORT_NOT_SET and ERR_PORT_CHANGE_FORBIDDEN
- Add error text messages for new error codes

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Magnification device is working and updating its position
automatically.
- Updated Focus notification handler to compare current position with target
- When positions match, set Busy state to false in the model
- Applied same pattern to Nosepiece device
- This ensures Busy() returns false once movement completes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: During device initialization, when enabling notifications,
the microscope sends immediate notification messages (e.g., NCA 1 after
enabling magnification notifications). These notifications could be
read by the command thread when expecting command responses, causing
ERR_NEGATIVE_ACK (10102) errors.

Solution: Modified GetResponse() to skip over notification messages
and only return command responses. Notifications start with 'N' prefix
(NFP, NCA, NOB, etc.) and are now filtered out during command-response
sequences. The monitoring thread will handle these notifications
asynchronously.

Changes:
- GetResponse() now identifies notification tags and skips them
- Logs skipped notifications for debugging
- Continues reading until actual command response is found
- Monitoring thread code simplified for clarity

This ensures clean command-response sequences during initialization
even when notifications are enabled.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: GetResponse() was incorrectly filtering out command
acknowledgments like "NCA +" thinking they were notifications,
causing timeout errors (10101) when waiting for responses.

Root cause: Notification enable commands (e.g., "NCA 1") receive
TWO responses:
1. "NCA +" - acknowledgment that notifications are enabled
2. "NCA 1" - immediate notification of current state

The previous code skipped both, causing timeouts.

Solution: Added check to distinguish between:
- Acknowledgments: "NCA +", "NFP +" (should be returned)
- Notifications: "NCA 1", "NFP 3110" (should be skipped)

Now only skips notifications that contain data, not ack responses.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Major architectural change: Only the monitoring thread now reads from
the serial port. All messages are routed through this thread to either:
- Command responses -> Passed to waiting command thread via condition variable
- Notifications -> Processed directly and update model state

Benefits:
- Eliminates all race conditions between threads
- No messages are lost or skipped
- All notifications are processed regardless of timing
- Cleaner separation of concerns

Implementation:
- Added condition_variable for command-response communication
- MonitorThreadFunc() is now sole serial port reader
- GetResponse() waits on condition variable instead of reading
- ProcessNotification() handles all notification types
- IsNotificationTag() distinguishes notifications from responses

This fixes the timeout issue where notifications arriving during
command execution were causing state management problems.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: ParseIntParameter and ParseLongParameter would crash with
C++ exceptions when encountering non-numeric strings like "+", "!",
or empty strings. std::stoi/std::stol throw exceptions on invalid input.

Solution: Added comprehensive defensive checks:
- Handle empty strings -> return -1
- Handle "X" or "x" (unknown) -> return -1
- Handle "+" or "!" (acknowledgments) -> return -1
- Wrap std::stoi/std::stol in try-catch blocks
- Catch std::invalid_argument (not a number)
- Catch std::out_of_range (number too large)
- Return -1 for all error cases

This prevents application crashes when the microscope returns
unexpected response formats or when parsing acknowledgment messages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: IsNotificationTag() was identifying "NCA +" as a notification
because the tag "NCA" matches CMD_MAGNIFICATION_NOTIFY. But "NCA +" is
actually a command acknowledgment (response to "NCA 1" enable command),
not a notification. This caused the monitoring thread to process it as
a notification instead of passing it to the waiting command thread,
resulting in timeout errors (10101).

Root cause: The function only checked the tag, not the message content.
Both acknowledgments ("NCA +") and notifications ("NCA 1") have the
same tag "NCA".

Solution: Changed IsNotificationTag() to:
1. Take the full message instead of just the tag
2. Check if it's a known notification tag
3. Verify it's NOT an acknowledgment (+ or !)
4. Only return true for actual data notifications

Now:
- "NCA +" -> IsNotificationTag = false -> Passed to command thread ✓
- "NCA 1" -> IsNotificationTag = true -> Processed as notification ✓

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: Focus device's Busy() function would return true indefinitely
after movement because the exact position comparison (pos == targetPos)
would almost never succeed. Mechanical focus drives have settling
tolerance, backlash, and encoder quantization that prevent landing
exactly on the requested position.

Solution: Added tolerance-based comparison for continuous position devices:
1. Added FOCUS_POSITION_TOLERANCE constant (10 steps = 100nm)
2. Created IsAtTargetPosition() helper function
3. Updated ProcessNotification() to use tolerance comparison
4. Added debug logging when target is reached

Now the focus is considered "at position" when within ±100nm of target,
which is well within mechanical precision and allows Busy state to
clear properly.

For discrete state devices (Nosepiece), exact equality is still used
since positions are discrete and well-defined.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: Notifications could arrive before the command thread set the
target position and busy state, causing race conditions:

Timeline of the bug:
1. Send "FG 196000" command
2. Notification "NFP 196000" arrives (but target not set yet)
3. Acknowledgment "FG +" arrives
4. Set target=196000 and busy=true
5. No more notifications → busy stays true forever

The notification arrived BEFORE the target was set, so it couldn't
clear the busy state. Then after setting busy=true, no more
notifications arrive to clear it.

Solution: Set target position and busy state BEFORE sending command:
1. Set target=196000 and busy=true
2. Send "FG 196000" command
3. Notification "NFP 196000" arrives → clears busy (matches target)
4. Acknowledgment "FG +" arrives → command succeeded
5. Return success

If command fails, clear busy state before returning error.

Applied to both Focus and Nosepiece devices. This allows notifications
to arrive in any order relative to acknowledgments and still work
correctly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Changed EvidentMagnification from CStateDeviceBase to CMagnifierBase
for better semantic correctness as a magnification changer device.

Changes:
- Inherit from CMagnifierBase<EvidentMagnification>
- Implemented GetMagnification() returning actual magnification value
- Changed property from "State" to "Magnification" (MM::g_Keyword_Magnification)
- Property values are now 1.0, 1.6, 2.0 instead of 0, 1, 2
- Added static magnifications_[3] array with actual values
- Removed GetNumberOfPositions() (not needed for CMagnifierBase)
- Updated OnState() to OnMagnification()
- Updated notification handler to use Magnification property

Benefits:
- More semantically correct device type
- Properties are actual magnification values, not abstract states
- Better integration with Micro-Manager's magnification system
- Consistent with other magnification changer implementations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Changes made to make the code build and run correctly:

1. Added custom Magnification property constant
   - MM::g_Keyword_Magnification doesn't exist in MMDevice API
   - Defined g_Keyword_Magnification = "Magnification" in EvidentIX85.cpp
   - Added extern declaration in EvidentHub.cpp

2. Corrected device type registration
   - Changed from MM::StateDevice to MM::MagnifierDevice
   - Properly identifies device as magnification changer

3. Fixed GetMagnification() signature
   - Removed 'const' qualifier (was causing issues)
   - GetHub() is not const, so method can't be const

4. Updated all property references
   - Use g_Keyword_Magnification consistently
   - Applied in Initialize() and notification handler

These changes ensure proper compilation and correct runtime behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Added GetCoreCallback()->OnMagnifierChanged() when magnification
position changes. This is a specialized callback that notifies the
Micro-Manager core when the magnification changer position changes.

The callback is called in addition to OnPropertyChanged to ensure:
1. Property value updates correctly
2. Core is notified of magnification system change
3. Dependent calculations (pixel size, etc.) can be updated

Called in ProcessNotification() when magnification notification
arrives from the microscope.

Fix: OnMagnifierChanged() requires the device parameter to identify
which magnifier changed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: During shutdown, each command was timing out (2 seconds each),
making device unload take 8+ seconds. The issue was that StopMonitoring()
was called BEFORE sending shutdown commands.

Timeline of the bug:
1. StopMonitoring() called → monitoring thread stopped
2. Send "NFP 0" command → waits for response
3. No monitoring thread to read response → timeout (2s)
4. Send "NOB 0" command → timeout (2s)
5. Send "NCA 0" command → timeout (2s)
6. Send "L 0" command → timeout (2s)
7. Total: 8 seconds of timeouts

Root cause: GetResponse() waits on a condition variable that's signaled
by the monitoring thread. With the thread stopped, responses are never
read from the serial port and never delivered to GetResponse().

Solution: Reordered shutdown sequence:
1. Send all shutdown commands (disable notifications, local mode)
2. Commands complete quickly (responses read by monitoring thread)
3. THEN stop monitoring thread
4. Total: milliseconds instead of 8 seconds

Shutdown is now instant instead of 8+ seconds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add a property (enabled by default) to the Nosepiece device that protects
expensive objective lenses by lowering the focus to zero before changing
nosepiece positions, then restoring the original focus position after the
change completes.

Implementation details:
- Added SafeNosepieceChange property (Disabled/Enabled, default=Enabled)
- Added SafeNosepieceChange() helper method that orchestrates the sequence:
  1. Save current focus position
  2. Move focus to zero
  3. Wait for focus to complete (with 10s timeout)
  4. Change nosepiece position
  5. Wait for nosepiece to complete (with 10s timeout)
  6. Restore original focus position
- Gracefully handles missing Focus device (falls back to regular change)
- Includes error recovery: attempts to restore focus even if nosepiece change fails
- Modified OnState() to use SafeNosepieceChange when property is enabled

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implemented full functionality for all remaining IX85 devices:

State Devices (with notifications):
- EvidentCondenserTurret: Condenser turret control with position tracking
- EvidentMirrorUnit1: Filter cube turret with notifications
- EvidentPolarizer: Polarizer position control with notifications

State Devices (query-based):
- EvidentLightPath: Light path selector (Left/Bi50/Bi100/Right)
- EvidentDICPrism: DIC prism position control
- EvidentEPIND: EPI ND filter position control

Shutter Devices:
- EvidentDIAShutter: Transmitted light shutter (open/closed)
- EvidentEPIShutter1: Reflected light shutter (open/closed)

Generic Devices:
- EvidentCorrectionCollar: Objective correction collar (0-100 range)

Implementation patterns:
- Devices with notifications: Set target/busy BEFORE sending command
- Devices without notifications: Direct query and command execution
- All devices check for hardware presence during initialization
- Proper error handling with busy state cleanup on failures
- Follow established patterns from Focus, Nosepiece, and Magnification

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The MirrorUnit1 device was incorrectly using CMD_MIRROR_UNIT_NOTIFY1
(NMUINIT1) which is an initialization notification, not a position change
notification. This caused invalid position errors during property queries.

Changed MirrorUnit1 to use query-based position tracking like DICPrism
and EPIND:
- Removed notification-based position tracking
- Query hardware position in OnState BeforeGet handler
- Busy() now returns false (instantaneous changes)
- Removed EnableNotifications call from Initialize and Shutdown
- Added comment explaining why notifications aren't used

This matches the behavior of other query-based state devices in the adapter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixed the root cause of the "Invalid state (position) requested" error.
Several Query functions were not calling SetNumPositions(), causing
numPos_ to remain 0 in device Initialize() functions, which resulted in
invalid property limits.

Changes:
- Added constants to EvidentProtocol.h:
  * CONDENSER_TURRET_MAX_POS = 6
  * MIRROR_UNIT_MAX_POS = 6
  * POLARIZER_MAX_POS = 6
  * DIC_PRISM_MAX_POS = 6
  * EPIND_MAX_POS = 6

- Fixed Query functions in EvidentHub.cpp to call SetNumPositions():
  * QueryCondenserTurret()
  * QueryPolarizer()
  * QueryDICPrism()
  * QueryMirrorUnit1() (original reported issue)
  * QueryMirrorUnit2()
  * QueryEPIND()

Now all multi-position devices properly initialize their position limits,
preventing out-of-bounds errors during property access.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixed initialization failure of CondenserTurret and other devices.
The issue was attempting to send notification tags (NTR, NFP, NOB, etc.)
as commands to the microscope, which are invalid.

Notification tags are what the microscope sends TO us automatically when
positions change - they are not commands we send TO the microscope.

Changes:
- Removed all EnableNotification() calls from device Initialize() and
  Shutdown() methods
- Removed EnableNotifications() member functions from:
  * EvidentFocus
  * EvidentNosepiece
  * EvidentMagnification
  * EvidentCondenserTurret
  * EvidentPolarizer
- Removed EnableNotifications() declarations from header file
- Added comments explaining that notifications are automatic

The monitoring thread already correctly receives and processes these
notifications (NFP, NOB, NCA, NTR, NPO) when the microscope sends them.

Fixes error where "NTR 1" command was being sent and microscope
responded with "x" (command not recognized).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
… command

Restored EnableNotifications functionality which is required for devices
to receive position update notifications from the microscope.

The key fix: CondenserTurret now uses CMD_CONDENSER_TURRET ("TR") instead
of CMD_CONDENSER_TURRET_NOTIFY ("NTR") to enable notifications.

Changes:
- Restored EnableNotifications() calls in Initialize() and Shutdown() for:
  * EvidentFocus (uses NFP)
  * EvidentNosepiece (uses NOB)
  * EvidentMagnification (uses NCA)
  * EvidentCondenserTurret (uses TR - FIXED from NTR)
  * EvidentPolarizer (uses NPO)

- Restored EnableNotifications() member functions in all affected devices
- Restored declarations in header file

Without these notification enable commands, the devices don't receive
position updates from the microscope when users manually change positions.

Fixes CondenserTurret initialization failure where "NTR 1" was being sent
(which is invalid) - now correctly sends "TR 1" to enable notifications.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixed timeout issue when changing CondenserTurret position. The problem
was that the Busy flag was never cleared because CondenserTurret does not
send notifications (NTR) when movement completes.

Analysis:
- Unlike other devices (Focus, Nosepiece, etc.) that send notifications
  (NFP, NOB) upon movement completion, CondenserTurret does not send NTR
- The "TR +" acknowledgment is only returned AFTER the movement completes
- This appears to be a firmware oversight in the IX85

Solution:
- Clear Busy flag immediately after receiving "TR +" positive acknowledgment
- Update position in model when ack is received
- Removed SetTargetPosition call (not needed without notifications)

The positive ack itself indicates completion, so no separate notification
is needed. This matches the actual firmware behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Extends default timeout to 5 seconds (which is needed for devices
that takes a long time to move such as the Condenser).
Hardware testing revealed several firmware quirks and bugs that needed
fixing:

Device Detection:
- Fix DIAShutter detection: was calling QueryDIAAperture instead of
  QueryDIAShutter, causing device not to be marked as present
- Work around firmware bug in Polarizer and DICPrism: first query
  returns "X" even when device is present. Now sends test command
  (PO 0 or DIC 0) to verify presence by checking for positive ack

Polarizer Fixes:
- Fix notification command: use CMD_POLARIZER ("PO") instead of
  CMD_POLARIZER_NOTIFY ("NPO") to enable notifications
- Change from 6 positions to 2 positions with labels "Out" and "In"
- Fix indexing: Polarizer uses 0-based indexing (PO 0, PO 1) unlike
  other devices that use 1-based indexing
- Fix timeout: Polarizer doesn't send NPO notifications, only returns
  "PO +" after movement completes, so clear Busy flag immediately

Focus Drive Fixes:
- Fix Busy flag not clearing when commanded to move to current position:
  firmware doesn't send NFP notifications if position doesn't change,
  so now check if already at target and clear Busy immediately
- Fix SafeNosepieceChange timeout when focus already at zero: apply
  same fix when moving to zero and when restoring original position

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ion properties

Replace individual device queries with V command-based detection which is more
reliable and avoids firmware bugs where some queries return "X" on first attempt.
The V command takes unit numbers 1-18 and returns firmware version if device is
present or error if absent. This approach properly handles compound units (V7
contains Polarizer/CondenserTurret/DIAShutter, V8 contains DICPrism).

Add firmware version storage to MicroscopeModel and expose versions as read-only
properties in device property browser for 10 devices: Focus, Nosepiece, LightPath,
CondenserTurret, DIAShutter, Polarizer, EPIShutter1, MirrorUnit1, DICPrism, EPIND.

Simplify QueryPolarizer and QueryDICPrism by removing workaround test commands
since V7/V8 detection now confirms device presence before these queries are called.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…dicator support

Add support for MirrorUnit2 and EPIShutter2 devices following the same pattern
as MirrorUnit1 and EPIShutter1. Both devices detected via V11/V12 commands and
include firmware version properties.

Implement Manual Control Unit (MCU) indicator support for visual feedback on the
microscope's control panel when in external control mode (L1):

- I1 indicator displays nosepiece position (1-6) or "---" when undetermined
- I2 indicator displays mirror unit position (1-6) or "---" when undetermined
- Added 7-segment display hex codes (0xEE-0x6F) for digits 0-9 and dash (0x01)
- Indicators update automatically via notification system (I1) and after position
  changes (I2)
- I1/I2 responses consumed by monitoring thread to avoid command/response sync issues

MCU detected via V13 command. Indicators initialized on startup and update during
operation without requiring changes to device implementations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…lbacks

Encoder Support:
- Fixed E1 (nosepiece) encoder acknowledgment handling to prevent spurious moves
- Implemented E2 (mirror unit) encoder with position wrapping and indicator updates
- Added OnPropertyChanged callbacks for encoder-driven position changes

MCU Indicators:
- Implemented I4 indicator for LightPath position display
  - Maps 4 positions to I4 values: Left Port→1, 50/50→2, Binocular 100%→4, Right Port→0
- Implemented I5 indicator for EPIShutter1 state display
  - Closed→I5 1, Open→I5 2
- All indicators use fire-and-forget pattern to avoid deadlock

MCU Focus Control:
- Enable jog (focus) control dials on initialization with JG 1 command
- Disable on shutdown with JG 0 command

Stage Position Callbacks:
- Added OnStagePositionChanged callback for NFP (focus position) notifications
- Enables core to track focus position changes from manual jog dials and other sources
- Position properly converted from 10nm units to micrometers

Device Registration:
- Registered Focus, Nosepiece, MirrorUnit1, LightPath, and EPIShutter1 with hub
- Enables proper callback routing for property and position change notifications

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add three jog control properties to Focus device:
  - Jog Direction (Default/Reverse) via JGDR command
  - Jog Fine Sensitivity (1-100) via JGSF command
  - Jog Coarse Sensitivity (1-100) via JGSC command

- Fix DIA brightness property and I3 indicator behavior:
  - Brightness property now always shows remembered value (not 0 when closed)
  - I3 indicator now always shows remembered brightness
  - Both stay in sync when adjusting brightness with shutter closed
  - E3 encoder updates both property and I3 when shutter closed
  - Actual lamp brightness only changes when logical shutter is open

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
nicost and others added 10 commits November 20, 2025 17:50
Add three autofocus workflow modes controlled by "AF-Workflow-Mode" property:
- Measure-Offset: User focuses manually, AF 1 runs, offset calculated and
  stored, then returns to original position
- Find-Focus-With-Offset: AF 3 runs to find focus, then applies stored
  offset to Focus Drive and stops
- Continuous-Focus: Starts AF 2 for continuous focus tracking

Add "Measured-Focus-Offset-um" property to display and set the Z-offset
value. Property updates automatically when offset is measured and triggers
OnPropertyChanged callbacks to notify core of changes.

Remove redundant "Tracking Mode" property - workflow modes now handle all
autofocus mode selection. Code simplified to always use AF mode 2 as default.

Fix AF mode 1 auto-stop detection to recognize success when position changes
and correct offset sign calculation to properly represent correction from
ZDC's focus position to user's desired focus position.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implemented a new device adapter for one-time objective configuration:
- Created lens database with 102 Evident objectives (EvidentLensDatabase.h)
- New EvidentObjectiveSetup device with per-position configuration
- Auto-detects installed objectives via GOB queries
- Allows database override selection with filtering by magnification/immersion
- Per-position Send-To-SDK buttons for individual objective configuration
- Sends objective info to SDK using S_OB command
- Supports clearing positions with NONE command
- Dynamic property updates after sending objectives

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…onEx.

Replaced 48 individual position-specific handler functions with 8
parameterized handlers using CPropertyActionEx. This reduces code
duplication and improves maintainability.

Changes:
- Replaced OnPos1-6DetectedName/Specs/DatabaseSelection/SendToSDK with
  parameterized OnPos* handlers
- Replaced OnPos1-6Special* handlers with parameterized OnPosSpecial*
  handlers
- Updated property creation to use CPropertyActionEx instead of switch
  statements
- Net reduction of ~200 lines of code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…e measured first, because we now have the offset as a property with a default value of zero. It can for instance be set as a pre-initialization property.
Ensure that OnPropertyChanged is called whenever the autofocus status
changes, so that the MMCore and applications are properly notified of
status changes.

Changes:
- UpdateAFStatus(): Now calls OnPropertyChanged when status changes
  (triggered by NAFST notifications from microscope)
- StopAF(): Calls OnPropertyChanged when AF is stopped
- IsContinuousFocusLocked(): Calls OnPropertyChanged when querying
  updates the status

All callbacks check if the status actually changed before notifying
to avoid unnecessary property change events.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
ports are offered as options in the HCW.
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 pull request adds comprehensive device adapter support for the Evident IX85 microscope system, implementing three separate adapters to support different communication methods and hardware configurations:

  • EvidentIX85XYStage: COM-based XY stage controller for the IX5-SSA hardware
  • EvidentIX85Win: SDK-based adapter providing full microscope control (focus, nosepiece, shutters, filters, etc.)
  • EvidentIX85: COM-based adapter for general microscope control

The implementation includes protocol definitions, thread-safe state management, device detection, and proper resource handling. Additionally, a HamamatsuHamU adapter reference and documentation (CLAUDE.md) are added.

Reviewed changes

Copilot reviewed 37 out of 39 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
micromanager.sln Adds four new project entries: HamamatsuHamU, EvidentIX85, EvidentIX85Win, and EvidentIX85XYStage with build configurations
DeviceAdapters/configure.ac Adds EvidentIX85 to autotools build system
DeviceAdapters/PVCAM/StreamWriter.cpp Contains extraneous blank line additions
DeviceAdapters/EvidentIX85XYStage/* Complete XY stage adapter implementation with protocol, model, and device classes
DeviceAdapters/EvidentIX85Win/* Full SDK-based microscope adapter with hub, multiple device types, lens database, and objective setup
DeviceAdapters/EvidentIX85/* COM-based microscope adapter with hub and device implementations
CLAUDE.md Repository documentation for Claude AI assistance

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

}

// Load the DLL
dllHandle_ = LoadLibraryExA(dllFullPath.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

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

Untrusted DLL load via LoadLibraryExA with a relative filename and LOAD_WITH_ALTERED_SEARCH_PATH enables DLL hijacking and arbitrary code execution. An attacker who can place a crafted msl_pm_ix85.dll in the working directory, application directory, or a searched path can get it loaded instead of the intended vendor DLL. To fix: resolve and validate an absolute, trusted path to the SDK DLL (e.g., program files directory), call SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) and load with LoadLibraryExW(absPath, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR), or use AddDllDirectory to a trusted directory; avoid passing just a filename and avoid LOAD_WITH_ALTERED_SEARCH_PATH. Optionally verify the DLL’s signature before use.

Copilot uses AI. Check for mistakes.
… current directory. This should match instructions on the website about downloading and installting the SDK zip file.
Copy link
Member

@marktsuchida marktsuchida left a comment

Choose a reason for hiding this comment

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

Awesome! I took a quick look focusing on organization and added some comments.

@@ -200,6 +200,7 @@ int StreamWriter::Start()
camera_->LogAdapterMessage(std::string("Started streaming to '") + path_ + "'", false);

return DEVICE_OK;

Copy link
Member

Choose a reason for hiding this comment

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

Would be nice to remove changes to PVCAM from this PR branch.

@@ -0,0 +1,12 @@

AM_CXXFLAGS = $(MMDEVAPI_CXXFLAGS) $(BOOST_CPPFLAGS) -std=c++17
Copy link
Member

Choose a reason for hiding this comment

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

Boost flags are not needed because Boost is not used (I think?).

Comment on lines +1174 to +1177
catch(...)
{
LogMessage("Exception in DetectDevice!",false);
}
Copy link
Member

Choose a reason for hiding this comment

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

What part of the above code is expected to throw an exception?

{
detectedObjectives_[idx].na = std::stod(params[2]);
}
catch (...)
Copy link
Member

Choose a reason for hiding this comment

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

Let's catch exceptions by type: catch (const std::logic_error&). Ditto for the 2 other catch (...) below in this file.

(Unlikely here, but catch (...) can obscure unexpected problems (e.g., out of memory) and complicate debugging.)

(Will need #include <stdexcept>.)

#include "DeviceBase.h"
#include "EvidentModelWin.h"
#include "EvidentProtocolWin.h"
#include "EvidentIX85Win.h"
Copy link
Member

Choose a reason for hiding this comment

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

This include is circular; one direction should be removed.

};

// Complete lens database from SDK LensInfo.unit file
static const LensInfo LENS_DATABASE[] = {
Copy link
Member

Choose a reason for hiding this comment

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

If this gets included in more than one .cpp file, the big static array will be duplicated. Probably won't actually get duplicated if not accessed, but I think it's better to put this (and the implementations of the functions that access it) into a .cpp file.

@marktsuchida marktsuchida changed the title Evident IX85 - DeviceAdapters for the XY stage(COM) and Hub (both through COM and SDK( Evident IX85 - DeviceAdapters for the XY stage (COM) and Hub (both through COM and SDK) Dec 3, 2025
asynchronously.  This mechanism shoudl be used from with the Notification
processing code, otherwise there will either be deadlock and timeout, and/or
the command responses will not be processed leading to chaose down the line.  Tested this code with 528 channel switches, autofocus and z-stacks, and
no issues observed,
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.

3 participants