Skip to main content

Lab Equipment Automation Package

Project description

Control-lab-ly

Lab Equipment Automation Package

PyPI PyPI - Python Version Tests

Description

User-friendly package that simplifies the definition and control of reconfigurable setups for high-throughput experimentation and machine learning.

Installation

Control-lab-ly can be found on PyPI and can be easily installed with pip install.

$ python -m pip install control-lab-ly[all]

Quickstart

Import the desired class from the library and initialize to use.

from controllably.Move.Cartesian import Gantry
mover = Gantry(...)
mover.connect()
mover.safeMoveTo((x,y,z))

Explore the details for each object using the help() function, or the ? operator within the IPython / Jupyter Notebook environment.

help(Gantry)

Device support

  • Make
    • (QInstruments) BioShake Orbital Shaker
    • (Arduino-based devices)
      • Multi-channel LED array
      • Multi-channel spin-coater
      • Peltier device
  • Measure
    • (BioLogic) via easy-biologic (optional)
    • (Keithley) via PyMeasure (optional)
    • (Sentron) SI series pH meters
    • (Arduino-based device)
      • Precision mass balance
      • Load cell
  • Move
    • (Creality) Ender-3
    • (Dobot) with external/../dobot_api
      • M1 Pro
      • MG400
    • (Arduino-based device) gantry robot running on GRBL
  • Transfer
    • (Sartorius) rLINE® dispensing modules
    • (TriContinent) C Series syringe pumps
  • View
    • (FLIR) AX8 thermal imaging camera via pyModbusTCP (optional)
    • (General) Web cameras with cv2

Advanced Usage

Setup initialization can be greatly simplified with Control-lab-ly.

To access files / folders in the project repository as you would with an installed package, use the init() function to add the project directory into PATH.

from controllably import init
init('project_root')

from tools import ToolSetup01
setup = ToolSetup01.setup()
setup.MoverDevice.loadDeckFromFile(ToolSetup01.LAYOUT_FILE)

Here, the setup is initialized and returned with just ToolSetup01.setup(), and the layout is loaded with the loadDeckFromFile method.

Folder structure

To make full use of Control-lab-ly's features, a typical project file structure will need the library and tools folders.

project_root/
|
├── library/
|   ├── deck/
|   |   ├── layout_board_30x30.json
|   |   └── layout_board_60x30.json
|   ├── labware/
|   |   ├── generic_96_tiprack.json
|   |   ├── generic_8_wellplate.json
|   |   └── generic_1_bin.json
|   ├── plugins/
|   |   ├── tool_part_1.py
|   |   ├── tool_part_2.py
|   |   └── mock_module.py
|   └── __init__.py
|
├── tools/
|   ├── ToolSetup01/
|   |   ├── __init__.py
|   |   ├── config.yaml
|   |   └── layout.json
|   ├── ToolSetup02/
|   |   ├── __init__.py
|   |   ├── config.yaml
|   |   └── layout.json
|   ├── __init__.py
|   └── registry.yaml
|
├── scripts/
|   ├── experiment_script_1.py
|   ├── experiment_2.ipynb
|   └── ...
└── ...

Use start_project_here(target_dir) to generate the above file structure,

from controllably import start_project_here
controllably.start_project_here(".")

or the CLI to create the required directories.

$ python -m controllably .

1. Features

For more advanced uses, Control-lab-ly provides a host of tools to streamline the development of lab equipment automation. This includes setting up configuration files and writing plugins.

  1. Dynamic object initialization
  2. Reconfigurable complex tools
  3. Modular positioning system
  4. Application and network interoperability

1.1 Dynamic object initialization

Control-lab-ly allows users to store all their tool configuration data in a YAML file, providing a single source of truth for all projects using the same set up. The config.yaml file stores the configuration for all the tools in the set up, which can be parsed by Control-lab-ly to initialize the tools using get_setup().

MyDevice:                                   # user-defined name
  module: controllably.Move.Cartesian       # "from" ...
  class: Gantry                             # "import" ...
  settings:
    port: COM1                              # serial port address
    setting_A: [300,0,200]
    setting_B: [[0,1,0],[-1,0,0]]

A different serial port address or camera index may be used by different machines for the same device.

See Section 2.1 to find out how to manage the different addresses used by different machines.

1.2 Reconfigurable complex tools

Compound devices are similarly configured in the config.yaml file. The configuration details of the component tools are nested in details.

MyCompoundDevice:                           # user-defined name
  module: controllably.Compound.LiquidMover
  class: LiquidMover
  settings:                                 # settings for compound device
    speed_factor_lateral: null
    speed_factor_up: 0.2
    speed_factor_down: 0.2
    speed_factor_pick_tip: 0.01
    tip_approach_distance: 20
    details:                                # nest component configuration in "details"
      mover:                                # component name (defined in LiquidMover)
        module: controllably.Move.Cartesian
        class: Gantry
        settings:
          port: COM1 
      liquid:                               # component name (defined in LiquidMover)
        module: controllably.Transfer.Liquid.Pipette.Sartorius
        class: Sartorius
        settings:
          port: COM22

Lastly, you can define shortcuts (or aliases) at the end of config.yaml to easily access the nested components of compound devices.

SHORTCUTS:
  LiquidDevice: 'MyCompoundDevice.liquid'
  MoverDevice: 'MyCompoundDevice.mover'

1.3 Modular positioning system

Control-lab-ly allows users to easily combine multiple modules and switch between local and global coordinates. The layout.json file stores the layout configuration of your physical workspace (Deck).

Optional: if your setup does not involve moving objects around in a pre-defined workspace, a layout configuration may not be required

{
    "metadata": {
        "displayName": "Example Layout (main)",
        "displayCategory": "deck",
        "displayVolumeUnits": "µL",
        "displayLengthUnits": "mm",
        "tags": []
    },
    "dimensions": [600,300,0],
    "cornerOffset": [0,0,0],
    "orientation": [0,0,0],
    "slots": {
        "1": {
            "name": "slotOne",
            "dimensions": [127.76,85.48,0],
            "cornerOffset": [160.5,6.5,0],
            "orientation": [0,0,0]
        },
        "2": {
            "name": "slotTwo",
            "dimensions": [127.76,85.48,0],
            "cornerOffset": [310.5,6.5,0],
            "orientation": [0,0,0],
            "labware_file": "project_root/library/labware/labware_wellplate.json"
        },
        "3": {
            "name": "slotThree",
            "dimensions": [127.76,85.48,0],
            "cornerOffset": [460.5,6.5,0],
            "orientation": [0,0,0]
        }
    },
    "zones":{
        "A":{ 
            "dimensions": [600,300,0],
            "cornerOffset": [600,600,0],
            "orientation": [-90,0,0],
            "deck_file": "project_root/library/deck/layout_sub.json",
            "entry_waypoints": [
                [653.2, 224.6, 232]
            ]
        }
    }
}

The size and position of the Deck is defined by the dimensions, and combination of cornerOffset and orientation respectively.

  • dimensions is the (x,y,z) dimensions with respect to the deck's own coordinate system.
  • cornerOffset is the (x,y,z) coordinates of the bottom-left corner of the deck with respect to world coordinates (typically the origin).
  • orientation is the (rz,ry,rx) rotation of the deck about the bottom-left corner with respect to world coordinates (typically the identity rotation or zero rotation).

Within the deck, slots and zones can be defined.

  • slots are spaces where Labware can be placed. These Labware can be individual tools or vessel holders. Indexing of slots increments numerically, typically starting from 1.
  • zones are regions of nested layouts. As such, a Deck of a smaller modular setup layout can be incorporated as part of a larger layout. Indexing of zones increments alphabetically, typically starting with 'A'.

Here, the dimensions, cornerOffset, and orientation definitions apply similarly, except the latter two takes reference from the parent's origin and orientation. The filename definition in labware_file and deck_file can either be absolute filepaths, or relative to the project repository.

This package uses the same Labware files as those provided by Opentrons, which can be found here, and custom Labware files can be created here. Additional fields can be added to the these Labware files to enable features such as plate stacking and collision avoidance.

  • parameters.isStackable is a boolean value defining if another Labware can be stacked above.
  • slotAbove defines a new slot above the Labware, with similar subfields slotAbove.name, slotAbove.dimensions, slotAbove.cornerOffset, and slotAbove.orientation.
  • exclusionBuffer is the offset from the lower and upper bounds of the Labware bounding box. i.e. [ [left, front, bottom], [right, back, top] ]

WARNING: avoidance checks only apply to destination coordinates. Does not guarantee collision avoidance along intermediate path coordinates when using point-to-point move actions such as move, moveBy or moveTo. Use safeMoveTo instead.

For zones, entry_waypoints lists a sequence of coordinates that defines a safe path a translation tool can take to transit into that particular zone.

1.4 Application and network interoperability

To allow control of the setups over the network, or with other applications, Control-lab-ly provides a way to access the attributes and methods over a communication layer. A Controller encodes and decodes requests and responses using an Interpreter, serializing the data to be sent.

from controllably.core.control import Controller
from controllably.core.interpreter import JSONInterpreter

# 'model' controllers receives requests, triggers execution in registered objects, 
# and transmits the resultant data
worker = Controller(role='model', interpreter=JSONInterpreter())
worker.setAddress('WORKER')

# 'view' controllers transmits requests and receives the resultant data
user = Controller(role='view', interpreter=JSONInterpreter())
user.setAddress('USER')

Each controller subscribes to one or more callbacks that will be called when the controller transmits. In this example, when user tries to transmit a request to target controller ('WORKER'), it will call worker.receiveRequest. Likewise, when worker tries to transmit data back to the request originator ('USER'), it will call user.receiveData.

# request flow: USER -> WORKER
user.subscribe(callback=worker.receiveRequest, callback_type='request', address='WORKER')
# data flow: USER -> WORKER
worker.subscribe(callback=user.receiveData, callback_type='data', address='USER')

A hub-and-spoke network can also be achieved using a new 'relay' controller.

# 'relay' controllers bridges communication between `model` and `view` controllers
hub = Controller(role='relay', interpreter=JSONInterpreter())
hub.setAddress('HUB')

# request flow: USER -> HUB -> WORKER
user.subscribe(callback=hub.relayRequest, callback_type='request', address='HUB', relay=True)
hub.subscribe(callback=worker.receiveRequest, callback_type='request', address='WORKER')

# data flow: WORKER -> HUB -> USER
worker.subscribe(callback=hub.relayData, callback_type='data', address='HUB', relay=True)
hub.subscribe(callback=user.receiveData, callback_type='data', address='USER')

These callbacks should be replaced with user implementation of communication layers, (e.g. socket communication or FastAPI).

2. Additional features

2.1 Managing hardware addresses

Hardware addresses may vary from machine to machine, especially for serial ports and cameras. To keep track of all the different port addresses, the machine ID and its corresponding port addresses are stored in registry.yaml

In the tools folder, a template of registry.yaml has been added to manage the machine-specific addresses of your connected devices (e.g. serial port and camera index). First, use the get_node and get_ports functions to identify your machine's ID and the serial port addresses of your tools.

from controllably.core.connection import get_node, get_ports
get_node()           # Get the unique identifier of your machine
get_ports()          # Get a list of serial port addresses of your connect devices

Next, populate the registry.yaml file with the relevant information.

'012345678901234':              # insert your machine's unique identifier
    cam_index:                  # camera index of the connected imaging devices
      __cam_01__: 1             # NOTE: retain leading and trailing double underscores
    port:                       # addresses of serial ports
      __MyDevice__: COM1        # NOTE: retain leading and trailing double underscores

Lastly, change the value for the serial port address in the config.yaml file(s) to match the registry.

MyDevice:                                   # user-defined name
  module: controllably.Move.Cartesian       # "from" ...
  class: Gantry                             # "import" ...
  settings:
    port: __MyDevice__                      # serial port address
    setting_A: [300,0,200]
    setting_B: [[0,1,0],[-1,0,0]]

2.2 Linting and coding assists

To help with development, linters such as Pylance provide suggestions while coding, based on the types of the objects. To make use of this feature, furnish the __init__.py file with the corresponding tool names and classes from the config.yaml file.

from dataclasses import dataclass
...

# ========== Optional (for typing) ========== #
from controllably.Compound.LiquidMover import LiquidMover
from controllably.Transfer.Liquid.Pipette.Sartorius import Sartorius
from controllably.Move.Cartesian import Gantry

@dataclass
class Platform:
    MyCompoundDevice: LiquidMover
    LiquidDevice: Sartorius
    MoverDevice: Gantry
# ========================================== #

...

More additional features to be documented...


Dependencies

  • matplotlib (>=3.9.2)
  • numpy (>=2.1.0)
  • opencv-python (>=4.11.0.86)
  • pandas (>=2.2.2)
  • parse (>=1.20.2)
  • pyserial (>=3.5)
  • PyYAML (>=6.0.1)
  • scipy (>=1.14.1)
  • pyModbusTCP (>=0.2.0)
  • easy-biologic (>=0.4.0)
  • nest-asyncio (>=1.6.0)
  • setuptools (>=71.0.3)
  • PyMeasure (>=0.15.0)

Contributors

@kylejeanlewis / @Quijanove / @mat-fox

How to Contribute

Issues and feature requests are welcome!

Check the Contributing document to see how to contribute to this project.

License

This project is distributed under the MIT License.


Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

control_lab_ly-2.1.0.tar.gz (239.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

control_lab_ly-2.1.0-py3-none-any.whl (315.3 kB view details)

Uploaded Python 3

File details

Details for the file control_lab_ly-2.1.0.tar.gz.

File metadata

  • Download URL: control_lab_ly-2.1.0.tar.gz
  • Upload date:
  • Size: 239.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for control_lab_ly-2.1.0.tar.gz
Algorithm Hash digest
SHA256 f731e6a0476351956343c03a17da396de02b941782ed578e3309cd4c90d409e7
MD5 62a881bd2aa5aa3d5f7e2de5b6d1a1a4
BLAKE2b-256 4283497b18fe72ed2391ed983a89eff5e1af2cbf88e20bdec75871cdd10f0d75

See more details on using hashes here.

Provenance

The following attestation bundles were made for control_lab_ly-2.1.0.tar.gz:

Publisher: release.yml on kylejeanlewis/control-lab-ly

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file control_lab_ly-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: control_lab_ly-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 315.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for control_lab_ly-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cd7d8e782877339dc0a7c2dbee5f7d7d829d52bb77ad1d8278d6d643d5865d81
MD5 37232abc64ed98cc314482d720d2ed7d
BLAKE2b-256 680c0feb330dfe1ce88801e290ae7dfa02d801927708df3b7d3708b3706d70fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for control_lab_ly-2.1.0-py3-none-any.whl:

Publisher: release.yml on kylejeanlewis/control-lab-ly

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page