Metadata-Version: 2.4
Name: pyThorlabsElliptec
Version: 0.11
Summary: A python library/GUI to access and control Elliptec Piezo Resonant Stages and Mounts.
Home-page: https://github.com/MicheleCotrufo/
Author: Michele Cotrufo
Author-email: michele.cotrufo@gmail.com
License: MIT
Description-Content-Type: text/markdown
Requires-Dist: pyserial
Requires-Dist: thorlabs_elliptec
Dynamic: author
Dynamic: author-email
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: requires-dist
Dynamic: summary

# pyThorlabsElliptec

`pyThorlabsElliptec` is a Python library/GUI interface to control Thorlabs Elliptec piezo motors. The package is composed of two parts: a low-level driver to perform basic operations, and a high-level GUI, written with PyQt5, which can be easily embedded into other GUIs. The low-level driver is a wrapper of the [thorlabs_elliptec](https://github.com/cdbaird/TL-rotation-control) library, with a few tweaks for device discovery and error handling.

The interface can work either as a stand-alone application, or as a module of [ergastirio](https://github.com/MicheleCotrufo/ergastirio).

## Table of Contents
 - [Installation](#installation)
 - [Usage via the low-level driver](#usage-via-the-low-level-driver)
   * [Creating a driver instance](#creating-a-driver-instance)
   * [Properties](#properties)
   * [Other attributes](#other-attributes)
   * [Methods](#methods)
   * [Direction constants](#direction-constants)
   * [Examples](#examples)
 - [Usage as a stand-alone GUI interface](#usage-as-a-stand-alone-gui-interface)
 - [Embed the GUI within another GUI](#embed-the-gui-within-another-gui)


## Installation

1. Install the package via pip:
```bash
pip install pyThorlabsElliptec
```

2. Install the `thorlabs_elliptec` library:
```bash
pip install thorlabs-elliptec
```

No additional DLLs or vendor software are required. The Elliptec controller communicates over USB-serial (virtual COM port), which is handled automatically by `thorlabs_elliptec`.

These two steps are enough to use the low-level driver. To use the GUI, install the additional dependencies listed in `requirements.txt`:
```bash
pip install abstract_instrument_interface>=0.10
pip install "PyQt5>=5.15.6"
pip install pyqtgraph
pip install numpy
```

## Usage via the low-level driver

`pyThorlabsElliptec` can be used to control a device from the command line or from your Python script.

The class `pyThorlabsElliptec` wraps the connected device's low-level `ELLx` object (defined in `thorlabs_elliptec`) and exposes the subset of its properties and methods needed to find, connect to, and operate a stage. A full list of properties, attributes, and methods is available below. **Note**: the documentation below was partially compiled with the help of Claude â mistakes are possible.

### Creating a driver instance

```python
pyThorlabsElliptec(virtual=False)
```

| Parameter | Type | Description |
| --- | --- | --- |
| `virtual` | bool, optional | If `True`, use the virtual backend instead of real hardware. Virtual mode is not yet implemented. Default is `False`. |

### Properties

The following are implemented as Python `@property`. Reading or setting either of these requires a device to be connected; otherwise, accessing them raises an `AttributeError` (no underlying `ELLx` object has been created yet).

| Property | Type | Description | Can be set? | Notes |
| --- | --- | --- | --- | --- |
| `position` | float | Current position of the motor in its native units. | Yes | Setting it starts a non-blocking absolute move. Poll `is_in_motion` to know when the move has finished. |
| `jog_step_size` | float | Step size used by `move_jog()`, in native units. Must be positive. | Yes | Can be read/set without a device connected. |
| `units` | str | Physical units of the stage (e.g. `'mm'` or `'deg'`). | No | |
| `is_in_motion` | bool | Whether the motor is currently moving. | No | |

### Other attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `connected` | bool | `True` if a device is currently connected, `False` otherwise. |
| `motor` | `ELLx` instance | The underlying `ELLx` object, created by `connect_device()` upon a successful connection. Does not exist until a device has been connected. |

### Methods

| Method | Returns | Description |
| --- | --- | --- |
| `list_devices()` | list of list | Scans all COM ports and returns those that respond as Elliptec devices. Each element is a 4-element list `[port, vid, pid, serial_number]`. Returns an empty list if no devices are found. |
| `connect_device(device_addr)` | (object, int) | Attempts to connect to the device identified by `device_addr` (a `[vid, pid]` list, as returned in elements 1 and 2 of each entry from `list_devices()`). Returns `(Msg, ID)`: `Msg` is `device_addr` on success, or an error message on failure; `ID` is `1` if connection was successful, `0` otherwise. Raises `ValueError` if `device_addr` is not a 2-element list. |
| `disconnect_device()` | (str, int) | Attempts to disconnect the currently connected device. Returns `(Msg, ID)`, analogous to `connect_device()`. Raises `RuntimeError` if no device is currently connected. |
| `move_home(blocking=False)` | None | Moves the motor to its home position. |
| `move_jog(direction, blocking=False)` | None | Jogs the motor by `jog_step_size` in the given `direction` (see [Direction constants](#direction-constants) below). |
| `move_to(position, blocking=False)` | None | Moves the motor to an absolute position. Equivalent to setting `self.position = value`, but exposes the `blocking` parameter. |

### Direction constants

`move_jog()` uses integer direction codes. These are defined at the module level in `main.py`:

```python
from pyThorlabsElliptec.main import MOVE_FWD, MOVE_REV
```

| Constant | Value | Meaning |
| --- | --- | --- |
| `MOVE_FWD` | +1 | Move/jog in the forward direction. |
| `MOVE_REV` | -1 | Move/jog in the reverse direction. |

### Examples

```python
from pyThorlabsElliptec.driver import pyThorlabsElliptec
from pyThorlabsElliptec.main import MOVE_FWD
import time

instrument = pyThorlabsElliptec()
available_devices = instrument.list_devices()   # Scan for Elliptec devices
print(available_devices)
# e.g. [['COM3', 0x1313, 0x804A, 'ELL14K00000001']]

device_addr = [available_devices[0][1], available_devices[0][2]]  # [vid, pid]
instrument.connect_device(device_addr=device_addr)

instrument.move_home()                          # Start homing (non-blocking)
while instrument.is_in_motion:                  # Poll until homing is done
    time.sleep(0.1)

instrument.position = 45                        # Start a non-blocking absolute move to 45Â°
while instrument.is_in_motion:
    time.sleep(0.1)
print(instrument.position)

instrument.jog_step_size = 5                    # Set the jog step size to 5Â°
instrument.move_jog(MOVE_FWD)                   # Jog forward by 5Â°
while instrument.is_in_motion:
    time.sleep(0.1)

instrument.disconnect_device()
```

## Usage as a stand-alone GUI interface

The installation sets up an entry point for the GUI. Just type:
```bash
pyThorlabsElliptec
```
in the command prompt to start the GUI. The GUI will automatically scan for connected Elliptec devices on startup. Select the device from the drop-down menu and click **Connect**.

## Embed the GUI within another GUI

The GUI controller can be easily integrated within a larger graphical interface, as shown in the example below.

```python
import PyQt5.QtWidgets as Qt
import pyThorlabsElliptec

app = Qt.QApplication([])
window = Qt.QWidget()

# The GUI needs to be contained inside a widget object
widget_containing_interface_GUI = Qt.QWidget()
widget_containing_interface_GUI.setStyleSheet(
    ".QWidget {
"
    "border: 1px solid black;
"
    "border-radius: 4px;
"
    "}"
)

# Create the interface object for the motor
Interface = pyThorlabsElliptec.interface(app=app, virtual=False)
Interface.verbose = False  # set the verbosity of the interface logger to False
# At any time during the software execution, the position read by the instrument can be
# accessed via Interface.output['Position']. One can also connect a callback to be called
# automatically whenever the position is updated:
#
#       Interface.sig_update_position.connect(foo)
#
# Every time the position is read from the instrument, foo(new_position) is called.

# Create the GUI for the motor
view = pyThorlabsElliptec.gui(interface=Interface, parent=widget_containing_interface_GUI)

# Create additional GUI elements
gridlayoutwidget = Qt.QWidget()
gridlayout = Qt.QGridLayout()
gridlayout.addWidget(Qt.QLabel("Additional GUI 1"), 0, 0)
gridlayout.addWidget(Qt.QLabel("Additional GUI 2"), 1, 0)
gridlayout.addWidget(Qt.QLabel("Additional GUI 3"), 0, 1)
gridlayout.addWidget(Qt.QLabel("Additional GUI 4"), 1, 1)
gridlayoutwidget.setLayout(gridlayout)

layout = Qt.QVBoxLayout()
layout.addWidget(widget_containing_interface_GUI)
layout.addWidget(gridlayoutwidget)
layout.addStretch(1)
window.setLayout(layout)

window.show()
app.exec()  # Start the event loop.
```
