Control.lab.ly
Lab Equipment Automation Package
Description
User-friendly package that enables flexible automation an reconfigurable setups for high-throughput experimentation and machine learning.
Package Structure
- Compound
- Control
- Make
- Measure
- Move
- Transfer
- View
Device support
- Make
- (QInstruments) BioShake Orbital Shaker
- (Arduino-based devices)
- Multi-channel LED array
- Multi-channel spin-coater
- Peltier device
- Measure
- (Keithley)
- SMU 2450 Source Measure Unit Instrument
- DAQ 6510 Data Acquisition and Multimeter System
- (PiezoRobotics) Dynamic Mechanical Analyser (DMA)
- (Sentron) SI series pH meters
- (Arduino-based device)
- Precision mass balance
- Load cell
- Move
- (Creality) Ender-3
- (Dobot)
- (Arduino-based device) gantry robot running on GRBL
- Transfer
- (Dobot) Gripper attachments
- (Sartorius) rLINE® dispensing modules
- (TriContinent) C Series syringe pumps
- (Arduino-based device) Peristaltic pump and syringe system
- View
- (FLIR) AX8 thermal imaging camera
- (General) Web cameras
Installation
Control.lab.ly can be found on PyPI and can be easily installed with pip install
.
$ pip install control-lab-ly
Basic Usage
Simple start-up guide for basic usage of the package.
Import desired class
from controllably.Move.Cartesian import Ender
mover = Ender(...)
mover.safeMoveTo((x,y,z))
View documentation
Use the built-in guide to read the documentation for the package.
from controllably import guide_me
guide_me()
Alternatively, details for each class / module / package can be explored by using the help
function.
help(Ender)
For basic usage, this is all you need to know. Check the documentation for more details on each class and function.
Advanced Usage
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 adding plugin drivers.
Import package
import controllably as lab
Optionally, you can set the safety policy for the session. This feature allows the user to prevent collisions before each movement is made. The safety policy has to be assigned before importing any of the Mover
objects.
lab.set_safety('high')
lab.set_safety('low')
lab.set_safety(None)
Contents
- Setups
- Decks
- Addresses
- Plugins
1. Creating a new setup
Create a /configs/MySetup
folder that holds the configuration files for the setup, which includes config.yaml
and layout.json
.
lab.create_setup(setup_name = "MySetup")
1.1 config.yaml
This file stores the configuration and calibration values for your devices.
MyDevice:
module: Move
class: Cartesian.Ender
settings:
port: COM1
setting_A: {'tuple': [300,0,200]}
setting_B: {'array': [[0,1,0],[-1,0,0]]}
Each device configuration starts with the device name, then the following parameters:
module
: top-level category (such as Make, Measure, Move,Transfer, View)
class
: point to specific subclass using dot notation
settings
: various initialisation settings
See the guide for more details on these parameters
Compound devices are similarly configured.
MyCompoundDevice:
module: Compound
class: LiquidMover.LiquidMoverSetup
settings:
setting_C: True
component_config:
MyFirstDevice:
module: Transfer
class: Liquid.SyringeAssembly
settings:
port: COM22
setting_D: 2
MySecondDevice:
module: Mover
class: Jointed.Dobot.M1Pro
settings:
ip_address: '192.0.0.1'
The configuration values for the component devices are nested under the component_config
setting of the compound device.
Lastly, you can define shortcuts (or nicknames) to quickly access the components of compound devices.
SHORTCUTS:
First: 'MyCompoundDevice.MyFirstDevice'
Second: 'MyCompoundDevice.MySecondDevice'
A different serial port address or camera index may be used by different machines for the same device.
See Section 3 to find out how to manage the different addresses used by different machines.
1.2 layout.json
This file stores the layout configuration of your physical workspace, also known as a Deck
.
See Section 2 on how to load this information into the setup.
Optional: if your setup does not involve moving objects around in a pre-defined workspace, a layout configuration may not be required
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. Labware files are JSON files that specifies the external and internal dimensions of a Labware block / object.
{
"reference_points":{
"1": [11.1,22.2,33.3],
"2": [44.4,55.5,66.6],
"3": [77.7,88.8,99.9]
},
"slots":{
"1": {
"name": "MyLabware01",
"exclusion_height": -1,
"filepath": "MyREPO/.../MyLabware01.json"
},
"2": {
"name": "MyLabware02",
"exclusion_height": 0,
"filepath": "MyREPO/.../MyLabware02.json"
},
"3": {
"name": "MyLabware03",
"exclusion_height": 10,
"filepath": "MyREPO/.../MyLabware03.json"
}
}
}
In reference_points
, the bottom-left coordinates of each slot on the deck are defined. Slots are positions where Labware blocks may be placed.
In slots
, the name
of the Labware and the filepath
to the JSON file containing Labware details are defined. The filepath starts with the name of the repository's base folder.
The exclusion_height
is the height (in mm) above the dimensions of the Labware block to steer clear from when performing movement actions. Values less than 0 means the Labware is not avoided.
(Note: Labware avoidance only applies to final coordinates (i.e. destination). Does not guarantee collision avoidance when using point-to-point move actions. Use safeMoveTo()
instead.)
1.3 Load setup
The initialisation of the setup occurs when importing setup
from configs.MySetup
. With setup
, you can access all the devices that you have defined in Section 1.1.
from pathlib import Path
import sys
REPO = 'MyREPO'
ROOT = str(Path().absolute()).split(REPO)[0]
sys.path.append(f'{ROOT}{REPO}')
from configs.MySetup import setup
setup.MyDevice
setup.First
2. Managing a deck
Optional: if your setup does not involve moving items around in a pre-defined workspace, a Deck
may not be required
2.1 Loading a deck
To load a Deck
from the layout file, use the loadDeck()
function of a Mover
object (or its subclasses).
from configs.MySetup import setup, LAYOUT_FILE
setup.Mover.loadDeck(LAYOUT_FILE)
LAYOUT_FILE
contains the details that has been defined in layout.json
(see Section 1.2)
2.2 Loading a Labware
To load a Labware
onto the deck, use the loadLabware()
method of the Deck
object.
setup.Mover.deck.loadLabware(...)
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. Labware files are JSON files that specifies the external and internal dimensions of a Labware block / object.
3. Managing project addresses
A /configs
folder will have been created in the base folder of your project repository to store all configuration related files from which the package will read from, in Section 1. A template of registry.yaml
has also been added to the /configs
folder to manage the machine-specific addresses of your connected devices (e.g. serial port and camera index).
'012345678901234':
cam_index:
__cam_01__: 1
__cam_02__: 0
port:
__MyDevice__: COM1
__MyFirstDevice__: COM22
Use the Helper.get_node()
function to get the 15-digit unique identifier of your machine
Use the Helper.get_port()
function to get the serial port addresses of your connect devices
lab.Helper.get_node()
lab.Helper.get_ports()
Afterwards, change the value for the serial port address in the config.yaml
file to match the registry.
MyDevice:
module: Move
class: Cartesian.Ender
settings:
port: __MyDevice__
4. Using plugins
Optional: if drivers for your hardware components are already included, plugins may not be required
User-defined plugins can be easily written and integrated into Control.lab.ly. All available classes and functions can be found in lab.modules
.
lab.guide_me()
lab.modules.at.Make.Something.Good.MyClass
4.1 Registering a Class or Function
You can import the class and register the object using the Factory.register()
function.
from my_module import MyClass
lab.Factory.register(MyClass, "Make.Something.Good")
4.2 Registering a Python module
Alternatively, you can automatically register all Classes and Functions in a Python module just by importing it.
- Declare a
__where__
global variable to indicate where to register the module - At the end of the .py file, import and call the
include_this_module()
function
__where__ = "Make.Something.Good"
def MyClass:
...
def my_function:
...
from controllably import include_this_module
include_this_module()
The Classes and Functions in the module will be registered when you import the module.
from my_module import MyClass, my_function
Dependencies
- Markdown (>=3.3.4)
- numpy (>=1.19.5)
- opencv-python (>=4.5.4.58)
- pandas (>=1.2.4)
- pyModbusTCP (>=0.2.0)
- pyserial (>=3.5)
- PySimpleGUI (>=4.60.5)
- PyVISA (>=1.12.0)
- PyVISA-py (>=0.7)
- PyYAML (>=6.0)
- tkhtmlview (>=0.2.0)
Contributors
@kylejeanlewis
@mat-fox
@Quijanove
@AniketChitre
How to Contribute
Issues and feature requests are welcome!
License
This project is distributed under the MIT License.
Change Log
Version 1.3.2
Feature enhancements, bug fixes and patches. First released 24 Apr 2024.
Added
- add new
delay
parameter in Keithley.programs.IV_Scan
Changed
- fix critical bug in setting sense/source limits for
KeithleyDevice
- fix bugs in
KeithleyDevice
, Peltier
, ForceSensor
Version 1.3.1
Feature enhancements, bug fixes and patches. First released 11 Apr 2024.
Added
- implementation of
TriContinent.pullback()
- new
Well
properties and option in return list of wells by rows instead of columns
Changed
- fix bugs in
Peltier
(setTemperature()
and getTemperature()
) - fix bugs in
Ender
(setTemperature()
and getTemperature()
) - fix bug in
Keithley.setFunction()
- generalise
IV_Scan
to take either currents or voltages
Version 1.3.0
Feature enhancements, bug fixes and patches. First released 19 Feb 2024.
Added
- added check for poor physical connection with
PiezoRoboticsDevice
- Keithley
- added new subclasses of
KeithleyDevice
: DAQ6510
and SMU2450
- added way to read and save model name of
KeithleyDevice
- added new Keithley program for DAQ to scan multiple channels
- new methods
clearErrors()
, setDisplay()
, setFunction()
Changed
- changed the way travel times are calculated for
Mover
tools, so that they reflect the actual physical travel times more accurately - changed ability to delay initialisation of TriContinent pumps until it is in a more convenient location
- fixed few bugs with
SentronProbe
tool
Removed
- removed old archived files
Version 1.2.0
Feature enhancements, bug fixes and patches. First released 22 Aug 2023.
Added
ForceClampSetup
classLoadCell
classBalance
class (subclass of LoadCell
)
Changed
- update
LEDArray
to delay timing loop by 0.1s - fix bug with initialising
PiezoRoboticsDevice
- update
getTemperature()
across multiple classes to standardise output Mover
class
- speed-related attributes and properties
- add method to calculate travel time based on target speed, acceleration and deceleration
- modify how speeds and max speeds interact with
move()
and safeMoveTo()
Cartesian
class
setSpeed()
and setSpeedFraction()
- get max speed settings from device upon connecting
- change calculation of movement wait times using device speed and acceleration
Primitiv
class
- change the class name to
Grbl
and Primitiv
as a subclass name to retain compatibility - overload
moveTo()
and _query()
methods to use jogging mode - modify the sequence of commands to halt movement
- implement
getAcceleration()
, getCoordinates()
, getMaxSpeed()
- clear errors and setting feed rate upon establishing connection
Ender
class
- change the class name to
Marlin
and Ender
as a subclass name to retain compatibility - added method to immediately stop movement
- implement
getAcceleration()
, getCoordinates()
, getMaxSpeed()
- separate methods for
setSpeed()
(absolute speed in mm/s) and setSpeedFraction()
(proportional speed to max speed)
Dobot
class
- Flir
AX8
class
- added
invertPalette()
method - added data parsing methods
_decode_from_modbus()
and _encode_to_modbus()
KeithleyDevice()
class
- added
ip_address
property - added options for
_read()
method - added
readline()
method - implement
disconnect()
method
- fix bug with Keithley programs using
device.run()
instead of device.start()
Removed
Thermal
class- removed dependency on
imutils
package
Versions 1.1.2 & 1.1.1
Bug fixes and patches. First released 12 Jul 2023.
Added
- import
Device
classes in init files to view documentation - added library for GRBL status and error codes
- add
update_root_direcctory()
function to Helper
Changed
- fix bug with adding new rows into Dataframes
- use
reset_input_buffer()
instead of flushInput()
for pyserial.Serial
objects - print the actual string sent to Serial devices
- update methods in
Deck
, Labware
, and Well
to camelCase - update
Deck.isExcluded()
to apply strict inequalities when determining out-of-range coordinates - update
LiquidMover
to insert a portion of tip into rack before ejecting - update
Spinner
- fix bug with sending commands
- added
_query()
method - pass verbosity to individual spinners
- verbosity of
Measure
objects pass through to devices - update
PiezoRoboticsDevice
- initialize upon connection
- raise errors when encountering them
- update
Mover
- modify
setFlag()
to print kwargs instead of raising error if assigned values are not boolean - use
safe_height
(if defined) instead of z-coordinate of home in safeMoveTo()
- added
getSettings()
method
- update
Gantry
class
- read multiple flines in
_query()
- check that commands end with newline before sending to device
- fix bug with changing speeds
- update
Ender
- added
getTemperature()
, holdTemperature()
, isAtTemperature()
methods - modified
setTemperature()
to use Marlin code to wait for temperature
- update
Primitiv
class
- add
getStatus()
and stop()
methods - add
_get_settings()
method
- fix bug in
M1Pro.setHandedness()
- update
Sartorius
class
tip_inset_mm
now an instance attribute with initialisation parameters- set
tip_on
flag to False when performing eject()
Version 1.1.0
Bug fixes and feature enhancements. First released 15 Jun 2023.
Added
ForceSensor
- DIY force sensor (#55)BioShake
- orbital shaker from QInstruments (#56)SentronProbe
- pH meter probe from Sentron (#75)Maker
- added
execute()
abstract method and implemented in subclasses
- GUI
Guide
- documentation guideMakerPanel
- daptive GUI controls for Maker
objects (#87)
Changed
M1Pro
- fix issue with changing handedness (#86)
Peltier
- rename
getTemperatures()
to getTemperature()
- rename
isReady()
to isAtTemperature()
- rename
set_point
to set_temperature
Ender
- rename
set_point
to set_temperature
TriContinent
- rename
step_limit
to limits
- Refactor and reorganize
GUI
code - Refactor code in
helper
and factory
- Updated documentation
Removed
Analyse
sub-package removedControl.Schedule
sub-package removed- Unnecessary commented-out blocks of code
Version 1.0.1
Bug fixes and minor feature enhancements. First released 08 May 2023.
Added
LiquidMover
- Added
LiquidMover.touchTip()
method to touch the pipette tip against the walls of the vessel to remove excess liquid on the outside of the tip (#62) - Added option to indicate the position of the first available pipette tip in
LiquidMover
(#61)
- Added adaptive GUI controls for
Liquid
objects (#70) - Added option to indicate which digital IO channel to use with Dobot attachments (#53)
Changed
MassBalance
- Updated to the use
pd.concat()
instead of pd.DataFrame.append()
, which is going ot be deprecated (#63) - Fixed endless loop for when
MassBalance
tries to zero()
while recording data (#60)
- Changed the
Image
class and methods into functions within a module (#54) - Fixed the tool offset of pipette when pipette tip is attached, and accounts for the length of pipette that enters the pipette tip (#64)
- Changed to using more precise time interval measurements by moving from
time.time()
to time.perf_counter()
(#68) - Fixed discrepancy in aspirate and dispense speed for
Sartorius
(#73) and let speed return to a global default value (#72) - Updated documentation
Version 1.0.0
Major overhaul in package structure. Standardisation of methods and consolidation of common methods. First released 12 Apr 2023.
Added
- Usage of Abstract Base Classes (ABCs) to define a base class with abstract methods that needs to be implemented through sub-classing (#31)
- Usage of Protocols to provide an interface between different classes of objects (#30)
- Usage of Dataclasses to store complex data
- Usage of decorators to modify methods
- Introduced different functions to parse the program docstring and find program parameters
Changed
- Standardised methods and consolidated common methods
- Added type hints (#28)
- Moved Dobot attachments from Mover to Transfer.Substrate
- Split GUI Panels into individual files
- Split Dobot arms into individual files
- Split functions/methods in
misc.py
into individual files. - Changed
_flags
to a public attribute flags
- Update documentation (#27, #28, #29)
Removed
- Unnecessary commented-out blocks of code
Version 0.0.x
(0.0.4.x) Introduced control for Peltier device and TriContinent Series C syringe pumps. First released 10 Mar 2023.
(0.0.3.x) Minor changes to movement robot safety and pipette control. Introduced control for LED array. First released 08 Mar 2023.
(0.0.2.x) Updates in setting up configuration files. First released 24 Feb 2023.
(0.0.1.x) First release of Control.lab.ly distributed on 23 Feb 2023.
(0.0.0.x) Pre-release packaging checks
Added
0.0.4
- Added control for
Peltier
(#23)
- set and get temperatures
- hold temperatures for desired duration
- checks if target temperature has been reached by checking power level lower than a threshold or time passed over a predefined duration, once the temperature is within tolerance
- ability to record temperatures and timestamps
- Added control for
TriContinent
and TriContinentEnsemble
(#25)
- single actions such as
empty
, fill
, initialise
, move actions, set speeds and valves, and wait - compound actions such as
aspirate
, dispense
, and prime
0.0.3
- Added safety measures for movement actions (#24)
- In
Deck
, added exclusion zones when reading the layout.json
file and new method is_excluded()
to check if target coordinate is within the exclusion zone - In
Mover
, update isFeasible()
method to check if target coordinates violates the deck's exclusion zone - New function
set_safety()
defines safety modes when starting a new session to pause for input (in "high" safety setting) and to wait for safety countdown (in "low" safety setting)
Make.Light.LEDArray
for controlling LEDs in the photo-reactor, as well as timing the LED "on" durations (#35)
0.0.2.2
- Added import of
CompoundSetup
class
0.0.2
Deck.at()
method for directly referencing slots using either index numbers or names- New
CompoundSetup
class for common methods of Compound
devices - New
load_deck()
function to load Deck
after initialisation
0.0.1
- Make
- Multi-channel spin-coater [Arduino]
- Measure
- (Keithley) 2450 Source Measure Unit (SMU) Instrument
- (PiezoRobotics) Dynamic Mechanical Analyser (DMA)
- Precision mass balance [Arduino]
- Move
- (Creality) Ender-3
- (Dobot) M1 Pro
- (Dobot) MG400
- Primitiv [Arduino]
- Transfer
- (Sartorius) rLINE® dispensing modules
- Peristaltic pump and syringe system [Arduino]
- View
- (FLIR) AX8 thermal imaging camera - full functionality in development
- Web cameras [General]
- misc
- Helper class for most common actions
- create_configs: make new directory for configuration files
- create_setup: make new directory for specific setup-related files
- load_setup: initialise setup on import during runtime
Changed
0.0.4
0.0.3.1
0.0.3
Sartorius
- made the blowout/home optional for the dispense method upon emptying the pipette
- Update documentation
0.0.2.1
- Changed template files for
lab.create_setup()
0.0.2