Byonoy Absorbance 96 Automate#
Communication Protocol / Hardware: HID / USB-A/C
Communication Level: Firmware
VID:PID
16d0:1199Takes a single SLAS-format 96-wellplate on the detection unit, enables movement of the cap/illumination unit over it, and reads all 96 wells simultaneously.
Up to 6 configurable absorbance wavelengths (dependent on specifications during purchase).
Setup Instructions (Physical)#
The Byonoy Absorbance 96 Automate (A96A) is a an absorbance plate reader consisting of…
a
detection_unitcontaining the liqht sensors,a
illumination_unitcontaining the light source,a
parking_unitrepresenting a simple resource_holder for theillumination_unit, andan
sbs_adapterwhich is an optional holder for thedetection_unitorparking_unit, enabling placement of this machine onto a standard SLAS/SBS-format plate holder.
Communication#
It requires only one cable connections to be operational:
USB cable (USB-C at
baseend; USB-A at control PC end)
Setup Instructions (Programmatic)#
If used with a liquid handler, first setup the liquid handler:
from pylabrobot.liquid_handling import LiquidHandler, LiquidHandlerChatterboxBackend
from pylabrobot.resources import STARDeck
lh = LiquidHandler(deck=STARDeck(), backend=LiquidHandlerChatterboxBackend())
from pylabrobot.resources import (
hamilton_mfx_carrier_L5_base, MFX_CAR_L4_SHAKER , # MFX CARRIERS
MFX_DWP_rackbased_module, hamilton_mfx_plateholder_DWP_metal_tapped,
)
#
mfx_carrier_2_plateholders = hamilton_mfx_carrier_L5_base(
name="mfx_carrier_2_plateholders",
modules={
4: hamilton_mfx_plateholder_DWP_metal_tapped(name=f"mfx_plateholder_1"),
2: hamilton_mfx_plateholder_DWP_metal_tapped(name=f"mfx_plateholder_parking_unit"),
0: hamilton_mfx_plateholder_DWP_metal_tapped(name=f"mfx_plateholder_detection_unit")
}
)
lh.deck.assign_child_resource(mfx_carrier_2_plateholders, rails=12)
await lh.setup()
Setting up the liquid handler.
Then generate a plate definition for the plate you want to read:
from pylabrobot.resources.coordinate import Coordinate
from pylabrobot.resources.cellvis.plates import CellVis_96_wellplate_350uL_Fb
demo_plate = CellVis_96_wellplate_350uL_Fb(name='demo_plate')
mfx_carrier_2_plateholders[4] = demo_plate
from pylabrobot.plate_reading.byonoy import (
byonoy_sbs_adapter,
byonoy_a96a_detection_unit,
byonoy_a96a_illumination_unit,
byonoy_a96a_parking_unit
)
# Detection Unit
a96a_sbs_adapter_DU = byonoy_sbs_adapter(name="a96a_sbs_adapter_DU")
a96a_detection_unit = byonoy_a96a_detection_unit(name="a96a_detection_unit")
a96a_sbs_adapter_DU.assign_child_resource(a96a_detection_unit)
mfx_carrier_2_plateholders[0] = a96a_sbs_adapter_DU
# Parking Unit
a96a_sbs_adapter_PU = byonoy_sbs_adapter(name="a96a_sbs_adapter_PU")
a96a_parking_unit = byonoy_a96a_parking_unit(name="a96a_parking_unit")
a96a_sbs_adapter_PU.assign_child_resource(a96a_parking_unit)
mfx_carrier_2_plateholders[2] = a96a_sbs_adapter_PU
a96a_illumination_unit = byonoy_a96a_illumination_unit(name="a96a_illumination_unit")
a96a_detection_unit.assign_child_resource(a96a_illumination_unit)
Resource 'a96a_illumination_unit' is very high on the deck: 257.948 mm. Be careful when traversing the deck.
a96a_detection_unit.get_location_wrt(lh.deck)
Coordinate(x=337.75, y=67.0, z=200.95)
lh.summary()
Rail Resource Type Coordinates (mm)
===========================================================================================
(-6) ├── trash_core96 Trash (-58.200, 106.000, 216.400)
│
(12) ├── mfx_carrier_2_plateholders MFXCarrier (347.500, 063.000, 100.000)
│ ├── demo_plate Plate (351.500, 456.000, 182.050)
│ ├── a96a_sbs_adapter_PU ResourceHolder (351.500, 264.000, 183.950)
│ │ ├── a96a_parking_unit ByonoyBaseUnit (337.750, 259.000, 200.950)
│ ├── a96a_sbs_adapter_DU ResourceHolder (351.500, 072.000, 183.950)
│ │ ├── a96a_detection_unit ByonoyBaseUnit (337.750, 067.000, 200.950)
│
(55) ├── waste_block Resource (1315.000, 115.000, 100.000)
│ ├── teaching_tip_rack TipRack (1320.900, 461.100, 100.000)
│ ├── core_grippers HamiltonCoreGrippers (1337.500, 125.000, 205.000)
│
(56) ├── trash Trash (1340.000, 190.600, 137.100)
lh.summary()
Rail Resource Type Coordinates (mm)
===============================================================================================
(-6) ├── trash_core96 Trash (-58.200, 106.000, 216.400)
│
(12) ├── mfx_carrier_2_plateholders MFXCarrier (347.500, 063.000, 100.000)
│ ├── demo_plate Plate (351.500, 456.000, 182.050)
│ ├── a96a_sbs_adapter_PU ResourceHolder (351.500, 264.000, 183.950)
│ │ ├── a96a_parking_unit ByonoyBaseUnit (337.750, 259.000, 200.950)
│ ├── a96a_sbs_adapter_DU ResourceHolder (351.500, 072.000, 183.950)
│ │ ├── a96a_detection_unit ByonoyBaseUnit (337.750, 067.000, 200.950)
│ │ │ ├── a96a_illumination_unitResource (337.750, 067.000, 215.050)
│
(55) ├── waste_block Resource (1315.000, 115.000, 100.000)
│ ├── teaching_tip_rack TipRack (1320.900, 461.100, 100.000)
│ ├── core_grippers HamiltonCoreGrippers (1337.500, 125.000, 205.000)
│
(56) ├── trash Trash (1340.000, 190.600, 137.100)
await lh.move_resource(a96a_illumination_unit, a96a_parking_unit, pickup_distance_from_top=13.5)
Resource 'a96a_illumination_unit' is very high on the deck: 257.948 mm. Be careful when traversing the deck.
Picking up resource: ResourcePickup(resource=Resource(name='a96a_illumination_unit', location=Coordinate(000.000, 000.000, 014.100), size_x=155.26, size_y=95.48, size_z=42.898, category=None), offset=Coordinate(x=0, y=0, z=0), pickup_distance_from_top=13.5, direction=<GripDirection.FRONT: 1>)
Dropping resource: ResourceDrop(resource=Resource(name='a96a_illumination_unit', location=Coordinate(000.000, 000.000, 014.100), size_x=155.26, size_y=95.48, size_z=42.898, category=None), destination=Coordinate(x=337.75, y=259.0, z=200.95), destination_absolute_rotation=Rotation(x=0, y=0, z=0), offset=Coordinate(x=0, y=0, z=0), pickup_distance_from_top=13.5, pickup_direction=<GripDirection.FRONT: 1>, direction=<GripDirection.FRONT: 1>, rotation=0)
lh.summary()
Rail Resource Type Coordinates (mm)
===============================================================================================
(-6) ├── trash_core96 Trash (-58.200, 106.000, 216.400)
│
(12) ├── mfx_carrier_2_plateholders MFXCarrier (347.500, 063.000, 100.000)
│ ├── <empty>
│ ├── a96a_sbs_adapter_PU ResourceHolder (351.500, 264.000, 183.950)
│ │ ├── a96a_parking_unit ByonoyBaseUnit (337.750, 259.000, 200.950)
│ │ │ ├── a96a_illumination_unitResource (337.750, 259.000, 215.050)
│ ├── a96a_sbs_adapter_DU ResourceHolder (351.500, 072.000, 183.950)
│ │ ├── a96a_detection_unit ByonoyBaseUnit (337.750, 067.000, 200.950)
│
(55) ├── waste_block Resource (1315.000, 115.000, 100.000)
│ ├── teaching_tip_rack TipRack (1320.900, 461.100, 100.000)
│ ├── core_grippers HamiltonCoreGrippers (1337.500, 125.000, 205.000)
│
(56) ├── trash Trash (1340.000, 190.600, 137.100)
await lh.move_plate(demo_plate, a96a_detection_unit)
Picking up resource: ResourcePickup(resource=Plate(name='demo_plate', size_x=127.6, size_y=85.75, size_z=13.83, location=None), offset=Coordinate(x=0, y=0, z=0), pickup_distance_from_top=9.87, direction=<GripDirection.FRONT: 1>)
Dropping resource: ResourceDrop(resource=Plate(name='demo_plate', size_x=127.6, size_y=85.75, size_z=13.83, location=None), destination=Coordinate(x=337.75, y=67.0, z=200.95), destination_absolute_rotation=Rotation(x=0, y=0, z=0), offset=Coordinate(x=0, y=0, z=0), pickup_distance_from_top=9.87, pickup_direction=<GripDirection.FRONT: 1>, direction=<GripDirection.FRONT: 1>, rotation=0)
lh.summary()
Rail Resource Type Coordinates (mm)
===============================================================================================
(-6) ├── trash_core96 Trash (-58.200, 106.000, 216.400)
│
(12) ├── mfx_carrier_2_plateholders MFXCarrier (347.500, 063.000, 100.000)
│ ├── <empty>
│ ├── a96a_sbs_adapter_PU ResourceHolder (351.500, 264.000, 183.950)
│ │ ├── a96a_parking_unit ByonoyBaseUnit (337.750, 259.000, 200.950)
│ │ │ ├── a96a_illumination_unitResource (337.750, 259.000, 215.050)
│ ├── a96a_sbs_adapter_DU ResourceHolder (351.500, 072.000, 183.950)
│ │ ├── a96a_detection_unit ByonoyBaseUnit (337.750, 067.000, 200.950)
│ │ │ ├── demo_plate Plate (360.250, 072.000, 216.950)
│
(55) ├── waste_block Resource (1315.000, 115.000, 100.000)
│ ├── teaching_tip_rack TipRack (1320.900, 461.100, 100.000)
│ ├── core_grippers HamiltonCoreGrippers (1337.500, 125.000, 205.000)
│
(56) ├── trash Trash (1340.000, 190.600, 137.100)
await lh.move_resource(a96a_illumination_unit, a96a_detection_unit, pickup_distance_from_top=13.5)
Resource 'a96a_illumination_unit' is very high on the deck: 257.948 mm. Be careful when traversing the deck.
Picking up resource: ResourcePickup(resource=Resource(name='a96a_illumination_unit', location=Coordinate(000.000, 000.000, 014.100), size_x=155.26, size_y=95.48, size_z=42.898, category=None), offset=Coordinate(x=0, y=0, z=0), pickup_distance_from_top=13.5, direction=<GripDirection.FRONT: 1>)
Dropping resource: ResourceDrop(resource=Resource(name='a96a_illumination_unit', location=Coordinate(000.000, 000.000, 014.100), size_x=155.26, size_y=95.48, size_z=42.898, category=None), destination=Coordinate(x=337.75, y=67.0, z=200.95), destination_absolute_rotation=Rotation(x=0, y=0, z=0), offset=Coordinate(x=0, y=0, z=0), pickup_distance_from_top=13.5, pickup_direction=<GripDirection.FRONT: 1>, direction=<GripDirection.FRONT: 1>, rotation=0)
lh.summary()
Rail Resource Type Coordinates (mm)
===============================================================================================
(-6) ├── trash_core96 Trash (-58.200, 106.000, 216.400)
│
(12) ├── mfx_carrier_2_plateholders MFXCarrier (347.500, 063.000, 100.000)
│ ├── <empty>
│ ├── a96a_sbs_adapter_PU ResourceHolder (351.500, 264.000, 183.950)
│ │ ├── a96a_parking_unit ByonoyBaseUnit (337.750, 259.000, 200.950)
│ ├── a96a_sbs_adapter_DU ResourceHolder (351.500, 072.000, 183.950)
│ │ ├── a96a_detection_unit ByonoyBaseUnit (337.750, 067.000, 200.950)
│ │ │ ├── demo_plate Plate (360.250, 072.000, 216.950)
│ │ │ ├── a96a_illumination_unitResource (337.750, 067.000, 215.050)
│
(55) ├── waste_block Resource (1315.000, 115.000, 100.000)
│ ├── teaching_tip_rack TipRack (1320.900, 461.100, 100.000)
│ ├── core_grippers HamiltonCoreGrippers (1337.500, 125.000, 205.000)
│
(56) ├── trash Trash (1340.000, 190.600, 137.100)
await lh.move_resource(demo_plate, mfx_carrier_2_plateholders[4], pickup_distance_from_top=13.5)
Picking up resource: ResourcePickup(resource=Plate(name='demo_plate', size_x=127.6, size_y=85.75, size_z=13.83, location=Coordinate(022.500, 005.000, 016.000)), offset=Coordinate(x=0, y=0, z=0), pickup_distance_from_top=13.5, direction=<GripDirection.FRONT: 1>)
Dropping resource: ResourceDrop(resource=Plate(name='demo_plate', size_x=127.6, size_y=85.75, size_z=13.83, location=Coordinate(022.500, 005.000, 016.000)), destination=Coordinate(x=351.5, y=456.0, z=182.05), destination_absolute_rotation=Rotation(x=0, y=0, z=0), offset=Coordinate(x=0, y=0, z=0), pickup_distance_from_top=13.5, pickup_direction=<GripDirection.FRONT: 1>, direction=<GripDirection.FRONT: 1>, rotation=0)
demo_plate.get_location_wrt(lh.deck)
Coordinate(x=360.25, y=72.0, z=216.95)
a96a_detection_unit.get_location_wrt(lh.deck)
Coordinate(x=337.75, y=67.0, z=200.95)
a96a_detection_unit.get_location_wrt(lh.deck)+ a96a_detection_unit.child_location
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[16], line 1
----> 1 a96a_detection_unit.get_location_wrt(lh.deck)+ a96a_detection_unit.child_location
AttributeError: 'ByonoyBaseUnit' object has no attribute 'child_location'
a96a_detection_unit.assign_child_resource(a96a_illumination_unit)
lh.summary()
[print(x) for x in a96a_sbs_adapter.children]
ByonoyA96ABaseUnit(name='a96a_detection_unit', location=Coordinate(-13.750, -05.000, 017.000), size_x=155.26, size_y=95.48, size_z=18.5, category=resource_holder)
[None]
[print(x) for x in a96a_detection_unit.children]
Plate(name='demo_plate', size_x=127.6, size_y=85.75, size_z=13.83, location=Coordinate(022.500, 005.000, 016.000))
Resource(name='a96a_illumination_unit', location=Coordinate(000.000, 000.000, 014.100), size_x=155.26, size_y=95.48, size_z=42.898, category=None)
[None, None]
Now instantiate the Byonoy absorbance plate reader:
from pylabrobot.plate_reading.byonoy import (
byonoy_absorbance_adapter,
byonoy_absorbance96_base_and_reader
)
cap_adapter = byonoy_absorbance_adapter(name='cap_adapter')
base, reader_cap = byonoy_absorbance96_base_and_reader(name='base', assign=True)
lh.deck.assign_child_resource(cap_adapter, location=Coordinate(400, 0, 0))
await reader_cap.setup(verbose=True)
reader_cap.setup_finished
Connected to Bynoy Absorbance 96 Automate (via HID with VID=5840:PID=4505) on b'DevSrvsID:4308410804'
Identified available wavelengths: [420, 600] nm
True
reader_cap.backend.io.device_info
{'path': b'DevSrvsID:4308410804',
'vendor_id': 5840,
'product_id': 4505,
'serial_number': 'BYOMAA00058',
'release_number': 512,
'manufacturer_string': 'Byonoy GmbH',
'product_string': 'Absorbance 96 Automate',
'usage_page': 65280,
'usage': 1,
'interface_number': 0,
'bus_type': <BusType.USB: 1>}
reader_cap.backend.available_wavelengths
[420, 600]
Test Movement for Plate Reading#
cap_adapter, base, reader_cap
(ResourceHolder(name='cap_adapter', location=Coordinate(400.000, 000.000, 000.000), size_x=127.76, size_y=85.59, size_z=14.07, category=resource_holder),
ByonoyBase(name='base_base', location=None, size_x=138, size_y=95.7, size_z=27.7, category=None),
PlateReader(name='base_reader', location=Coordinate(000.000, 000.000, 010.660), size_x=138, size_y=95.7, size_z=0, category=None))
Usage / Machine Features#
Query Machine Configuration#
await reader_cap.backend.get_available_absorbance_wavelengths()
[420, 600]
Measure Absorbance#
readings_420_nested_list = await reader_cap.backend.read_absorbance(
wells=plate.children[:55],
wavelength = 420, # units: nm
output_nested_list=True
)
import pandas as pd
pd.DataFrame(readings_420_nested_list)
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.000002 | -0.000002 | 0.000083 | 0.000038 | 0.000048 | 2.975314e-05 | 0.000075 | None | None | None | None | None |
| 1 | 0.000062 | 0.000051 | 0.000040 | 0.000018 | 0.000064 | 3.082320e-05 | 0.000044 | None | None | None | None | None |
| 2 | 0.000088 | 0.000055 | 0.000069 | 0.000009 | 0.000079 | 7.937726e-05 | 0.000078 | None | None | None | None | None |
| 3 | 0.000080 | 0.000050 | 0.000009 | 0.000069 | 0.000067 | 3.182423e-05 | 0.000070 | None | None | None | None | None |
| 4 | 0.000042 | 0.000003 | 0.000110 | 0.000005 | -0.000005 | -1.815412e-05 | 0.000070 | None | None | None | None | None |
| 5 | 0.000055 | 0.000054 | -0.000023 | 0.000041 | 0.000036 | 9.664112e-07 | 0.000039 | None | None | None | None | None |
| 6 | 0.000046 | 0.000025 | 0.000019 | 0.000017 | 0.000039 | 3.658781e-05 | 0.000066 | None | None | None | None | None |
| 7 | 0.000038 | 0.000018 | 0.000055 | 0.000041 | 0.000034 | -3.216584e-05 | NaN | None | None | None | None | None |
import time
start_time = time.time()
readings_600_nested_list = await reader_cap.backend.read_absorbance(
wells=plate.children[:],
wavelength = 600, # units: nm
output_nested_list=True
)
display(pd.DataFrame(readings_600_nested_list))
time.time() - start_time
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.000097 | 0.000079 | 0.000087 | 0.000092 | 0.000085 | 0.000097 | 0.000086 | 0.000088 | 0.000074 | 0.000111 | 0.000066 | 0.000076 |
| 1 | 0.000050 | 0.000074 | 0.000063 | 0.000054 | 0.000073 | 0.000066 | 0.000050 | 0.000061 | 0.000082 | 0.000095 | 0.000051 | 0.000059 |
| 2 | 0.000093 | 0.000049 | 0.000031 | 0.000081 | 0.000067 | 0.000083 | 0.000066 | 0.000104 | 0.000074 | 0.000064 | 0.000040 | 0.000069 |
| 3 | 0.000096 | 0.000074 | 0.000023 | 0.000075 | 0.000100 | 0.000053 | 0.000064 | 0.000087 | 0.000070 | 0.000073 | 0.000050 | 0.000054 |
| 4 | 0.000087 | 0.000074 | 0.000161 | 0.000070 | 0.000080 | 0.000069 | 0.000101 | 0.000106 | 0.000112 | 0.000103 | 0.000059 | 0.000062 |
| 5 | 0.000058 | 0.000067 | 0.000023 | 0.000068 | 0.000036 | 0.000053 | 0.000035 | 0.000044 | 0.000045 | 0.000097 | 0.000039 | 0.000033 |
| 6 | 0.000080 | 0.000036 | 0.000012 | 0.000079 | 0.000062 | 0.000061 | 0.000046 | 0.000084 | 0.000043 | 0.000050 | 0.000026 | 0.000064 |
| 7 | 0.000087 | 0.000053 | 0.000072 | 0.000060 | 0.000076 | 0.000031 | 0.000034 | 0.000084 | 0.000086 | 0.000054 | 0.000032 | 0.000079 |
1.5100939273834229
start_time = time.time()
readings_600_nested_list = await reader_cap.backend.read_absorbance(
wells=plate.children[:],
wavelength = 600, # units: nm
output_nested_list=True
)
display(pd.DataFrame(readings_600_nested_list))
time.time() - start_time
first_n_columns = 8
readings_420 = await reader_cap.backend.read_absorbance(
wells=plate.children[:8*first_n_columns],
wavelength = 420 # units: nm
)
readings_600 = await reader_cap.backend.read_absorbance(
wells=plate.children[:8*first_n_columns],
wavelength = 600 # units: nm
)
well_indexed_df = pd.DataFrame([readings_420, readings_600], index=["420nm", "600nm"]).T
well_indexed_df
| 420nm | 600nm | |
|---|---|---|
| A1 | 0.000064 | 0.000100 |
| B1 | 0.000097 | 0.000033 |
| C1 | 0.000165 | 0.000086 |
| D1 | 0.000105 | 0.000082 |
| E1 | 0.000106 | 0.000132 |
| ... | ... | ... |
| D8 | 0.000073 | 0.000117 |
| E8 | 0.000085 | 0.000107 |
| F8 | 0.000057 | 0.000053 |
| G8 | 0.000124 | 0.000102 |
| H8 | 0.000079 | 0.000128 |
64 rows × 2 columns
Disconnect from Reader#
await reader_cap.stop()