A Python interface for controlling Luminos motorised 6-axis positioning stages via Zaber motion control. This library provides intuitive programmatic access and a professional GUI for controlling linear (X, Y, Z) and rotational (Roll, Pitch, Yaw) axes.
The Luminos stage is a precision motorised positioning platform with up to 6 degrees of freedom. All axes use identical linear actuators with different mechanical interpretations:
- Linear axes (X, Y, Z): Direct linear displacement in micrometres
- Rotational axes (Roll, Pitch, Yaw): Mechanically coupled rotation, exposed as degrees
This Python package provides:
- Type-annotated classes for linear and rotational axes
- Flexible axis configuration for any subset of the 6 axes
- Real-time position monitoring with PyQt5 GUI
- Comprehensive logging throughout for debugging
- Context manager support for clean resource management
- Thread-safe operations via worker threads
- TCP/IP control via Zaber motion library
- Support for 3 to 6 axes with customisable ordering
- Separate position APIs for linear (µm) and rotational (°) axes
- Absolute and relative motion commands
- Homing functionality for reference positioning
- Speed and acceleration configuration
- Microstep resolution control
- Real-time position monitoring with scrolling display
- Preset buttons for common jog distances
- Multi-stage support (multiple independent controllers)
- Comprehensive Python logging
- Python 3.7+
- zaber-motion ≥ 5.0.0
- (Optional) PyQt5 ≥ 5.15.0 and NumPy ≥ 1.20.0 for GUI
pip install zaber-motionFor GUI support:
pip install PyQt5 numpyfrom luminos_stage import LuminosStagefrom luminos_stage import LuminosStage
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
# Open connection (uses default axis order)
with LuminosStage(port='COM8') as stage:
# Home all axes
stage.home_all()
# Move X axis to 100 µm
stage.x.move_absolute_um(100.0)
# Move Y axis relative
stage.y.move_relative_um(50.0)
# Rotate roll axis to 5°
stage.roll.move_absolute_degree(5.0)
# Get current positions
linear_pos = stage.get_position_um()
rotational_pos = stage.get_position_deg()
print(f"Linear: {linear_pos}")
print(f"Rotational: {rotational_pos}")with LuminosStage(
port='COM8',
axis_order={'x': 0, 'y': 1, 'roll': 2, 'pitch': 3, 'yaw': 4}
) as stage:
print(f"Z axis: {stage.z}") # None
print(f"Roll axis: {stage.roll}") # <_RotationalAxis>python -m src.luminos.guiAlternatively from Python:
from luminos.gui import main
main()LuminosStage(
port,
reverse_x=False,
reverse_y=False,
reverse_z=False,
axis_order=None
)Parameters:
-
port(str): Serial port name (e.g.'COM8','/dev/ttyUSB0') -
reverse_x, reverse_y, reverse_z(bool): Reverse direction for linear axes -
axis_order(dict, optional): Custom axis orderingDefault:
{'z': 0, 'x': 1, 'y': 2, 'roll': 3, 'pitch': 4, 'yaw': 5}
Example:
stage = LuminosStage(port='COM8', reverse_y=True)Close the serial connection.
stage.close()Preferred method for automatic connection cleanup:
with LuminosStage(port='COM8') as stage:
# Use stage
pass # Connection automatically closedHome all axes sequentially to reference position.
stage.home_all()Home only linear axes (X, Y, Z) that are present.
stage.home_linear()Emergency stop all axes immediately.
stage.stop_all()Get positions of all linear axes in micrometres.
Returns: Dictionary with axis names as keys
Example:
pos = stage.get_position_um()
print(f"X: {pos['x']:.3f} µm")
print(f"Y: {pos['y']:.3f} µm")Get positions of all rotational axes in degrees.
Example:
pos = stage.get_position_deg()
print(f"Roll: {pos['roll']:.4f}°")Each linear axis (X, Y, Z) provides these methods:
stage.x.move_absolute_um(100.0) # Move to 100 µm
stage.x.move_absolute_mm(1.5) # Move to 1.5 mm
stage.x.move_relative_um(50.0) # Move 50 µm forward
stage.x.move_relative_um(-10.0) # Move 10 µm backwardpos_um = stage.x.get_position_um() # Position in micrometres
pos_mm = stage.x.get_position_mm() # Position in millimetresstage.x.home() # Home to reference
stage.x.stop() # Emergency stop
busy = stage.x.is_busy() # Check if movingstage.x.set_speed(800) # Set velocity
stage.x.set_acceleration(30) # Set acceleration
stage.x.set_microstep_resolution(64) # 1, 2, 4, 8, 16, 32, 64, 128Each rotational axis (Roll, Pitch, Yaw) provides:
stage.roll.move_absolute_degree(5.0) # Move to 5°
stage.roll.move_relative_degree(1.0) # Rotate 1° forward
stage.roll.move_relative_degree(-0.5) # Rotate 0.5° backwardpos_deg = stage.roll.get_position_degree() # Position in degreesstage.roll.home() # Home to reference
stage.roll.stop() # Emergency stop
busy = stage.roll.is_busy() # Check if movingstage.roll.set_speed(600) # Set velocity
stage.roll.set_acceleration(22) # Set accelerationAll axes use the same linear actuator:
| Parameter | Value |
|---|---|
| Step size | 99.21875 nm/step |
| Max steps | 131,072 steps |
| Max travel | ~13.0 mm |
| Parameter | Value |
|---|---|
| Speed | 600 |
| Acceleration | 22 |
| Microsteps | 128 |
| Axis | Arc-sec/step | Max range |
|---|---|---|
| Roll | 0.1 | ~3.64° |
| Pitch/Yaw | 0.2 | ~7.28° |
from luminos_stage import LuminosStage
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def alignment_sequence(port='COM8'):
"""
Perform automated alignment sequence on 6-axis stage.
"""
with LuminosStage(port=port) as stage:
logger.info("Starting alignment sequence")
# Home all axes
logger.info("Homing all axes...")
stage.home_all()
# Move to alignment position
logger.info("Moving to alignment position...")
stage.x.move_absolute_um(500.0)
stage.y.move_absolute_um(500.0)
stage.z.move_absolute_um(250.0)
# Fine adjustments
logger.info("Fine tuning position...")
stage.roll.move_absolute_degree(0.5)
stage.pitch.move_absolute_degree(1.0)
# Report final positions
linear_pos = stage.get_position_um()
rotational_pos = stage.get_position_deg()
logger.info(f"Final linear positions (µm): {linear_pos}")
logger.info(f"Final rotational positions (°): {rotational_pos}")
logger.info("Alignment complete")
if __name__ == '__main__':
alignment_sequence()The included PyQt5 GUI provides:
- Multi-stage support: Control multiple stages in separate tabs
- Real-time monitoring: Position display updates every 500 ms
- Intuitive controls: Home, absolute move, and relative jog buttons
- Preset buttons: Quick access to common jog distances
- Linear: 1 nm, 10 nm, 100 nm, 1 µm, 10 µm, 100 µm, 1 mm
- Rotational: 0.001°, 0.01°, 0.1°, 1.0°
- Flexible configuration: Custom axis ordering via JSON
- Focus-aware presets: Presets fill whichever spinbox last received focus
- Emergency stop: Red stop button for all axes
python -m luminos_stage.guiProblem: RuntimeError: Failed to open serial connection
Solutions:
- Verify correct serial port (use Device Manager on Windows)
- Ensure Zaber devices are powered on
- Check USB cable connections
- Verify no other application is using the port
Problem: No motion after move command
Solutions:
- Verify axis is not already at target position
- Check axis is not busy:
stage.x.is_busy() - Try homing axis:
stage.x.home() - Check speed/acceleration settings
- Verify axis power supply
Problem: get_position_um() returns stale values
Solutions:
- Ensure axis has finished moving:
stage.x.is_busy() - Try calling position multiple times
- Check serial communication (logging at DEBUG level)
Configure Python logging to see detailed information:
import logging
# Show all debug messages
logging.basicConfig(level=logging.DEBUG)
# Or configure just this package
logger = logging.getLogger('luminos_stage')
logger.setLevel(logging.DEBUG)Log levels:
DEBUG: Detailed hardware commands and responsesINFO: Connection status, major operationsWARNING: Unexpected conditionsERROR: Operation failures
The default axis ordering assumes:
Port 0 (closest to PC) → Z axis
Port 1 → X axis
Port 2 → Y axis
Port 3 → Roll axis
Port 4 → Pitch axis
Port 5 → Yaw axis
Customise with axis_order parameter:
# 5-axis: X->Y->Roll->Pitch->Yaw (no Z)
LuminosStage(port='COM8', axis_order={
'x': 0, 'y': 1, 'roll': 2, 'pitch': 3, 'yaw': 4
})
# 3-axis XYZ only
LuminosStage(port='COM8', axis_order={
'x': 0, 'y': 1, 'z': 2
})- Typical position query response: ~50 ms
- Homing duration: Variable, depends on current position
- GUI update rate: 500 ms (user configurable)
- Thread-safe: All hardware operations run in worker threads
MIT License. See LICENSE file for details.
- Zaber Motion library: https://github.com/zabertech/zaber-python-api
- PyQt5 documentation: https://www.riverbankcomputing.com/software/pyqt/
For issues with this Python implementation:
- Enable logging:
logging.basicConfig(level=logging.DEBUG) - Check serial port configuration
- Verify Zaber devices are detected: Check Device Manager or
/dev/ttyUSB* - Review error messages in console output
For device-specific issues, consult Zaber documentation or contact support.
Note: This implementation requires proper mechanical assembly and calibration of the Luminos stage before use.