opentrons 8.6.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/__init__.py +150 -0
- opentrons/_version.py +34 -0
- opentrons/calibration_storage/__init__.py +54 -0
- opentrons/calibration_storage/deck_configuration.py +62 -0
- opentrons/calibration_storage/encoder_decoder.py +31 -0
- opentrons/calibration_storage/file_operators.py +142 -0
- opentrons/calibration_storage/helpers.py +103 -0
- opentrons/calibration_storage/ot2/__init__.py +34 -0
- opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
- opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
- opentrons/calibration_storage/ot2/models/__init__.py +0 -0
- opentrons/calibration_storage/ot2/models/v1.py +149 -0
- opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
- opentrons/calibration_storage/ot2/tip_length.py +281 -0
- opentrons/calibration_storage/ot3/__init__.py +31 -0
- opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
- opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
- opentrons/calibration_storage/ot3/models/__init__.py +0 -0
- opentrons/calibration_storage/ot3/models/v1.py +122 -0
- opentrons/calibration_storage/ot3/module_offset.py +138 -0
- opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
- opentrons/calibration_storage/types.py +45 -0
- opentrons/cli/__init__.py +21 -0
- opentrons/cli/__main__.py +5 -0
- opentrons/cli/analyze.py +501 -0
- opentrons/config/__init__.py +631 -0
- opentrons/config/advanced_settings.py +871 -0
- opentrons/config/defaults_ot2.py +214 -0
- opentrons/config/defaults_ot3.py +499 -0
- opentrons/config/feature_flags.py +86 -0
- opentrons/config/gripper_config.py +55 -0
- opentrons/config/reset.py +203 -0
- opentrons/config/robot_configs.py +187 -0
- opentrons/config/types.py +183 -0
- opentrons/drivers/__init__.py +0 -0
- opentrons/drivers/absorbance_reader/__init__.py +11 -0
- opentrons/drivers/absorbance_reader/abstract.py +72 -0
- opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
- opentrons/drivers/absorbance_reader/driver.py +81 -0
- opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
- opentrons/drivers/absorbance_reader/simulator.py +84 -0
- opentrons/drivers/asyncio/__init__.py +0 -0
- opentrons/drivers/asyncio/communication/__init__.py +22 -0
- opentrons/drivers/asyncio/communication/async_serial.py +183 -0
- opentrons/drivers/asyncio/communication/errors.py +88 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +552 -0
- opentrons/drivers/command_builder.py +102 -0
- opentrons/drivers/flex_stacker/__init__.py +13 -0
- opentrons/drivers/flex_stacker/abstract.py +214 -0
- opentrons/drivers/flex_stacker/driver.py +768 -0
- opentrons/drivers/flex_stacker/errors.py +68 -0
- opentrons/drivers/flex_stacker/simulator.py +309 -0
- opentrons/drivers/flex_stacker/types.py +367 -0
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/drivers/heater_shaker/__init__.py +5 -0
- opentrons/drivers/heater_shaker/abstract.py +76 -0
- opentrons/drivers/heater_shaker/driver.py +204 -0
- opentrons/drivers/heater_shaker/simulator.py +94 -0
- opentrons/drivers/mag_deck/__init__.py +6 -0
- opentrons/drivers/mag_deck/abstract.py +44 -0
- opentrons/drivers/mag_deck/driver.py +208 -0
- opentrons/drivers/mag_deck/simulator.py +63 -0
- opentrons/drivers/rpi_drivers/__init__.py +33 -0
- opentrons/drivers/rpi_drivers/dev_types.py +94 -0
- opentrons/drivers/rpi_drivers/gpio.py +282 -0
- opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
- opentrons/drivers/rpi_drivers/interfaces.py +15 -0
- opentrons/drivers/rpi_drivers/types.py +364 -0
- opentrons/drivers/rpi_drivers/usb.py +102 -0
- opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
- opentrons/drivers/serial_communication.py +151 -0
- opentrons/drivers/smoothie_drivers/__init__.py +4 -0
- opentrons/drivers/smoothie_drivers/connection.py +51 -0
- opentrons/drivers/smoothie_drivers/constants.py +121 -0
- opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
- opentrons/drivers/smoothie_drivers/errors.py +49 -0
- opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
- opentrons/drivers/smoothie_drivers/simulator.py +99 -0
- opentrons/drivers/smoothie_drivers/types.py +16 -0
- opentrons/drivers/temp_deck/__init__.py +10 -0
- opentrons/drivers/temp_deck/abstract.py +54 -0
- opentrons/drivers/temp_deck/driver.py +197 -0
- opentrons/drivers/temp_deck/simulator.py +57 -0
- opentrons/drivers/thermocycler/__init__.py +12 -0
- opentrons/drivers/thermocycler/abstract.py +99 -0
- opentrons/drivers/thermocycler/driver.py +395 -0
- opentrons/drivers/thermocycler/simulator.py +126 -0
- opentrons/drivers/types.py +107 -0
- opentrons/drivers/utils.py +222 -0
- opentrons/execute.py +742 -0
- opentrons/hardware_control/__init__.py +65 -0
- opentrons/hardware_control/__main__.py +77 -0
- opentrons/hardware_control/adapters.py +98 -0
- opentrons/hardware_control/api.py +1347 -0
- opentrons/hardware_control/backends/__init__.py +7 -0
- opentrons/hardware_control/backends/controller.py +400 -0
- opentrons/hardware_control/backends/errors.py +9 -0
- opentrons/hardware_control/backends/estop_state.py +164 -0
- opentrons/hardware_control/backends/flex_protocol.py +497 -0
- opentrons/hardware_control/backends/ot3controller.py +1930 -0
- opentrons/hardware_control/backends/ot3simulator.py +900 -0
- opentrons/hardware_control/backends/ot3utils.py +664 -0
- opentrons/hardware_control/backends/simulator.py +442 -0
- opentrons/hardware_control/backends/status_bar_state.py +240 -0
- opentrons/hardware_control/backends/subsystem_manager.py +431 -0
- opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
- opentrons/hardware_control/backends/types.py +14 -0
- opentrons/hardware_control/constants.py +6 -0
- opentrons/hardware_control/dev_types.py +125 -0
- opentrons/hardware_control/emulation/__init__.py +0 -0
- opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
- opentrons/hardware_control/emulation/app.py +56 -0
- opentrons/hardware_control/emulation/connection_handler.py +38 -0
- opentrons/hardware_control/emulation/heater_shaker.py +150 -0
- opentrons/hardware_control/emulation/magdeck.py +60 -0
- opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
- opentrons/hardware_control/emulation/module_server/client.py +78 -0
- opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
- opentrons/hardware_control/emulation/module_server/models.py +31 -0
- opentrons/hardware_control/emulation/module_server/server.py +110 -0
- opentrons/hardware_control/emulation/parser.py +74 -0
- opentrons/hardware_control/emulation/proxy.py +241 -0
- opentrons/hardware_control/emulation/run_emulator.py +68 -0
- opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
- opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
- opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
- opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
- opentrons/hardware_control/emulation/settings.py +119 -0
- opentrons/hardware_control/emulation/simulations.py +133 -0
- opentrons/hardware_control/emulation/smoothie.py +192 -0
- opentrons/hardware_control/emulation/tempdeck.py +69 -0
- opentrons/hardware_control/emulation/thermocycler.py +128 -0
- opentrons/hardware_control/emulation/types.py +10 -0
- opentrons/hardware_control/emulation/util.py +38 -0
- opentrons/hardware_control/errors.py +43 -0
- opentrons/hardware_control/execution_manager.py +164 -0
- opentrons/hardware_control/instruments/__init__.py +5 -0
- opentrons/hardware_control/instruments/instrument_abc.py +39 -0
- opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
- opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
- opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
- opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
- opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
- opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
- opentrons/hardware_control/module_control.py +332 -0
- opentrons/hardware_control/modules/__init__.py +69 -0
- opentrons/hardware_control/modules/absorbance_reader.py +373 -0
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/flex_stacker.py +948 -0
- opentrons/hardware_control/modules/heater_shaker.py +426 -0
- opentrons/hardware_control/modules/lid_temp_status.py +35 -0
- opentrons/hardware_control/modules/magdeck.py +233 -0
- opentrons/hardware_control/modules/mod_abc.py +245 -0
- opentrons/hardware_control/modules/module_calibration.py +93 -0
- opentrons/hardware_control/modules/plate_temp_status.py +61 -0
- opentrons/hardware_control/modules/tempdeck.py +299 -0
- opentrons/hardware_control/modules/thermocycler.py +731 -0
- opentrons/hardware_control/modules/types.py +417 -0
- opentrons/hardware_control/modules/update.py +255 -0
- opentrons/hardware_control/modules/utils.py +73 -0
- opentrons/hardware_control/motion_utilities.py +318 -0
- opentrons/hardware_control/nozzle_manager.py +422 -0
- opentrons/hardware_control/ot3_calibration.py +1171 -0
- opentrons/hardware_control/ot3api.py +3227 -0
- opentrons/hardware_control/pause_manager.py +31 -0
- opentrons/hardware_control/poller.py +112 -0
- opentrons/hardware_control/protocols/__init__.py +106 -0
- opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
- opentrons/hardware_control/protocols/calibratable.py +45 -0
- opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
- opentrons/hardware_control/protocols/configurable.py +48 -0
- opentrons/hardware_control/protocols/event_sourcer.py +18 -0
- opentrons/hardware_control/protocols/execution_controllable.py +33 -0
- opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
- opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
- opentrons/hardware_control/protocols/gripper_controller.py +55 -0
- opentrons/hardware_control/protocols/hardware_manager.py +51 -0
- opentrons/hardware_control/protocols/identifiable.py +16 -0
- opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
- opentrons/hardware_control/protocols/liquid_handler.py +266 -0
- opentrons/hardware_control/protocols/module_provider.py +16 -0
- opentrons/hardware_control/protocols/motion_controller.py +243 -0
- opentrons/hardware_control/protocols/position_estimator.py +45 -0
- opentrons/hardware_control/protocols/simulatable.py +10 -0
- opentrons/hardware_control/protocols/stoppable.py +9 -0
- opentrons/hardware_control/protocols/types.py +27 -0
- opentrons/hardware_control/robot_calibration.py +224 -0
- opentrons/hardware_control/scripts/README.md +28 -0
- opentrons/hardware_control/scripts/__init__.py +1 -0
- opentrons/hardware_control/scripts/gripper_control.py +208 -0
- opentrons/hardware_control/scripts/ot3gripper +7 -0
- opentrons/hardware_control/scripts/ot3repl +7 -0
- opentrons/hardware_control/scripts/repl.py +187 -0
- opentrons/hardware_control/scripts/tc_control.py +97 -0
- opentrons/hardware_control/simulator_setup.py +260 -0
- opentrons/hardware_control/thread_manager.py +431 -0
- opentrons/hardware_control/threaded_async_lock.py +97 -0
- opentrons/hardware_control/types.py +792 -0
- opentrons/hardware_control/util.py +234 -0
- opentrons/legacy_broker.py +53 -0
- opentrons/legacy_commands/__init__.py +1 -0
- opentrons/legacy_commands/commands.py +483 -0
- opentrons/legacy_commands/helpers.py +153 -0
- opentrons/legacy_commands/module_commands.py +215 -0
- opentrons/legacy_commands/protocol_commands.py +54 -0
- opentrons/legacy_commands/publisher.py +155 -0
- opentrons/legacy_commands/robot_commands.py +51 -0
- opentrons/legacy_commands/types.py +1115 -0
- opentrons/motion_planning/__init__.py +32 -0
- opentrons/motion_planning/adjacent_slots_getters.py +168 -0
- opentrons/motion_planning/deck_conflict.py +396 -0
- opentrons/motion_planning/errors.py +35 -0
- opentrons/motion_planning/types.py +42 -0
- opentrons/motion_planning/waypoints.py +218 -0
- opentrons/ordered_set.py +138 -0
- opentrons/protocol_api/__init__.py +105 -0
- opentrons/protocol_api/_liquid.py +157 -0
- opentrons/protocol_api/_liquid_properties.py +814 -0
- opentrons/protocol_api/_nozzle_layout.py +31 -0
- opentrons/protocol_api/_parameter_context.py +300 -0
- opentrons/protocol_api/_parameters.py +31 -0
- opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
- opentrons/protocol_api/_types.py +43 -0
- opentrons/protocol_api/config.py +23 -0
- opentrons/protocol_api/core/__init__.py +23 -0
- opentrons/protocol_api/core/common.py +33 -0
- opentrons/protocol_api/core/core_map.py +74 -0
- opentrons/protocol_api/core/engine/__init__.py +22 -0
- opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
- opentrons/protocol_api/core/engine/deck_conflict.py +348 -0
- opentrons/protocol_api/core/engine/exceptions.py +19 -0
- opentrons/protocol_api/core/engine/instrument.py +2391 -0
- opentrons/protocol_api/core/engine/labware.py +238 -0
- opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
- opentrons/protocol_api/core/engine/module_core.py +1025 -0
- opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
- opentrons/protocol_api/core/engine/point_calculations.py +64 -0
- opentrons/protocol_api/core/engine/protocol.py +1153 -0
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/stringify.py +74 -0
- opentrons/protocol_api/core/engine/transfer_components_executor.py +990 -0
- opentrons/protocol_api/core/engine/well.py +241 -0
- opentrons/protocol_api/core/instrument.py +459 -0
- opentrons/protocol_api/core/labware.py +151 -0
- opentrons/protocol_api/core/legacy/__init__.py +11 -0
- opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
- opentrons/protocol_api/core/legacy/deck.py +369 -0
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
- opentrons/protocol_api/core/legacy/load_info.py +67 -0
- opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
- opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
- opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
- opentrons/protocol_api/core/module.py +484 -0
- opentrons/protocol_api/core/protocol.py +311 -0
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/core/well.py +116 -0
- opentrons/protocol_api/core/well_grid.py +45 -0
- opentrons/protocol_api/create_protocol_context.py +177 -0
- opentrons/protocol_api/deck.py +223 -0
- opentrons/protocol_api/disposal_locations.py +244 -0
- opentrons/protocol_api/instrument_context.py +3212 -0
- opentrons/protocol_api/labware.py +1579 -0
- opentrons/protocol_api/module_contexts.py +1425 -0
- opentrons/protocol_api/module_validation_and_errors.py +61 -0
- opentrons/protocol_api/protocol_context.py +1688 -0
- opentrons/protocol_api/robot_context.py +303 -0
- opentrons/protocol_api/validation.py +761 -0
- opentrons/protocol_engine/__init__.py +155 -0
- opentrons/protocol_engine/actions/__init__.py +65 -0
- opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
- opentrons/protocol_engine/actions/action_handler.py +13 -0
- opentrons/protocol_engine/actions/actions.py +302 -0
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/__init__.py +5 -0
- opentrons/protocol_engine/clients/sync_client.py +174 -0
- opentrons/protocol_engine/clients/transports.py +197 -0
- opentrons/protocol_engine/commands/__init__.py +757 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
- opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
- opentrons/protocol_engine/commands/aspirate.py +244 -0
- opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
- opentrons/protocol_engine/commands/blow_out.py +146 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
- opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
- opentrons/protocol_engine/commands/command.py +308 -0
- opentrons/protocol_engine/commands/command_unions.py +974 -0
- opentrons/protocol_engine/commands/comment.py +57 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
- opentrons/protocol_engine/commands/custom.py +67 -0
- opentrons/protocol_engine/commands/dispense.py +194 -0
- opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +232 -0
- opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +326 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
- opentrons/protocol_engine/commands/hash_command_params.py +38 -0
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
- opentrons/protocol_engine/commands/home.py +100 -0
- opentrons/protocol_engine/commands/identify_module.py +86 -0
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +464 -0
- opentrons/protocol_engine/commands/load_labware.py +210 -0
- opentrons/protocol_engine/commands/load_lid.py +154 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
- opentrons/protocol_engine/commands/load_liquid.py +95 -0
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +223 -0
- opentrons/protocol_engine/commands/load_pipette.py +167 -0
- opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
- opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
- opentrons/protocol_engine/commands/move_labware.py +546 -0
- opentrons/protocol_engine/commands/move_relative.py +102 -0
- opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
- opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
- opentrons/protocol_engine/commands/move_to_well.py +119 -0
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
- opentrons/protocol_engine/commands/pipetting_common.py +443 -0
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
- opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
- opentrons/protocol_engine/commands/reload_labware.py +90 -0
- opentrons/protocol_engine/commands/retract_axis.py +75 -0
- opentrons/protocol_engine/commands/robot/__init__.py +70 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/save_position.py +109 -0
- opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
- opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
- opentrons/protocol_engine/commands/set_status_bar.py +89 -0
- opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
- opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
- opentrons/protocol_engine/commands/touch_tip.py +189 -0
- opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
- opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
- opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
- opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
- opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
- opentrons/protocol_engine/create_protocol_engine.py +193 -0
- opentrons/protocol_engine/engine_support.py +28 -0
- opentrons/protocol_engine/error_recovery_policy.py +81 -0
- opentrons/protocol_engine/errors/__init__.py +191 -0
- opentrons/protocol_engine/errors/error_occurrence.py +182 -0
- opentrons/protocol_engine/errors/exceptions.py +1308 -0
- opentrons/protocol_engine/execution/__init__.py +50 -0
- opentrons/protocol_engine/execution/command_executor.py +216 -0
- opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
- opentrons/protocol_engine/execution/door_watcher.py +119 -0
- opentrons/protocol_engine/execution/equipment.py +819 -0
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +686 -0
- opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
- opentrons/protocol_engine/execution/labware_movement.py +297 -0
- opentrons/protocol_engine/execution/movement.py +349 -0
- opentrons/protocol_engine/execution/pipetting.py +607 -0
- opentrons/protocol_engine/execution/queue_worker.py +86 -0
- opentrons/protocol_engine/execution/rail_lights.py +25 -0
- opentrons/protocol_engine/execution/run_control.py +33 -0
- opentrons/protocol_engine/execution/status_bar.py +34 -0
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
- opentrons/protocol_engine/execution/tip_handler.py +550 -0
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/notes/__init__.py +17 -0
- opentrons/protocol_engine/notes/notes.py +59 -0
- opentrons/protocol_engine/plugins.py +104 -0
- opentrons/protocol_engine/protocol_engine.py +683 -0
- opentrons/protocol_engine/resources/__init__.py +26 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +58 -0
- opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
- opentrons/protocol_engine/resources/labware_validation.py +73 -0
- opentrons/protocol_engine/resources/model_utils.py +32 -0
- opentrons/protocol_engine/resources/module_data_provider.py +44 -0
- opentrons/protocol_engine/resources/ot3_validation.py +21 -0
- opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
- opentrons/protocol_engine/slot_standardization.py +128 -0
- opentrons/protocol_engine/state/__init__.py +1 -0
- opentrons/protocol_engine/state/_abstract_store.py +27 -0
- opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
- opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
- opentrons/protocol_engine/state/_move_types.py +83 -0
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +699 -0
- opentrons/protocol_engine/state/command_history.py +309 -0
- opentrons/protocol_engine/state/commands.py +1158 -0
- opentrons/protocol_engine/state/config.py +39 -0
- opentrons/protocol_engine/state/files.py +57 -0
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/geometry.py +2359 -0
- opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
- opentrons/protocol_engine/state/labware.py +1459 -0
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +73 -0
- opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
- opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
- opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
- opentrons/protocol_engine/state/modules.py +1500 -0
- opentrons/protocol_engine/state/motion.py +373 -0
- opentrons/protocol_engine/state/pipettes.py +905 -0
- opentrons/protocol_engine/state/state.py +421 -0
- opentrons/protocol_engine/state/state_summary.py +36 -0
- opentrons/protocol_engine/state/tips.py +420 -0
- opentrons/protocol_engine/state/update_types.py +904 -0
- opentrons/protocol_engine/state/wells.py +290 -0
- opentrons/protocol_engine/types/__init__.py +308 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +81 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +131 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +303 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/__init__.py +37 -0
- opentrons/protocol_reader/extract_labware_definitions.py +66 -0
- opentrons/protocol_reader/file_format_validator.py +152 -0
- opentrons/protocol_reader/file_hasher.py +27 -0
- opentrons/protocol_reader/file_identifier.py +284 -0
- opentrons/protocol_reader/file_reader_writer.py +90 -0
- opentrons/protocol_reader/input_file.py +16 -0
- opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
- opentrons/protocol_reader/protocol_reader.py +188 -0
- opentrons/protocol_reader/protocol_source.py +124 -0
- opentrons/protocol_reader/role_analyzer.py +86 -0
- opentrons/protocol_runner/__init__.py +26 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
- opentrons/protocol_runner/json_file_reader.py +55 -0
- opentrons/protocol_runner/json_translator.py +314 -0
- opentrons/protocol_runner/legacy_command_mapper.py +848 -0
- opentrons/protocol_runner/legacy_context_plugin.py +116 -0
- opentrons/protocol_runner/protocol_runner.py +530 -0
- opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
- opentrons/protocol_runner/run_orchestrator.py +496 -0
- opentrons/protocol_runner/task_queue.py +95 -0
- opentrons/protocols/__init__.py +6 -0
- opentrons/protocols/advanced_control/__init__.py +0 -0
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +60 -0
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +180 -0
- opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
- opentrons/protocols/api_support/__init__.py +0 -0
- opentrons/protocols/api_support/constants.py +8 -0
- opentrons/protocols/api_support/deck_type.py +110 -0
- opentrons/protocols/api_support/definitions.py +18 -0
- opentrons/protocols/api_support/instrument.py +151 -0
- opentrons/protocols/api_support/labware_like.py +233 -0
- opentrons/protocols/api_support/tip_tracker.py +175 -0
- opentrons/protocols/api_support/types.py +32 -0
- opentrons/protocols/api_support/util.py +403 -0
- opentrons/protocols/bundle.py +89 -0
- opentrons/protocols/duration/__init__.py +4 -0
- opentrons/protocols/duration/errors.py +5 -0
- opentrons/protocols/duration/estimator.py +628 -0
- opentrons/protocols/execution/__init__.py +0 -0
- opentrons/protocols/execution/dev_types.py +181 -0
- opentrons/protocols/execution/errors.py +40 -0
- opentrons/protocols/execution/execute.py +84 -0
- opentrons/protocols/execution/execute_json_v3.py +275 -0
- opentrons/protocols/execution/execute_json_v4.py +359 -0
- opentrons/protocols/execution/execute_json_v5.py +28 -0
- opentrons/protocols/execution/execute_python.py +169 -0
- opentrons/protocols/execution/json_dispatchers.py +87 -0
- opentrons/protocols/execution/types.py +7 -0
- opentrons/protocols/geometry/__init__.py +0 -0
- opentrons/protocols/geometry/planning.py +297 -0
- opentrons/protocols/labware.py +312 -0
- opentrons/protocols/models/__init__.py +0 -0
- opentrons/protocols/models/json_protocol.py +679 -0
- opentrons/protocols/parameters/__init__.py +0 -0
- opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
- opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
- opentrons/protocols/parameters/exceptions.py +34 -0
- opentrons/protocols/parameters/parameter_definition.py +272 -0
- opentrons/protocols/parameters/types.py +17 -0
- opentrons/protocols/parameters/validation.py +267 -0
- opentrons/protocols/parse.py +671 -0
- opentrons/protocols/types.py +159 -0
- opentrons/py.typed +0 -0
- opentrons/resources/scripts/lpc21isp +0 -0
- opentrons/resources/smoothie-edge-8414642.hex +23010 -0
- opentrons/simulate.py +1065 -0
- opentrons/system/__init__.py +6 -0
- opentrons/system/camera.py +51 -0
- opentrons/system/log_control.py +59 -0
- opentrons/system/nmcli.py +856 -0
- opentrons/system/resin.py +24 -0
- opentrons/system/smoothie_update.py +15 -0
- opentrons/system/wifi.py +204 -0
- opentrons/tools/__init__.py +0 -0
- opentrons/tools/args_handler.py +22 -0
- opentrons/tools/write_pipette_memory.py +157 -0
- opentrons/types.py +618 -0
- opentrons/util/__init__.py +1 -0
- opentrons/util/async_helpers.py +166 -0
- opentrons/util/broker.py +84 -0
- opentrons/util/change_notifier.py +47 -0
- opentrons/util/entrypoint_util.py +278 -0
- opentrons/util/get_union_elements.py +26 -0
- opentrons/util/helpers.py +6 -0
- opentrons/util/linal.py +178 -0
- opentrons/util/logging_config.py +265 -0
- opentrons/util/logging_queue_handler.py +61 -0
- opentrons/util/performance_helpers.py +157 -0
- opentrons-8.6.0a1.dist-info/METADATA +37 -0
- opentrons-8.6.0a1.dist-info/RECORD +600 -0
- opentrons-8.6.0a1.dist-info/WHEEL +4 -0
- opentrons-8.6.0a1.dist-info/entry_points.txt +3 -0
- opentrons-8.6.0a1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from typing import (
|
|
3
|
+
Any,
|
|
4
|
+
Dict,
|
|
5
|
+
List,
|
|
6
|
+
Optional,
|
|
7
|
+
Union,
|
|
8
|
+
NamedTuple,
|
|
9
|
+
Callable,
|
|
10
|
+
Generator,
|
|
11
|
+
Iterator,
|
|
12
|
+
Sequence,
|
|
13
|
+
Tuple,
|
|
14
|
+
TypedDict,
|
|
15
|
+
TypeAlias,
|
|
16
|
+
TYPE_CHECKING,
|
|
17
|
+
)
|
|
18
|
+
from opentrons.protocol_api.labware import Labware, Well
|
|
19
|
+
from opentrons import types
|
|
20
|
+
from opentrons.protocols.api_support.types import APIVersion
|
|
21
|
+
|
|
22
|
+
from . import common as tx_commons
|
|
23
|
+
from ..common import Mix, MixOpts, MixStrategy
|
|
24
|
+
|
|
25
|
+
AdvancedLiquidHandling = Union[
|
|
26
|
+
Well,
|
|
27
|
+
types.Location,
|
|
28
|
+
Sequence[Union[Well, types.Location]],
|
|
29
|
+
Sequence[Sequence[Well]],
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TransferStep(TypedDict):
|
|
34
|
+
method: str
|
|
35
|
+
args: Optional[List[Any]]
|
|
36
|
+
kwargs: Optional[Dict[Any, Any]]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from opentrons.protocol_api import InstrumentContext
|
|
41
|
+
|
|
42
|
+
_PARTIAL_TIP_SUPPORT_ADDED = APIVersion(2, 18)
|
|
43
|
+
"""The version after which partial tip support and nozzle maps were made available."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DropTipStrategy(enum.Enum):
|
|
47
|
+
TRASH = enum.auto()
|
|
48
|
+
RETURN = enum.auto()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TouchTipStrategy(enum.Enum):
|
|
52
|
+
NEVER = enum.auto()
|
|
53
|
+
ALWAYS = enum.auto()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class BlowOutStrategy(enum.Enum):
|
|
57
|
+
NONE = enum.auto()
|
|
58
|
+
TRASH = enum.auto()
|
|
59
|
+
DEST = enum.auto()
|
|
60
|
+
SOURCE = enum.auto()
|
|
61
|
+
CUSTOM_LOCATION = enum.auto()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TransferMode(enum.Enum):
|
|
65
|
+
DISTRIBUTE = enum.auto()
|
|
66
|
+
CONSOLIDATE = enum.auto()
|
|
67
|
+
TRANSFER = enum.auto()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Transfer(NamedTuple):
|
|
71
|
+
"""
|
|
72
|
+
Options pertaining to behavior of the transfer.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
new_tip: types.TransferTipPolicy = types.TransferTipPolicy.ONCE
|
|
77
|
+
air_gap: float = 0
|
|
78
|
+
carryover: bool = True
|
|
79
|
+
gradient_function: Optional[Callable[[float], float]] = None
|
|
80
|
+
disposal_volume: float = 0
|
|
81
|
+
mix_strategy: MixStrategy = MixStrategy.NEVER
|
|
82
|
+
drop_tip_strategy: DropTipStrategy = DropTipStrategy.TRASH
|
|
83
|
+
blow_out_strategy: BlowOutStrategy = BlowOutStrategy.NONE
|
|
84
|
+
touch_tip_strategy: TouchTipStrategy = TouchTipStrategy.NEVER
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
Transfer.new_tip.__doc__ = """
|
|
88
|
+
Control when or if to pick up tip during a transfer
|
|
89
|
+
|
|
90
|
+
:py:attr:`types.TransferTipPolicy.ALWAYS`
|
|
91
|
+
Drop and pick up a new tip after each dispense.
|
|
92
|
+
|
|
93
|
+
:py:attr:`types.TransferTipPolicy.ONCE`
|
|
94
|
+
Pick up tip at the beginning of the transfer and use it
|
|
95
|
+
throughout the transfer. This would speed up the transfer.
|
|
96
|
+
|
|
97
|
+
:py:attr:`types.TransferTipPolicy.NEVER`
|
|
98
|
+
Do not ever pick up or drop tip. The protocol should explicitly
|
|
99
|
+
pick up a tip before transfer and drop it afterwards.
|
|
100
|
+
|
|
101
|
+
To customize where to drop tip, see :py:attr:`.drop_tip_strategy`.
|
|
102
|
+
To customize the behavior of pickup tip, see
|
|
103
|
+
:py:attr:`.TransferOptions.pick_up_tip`.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
Transfer.air_gap.__doc__ = """
|
|
107
|
+
Controls the volume (in uL) of air gap aspirated when moving to
|
|
108
|
+
dispense.
|
|
109
|
+
|
|
110
|
+
Adding an air gap would slow down a transfer since less liquid will
|
|
111
|
+
now fit in the pipette but it prevents the loss of liquid while
|
|
112
|
+
moving between wells.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
Transfer.carryover.__doc__ = """
|
|
116
|
+
Controls whether volumes larger than pipette's max volume will be
|
|
117
|
+
split into smaller volumes.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
Transfer.gradient_function.__doc__ = """
|
|
121
|
+
Specify a nonlinear gradient for volumes.
|
|
122
|
+
|
|
123
|
+
This should be a function that takes a single float between 0 and 1
|
|
124
|
+
and returns a single float between 0 and 1. This function is used
|
|
125
|
+
to determine the path the transfer takes between the volume
|
|
126
|
+
gradient minimum and maximum if the transfer volume is specified as
|
|
127
|
+
a gradient. For instance, specifying the function as
|
|
128
|
+
|
|
129
|
+
.. code-block:: python
|
|
130
|
+
|
|
131
|
+
def gradient(a):
|
|
132
|
+
if a > 0.5:
|
|
133
|
+
return 1.0
|
|
134
|
+
else:
|
|
135
|
+
return 0.0
|
|
136
|
+
|
|
137
|
+
would transfer the minimum volume of the gradient to the first half
|
|
138
|
+
of the target wells, and the maximum to the other half.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
Transfer.disposal_volume.__doc__ = """
|
|
142
|
+
The amount of liquid (in uL) to aspirate as a buffer.
|
|
143
|
+
|
|
144
|
+
The remaining buffer will be blown out into the location specified
|
|
145
|
+
by :py:attr:`.blow_out_strategy`.
|
|
146
|
+
|
|
147
|
+
This is useful to avoid under-pipetting but can waste reagent and
|
|
148
|
+
slow down transfer.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
Transfer.mix_strategy.__doc__ = """
|
|
152
|
+
If and when to mix during a transfer.
|
|
153
|
+
|
|
154
|
+
:py:attr:`MixStrategy.NEVER`
|
|
155
|
+
Do not ever perform a mix during the transfer.
|
|
156
|
+
|
|
157
|
+
:py:attr:`MixStrategy.BEFORE`
|
|
158
|
+
Mix before each aspirate.
|
|
159
|
+
|
|
160
|
+
:py:attr:`MixStrategy.AFTER`
|
|
161
|
+
Mix after each dispense.
|
|
162
|
+
|
|
163
|
+
:py:attr:`MixStrategy.BOTH`
|
|
164
|
+
Mix before each aspirate and after each dispense.
|
|
165
|
+
|
|
166
|
+
To customize the mix behavior, see :py:attr:`.TransferOptions.mix`
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
Transfer.drop_tip_strategy.__doc__ = """
|
|
170
|
+
Specifies the location to drop tip into.
|
|
171
|
+
|
|
172
|
+
:py:attr:`DropTipStrategy.TRASH`
|
|
173
|
+
Drop the tip into the trash container.
|
|
174
|
+
|
|
175
|
+
:py:attr:`DropTipStrategy.RETURN`
|
|
176
|
+
Return the tip to tiprack.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
Transfer.blow_out_strategy.__doc__ = """
|
|
180
|
+
Specifies the location to blow out the liquid in the pipette to.
|
|
181
|
+
|
|
182
|
+
:py:attr:`BlowOutStrategy.TRASH`
|
|
183
|
+
Blow out to trash container.
|
|
184
|
+
|
|
185
|
+
:py:attr:`BlowOutStrategy.SOURCE`
|
|
186
|
+
Blow out into the source well in order to dispense any leftover
|
|
187
|
+
liquid.
|
|
188
|
+
|
|
189
|
+
:py:attr:`BlowOutStrategy.DEST`
|
|
190
|
+
Blow out into the destination well in order to dispense any leftover
|
|
191
|
+
liquid.
|
|
192
|
+
|
|
193
|
+
:py:attr:`BlowOutStrategy.CUSTOM_LOCATION`
|
|
194
|
+
If using any other location to blow out to. Specify the location in
|
|
195
|
+
:py:attr:`.TransferOptions.blow_out`.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
Transfer.touch_tip_strategy.__doc__ = """
|
|
199
|
+
Controls whether to touch tip during the transfer
|
|
200
|
+
|
|
201
|
+
This helps in getting rid of any droplets clinging to the pipette
|
|
202
|
+
tip at the cost of slowing down the transfer.
|
|
203
|
+
|
|
204
|
+
:py:attr:`TouchTipStrategy.NEVER`
|
|
205
|
+
Do not touch tip ever during the transfer.
|
|
206
|
+
|
|
207
|
+
:py:attr:`TouchTipStrategy.ALWAYS`
|
|
208
|
+
Touch tip after each aspirate.
|
|
209
|
+
|
|
210
|
+
To customize the behavior of touch tips, see
|
|
211
|
+
:py:attr:`.TransferOptions.touch_tip`.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class PickUpTipOpts(NamedTuple):
|
|
216
|
+
"""
|
|
217
|
+
Options to customize :py:attr:`.Transfer.new_tip`.
|
|
218
|
+
|
|
219
|
+
These options will be passed to
|
|
220
|
+
:py:meth:`InstrumentContext.pick_up_tip` when it is called during
|
|
221
|
+
the transfer.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
location: Optional[types.Location] = None
|
|
225
|
+
presses: Optional[int] = None
|
|
226
|
+
increment: Optional[int] = None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
PickUpTipOpts.location.__doc__ = ":py:class:`types.Location`"
|
|
230
|
+
PickUpTipOpts.presses.__doc__ = ":py:class:`int`"
|
|
231
|
+
PickUpTipOpts.increment.__doc__ = ":py:class:`int`"
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
Mix.mix_before.__doc__ = """
|
|
235
|
+
Options applied to mix before aspirate.
|
|
236
|
+
See :py:class:`.Mix.MixOpts`.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
Mix.mix_after.__doc__ = """
|
|
240
|
+
Options applied to mix after dispense. See :py:class:`.Mix.MixOpts`.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class BlowOutOpts(NamedTuple):
|
|
245
|
+
"""
|
|
246
|
+
Location where to blow out instead of the trash.
|
|
247
|
+
|
|
248
|
+
This location will be passed to :py:meth:`InstrumentContext.blow_out`
|
|
249
|
+
when called during the transfer
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
location: Optional[Union[types.Location, Well]] = None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
BlowOutOpts.location.__doc__ = ":py:class:`types.Location`"
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class TouchTipOpts(NamedTuple):
|
|
259
|
+
"""
|
|
260
|
+
Options to customize touch tip.
|
|
261
|
+
|
|
262
|
+
These options will be passed to
|
|
263
|
+
:py:meth:`InstrumentContext.touch_tip` when called during the
|
|
264
|
+
transfer.
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
radius: Optional[float] = None
|
|
268
|
+
v_offset: Optional[float] = None
|
|
269
|
+
speed: Optional[float] = None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
TouchTipOpts.radius.__doc__ = ":py:class:`float`"
|
|
273
|
+
TouchTipOpts.v_offset.__doc__ = ":py:class:`float`"
|
|
274
|
+
TouchTipOpts.speed.__doc__ = ":py:class:`float`"
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class AspirateOpts(NamedTuple):
|
|
278
|
+
"""
|
|
279
|
+
Option to customize aspirate rate.
|
|
280
|
+
|
|
281
|
+
This option will be passed to :py:meth:`InstrumentContext.aspirate`
|
|
282
|
+
when called during the transfer.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
rate: Optional[float] = 1.0
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
AspirateOpts.rate.__doc__ = ":py:class:`float`"
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class DispenseOpts(NamedTuple):
|
|
292
|
+
"""
|
|
293
|
+
Option to customize dispense rate.
|
|
294
|
+
|
|
295
|
+
This option will be passed to :py:meth:`InstrumentContext.dispense`
|
|
296
|
+
when called during the transfer.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
rate: Optional[float] = 1.0
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
DispenseOpts.rate.__doc__ = ":py:class:`float`"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class TransferOptions(NamedTuple):
|
|
306
|
+
"""
|
|
307
|
+
All available options for a transfer, distribute or consolidate function
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
transfer: Transfer = Transfer()
|
|
311
|
+
pick_up_tip: PickUpTipOpts = PickUpTipOpts()
|
|
312
|
+
mix: Mix = Mix()
|
|
313
|
+
blow_out: BlowOutOpts = BlowOutOpts()
|
|
314
|
+
touch_tip: TouchTipOpts = TouchTipOpts()
|
|
315
|
+
aspirate: AspirateOpts = AspirateOpts()
|
|
316
|
+
dispense: DispenseOpts = DispenseOpts()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
FormatDictArgs: TypeAlias = Union[
|
|
320
|
+
PickUpTipOpts, MixOpts, BlowOutOpts, TouchTipOpts, AspirateOpts, DispenseOpts
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
TransferOptions.transfer.__doc__ = """
|
|
325
|
+
Options pertaining to behavior of the transfer.
|
|
326
|
+
|
|
327
|
+
For instance you can control how frequently to get a new tip using
|
|
328
|
+
:py:attr:`.Transfer.new_tip`. For documentation of all transfer options
|
|
329
|
+
see :py:class:`.Transfer`.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
TransferOptions.pick_up_tip.__doc__ = """
|
|
333
|
+
Options used when picking up a tip during transfer.
|
|
334
|
+
See :py:class:`.PickUpTipOpts`.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
TransferOptions.mix.__doc__ = """
|
|
338
|
+
Options to control mix behavior before aspirate and after dispense.
|
|
339
|
+
See :py:class:`.Mix`.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
TransferOptions.blow_out.__doc__ = """
|
|
343
|
+
Option to specify custom location for blow out. See
|
|
344
|
+
:py:class:`.BlowOutOpts`.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
TransferOptions.touch_tip.__doc__ = """
|
|
348
|
+
Options to customize touch tip. See
|
|
349
|
+
:py:class:`.TouchTipOpts`.
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
TransferOptions.aspirate.__doc__ = """
|
|
353
|
+
Option to customize aspirate rate. See
|
|
354
|
+
:py:class:`.AspirateOpts`.
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
TransferOptions.dispense.__doc__ = """
|
|
358
|
+
Option to customize dispense rate. See
|
|
359
|
+
:py:class:`.DispenseOpts`.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class TransferPlan:
|
|
364
|
+
"""Calculate and carry state for an arbitrary transfer
|
|
365
|
+
|
|
366
|
+
This class encapsulates the logic around planning an M:N transfer.
|
|
367
|
+
|
|
368
|
+
It handles calculations based on pipette channels, tip management, and all
|
|
369
|
+
the various little commands that can be involved in a transfer. It can be
|
|
370
|
+
iterated to resolve methods to call to execute the plan.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
def __init__(
|
|
374
|
+
self,
|
|
375
|
+
volume: Union[float, Sequence[float]],
|
|
376
|
+
srcs: AdvancedLiquidHandling,
|
|
377
|
+
dsts: AdvancedLiquidHandling,
|
|
378
|
+
# todo(mm, 2021-03-10):
|
|
379
|
+
# Refactor to not need an InstrumentContext, so we can more
|
|
380
|
+
# easily test this class's logic on its own.
|
|
381
|
+
instr: "InstrumentContext",
|
|
382
|
+
max_volume: float,
|
|
383
|
+
api_version: APIVersion,
|
|
384
|
+
mode: str,
|
|
385
|
+
options: Optional[TransferOptions] = None,
|
|
386
|
+
) -> None:
|
|
387
|
+
"""Build the transfer plan.
|
|
388
|
+
|
|
389
|
+
This method initializes the object and does the work of preparing the
|
|
390
|
+
transfer plan. Its arguments are as those of
|
|
391
|
+
:py:meth:`.InstrumentContext.transfer`.
|
|
392
|
+
"""
|
|
393
|
+
self._instr = instr
|
|
394
|
+
self._api_version = api_version
|
|
395
|
+
# Convert sources & dests into proper format
|
|
396
|
+
# CASES:
|
|
397
|
+
# i. if using multi-channel pipette,
|
|
398
|
+
# and the source or target is a row/column of Wells (i.e list of Wells)
|
|
399
|
+
# then avoid iterating through its Wells.
|
|
400
|
+
# ii. if using single channel pipettes, flatten a multi-dimensional
|
|
401
|
+
# list of Wells into a 1 dimensional list of Wells
|
|
402
|
+
pipette_configuration_type = types.NozzleConfigurationType.FULL
|
|
403
|
+
normalized_sources: List[Union[Well, types.Location]]
|
|
404
|
+
normalized_dests: List[Union[Well, types.Location]]
|
|
405
|
+
if self._api_version >= _PARTIAL_TIP_SUPPORT_ADDED:
|
|
406
|
+
pipette_configuration_type = (
|
|
407
|
+
self._instr._core.get_nozzle_map().configuration
|
|
408
|
+
)
|
|
409
|
+
if (
|
|
410
|
+
self._instr.channels > 1
|
|
411
|
+
and pipette_configuration_type == types.NozzleConfigurationType.FULL
|
|
412
|
+
):
|
|
413
|
+
normalized_sources, normalized_dests = self._multichannel_transfer(
|
|
414
|
+
srcs, dsts
|
|
415
|
+
)
|
|
416
|
+
else:
|
|
417
|
+
if isinstance(srcs, List):
|
|
418
|
+
if isinstance(srcs[0], List):
|
|
419
|
+
# Source is a List[List[Well]]
|
|
420
|
+
normalized_sources = [
|
|
421
|
+
well for well_list in srcs for well in well_list
|
|
422
|
+
]
|
|
423
|
+
else:
|
|
424
|
+
normalized_sources = srcs
|
|
425
|
+
elif isinstance(srcs, Well) or isinstance(srcs, types.Location):
|
|
426
|
+
normalized_sources = [srcs]
|
|
427
|
+
if isinstance(dsts, List):
|
|
428
|
+
if isinstance(dsts[0], List):
|
|
429
|
+
# Dest is a List[List[Well]]
|
|
430
|
+
normalized_dests = [
|
|
431
|
+
well for well_list in dsts for well in well_list
|
|
432
|
+
]
|
|
433
|
+
else:
|
|
434
|
+
normalized_dests = dsts
|
|
435
|
+
elif isinstance(dsts, Well) or isinstance(dsts, types.Location):
|
|
436
|
+
normalized_dests = [dsts]
|
|
437
|
+
|
|
438
|
+
total_xfers = max(len(normalized_sources), len(normalized_dests))
|
|
439
|
+
|
|
440
|
+
self._volumes = self._create_volume_list(volume, total_xfers)
|
|
441
|
+
self._sources = normalized_sources
|
|
442
|
+
self._dests = normalized_dests
|
|
443
|
+
self._options = options or TransferOptions()
|
|
444
|
+
self._strategy = self._options.transfer
|
|
445
|
+
self._tip_opts = self._options.pick_up_tip
|
|
446
|
+
self._blow_opts = self._options.blow_out
|
|
447
|
+
self._touch_tip_opts = self._options.touch_tip
|
|
448
|
+
self._mix_before_opts = self._options.mix.mix_before
|
|
449
|
+
self._mix_after_opts = self._options.mix.mix_after
|
|
450
|
+
self._max_volume = max_volume
|
|
451
|
+
|
|
452
|
+
self._mode = TransferMode[mode.upper()]
|
|
453
|
+
|
|
454
|
+
def __iter__(self) -> Iterator[TransferStep]:
|
|
455
|
+
if self._strategy.new_tip == types.TransferTipPolicy.ONCE:
|
|
456
|
+
yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
|
|
457
|
+
yield from {
|
|
458
|
+
TransferMode.CONSOLIDATE: self._plan_consolidate,
|
|
459
|
+
TransferMode.DISTRIBUTE: self._plan_distribute,
|
|
460
|
+
TransferMode.TRANSFER: self._plan_transfer,
|
|
461
|
+
}[self._mode]()
|
|
462
|
+
if self._strategy.new_tip == types.TransferTipPolicy.ONCE:
|
|
463
|
+
if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN:
|
|
464
|
+
yield self._format_dict("return_tip")
|
|
465
|
+
else:
|
|
466
|
+
yield self._format_dict("drop_tip")
|
|
467
|
+
|
|
468
|
+
def _plan_transfer(self) -> Generator[TransferStep, None, None]:
|
|
469
|
+
"""
|
|
470
|
+
* **Source/ Dest:** Multiple sources to multiple destinations.
|
|
471
|
+
Src & dest should be equal length
|
|
472
|
+
|
|
473
|
+
* **Volume:** Single volume or List of volumes is acceptable. This list
|
|
474
|
+
should be same length as sources/destinations
|
|
475
|
+
|
|
476
|
+
* **Behavior with transfer options:**
|
|
477
|
+
|
|
478
|
+
- New_tip: can be either NEVER or ONCE or ALWAYS
|
|
479
|
+
- Air_gap: if specified, will be performed after every aspirate
|
|
480
|
+
- Blow_out: can be performed after each dispense (after mix, before
|
|
481
|
+
touch_tip) at the location specified. If there is
|
|
482
|
+
liquid present in the tip (as in the case of nonzero
|
|
483
|
+
disposal volume), blow_out will be performed at either
|
|
484
|
+
user-defined location or (default) trash.
|
|
485
|
+
If no liquid is supposed to be present in the tip after
|
|
486
|
+
dispense, blow_out will be performed at dispense well
|
|
487
|
+
location (if blow out strategy is DEST)
|
|
488
|
+
- Touch_tip: can be performed after each aspirate and/or after
|
|
489
|
+
each dispense
|
|
490
|
+
- Mix: can be performed before aspirate and/or after dispense
|
|
491
|
+
if there is no disposal volume (i.e. can be performed
|
|
492
|
+
only when the tip is supposed to be empty)
|
|
493
|
+
|
|
494
|
+
Considering all options, the sequence of actions is:
|
|
495
|
+
*New Tip -> Mix -> Aspirate (with disposal volume) -> Air gap ->
|
|
496
|
+
-> Touch tip -> Dispense air gap -> Dispense -> Mix if empty ->
|
|
497
|
+
-> Blow out -> Touch tip -> Drop tip*
|
|
498
|
+
"""
|
|
499
|
+
# reform source target lists
|
|
500
|
+
sources, dests = self._extend_source_target_lists(self._sources, self._dests)
|
|
501
|
+
tx_commons.check_valid_volume_parameters(
|
|
502
|
+
disposal_volume=self._strategy.disposal_volume,
|
|
503
|
+
air_gap=self._strategy.air_gap,
|
|
504
|
+
max_volume=self._instr.max_volume,
|
|
505
|
+
)
|
|
506
|
+
plan_iter = tx_commons.expand_for_volume_constraints(
|
|
507
|
+
self._volumes,
|
|
508
|
+
zip(sources, dests),
|
|
509
|
+
self._instr.max_volume
|
|
510
|
+
- self._strategy.disposal_volume
|
|
511
|
+
- self._strategy.air_gap,
|
|
512
|
+
)
|
|
513
|
+
for step_vol, (src, dest) in plan_iter:
|
|
514
|
+
if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
|
|
515
|
+
yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
|
|
516
|
+
max_vol = (
|
|
517
|
+
self._max_volume
|
|
518
|
+
- self._strategy.disposal_volume
|
|
519
|
+
- self._strategy.air_gap
|
|
520
|
+
)
|
|
521
|
+
xferred_vol = 0.0
|
|
522
|
+
while xferred_vol < step_vol:
|
|
523
|
+
# TODO: account for unequal length sources, dests
|
|
524
|
+
# TODO: ensure last transfer is > min_vol
|
|
525
|
+
vol = min(max_vol, step_vol - xferred_vol)
|
|
526
|
+
yield from self._aspirate_actions(vol, src)
|
|
527
|
+
yield from self._dispense_actions(vol=vol, dest=dest, src=src)
|
|
528
|
+
xferred_vol += vol
|
|
529
|
+
yield from self._new_tip_action()
|
|
530
|
+
|
|
531
|
+
@staticmethod
|
|
532
|
+
def _extend_source_target_lists(
|
|
533
|
+
sources: List[Union[Well, types.Location]],
|
|
534
|
+
targets: List[Union[Well, types.Location]],
|
|
535
|
+
) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]:
|
|
536
|
+
"""Extend source or target list to match the length of the other"""
|
|
537
|
+
if len(sources) < len(targets):
|
|
538
|
+
if len(targets) % len(sources) != 0:
|
|
539
|
+
raise ValueError("Source and destination lists must be divisible")
|
|
540
|
+
sources = [
|
|
541
|
+
source
|
|
542
|
+
for source in sources
|
|
543
|
+
for i in range(int(len(targets) / len(sources)))
|
|
544
|
+
]
|
|
545
|
+
elif len(sources) > len(targets):
|
|
546
|
+
if len(sources) % len(targets) != 0:
|
|
547
|
+
raise ValueError("Source and destination lists must be divisible")
|
|
548
|
+
targets = [
|
|
549
|
+
target
|
|
550
|
+
for target in targets
|
|
551
|
+
for i in range(int(len(sources) / len(targets)))
|
|
552
|
+
]
|
|
553
|
+
return sources, targets
|
|
554
|
+
|
|
555
|
+
def _plan_distribute(self) -> Generator[TransferStep, None, None]:
|
|
556
|
+
"""
|
|
557
|
+
* **Source/ Dest:** One source to many destinations
|
|
558
|
+
* **Volume:** Single volume or List of volumes is acceptable. This list
|
|
559
|
+
should be same length as destinations
|
|
560
|
+
* **Behavior with transfer options:**
|
|
561
|
+
|
|
562
|
+
- New_tip: can be either NEVER or ONCE
|
|
563
|
+
(ALWAYS will fallback to ONCE)
|
|
564
|
+
- Air_gap: if specified, will be performed after every aspirate and
|
|
565
|
+
also in-between dispenses (to keep air gap while moving
|
|
566
|
+
between wells)
|
|
567
|
+
- Blow_out: can be performed at the end of distribute (after mix,
|
|
568
|
+
before touch_tip) at the location specified. If there
|
|
569
|
+
is liquid present in the tip, blow_out will be
|
|
570
|
+
performed at either user-defined location or (default)
|
|
571
|
+
trash. If no liquid is supposed to be present in the
|
|
572
|
+
tip at the end of distribute, blow_out will be
|
|
573
|
+
performed at the last well the liquid was dispensed to
|
|
574
|
+
(if strategy is DEST)
|
|
575
|
+
- Touch_tip: can be performed after each aspirate and/or after
|
|
576
|
+
every dispense
|
|
577
|
+
- Mix: can be performed before aspirate and/or after the last
|
|
578
|
+
dispense if there is no disposal volume (i.e. can be
|
|
579
|
+
performed only when the tip is supposed to be empty)
|
|
580
|
+
|
|
581
|
+
Considering all options, the sequence of actions is:
|
|
582
|
+
|
|
583
|
+
1. Going from source to dest1:
|
|
584
|
+
*New Tip -> Mix -> Aspirate (with disposal volume) -> Air gap ->
|
|
585
|
+
-> Touch tip -> Dispense air gap -> Dispense -> Mix if empty ->
|
|
586
|
+
-> Blow out -> Touch tip -> Drop tip*
|
|
587
|
+
2. Going from destn to destn+1:
|
|
588
|
+
*.. Dispense air gap -> Dispense -> Touch tip -> Air gap ->
|
|
589
|
+
.. Dispense air gap -> ...*
|
|
590
|
+
|
|
591
|
+
"""
|
|
592
|
+
|
|
593
|
+
tx_commons.check_valid_volume_parameters(
|
|
594
|
+
disposal_volume=self._strategy.disposal_volume,
|
|
595
|
+
air_gap=self._strategy.air_gap,
|
|
596
|
+
max_volume=self._instr.max_volume,
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
# TODO: decide whether default disposal vol for distribute should be
|
|
600
|
+
# pipette min_vol or should we leave it to being 0 by default and
|
|
601
|
+
# recommend users to specify a disposal vol when using distribute.
|
|
602
|
+
# First method keeps distribute consistent with current behavior while
|
|
603
|
+
# the other maintains consistency in default behaviors of all functions
|
|
604
|
+
plan_iter = tx_commons.expand_for_volume_constraints(
|
|
605
|
+
self._volumes,
|
|
606
|
+
self._dests,
|
|
607
|
+
# todo(mm, 2021-03-09): Is it right for this to be
|
|
608
|
+
# _instr_.max_volume? Does/should this take the tip maximum volume
|
|
609
|
+
# into account?
|
|
610
|
+
self._instr.max_volume
|
|
611
|
+
- self._strategy.disposal_volume
|
|
612
|
+
- self._strategy.air_gap,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
done = False
|
|
616
|
+
current_xfer = next(plan_iter)
|
|
617
|
+
if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
|
|
618
|
+
yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
|
|
619
|
+
while not done:
|
|
620
|
+
asp_grouped: List[Tuple[float, Well | types.Location]] = []
|
|
621
|
+
try:
|
|
622
|
+
while (
|
|
623
|
+
sum(a[0] for a in asp_grouped)
|
|
624
|
+
+ self._strategy.disposal_volume
|
|
625
|
+
+ self._strategy.air_gap
|
|
626
|
+
+ current_xfer[0]
|
|
627
|
+
) <= self._max_volume:
|
|
628
|
+
append_xfer = self._check_volume_not_zero(
|
|
629
|
+
self._api_version, current_xfer[0]
|
|
630
|
+
)
|
|
631
|
+
if append_xfer:
|
|
632
|
+
asp_grouped.append(current_xfer)
|
|
633
|
+
current_xfer = next(plan_iter)
|
|
634
|
+
except StopIteration:
|
|
635
|
+
done = True
|
|
636
|
+
if not asp_grouped:
|
|
637
|
+
break
|
|
638
|
+
|
|
639
|
+
yield from self._aspirate_actions(
|
|
640
|
+
sum(a[0] for a in asp_grouped) + self._strategy.disposal_volume,
|
|
641
|
+
self._sources[0],
|
|
642
|
+
)
|
|
643
|
+
for step in asp_grouped:
|
|
644
|
+
|
|
645
|
+
yield from self._dispense_actions(
|
|
646
|
+
vol=step[0],
|
|
647
|
+
src=self._sources[0],
|
|
648
|
+
dest=step[1],
|
|
649
|
+
is_disp_next=step is not asp_grouped[-1],
|
|
650
|
+
)
|
|
651
|
+
yield from self._new_tip_action()
|
|
652
|
+
|
|
653
|
+
def _plan_consolidate(self) -> Generator[TransferStep, None, None]:
|
|
654
|
+
"""
|
|
655
|
+
* **Source/ Dest:** Many sources to one destination
|
|
656
|
+
* **Volume:** Single volume or List of volumes is acceptable. This list
|
|
657
|
+
should be same length as sources
|
|
658
|
+
* **Behavior with transfer options:**
|
|
659
|
+
|
|
660
|
+
- New_tip: can be either NEVER or ONCE
|
|
661
|
+
(ALWAYS will fallback to ONCE)
|
|
662
|
+
- Air_gap: if specified, will be performed after every aspirate
|
|
663
|
+
so that the aspirated liquids do not mix inside the tip.
|
|
664
|
+
The air gap will be dispensed while dispensing the
|
|
665
|
+
liquid into the destination well.
|
|
666
|
+
- Blow_out: can be performed after a dispense (after mix,
|
|
667
|
+
before touch_tip) at the location specified. If there
|
|
668
|
+
is liquid present in the tip (which shouldn't happen
|
|
669
|
+
since consolidate doesn't take a disposal vol, yet),
|
|
670
|
+
blow_out will be performed at either user-defined
|
|
671
|
+
location or (default) trash.
|
|
672
|
+
If no liquid is supposed to be present in the tip after
|
|
673
|
+
dispense, blow_out will be performed at dispense well
|
|
674
|
+
loc (if blow out strategy is DEST)
|
|
675
|
+
- Touch_tip: can be performed after each aspirate and/or after
|
|
676
|
+
dispense
|
|
677
|
+
- Mix: can be performed before the first aspirate and/or after
|
|
678
|
+
dispense if there is no disposal volume (i.e. can be
|
|
679
|
+
performed only when the tip is supposed to be empty)
|
|
680
|
+
|
|
681
|
+
Considering all options, the sequence of actions is:
|
|
682
|
+
1. Going from source to dest1:
|
|
683
|
+
*New Tip -> Mix -> Aspirate (with disposal volume?) -> Air gap
|
|
684
|
+
-> Touch tip -> Dispense air gap -> Dispense -> Mix if empty ->
|
|
685
|
+
-> Blow out -> Touch tip -> Drop tip*
|
|
686
|
+
2. Going from source(n) to source(n+1):
|
|
687
|
+
*.. Aspirate -> Air gap -> Touch tip ->..
|
|
688
|
+
.. Aspirate -> .....*
|
|
689
|
+
"""
|
|
690
|
+
# TODO: verify if _check_valid_volume_parameters should be re-enabled here
|
|
691
|
+
# self._check_valid_volume_parameters(
|
|
692
|
+
# disposal_volume=self._strategy.disposal_volume,
|
|
693
|
+
# air_gap=self._strategy.air_gap,
|
|
694
|
+
# max_volume=self._instr.max_volume,
|
|
695
|
+
# )
|
|
696
|
+
plan_iter = tx_commons.expand_for_volume_constraints(
|
|
697
|
+
# todo(mm, 2021-03-09): Is it right to use _instr.max_volume here?
|
|
698
|
+
# Why don't we account for tip max volume, disposal volume, or air
|
|
699
|
+
# gap?
|
|
700
|
+
self._volumes,
|
|
701
|
+
self._sources,
|
|
702
|
+
self._instr.max_volume,
|
|
703
|
+
)
|
|
704
|
+
current_xfer = next(plan_iter)
|
|
705
|
+
if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
|
|
706
|
+
yield self._format_dict("pick_up_tip", kwargs=self._tip_opts)
|
|
707
|
+
done = False
|
|
708
|
+
while not done:
|
|
709
|
+
asp_grouped: List[Tuple[float, Union[Well, types.Location]]] = []
|
|
710
|
+
try:
|
|
711
|
+
while (
|
|
712
|
+
sum([a[0] for a in asp_grouped])
|
|
713
|
+
+ self._strategy.disposal_volume
|
|
714
|
+
+ self._strategy.air_gap * len(asp_grouped)
|
|
715
|
+
+ current_xfer[0]
|
|
716
|
+
) <= self._max_volume:
|
|
717
|
+
append_xfer = self._check_volume_not_zero(
|
|
718
|
+
self._api_version, current_xfer[0]
|
|
719
|
+
)
|
|
720
|
+
if append_xfer:
|
|
721
|
+
asp_grouped.append(current_xfer)
|
|
722
|
+
current_xfer = next(plan_iter)
|
|
723
|
+
except StopIteration:
|
|
724
|
+
done = True
|
|
725
|
+
if not asp_grouped:
|
|
726
|
+
break
|
|
727
|
+
# Q: What accounts as disposal volume in a consolidate action?
|
|
728
|
+
# yield self._format_dict('aspirate',
|
|
729
|
+
# self._strategy.disposal_volume, loc)
|
|
730
|
+
for step in asp_grouped:
|
|
731
|
+
yield from self._aspirate_actions(step[0], step[1])
|
|
732
|
+
yield from self._dispense_actions(
|
|
733
|
+
vol=sum([a[0] + self._strategy.air_gap for a in asp_grouped])
|
|
734
|
+
- self._strategy.air_gap,
|
|
735
|
+
src=None,
|
|
736
|
+
dest=self._dests[0],
|
|
737
|
+
)
|
|
738
|
+
yield from self._new_tip_action()
|
|
739
|
+
|
|
740
|
+
def _aspirate_actions(
|
|
741
|
+
self, vol: float, loc: Union[Well, types.Location]
|
|
742
|
+
) -> Generator[TransferStep, None, None]:
|
|
743
|
+
yield from self._before_aspirate(loc)
|
|
744
|
+
yield self._format_dict("aspirate", [vol, loc, self._options.aspirate.rate])
|
|
745
|
+
yield from self._after_aspirate()
|
|
746
|
+
|
|
747
|
+
def _dispense_actions(
|
|
748
|
+
self,
|
|
749
|
+
vol: float,
|
|
750
|
+
dest: Union[Well, types.Location],
|
|
751
|
+
src: Optional[Union[Well, types.Location]] = None,
|
|
752
|
+
is_disp_next: bool = False,
|
|
753
|
+
) -> Generator[TransferStep, None, None]:
|
|
754
|
+
if self._strategy.air_gap:
|
|
755
|
+
vol += self._strategy.air_gap
|
|
756
|
+
yield self._format_dict("dispense", [vol, dest, self._options.dispense.rate])
|
|
757
|
+
yield from self._after_dispense(dest=dest, src=src, is_disp_next=is_disp_next)
|
|
758
|
+
|
|
759
|
+
def _before_aspirate(
|
|
760
|
+
self, loc: Union[Well, types.Location]
|
|
761
|
+
) -> Generator[TransferStep, None, None]:
|
|
762
|
+
if (
|
|
763
|
+
self._strategy.mix_strategy == MixStrategy.BEFORE
|
|
764
|
+
or self._strategy.mix_strategy == MixStrategy.BOTH
|
|
765
|
+
):
|
|
766
|
+
if self._instr.current_volume == 0:
|
|
767
|
+
mix_before_opts = self._mix_before_opts._asdict()
|
|
768
|
+
mix_before_opts["location"] = loc
|
|
769
|
+
yield self._format_dict("mix", kwargs=mix_before_opts)
|
|
770
|
+
|
|
771
|
+
def _after_aspirate(self) -> Generator[TransferStep, None, None]:
|
|
772
|
+
if self._strategy.air_gap:
|
|
773
|
+
yield self._format_dict("air_gap", [self._strategy.air_gap])
|
|
774
|
+
if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
|
|
775
|
+
yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
|
|
776
|
+
|
|
777
|
+
def _after_dispense_trash(self) -> Generator[TransferStep, None, None]:
|
|
778
|
+
if isinstance(self._instr.trash_container, Labware):
|
|
779
|
+
yield self._format_dict(
|
|
780
|
+
"blow_out", [self._instr.trash_container.wells()[0]]
|
|
781
|
+
)
|
|
782
|
+
else:
|
|
783
|
+
yield self._format_dict("blow_out", [self._instr.trash_container])
|
|
784
|
+
|
|
785
|
+
def _after_dispense_helper(self) -> Generator[TransferStep, None, None]:
|
|
786
|
+
# Used by distribute
|
|
787
|
+
if self._strategy.air_gap:
|
|
788
|
+
yield self._format_dict("air_gap", [self._strategy.air_gap])
|
|
789
|
+
if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
|
|
790
|
+
yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
|
|
791
|
+
|
|
792
|
+
def _after_dispense(
|
|
793
|
+
self,
|
|
794
|
+
dest: Union[Well, types.Location],
|
|
795
|
+
src: Optional[Union[types.Location, Well]],
|
|
796
|
+
is_disp_next: bool = False,
|
|
797
|
+
) -> Generator[TransferStep, None, None]:
|
|
798
|
+
# This sequence of actions is subject to change
|
|
799
|
+
if not is_disp_next:
|
|
800
|
+
# If the next command is an aspirate, we are switching
|
|
801
|
+
# between aspirate and dispense.
|
|
802
|
+
if self._instr.current_volume == 0:
|
|
803
|
+
# If we're empty, then this is when after mixes come into play
|
|
804
|
+
if (
|
|
805
|
+
self._strategy.mix_strategy == MixStrategy.AFTER
|
|
806
|
+
or self._strategy.mix_strategy == MixStrategy.BOTH
|
|
807
|
+
):
|
|
808
|
+
mix_after_opts = self._mix_after_opts._asdict()
|
|
809
|
+
mix_after_opts["location"] = dest
|
|
810
|
+
yield self._format_dict("mix", kwargs=mix_after_opts)
|
|
811
|
+
if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS:
|
|
812
|
+
yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts)
|
|
813
|
+
|
|
814
|
+
if self._strategy.blow_out_strategy == BlowOutStrategy.SOURCE:
|
|
815
|
+
yield self._format_dict("blow_out", [src])
|
|
816
|
+
elif self._strategy.blow_out_strategy == BlowOutStrategy.DEST:
|
|
817
|
+
yield self._format_dict("blow_out", [dest])
|
|
818
|
+
elif self._strategy.blow_out_strategy == BlowOutStrategy.CUSTOM_LOCATION:
|
|
819
|
+
yield self._format_dict("blow_out", kwargs=self._blow_opts)
|
|
820
|
+
elif (
|
|
821
|
+
self._strategy.blow_out_strategy == BlowOutStrategy.TRASH
|
|
822
|
+
or self._strategy.disposal_volume
|
|
823
|
+
):
|
|
824
|
+
yield from self._after_dispense_trash()
|
|
825
|
+
else:
|
|
826
|
+
yield from self._after_dispense_helper()
|
|
827
|
+
|
|
828
|
+
def _new_tip_action(self) -> Generator[TransferStep, None, None]:
|
|
829
|
+
if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS:
|
|
830
|
+
if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN:
|
|
831
|
+
yield self._format_dict("return_tip")
|
|
832
|
+
else:
|
|
833
|
+
yield self._format_dict("drop_tip")
|
|
834
|
+
|
|
835
|
+
def _format_dict(
|
|
836
|
+
self,
|
|
837
|
+
method: str,
|
|
838
|
+
args: Optional[List[Any]] = None,
|
|
839
|
+
kwargs: Optional[Union[Dict[Any, Any], FormatDictArgs]] = None,
|
|
840
|
+
) -> TransferStep:
|
|
841
|
+
if kwargs:
|
|
842
|
+
if isinstance(kwargs, Dict):
|
|
843
|
+
params = {key: val for key, val in kwargs.items() if val}
|
|
844
|
+
else:
|
|
845
|
+
params = {key: val for key, val in kwargs._asdict().items() if val}
|
|
846
|
+
else:
|
|
847
|
+
params = {}
|
|
848
|
+
if not args:
|
|
849
|
+
args = []
|
|
850
|
+
return {"method": method, "args": args, "kwargs": params}
|
|
851
|
+
|
|
852
|
+
def _create_volume_list(
|
|
853
|
+
self, volume: Union[Union[float, int], Sequence[float]], total_xfers: int
|
|
854
|
+
) -> List[float]:
|
|
855
|
+
if isinstance(volume, (float, int)):
|
|
856
|
+
return [float(volume)] * total_xfers
|
|
857
|
+
elif isinstance(volume, tuple):
|
|
858
|
+
return self._create_volume_gradient(
|
|
859
|
+
volume[0], volume[-1], total_xfers, self._strategy.gradient_function
|
|
860
|
+
)
|
|
861
|
+
else:
|
|
862
|
+
if not isinstance(volume, List):
|
|
863
|
+
raise TypeError(
|
|
864
|
+
"Volume expected as a number or List or"
|
|
865
|
+
" tuple but got {}".format(volume)
|
|
866
|
+
)
|
|
867
|
+
elif not len(volume) == total_xfers:
|
|
868
|
+
raise RuntimeError(
|
|
869
|
+
"List of volumes should be equal to number " "of transfers"
|
|
870
|
+
)
|
|
871
|
+
return volume
|
|
872
|
+
|
|
873
|
+
@staticmethod
|
|
874
|
+
def _create_volume_gradient(
|
|
875
|
+
min_v: float,
|
|
876
|
+
max_v: float,
|
|
877
|
+
total: int,
|
|
878
|
+
gradient: Optional[Callable[[float], float]] = None,
|
|
879
|
+
) -> List[float]:
|
|
880
|
+
|
|
881
|
+
diff_vol = max_v - min_v
|
|
882
|
+
|
|
883
|
+
def _map_volume(i: int) -> float:
|
|
884
|
+
nonlocal diff_vol, total
|
|
885
|
+
rel_x = i / (total - 1)
|
|
886
|
+
rel_y = gradient(rel_x) if gradient else rel_x
|
|
887
|
+
return (rel_y * diff_vol) + min_v
|
|
888
|
+
|
|
889
|
+
return [_map_volume(i) for i in range(total)]
|
|
890
|
+
|
|
891
|
+
def _check_valid_well_list(
|
|
892
|
+
self, well_list: List[Any], id: str, old_well_list: List[Any]
|
|
893
|
+
) -> None:
|
|
894
|
+
if self._api_version >= APIVersion(2, 2) and len(well_list) < 1:
|
|
895
|
+
raise RuntimeError(
|
|
896
|
+
f"Invalid {id} for multichannel transfer: {old_well_list}"
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
@staticmethod
|
|
900
|
+
def _check_volume_not_zero(api_version: APIVersion, volume: float) -> bool:
|
|
901
|
+
# We should only be adding volumes to transfer plans if it is
|
|
902
|
+
# greater than zero to prevent extraneous robot movements.
|
|
903
|
+
if api_version < APIVersion(2, 8):
|
|
904
|
+
return True
|
|
905
|
+
elif volume > 0:
|
|
906
|
+
return True
|
|
907
|
+
return False
|
|
908
|
+
|
|
909
|
+
def _multichannel_transfer(
|
|
910
|
+
self, s: AdvancedLiquidHandling, d: AdvancedLiquidHandling
|
|
911
|
+
) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]:
|
|
912
|
+
# TODO: add a check for container being multi-channel compatible?
|
|
913
|
+
# Helper function for multi-channel use-case
|
|
914
|
+
assert (
|
|
915
|
+
isinstance(s, Well)
|
|
916
|
+
or isinstance(s, types.Location)
|
|
917
|
+
or (isinstance(s, List) and isinstance(s[0], Well))
|
|
918
|
+
or (isinstance(s, List) and isinstance(s[0], List))
|
|
919
|
+
or (isinstance(s, List) and isinstance(s[0], types.Location))
|
|
920
|
+
), "Source should be a Well or List[Well] but is {}".format(s)
|
|
921
|
+
assert (
|
|
922
|
+
isinstance(d, Well)
|
|
923
|
+
or isinstance(d, types.Location)
|
|
924
|
+
or (isinstance(d, List) and isinstance(d[0], Well))
|
|
925
|
+
or (isinstance(d, List) and isinstance(d[0], List))
|
|
926
|
+
or (isinstance(d, List) and isinstance(d[0], types.Location))
|
|
927
|
+
), "Target should be a Well or List[Well] but is {}".format(d)
|
|
928
|
+
|
|
929
|
+
# TODO: Account for cases where a src/dest list has a non-first-row
|
|
930
|
+
# well (eg, 'B1') and would expect the robot/pipette to
|
|
931
|
+
# understand that it is referring to the whole first column
|
|
932
|
+
if isinstance(s, List) and isinstance(s[0], List):
|
|
933
|
+
# s is a List[List]]; flatten to 1D list
|
|
934
|
+
s = [well for list_elem in s for well in list_elem]
|
|
935
|
+
elif isinstance(s, Well) or isinstance(s, types.Location):
|
|
936
|
+
s = [s]
|
|
937
|
+
new_src = []
|
|
938
|
+
for well in s:
|
|
939
|
+
if self._is_valid_row(well):
|
|
940
|
+
new_src.append(well)
|
|
941
|
+
self._check_valid_well_list(new_src, "source", s)
|
|
942
|
+
|
|
943
|
+
if isinstance(d, List) and isinstance(d[0], List):
|
|
944
|
+
# s is a List[List]]; flatten to 1D list
|
|
945
|
+
d = [well for list_elem in d for well in list_elem]
|
|
946
|
+
elif isinstance(d, Well) or isinstance(d, types.Location):
|
|
947
|
+
d = [d]
|
|
948
|
+
new_dst = []
|
|
949
|
+
for well in d:
|
|
950
|
+
if self._is_valid_row(well):
|
|
951
|
+
new_dst.append(well)
|
|
952
|
+
self._check_valid_well_list(new_dst, "target", d)
|
|
953
|
+
return new_src, new_dst
|
|
954
|
+
|
|
955
|
+
def _is_valid_row(self, well: Union[Well, types.Location]) -> bool:
|
|
956
|
+
if isinstance(well, types.Location):
|
|
957
|
+
test_well = well.labware.as_well()
|
|
958
|
+
else:
|
|
959
|
+
test_well = well
|
|
960
|
+
|
|
961
|
+
if self._api_version < APIVersion(2, 2):
|
|
962
|
+
return test_well in test_well.parent.rows()[0]
|
|
963
|
+
else:
|
|
964
|
+
# Allow the first 2 rows to be accessible to 384-well plates;
|
|
965
|
+
# otherwise, only the first row is accessible
|
|
966
|
+
if test_well.parent.parameters["format"] == "384Standard":
|
|
967
|
+
valid_wells = [
|
|
968
|
+
well for row in test_well.parent.rows()[:2] for well in row
|
|
969
|
+
]
|
|
970
|
+
return test_well in valid_wells
|
|
971
|
+
else:
|
|
972
|
+
return test_well in test_well.parent.rows()[0]
|