Training a Custom Deck Position With the iSWAP
When configuring a liquid handler, one may need to define custom deck positions for specific experimental or operational setups. This can include positions that are not part of a standard deck configuration, for example, a custom 3D printed plate carrier, or an off-deck integrated plate reader position.
This guide will explain the process of creating and validating custom deck positions. In this example, we use a custom plate reader position that will be physically validated with the iSWAP and then added to the deck for use in future workflows. While it is also possible to perform with the CO-RE grippers, the iSWAP is more common for reaching beyond the standard deck range.
Prerequisites
- A switched on Microlab STAR device
- A plate carrier, a plate, and a plate reader or other custom position to validate
- A running Microlab STAR connector
- Basic understanding of the liquid handler class (See the liquid handler tutorial)
- Basic understanding of the iSWAP module (See Using the iSWAP)
- Basic understanding of how custom labware is created (See the creating custom labware tutorial)
Basic Setup
Before beginning the training, we need to ensure that the system is properly configured and the necessary components are initialized. This includes powering on the device, verifying the connector is running and connected, arranging the deck with the required labware, and picking up the labware with the iSWAP.
Power On the System
Ensure that the Microlab STAR is powered on and ready for operation. Verify that the connector is running and connected to the UniteLabs platform.
from unitelabs.liquid_handling.hamilton import MicrolabSTAR
# Initialize the Hamilton Microlab STAR
hamilton = MicrolabSTAR(name="Microlab STAR") # Or your connector's name on the UniteLabs platform
await hamilton.initialize()
Arrange the Deck
Arrange the deck layout using the components from the labware library. This guide uses a plate carrier with one standard 96 well microtiter plate that we move from one carrier site to the custom position.
from unitelabs.labware import Standard96Plate, Vector
from unitelabs.labware.hamilton import PLT_CAR_L5MD_A00
plate_carrier = PLT_CAR_L5MD_A00()
plate = Standard96Plate()
plate_carrier[0] = plate
hamilton.deck.add(plate_carrier, track=1)
Pick up Labware
Training locations should be done with labware in the iSWAP to ensure accuracy. This ensures that the positioning is valid for the specific labware to be transferred. Make sure that the plate is placed on the site and pass it to the iSWAP's pick_up_from method. The iSWAP moves on a safe traverse height to the site and picks up the plate. The method automatically transfers the plate from the carrier site to the iSWAP module. When the pick_up_direction argument is omitted, the iSWAP will approach from whichever orientation it is currently in. Here the strength parameter is set to the default value, which is used if not specified.
from unitelabs.liquid_handling.hamilton.modules.iswap.interfaces import Direction
await hamilton.iswap.pick_up_from(
plate_carrier[0],
# The direction from which the iSWAP arm approaches the labware to pick up.
pick_up_direction=Direction.LEFT,
# The pressure to apply on the plate ranging from 0 (low) to 99 (high).
strength=30,
)
The plate is now accessible on the iSWAP and the carrier site should be empty.
assert plate_carrier[0].get() == None
assert hamilton.iswap.get() == plate
Get & Validate Coordinates
To find the absolute coordinates of the target deck position, we will use the iSWAP's move_to and move_by methods while checking the physical position after each movement until we reach the desired location. This iterative approach ensures safe and precise positioning.
Move to Starting Point
Now that the plate is picked up, it can be moved to the target training position with the iSWAP's move_to method. The plate's center will be placed at the provided location (this will become an important distinction in later steps).
Here we demonstrate use of a Vector to specify the target deck coordinates. This represents a "best estimate" of the desired position that will need to be fine-tuned in subsequent steps. The z-height should be set to a safe distance above the deck to avoid collisions.
from unitelabs.labware import Vector
# Location at specific deck coordinates (in mm)
target_location = Vector(x=200.0, y=100.0, z=250.0)
await hamilton.iswap.move_to(location=target_location, direction=Direction.RIGHT)
It may also be desirable to estimate the target location relative to existing deck positions or labware. In this case it is useful to get and update the existing location before using it in the move_to command.
Important: When using relative positioning, keep in mind that .location properties are relative to the labware or resource's origin (the bottom-left corner), so we need to adjust the coordinates to properly center the plate relative to the iSWAP's center reference point. Conveniently, plates have a .center property that returns the center offset as a Vector.
# Estimated target location is 100mm to the right of the carrier position, with a safe z-height
target_location = plate_carrier[4].location.update(z=250.0)
# Location is relative to the CarrierSite's origin, so add the plate's center to center the iSWAP properly
target_location = target_location + plate.center
# Use the estimated target for the move
await hamilton.iswap.move_to(location=target_location, direction=Direction.RIGHT)
Fine-tuning
After moving to the estimated position, we will need to make small adjustments to achieve precise alignment with the actual physical position. This can be done by making small incremental movements using the move_by method with provided offset vectors. These offset vectors are relative to the current position of the iSWAP.
It will likely be necessary to repeat this step a few times. Generally speaking, each adjustment should be small (typically 1-5mm) and the process should be repeated until the position is visually confirmed to be correct (that is, until the plate held by the iSWAP is physically seated in the desired location). The z height should gradually be reduced until the plate is properly seated in the target position.
Important: The current_location method returns the current position of the iSWAP's center.
from unitelabs.labware import Vector
# For example: move 3mm right, 2mm forward, and 5mm down
await hamilton.iswap.move_by(offset=Vector(x=3.0, y=2.0, z=-5.0))
validated_position = await hamilton.iswap.current_location()
print(f"Validated position: {validated_position}")
Save the Coordinates
After fine-tuning the plate to the desired position and obtaining that position's precise coordinates, we need to integrate this validated position into a deck configuration for future use. We will do this by creating a custom HamiltonCarrier and adding it to the deck at the validated_position. This step ensures that the position is available for methods that interact with a CarrierSite, such as pick_up_from and put_down gripper commands; furthermore, it is much safer to save the position in the deck configuration this way than to directly reuse the validated_position Vector in future workflows.
Create a Custom Carrier
To add a single position to the deck, we will create a custom carrier with one carrier site. The carrier site will not be offset from the carrier, and will have the exact dimensions of the plate we are training.
It is strongly recommended to review the creating custom labware tutorial for more information on creating custom labware.
import collections.abc
import dataclasses
from unitelabs.labware import CarrierSite, Orientation, StandardMicroplateDimensions, Vector
from unitelabs.labware.hamilton import HamiltonCarrier, LabwareType
@dataclasses.dataclass
class PlateReaderCarrier(HamiltonCarrier[StandardMicroplateDimensions]):
rows: int = 1
dimensions: Vector = dataclasses.field(default_factory=lambda: Vector(x=127.0, y=86.0, z=0.0))
labware: LabwareType = LabwareType.PLATES
orientation: Orientation = Orientation.LANDSCAPE
children: collections.abc.Sequence[CarrierSite] = dataclasses.field(
repr=False,
default_factory=lambda: [
CarrierSite(dimensions=Vector(x=127.0, y=86.0))
],
)
This custom carrier can then be used in the deck configuration to define our custom position for the plate reader.
Use the Carrier
After creating a custom carrier, we must add it to the deck at the validated position. Importantly, the validated_position from current_location refers to the iSWAP's center, while the add method refers to a carrier's origin. Thus we must offset the position we use in the add call by half the plate's dimensions to align the carrier's center with the validated position. We will once again take advantage of the plate's center property to get these values.
Note: Here we subtract 100mm from the z-coordinate, since add will place the carrier on the deck relative to the deck origin, which is always 100mm above the absolute origin (what is referenced in current_location).
from . import PlateReaderCarrier
reader_carrier = PlateReaderCarrier(identifier="reader_carrier")
# Obtained from above process, for illustration only.
validated_position = Vector(x=243.2, y=181.0, z=146.8)
# Subtract plate center to get origin
validated_position_origin = validated_position - plate.center
# Subtract 100mm from z to account for deck origin
carrier_origin = validated_position_origin - Vector(z=100.0)
hamilton.deck.add(
reader_carrier,
location=carrier_origin
)
Verify the Carrier Position
Now that the carrier is added to the deck, we can verify that the carrier's position is correctly saved by simply attempting to put the plate down at that location.
await hamilton.iswap.pick_up_from(
source=plate_carrier[0],
pick_up_direction=Direction.LEFT
)
await hamilton.iswap.put_down(
target=reader_carrier[0],
drop_direction=Direction.RIGHT
)
Conclusion
You should now have a solid understanding of how to train custom deck positions using the iSWAP gripper and validate them for precise labware placement.