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,1171 @@
|
|
|
1
|
+
"""Functions and utilites for OT3 calibration."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from dataclasses import dataclass, replace
|
|
5
|
+
from typing_extensions import Final, Literal, TYPE_CHECKING
|
|
6
|
+
from typing import Tuple, List, Dict, Any, Optional, Union
|
|
7
|
+
import datetime
|
|
8
|
+
import numpy as np
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from math import floor, copysign
|
|
11
|
+
from logging import getLogger
|
|
12
|
+
from opentrons.util.linal import solve_attitude, SolvePoints, DoubleMatrix
|
|
13
|
+
|
|
14
|
+
from .types import OT3Mount, Axis, GripperProbe
|
|
15
|
+
from opentrons.types import Point
|
|
16
|
+
from opentrons.config.types import CapacitivePassSettings, EdgeSenseSettings, OT3Config
|
|
17
|
+
from opentrons.hardware_control.types import InstrumentProbeType
|
|
18
|
+
import json
|
|
19
|
+
|
|
20
|
+
from opentrons_shared_data.deck import (
|
|
21
|
+
get_calibration_square_position_in_slot,
|
|
22
|
+
Z_PREP_OFFSET,
|
|
23
|
+
CALIBRATION_PROBE_RADIUS,
|
|
24
|
+
CALIBRATION_SQUARE_EDGES as SQUARE_EDGES,
|
|
25
|
+
)
|
|
26
|
+
from opentrons_shared_data.errors.exceptions import (
|
|
27
|
+
CalibrationStructureNotFoundError,
|
|
28
|
+
EdgeNotFoundError,
|
|
29
|
+
EarlyCapacitiveSenseTrigger,
|
|
30
|
+
InaccurateNonContactSweepError,
|
|
31
|
+
MisalignedGantryError,
|
|
32
|
+
)
|
|
33
|
+
from .robot_calibration import (
|
|
34
|
+
RobotCalibration,
|
|
35
|
+
DeckCalibration,
|
|
36
|
+
)
|
|
37
|
+
from opentrons.calibration_storage import types
|
|
38
|
+
from opentrons.calibration_storage.ot3.deck_attitude import (
|
|
39
|
+
save_robot_belt_attitude,
|
|
40
|
+
get_robot_belt_attitude,
|
|
41
|
+
delete_robot_belt_attitude,
|
|
42
|
+
)
|
|
43
|
+
from opentrons.config.robot_configs import (
|
|
44
|
+
default_ot3_deck_calibration,
|
|
45
|
+
)
|
|
46
|
+
from opentrons.config import defaults_ot3
|
|
47
|
+
from .util import DeckTransformState
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from opentrons.hardware_control import OT3HardwareControlAPI
|
|
51
|
+
|
|
52
|
+
LOG = getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
LINEAR_TRANSIT_HEIGHT: Final[float] = 1
|
|
55
|
+
SEARCH_TRANSIT_HEIGHT: Final[float] = 5
|
|
56
|
+
GRIPPER_GRIP_FORCE: Final[float] = 20 # FIXME: (andy s) this adds error, reduce to 5N
|
|
57
|
+
|
|
58
|
+
SLOT_CENTER = 5
|
|
59
|
+
SLOT_FRONT_LEFT = 1
|
|
60
|
+
SLOT_FRONT_RIGHT = 3
|
|
61
|
+
SLOT_REAR_LEFT = 10
|
|
62
|
+
|
|
63
|
+
PREP_OFFSET_DEPTH = Point(*Z_PREP_OFFSET)
|
|
64
|
+
EDGES = {
|
|
65
|
+
"left": Point(*SQUARE_EDGES["left"]),
|
|
66
|
+
"right": Point(*SQUARE_EDGES["right"]),
|
|
67
|
+
"top": Point(*SQUARE_EDGES["top"]),
|
|
68
|
+
"bottom": Point(*SQUARE_EDGES["bottom"]),
|
|
69
|
+
}
|
|
70
|
+
OFFSET_SECONDARY_PROBE = {
|
|
71
|
+
8: Point(x=0, y=9 * 7, z=0),
|
|
72
|
+
96: Point(x=9 * -11, y=9 * 7, z=0),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CalibrationMethod(Enum):
|
|
77
|
+
BINARY_SEARCH = "binary search"
|
|
78
|
+
NONCONTACT_PASS = "noncontact pass"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class CalibrationTarget(Enum):
|
|
82
|
+
DECK_OBJECT = "deck_object"
|
|
83
|
+
GANTRY_INSTRUMENT = "gantry_instrument"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class CalibrationSlot:
|
|
88
|
+
slot: int
|
|
89
|
+
nominal: Point
|
|
90
|
+
actual: Point
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class AlignmentShift(Enum):
|
|
94
|
+
LEFT_TO_RIGHT_Y = "left_to_right_y"
|
|
95
|
+
LEFT_TO_RIGHT_Z = "left_to_right_z"
|
|
96
|
+
FRONT_TO_REAR_X = "front_to_rear_x"
|
|
97
|
+
FRONT_TO_REAR_Z = "front_to_rear_z"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# 96ch is 99mm from left->right, and 63mm front->rear
|
|
101
|
+
# deck calibration squares are 328mm left->right and 321mm front->rear
|
|
102
|
+
# we need to support <=0.1mm shift from 96ch left->right
|
|
103
|
+
# which means we would ideally spec left/right shift <=0.33mm
|
|
104
|
+
# and front/rear shift <=0.5 (but in reality will be bigger)
|
|
105
|
+
# TODO: these will need to update (increase) after testing
|
|
106
|
+
# on DVT2 lifetime units, as well as PVT units
|
|
107
|
+
MAX_SHIFT = {
|
|
108
|
+
AlignmentShift.LEFT_TO_RIGHT_Y: 0.5, # increased from 0.3, based on test results
|
|
109
|
+
AlignmentShift.LEFT_TO_RIGHT_Z: 0.5, # increased from 0.3, based on test results
|
|
110
|
+
AlignmentShift.FRONT_TO_REAR_X: 0.5,
|
|
111
|
+
AlignmentShift.FRONT_TO_REAR_Z: 0.5,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _verify_height(
|
|
116
|
+
found_pos: float, expected_pos: float, settings: EdgeSenseSettings
|
|
117
|
+
) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Evaluate the height found by capacitive probe against search settings.
|
|
120
|
+
"""
|
|
121
|
+
if found_pos > expected_pos + settings.early_sense_tolerance_mm:
|
|
122
|
+
raise EarlyCapacitiveSenseTrigger(found_pos, expected_pos)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def _verify_edge_pos(
|
|
126
|
+
hcapi: OT3HardwareControlAPI,
|
|
127
|
+
mount: OT3Mount,
|
|
128
|
+
search_axis: Union[Literal[Axis.X, Axis.Y]],
|
|
129
|
+
found_edge: Point,
|
|
130
|
+
last_stride: float,
|
|
131
|
+
search_direction: Literal[1, -1],
|
|
132
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
133
|
+
) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Probe both sides of the found edge in the search axis and compare the results.
|
|
136
|
+
If the position found is valid, we should see that the probe hit the deck on
|
|
137
|
+
one side and miss on the other. If the results are not opposite of each other,
|
|
138
|
+
we didn't find the edge properly.
|
|
139
|
+
"""
|
|
140
|
+
edge_settings = hcapi.config.calibration.edge_sense
|
|
141
|
+
# twice the last stride size
|
|
142
|
+
check_stride = last_stride * 2
|
|
143
|
+
last_result = None
|
|
144
|
+
edge_name_str = f"{'+' if search_direction == -1 else '-'}{search_axis}"
|
|
145
|
+
for dir in [-1, 1]:
|
|
146
|
+
checking_pos = found_edge + search_axis.set_in_point(
|
|
147
|
+
Point(0, 0, 0), check_stride * dir
|
|
148
|
+
)
|
|
149
|
+
LOG.info(
|
|
150
|
+
f"Checking {edge_name_str} in {dir} direction at {checking_pos}, stride_size: {check_stride}"
|
|
151
|
+
)
|
|
152
|
+
height, hit_deck = await _probe_deck_at(
|
|
153
|
+
hcapi, mount, checking_pos, edge_settings.pass_settings, probe=probe
|
|
154
|
+
)
|
|
155
|
+
_verify_height(height, found_edge.z, edge_settings)
|
|
156
|
+
LOG.info(f"Deck {'hit' if hit_deck else 'miss'} at check pos: {checking_pos}")
|
|
157
|
+
if last_result is not None and hit_deck != last_result:
|
|
158
|
+
LOG.info(
|
|
159
|
+
f"Edge {edge_name_str} verified successfully at {check_stride} mm resolution."
|
|
160
|
+
)
|
|
161
|
+
return
|
|
162
|
+
else:
|
|
163
|
+
last_result = hit_deck
|
|
164
|
+
raise EdgeNotFoundError(edge_name_str, check_stride)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def critical_edge_offset(
|
|
168
|
+
search_axis: Union[Literal[Axis.X, Axis.Y]], direction_if_hit: Literal[1, -1]
|
|
169
|
+
) -> Point:
|
|
170
|
+
"""
|
|
171
|
+
Offset to be applied when we are aligning the edge of the probe to the edge of the
|
|
172
|
+
calibration square.
|
|
173
|
+
"""
|
|
174
|
+
return search_axis.set_in_point(
|
|
175
|
+
Point(0, 0, 0), direction_if_hit * CALIBRATION_PROBE_RADIUS
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def find_edge_binary(
|
|
180
|
+
hcapi: OT3HardwareControlAPI,
|
|
181
|
+
mount: OT3Mount,
|
|
182
|
+
slot_edge_nominal: Point,
|
|
183
|
+
search_axis: Union[Literal[Axis.X, Axis.Y]],
|
|
184
|
+
edge_settings: EdgeSenseSettings,
|
|
185
|
+
direction_if_hit: Literal[1, -1],
|
|
186
|
+
raise_verify_error: bool = True,
|
|
187
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
188
|
+
) -> Point:
|
|
189
|
+
"""
|
|
190
|
+
Find the true position of one edge of the calibration slot in the deck.
|
|
191
|
+
The nominal position of the calibration slots is known because they're
|
|
192
|
+
machined into the deck, but if we haven't yet calibrated we won't know
|
|
193
|
+
quite where they are. This routine finds the XY position that will
|
|
194
|
+
place the calibration probe such that its center is in the slot, and
|
|
195
|
+
one edge is on the edge of the slot.
|
|
196
|
+
Params
|
|
197
|
+
------
|
|
198
|
+
hcapi: The api instance to run commands through
|
|
199
|
+
mount: The mount to calibrate
|
|
200
|
+
slot_edge_nominal: The point describing the nominal position of the
|
|
201
|
+
edge that we're checking. Its in-axis coordinate (i.e. its x coordinate
|
|
202
|
+
for an x edge) should be the nominal position that we'll compare to. Its
|
|
203
|
+
cross-axis coordiante (i.e. its y coordinate for an x edge) should be
|
|
204
|
+
the point along the edge to search at, usually the midpoint. Its z-axis
|
|
205
|
+
coordinate should be the current best estimate for the height of the deck.
|
|
206
|
+
search_axis: The axis along which to search
|
|
207
|
+
direction_if_hit: The direction to search next if the probe hits the deck.
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
The absolute position at which the center of the effector is inside the slot
|
|
211
|
+
and its edge is aligned with the calibration slot edge.
|
|
212
|
+
"""
|
|
213
|
+
# Our first search position is at the slot edge nominal at the probe's edge
|
|
214
|
+
checking_pos = slot_edge_nominal + critical_edge_offset(
|
|
215
|
+
search_axis, direction_if_hit
|
|
216
|
+
)
|
|
217
|
+
stride = edge_settings.search_initial_tolerance_mm * direction_if_hit
|
|
218
|
+
final_z_height_found = slot_edge_nominal.z
|
|
219
|
+
for _ in range(edge_settings.search_iteration_limit):
|
|
220
|
+
LOG.info(f"Checking position {checking_pos}")
|
|
221
|
+
interaction_pos, hit_deck = await _probe_deck_at(
|
|
222
|
+
hcapi, mount, checking_pos, edge_settings.pass_settings, probe=probe
|
|
223
|
+
)
|
|
224
|
+
_verify_height(interaction_pos, checking_pos.z, edge_settings)
|
|
225
|
+
if hit_deck:
|
|
226
|
+
# In this block, we've hit the deck
|
|
227
|
+
LOG.info(f"hit at {interaction_pos}, stride size: {stride}")
|
|
228
|
+
# store the final found Z height found
|
|
229
|
+
# because the height is most accurate next to the edge
|
|
230
|
+
final_z_height_found = interaction_pos
|
|
231
|
+
if copysign(stride, direction_if_hit) == stride:
|
|
232
|
+
# If we're in direction_if_hit direction, the last probe was on the deck,
|
|
233
|
+
# so we want to continue
|
|
234
|
+
stride = stride / 2
|
|
235
|
+
else:
|
|
236
|
+
# if we're against direction_if_hit, the last probe missed,
|
|
237
|
+
# so we want to switch back to narrow things down
|
|
238
|
+
stride = -stride / 2
|
|
239
|
+
else:
|
|
240
|
+
LOG.info(f"Miss at {interaction_pos}, stride size: {stride}")
|
|
241
|
+
# In this block, we've missed the deck
|
|
242
|
+
if copysign(stride, direction_if_hit) == stride:
|
|
243
|
+
# if we are moving in the same direction as direction_if_hit, then we
|
|
244
|
+
# need to reverse - this would be the first time we've missed the deck
|
|
245
|
+
stride = -stride / 2
|
|
246
|
+
else:
|
|
247
|
+
# if we are against direction_if_hit, the last test was off the
|
|
248
|
+
# deck too, so we want to continue
|
|
249
|
+
stride = stride / 2
|
|
250
|
+
checking_pos += search_axis.set_in_point(Point(0, 0, 0), stride)
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
await _verify_edge_pos(
|
|
254
|
+
hcapi,
|
|
255
|
+
mount,
|
|
256
|
+
search_axis,
|
|
257
|
+
checking_pos,
|
|
258
|
+
abs(stride * 2),
|
|
259
|
+
direction_if_hit,
|
|
260
|
+
probe=probe,
|
|
261
|
+
)
|
|
262
|
+
except EdgeNotFoundError as e:
|
|
263
|
+
if raise_verify_error:
|
|
264
|
+
raise
|
|
265
|
+
else:
|
|
266
|
+
LOG.warning(e)
|
|
267
|
+
# remove probe offset so we actually get position of the edge
|
|
268
|
+
found_edge = checking_pos - critical_edge_offset(search_axis, direction_if_hit)
|
|
269
|
+
# use the last-found Z height as the edge's most true Z position
|
|
270
|
+
found_edge = found_edge._replace(z=final_z_height_found)
|
|
271
|
+
return found_edge
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
async def find_slot_center_binary(
|
|
275
|
+
hcapi: OT3HardwareControlAPI,
|
|
276
|
+
mount: OT3Mount,
|
|
277
|
+
estimated_center: Point,
|
|
278
|
+
raise_verify_error: bool = True,
|
|
279
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
280
|
+
) -> Point:
|
|
281
|
+
"""Find the center of the calibration slot by binary-searching its edges.
|
|
282
|
+
Returns the XY-center of the slot.
|
|
283
|
+
"""
|
|
284
|
+
edge_settings = hcapi.config.calibration.edge_sense
|
|
285
|
+
# Find all four edges of the calibration slot
|
|
286
|
+
plus_x_edge = await find_edge_binary(
|
|
287
|
+
hcapi,
|
|
288
|
+
mount,
|
|
289
|
+
estimated_center + EDGES["right"],
|
|
290
|
+
Axis.X,
|
|
291
|
+
edge_settings,
|
|
292
|
+
-1,
|
|
293
|
+
raise_verify_error,
|
|
294
|
+
probe=probe,
|
|
295
|
+
)
|
|
296
|
+
LOG.info(f"Found +x edge at {plus_x_edge.x}mm")
|
|
297
|
+
estimated_center = estimated_center._replace(x=plus_x_edge.x - EDGES["right"].x)
|
|
298
|
+
|
|
299
|
+
plus_y_edge = await find_edge_binary(
|
|
300
|
+
hcapi,
|
|
301
|
+
mount,
|
|
302
|
+
estimated_center + EDGES["top"],
|
|
303
|
+
Axis.Y,
|
|
304
|
+
edge_settings,
|
|
305
|
+
-1,
|
|
306
|
+
raise_verify_error,
|
|
307
|
+
probe=probe,
|
|
308
|
+
)
|
|
309
|
+
LOG.info(f"Found +y edge at {plus_y_edge.y}mm")
|
|
310
|
+
estimated_center = estimated_center._replace(y=plus_y_edge.y - EDGES["top"].y)
|
|
311
|
+
|
|
312
|
+
# since we have a good idea where the edges are now we can reduce the second set of sweeps
|
|
313
|
+
fast_edge_settings = replace(
|
|
314
|
+
edge_settings,
|
|
315
|
+
search_initial_tolerance_mm=edge_settings.search_initial_tolerance_mm / 4,
|
|
316
|
+
search_iteration_limit=edge_settings.search_iteration_limit - 2,
|
|
317
|
+
)
|
|
318
|
+
minus_x_edge = await find_edge_binary(
|
|
319
|
+
hcapi,
|
|
320
|
+
mount,
|
|
321
|
+
estimated_center + EDGES["left"],
|
|
322
|
+
Axis.X,
|
|
323
|
+
fast_edge_settings,
|
|
324
|
+
1,
|
|
325
|
+
raise_verify_error,
|
|
326
|
+
probe=probe,
|
|
327
|
+
)
|
|
328
|
+
LOG.info(f"Found -x edge at {minus_x_edge.x}mm")
|
|
329
|
+
estimated_center = estimated_center._replace(x=(plus_x_edge.x + minus_x_edge.x) / 2)
|
|
330
|
+
|
|
331
|
+
minus_y_edge = await find_edge_binary(
|
|
332
|
+
hcapi,
|
|
333
|
+
mount,
|
|
334
|
+
estimated_center + EDGES["bottom"],
|
|
335
|
+
Axis.Y,
|
|
336
|
+
fast_edge_settings,
|
|
337
|
+
1,
|
|
338
|
+
raise_verify_error,
|
|
339
|
+
probe=probe,
|
|
340
|
+
)
|
|
341
|
+
LOG.info(f"Found -y edge at {minus_y_edge.y}mm")
|
|
342
|
+
estimated_center = estimated_center._replace(y=(plus_y_edge.y + minus_y_edge.y) / 2)
|
|
343
|
+
|
|
344
|
+
# Found XY center and the average of the edges' Zs
|
|
345
|
+
return estimated_center._replace(
|
|
346
|
+
z=(plus_x_edge.z + minus_x_edge.z + plus_y_edge.z + minus_y_edge.z) / 4,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
async def find_calibration_structure_height(
|
|
351
|
+
hcapi: OT3HardwareControlAPI,
|
|
352
|
+
mount: OT3Mount,
|
|
353
|
+
nominal_center: Point,
|
|
354
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
355
|
+
) -> float:
|
|
356
|
+
"""
|
|
357
|
+
Find the height of the calibration structure in this mount's frame of reference.
|
|
358
|
+
|
|
359
|
+
This could be the deck height or the module calibration adapter height.
|
|
360
|
+
|
|
361
|
+
The deck nominal height in deck coordinates is 0 (that's part of the
|
|
362
|
+
definition of deck coordinates) but if we have not yet calibrated a
|
|
363
|
+
particular tool on a particular mount, then the z deck coordinate that
|
|
364
|
+
will cause a collision is not 0. This routine finds that value.
|
|
365
|
+
"""
|
|
366
|
+
z_pass_settings = hcapi.config.calibration.z_offset.pass_settings
|
|
367
|
+
z_prep_point = nominal_center + PREP_OFFSET_DEPTH
|
|
368
|
+
z_limit = nominal_center.z - z_pass_settings.max_overrun_distance_mm
|
|
369
|
+
structure_z, hit_deck = await _probe_deck_at(
|
|
370
|
+
hcapi, mount, z_prep_point, z_pass_settings, probe=probe
|
|
371
|
+
)
|
|
372
|
+
if not hit_deck:
|
|
373
|
+
raise CalibrationStructureNotFoundError(structure_z, z_limit)
|
|
374
|
+
LOG.info(f"autocalibration: found structure at {structure_z}")
|
|
375
|
+
return structure_z
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
async def _probe_deck_at(
|
|
379
|
+
api: OT3HardwareControlAPI,
|
|
380
|
+
mount: OT3Mount,
|
|
381
|
+
target: Point,
|
|
382
|
+
settings: CapacitivePassSettings,
|
|
383
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
384
|
+
) -> Tuple[float, bool]:
|
|
385
|
+
here = await api.gantry_position(mount)
|
|
386
|
+
abs_transit_height = max(
|
|
387
|
+
target.z + LINEAR_TRANSIT_HEIGHT, target.z + settings.prep_distance_mm
|
|
388
|
+
)
|
|
389
|
+
safe_height = max(here.z, target.z, abs_transit_height)
|
|
390
|
+
await api.move_to(mount, here._replace(z=safe_height))
|
|
391
|
+
await api.move_to(mount, target._replace(z=safe_height))
|
|
392
|
+
await api.move_to(mount, target._replace(z=abs_transit_height))
|
|
393
|
+
_found_pos, contact = await api.capacitive_probe(
|
|
394
|
+
mount, Axis.by_mount(mount), target.z, settings, probe=probe
|
|
395
|
+
)
|
|
396
|
+
# don't use found Z position to calculate an updated transit height
|
|
397
|
+
# because the probe may have gone through the hole
|
|
398
|
+
await api.move_to(mount, target._replace(z=abs_transit_height))
|
|
399
|
+
return _found_pos, contact
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
async def find_axis_center(
|
|
403
|
+
hcapi: OT3HardwareControlAPI,
|
|
404
|
+
mount: OT3Mount,
|
|
405
|
+
minus_edge_nominal: Point,
|
|
406
|
+
plus_edge_nominal: Point,
|
|
407
|
+
axis: Literal[Axis.X, Axis.Y],
|
|
408
|
+
) -> float:
|
|
409
|
+
"""Find the center of the calibration slot on the specified axis.
|
|
410
|
+
|
|
411
|
+
Sweep from the specified left edge to the specified right edge while taking
|
|
412
|
+
capacitive sense data. When the probe is over the deck, the capacitance will
|
|
413
|
+
be higher than when the probe is over the slot. By postprocessing the data,
|
|
414
|
+
we determine where the slot edges are, and return those positions.
|
|
415
|
+
"""
|
|
416
|
+
WIDTH_TOLERANCE_MM: float = 0.5
|
|
417
|
+
here = await hcapi.gantry_position(mount)
|
|
418
|
+
await hcapi.move_to(mount, here._replace(z=SEARCH_TRANSIT_HEIGHT))
|
|
419
|
+
edge_settings = hcapi.config.calibration.edge_sense
|
|
420
|
+
|
|
421
|
+
start = axis.set_in_point(
|
|
422
|
+
minus_edge_nominal,
|
|
423
|
+
axis.of_point(minus_edge_nominal) - edge_settings.search_initial_tolerance_mm,
|
|
424
|
+
)
|
|
425
|
+
end = axis.set_in_point(
|
|
426
|
+
plus_edge_nominal,
|
|
427
|
+
axis.of_point(plus_edge_nominal) + edge_settings.search_initial_tolerance_mm,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
await hcapi.move_to(mount, start._replace(z=SEARCH_TRANSIT_HEIGHT))
|
|
431
|
+
|
|
432
|
+
data = await hcapi.capacitive_sweep(
|
|
433
|
+
mount, axis, start, end, edge_settings.pass_settings.speed_mm_per_s
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
left_edge, right_edge = _edges_from_data(
|
|
437
|
+
data,
|
|
438
|
+
axis.of_point(end) - axis.of_point(start),
|
|
439
|
+
{
|
|
440
|
+
"axis": axis.name,
|
|
441
|
+
"speed": edge_settings.pass_settings.speed_mm_per_s,
|
|
442
|
+
"start_absolute": axis.of_point(start),
|
|
443
|
+
"end_absolute": axis.of_point(end),
|
|
444
|
+
},
|
|
445
|
+
)
|
|
446
|
+
nominal_width = axis.of_point(plus_edge_nominal) - axis.of_point(minus_edge_nominal)
|
|
447
|
+
detected_width = right_edge - left_edge
|
|
448
|
+
left_edge_absolute = axis.of_point(start) + left_edge
|
|
449
|
+
right_edge_absolute = axis.of_point(start) + right_edge
|
|
450
|
+
if abs(detected_width - nominal_width) > WIDTH_TOLERANCE_MM:
|
|
451
|
+
raise InaccurateNonContactSweepError(nominal_width, detected_width)
|
|
452
|
+
return (left_edge_absolute + right_edge_absolute) / 2
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _edges_from_data(
|
|
456
|
+
data: List[float], distance: float, log_metadata: Optional[Dict[str, Any]] = None
|
|
457
|
+
) -> Tuple[float, float]:
|
|
458
|
+
"""
|
|
459
|
+
Postprocess the capacitance data taken from a sweep to find the calibration slot.
|
|
460
|
+
|
|
461
|
+
The sweep should have covered both edges, going off the deck into the slot,
|
|
462
|
+
all the way across the slot, and back onto the deck on the other side.
|
|
463
|
+
|
|
464
|
+
Capacitance is proportional to the area of the "plates" involved in the sensing. To
|
|
465
|
+
a first approximation, these are the flat circular face of the bottom of the probe,
|
|
466
|
+
and the deck. When the probe begins to cross the edge of the deck, the area of the
|
|
467
|
+
second "plate" - the deck - ends abruptly, in a straight line transverse to motion.
|
|
468
|
+
As the probe crosses, less and less of that circular face is over the deck.
|
|
469
|
+
|
|
470
|
+
That means that the rate of change with respect to the overlap distance (or time, at
|
|
471
|
+
constant velocity) is maximized as the center of the probe passes the edge of the
|
|
472
|
+
deck.
|
|
473
|
+
|
|
474
|
+
We can therefore apply a combined smoothing and differencing convolution kernel to
|
|
475
|
+
the timeseries data and set the locations of the edges as the locations of the
|
|
476
|
+
extrema of the difference of the series.
|
|
477
|
+
"""
|
|
478
|
+
now_str = datetime.datetime.now().strftime("%d-%m-%y-%H:%M:%S")
|
|
479
|
+
|
|
480
|
+
# The width of the averaging kernel defines how strong the averaging is - a wider
|
|
481
|
+
# kernel, or filter, has a lower rolloff frequency and will smooth more. This
|
|
482
|
+
# calculation sets the width at 5% of the length of the data, and then makes that
|
|
483
|
+
# value an even number
|
|
484
|
+
average_width_samples = (int(floor(0.05 * len(data))) // 2) * 2
|
|
485
|
+
# an averaging kernel would be an array of length N with elements each set to 1/N;
|
|
486
|
+
# when convolved with a data stream, this will (ignoring edge effects) produce
|
|
487
|
+
# an N-sample rolling average. by inverting the sign of half the kernel, which is
|
|
488
|
+
# why we need it to be even, we do the same thing but while also taking a finite
|
|
489
|
+
# difference.
|
|
490
|
+
average_difference_kernel = np.concatenate(
|
|
491
|
+
(
|
|
492
|
+
np.full(average_width_samples // 2, 1 / average_width_samples),
|
|
493
|
+
np.full(average_width_samples // 2, -1 / average_width_samples),
|
|
494
|
+
)
|
|
495
|
+
)
|
|
496
|
+
differenced = np.convolve(np.array(data), average_difference_kernel, mode="valid")
|
|
497
|
+
# These are the indices of the minimum difference (which should be the left edge,
|
|
498
|
+
# where the probe is halfway through moving off the deck, and the slope of the
|
|
499
|
+
# data is most negative) and the maximum difference (which should be the right
|
|
500
|
+
# edge, where the probe is halfway through moving back onto the deck, and the slope
|
|
501
|
+
# of the data is most positive)
|
|
502
|
+
left_edge_sample = np.argmin(differenced)
|
|
503
|
+
right_edge_sample = np.argmax(differenced)
|
|
504
|
+
mm_per_elem = distance / len(data)
|
|
505
|
+
# The differenced data is shorter than the input data because we used valid outputs
|
|
506
|
+
# of the convolution only to avoid edge effects; that means we need to account for
|
|
507
|
+
# the distance in the cut-off data
|
|
508
|
+
distance_prefix = ((len(data) - len(differenced)) / 2) * mm_per_elem
|
|
509
|
+
left_edge_offset = left_edge_sample * mm_per_elem
|
|
510
|
+
left_edge = left_edge_offset + distance_prefix
|
|
511
|
+
right_edge_offset = right_edge_sample * mm_per_elem
|
|
512
|
+
right_edge = right_edge_offset + distance_prefix
|
|
513
|
+
json.dump(
|
|
514
|
+
{
|
|
515
|
+
"metadata": log_metadata,
|
|
516
|
+
"inputs": {
|
|
517
|
+
"raw_data": data,
|
|
518
|
+
"distance": distance,
|
|
519
|
+
"kernel_width": len(average_difference_kernel),
|
|
520
|
+
},
|
|
521
|
+
"outputs": {
|
|
522
|
+
"differenced_data": [d for d in differenced],
|
|
523
|
+
"mm_per_elem": mm_per_elem,
|
|
524
|
+
"distance_prefix": distance_prefix,
|
|
525
|
+
"right_edge_offset": right_edge_offset,
|
|
526
|
+
"left_edge_offset": left_edge_offset,
|
|
527
|
+
"left_edge": left_edge,
|
|
528
|
+
"right_edge": right_edge,
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
open(f"/data/sweep_{now_str}.json", "w"),
|
|
532
|
+
)
|
|
533
|
+
LOG.info(
|
|
534
|
+
f"Found edges ({left_edge:.3f}, {right_edge:.3f}) "
|
|
535
|
+
f"from offsets ({left_edge_offset:.3f}, {right_edge_offset:.3f}) "
|
|
536
|
+
f"with {len(data)} cap samples over {distance}mm "
|
|
537
|
+
f"using a kernel width of {len(average_difference_kernel)}"
|
|
538
|
+
)
|
|
539
|
+
return float(left_edge), float(right_edge)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
async def find_slot_center_noncontact(
|
|
543
|
+
hcapi: OT3HardwareControlAPI, mount: OT3Mount, estimated_center: Point
|
|
544
|
+
) -> Point:
|
|
545
|
+
NONCONTACT_INTERVAL_MM: float = 0.1
|
|
546
|
+
travel_center = estimated_center + Point(0, 0, NONCONTACT_INTERVAL_MM)
|
|
547
|
+
x_center = await find_axis_center(
|
|
548
|
+
hcapi,
|
|
549
|
+
mount,
|
|
550
|
+
travel_center + EDGES["left"],
|
|
551
|
+
travel_center + EDGES["right"],
|
|
552
|
+
Axis.X,
|
|
553
|
+
)
|
|
554
|
+
y_center = await find_axis_center(
|
|
555
|
+
hcapi,
|
|
556
|
+
mount,
|
|
557
|
+
travel_center + EDGES["bottom"],
|
|
558
|
+
travel_center + EDGES["top"],
|
|
559
|
+
Axis.Y,
|
|
560
|
+
)
|
|
561
|
+
return Point(x_center, y_center, estimated_center.z)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
async def find_calibration_structure_center(
|
|
565
|
+
hcapi: OT3HardwareControlAPI,
|
|
566
|
+
mount: OT3Mount,
|
|
567
|
+
nominal_center: Point,
|
|
568
|
+
method: CalibrationMethod = CalibrationMethod.BINARY_SEARCH,
|
|
569
|
+
raise_verify_error: bool = True,
|
|
570
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
571
|
+
) -> Point:
|
|
572
|
+
|
|
573
|
+
# Perform xy offset search
|
|
574
|
+
if method == CalibrationMethod.BINARY_SEARCH:
|
|
575
|
+
found_center = await find_slot_center_binary(
|
|
576
|
+
hcapi, mount, nominal_center, raise_verify_error, probe=probe
|
|
577
|
+
)
|
|
578
|
+
elif method == CalibrationMethod.NONCONTACT_PASS:
|
|
579
|
+
# FIXME: use slot to find ideal position
|
|
580
|
+
found_center = await find_slot_center_noncontact(hcapi, mount, nominal_center)
|
|
581
|
+
else:
|
|
582
|
+
raise RuntimeError("Unknown calibration method")
|
|
583
|
+
return found_center
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
async def _calibrate_mount(
|
|
587
|
+
hcapi: OT3HardwareControlAPI,
|
|
588
|
+
mount: OT3Mount,
|
|
589
|
+
slot: int = SLOT_CENTER,
|
|
590
|
+
method: CalibrationMethod = CalibrationMethod.BINARY_SEARCH,
|
|
591
|
+
raise_verify_error: bool = True,
|
|
592
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
593
|
+
) -> Point:
|
|
594
|
+
"""
|
|
595
|
+
Run automatic calibration for the tool attached to the specified mount.
|
|
596
|
+
|
|
597
|
+
Before running this function, make sure that the appropriate probe
|
|
598
|
+
has been attached or prepped on the tool (for instance, a capacitive
|
|
599
|
+
tip has been attached, or the conductive probe has been attached,
|
|
600
|
+
or the probe has been lowered). The robot should be homed.
|
|
601
|
+
|
|
602
|
+
Note: To calibrate a gripper, this process must be performed on the front
|
|
603
|
+
and rear calibration pins separately. The gripper calibration offset is
|
|
604
|
+
the average of the pin offsets, which can be obtained by passing the
|
|
605
|
+
two offsets into the `gripper_pin_offsets_mean` func.
|
|
606
|
+
|
|
607
|
+
Params
|
|
608
|
+
------
|
|
609
|
+
hcapi: a hardware control api to run commands against
|
|
610
|
+
mount: The mount to calibration
|
|
611
|
+
|
|
612
|
+
Returns
|
|
613
|
+
-------
|
|
614
|
+
The estimated position of the XY center of the calibration slot in
|
|
615
|
+
the plane of the deck. This value is suitable for vector-subtracting
|
|
616
|
+
from the current instrument offset to set a new instrument offset.
|
|
617
|
+
"""
|
|
618
|
+
nominal_center = Point(*get_calibration_square_position_in_slot(slot))
|
|
619
|
+
if probe == InstrumentProbeType.SECONDARY and mount != OT3Mount.GRIPPER:
|
|
620
|
+
pip = hcapi.hardware_instruments[mount.to_mount()]
|
|
621
|
+
num_channels = int(pip.channels) # type: ignore[union-attr]
|
|
622
|
+
nominal_center += OFFSET_SECONDARY_PROBE.get(num_channels, Point())
|
|
623
|
+
try:
|
|
624
|
+
# find the center of the calibration sqaure
|
|
625
|
+
offset = await find_calibration_structure_position(
|
|
626
|
+
hcapi,
|
|
627
|
+
mount,
|
|
628
|
+
nominal_center,
|
|
629
|
+
method=method,
|
|
630
|
+
raise_verify_error=raise_verify_error,
|
|
631
|
+
probe=probe,
|
|
632
|
+
)
|
|
633
|
+
# update center with values obtained during calibration
|
|
634
|
+
LOG.info(f"Found calibration value {offset} for mount {mount.name}")
|
|
635
|
+
return offset
|
|
636
|
+
|
|
637
|
+
except (
|
|
638
|
+
InaccurateNonContactSweepError,
|
|
639
|
+
EarlyCapacitiveSenseTrigger,
|
|
640
|
+
CalibrationStructureNotFoundError,
|
|
641
|
+
EdgeNotFoundError,
|
|
642
|
+
):
|
|
643
|
+
LOG.info(
|
|
644
|
+
"Error occurred during calibration. Resetting to current saved calibration value."
|
|
645
|
+
)
|
|
646
|
+
await hcapi.reset_instrument_offset(mount, to_default=False)
|
|
647
|
+
# re-raise exception after resetting instrument offset
|
|
648
|
+
raise
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
async def find_calibration_structure_position(
|
|
652
|
+
hcapi: OT3HardwareControlAPI,
|
|
653
|
+
mount: OT3Mount,
|
|
654
|
+
nominal_center: Point,
|
|
655
|
+
method: CalibrationMethod = CalibrationMethod.BINARY_SEARCH,
|
|
656
|
+
target: CalibrationTarget = CalibrationTarget.GANTRY_INSTRUMENT,
|
|
657
|
+
raise_verify_error: bool = True,
|
|
658
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
659
|
+
) -> Point:
|
|
660
|
+
"""Find the calibration square offset given an arbitry postition on the deck."""
|
|
661
|
+
# Find the estimated structure plate height. This will be used to baseline the edge detection points.
|
|
662
|
+
z_height = await find_calibration_structure_height(
|
|
663
|
+
hcapi, mount, nominal_center, probe=probe
|
|
664
|
+
)
|
|
665
|
+
initial_center = nominal_center._replace(z=z_height)
|
|
666
|
+
LOG.info(f"Found structure plate at {z_height}mm")
|
|
667
|
+
|
|
668
|
+
# Find the calibration square center using the given method
|
|
669
|
+
found_center = await find_calibration_structure_center(
|
|
670
|
+
hcapi, mount, initial_center, method, raise_verify_error, probe=probe
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
offset = nominal_center - found_center
|
|
674
|
+
# NOTE: If the calibration target is a deck object the polarity of the calibrated
|
|
675
|
+
# offset needs to be reversed. This is because we are using the gantry instrument
|
|
676
|
+
# to calibrate a stationary object on the deck and need to find the offset of that
|
|
677
|
+
# deck object relative to the deck and not the instrument which sits above the deck.
|
|
678
|
+
if target == CalibrationTarget.DECK_OBJECT:
|
|
679
|
+
return offset * -1
|
|
680
|
+
return offset
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
async def find_slot_center_binary_from_nominal_center(
|
|
684
|
+
hcapi: OT3HardwareControlAPI,
|
|
685
|
+
mount: OT3Mount,
|
|
686
|
+
slot: int,
|
|
687
|
+
) -> Tuple[Point, Point]:
|
|
688
|
+
"""
|
|
689
|
+
For use with calibrate_belts. For specified slot, finds actual slot center via binary search and nominal slot center
|
|
690
|
+
|
|
691
|
+
Params
|
|
692
|
+
------
|
|
693
|
+
hcapi: a hardware control api to run commands against
|
|
694
|
+
mount: the mount to calibration
|
|
695
|
+
slot: a specific deck slot
|
|
696
|
+
|
|
697
|
+
Returns
|
|
698
|
+
-------
|
|
699
|
+
The actual and nominal centers of the specified slot.
|
|
700
|
+
"""
|
|
701
|
+
nominal_center = Point(*get_calibration_square_position_in_slot(slot))
|
|
702
|
+
offset = await find_calibration_structure_position(
|
|
703
|
+
hcapi, mount, nominal_center, method=CalibrationMethod.BINARY_SEARCH
|
|
704
|
+
)
|
|
705
|
+
return nominal_center - offset, nominal_center
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
async def _determine_transform_matrix(
|
|
709
|
+
hcapi: OT3HardwareControlAPI,
|
|
710
|
+
mount: OT3Mount,
|
|
711
|
+
) -> Tuple[types.AttitudeMatrix, Dict[str, Any]]:
|
|
712
|
+
"""
|
|
713
|
+
Run automatic calibration for the gantry x and y belts attached to the specified mount. Returned linear transform matrix is determined via the
|
|
714
|
+
actual and nominal center points of the back right (A), front right (B), and back left (C) slots.
|
|
715
|
+
|
|
716
|
+
Params
|
|
717
|
+
------
|
|
718
|
+
hcapi: a hardware control api to run commands against
|
|
719
|
+
mount: the mount to calibration
|
|
720
|
+
|
|
721
|
+
Returns
|
|
722
|
+
-------
|
|
723
|
+
A listed matrix of the linear transform in the x and y dimensions that accounts for the stretch of the gantry x and y belts.
|
|
724
|
+
"""
|
|
725
|
+
|
|
726
|
+
async def _find_slot(s: int) -> CalibrationSlot:
|
|
727
|
+
actual, nominal = await find_slot_center_binary_from_nominal_center(
|
|
728
|
+
hcapi, mount, s
|
|
729
|
+
)
|
|
730
|
+
await hcapi.retract(mount)
|
|
731
|
+
return CalibrationSlot(slot=s, nominal=nominal, actual=actual)
|
|
732
|
+
|
|
733
|
+
front_left = await _find_slot(SLOT_FRONT_LEFT)
|
|
734
|
+
front_right = await _find_slot(SLOT_FRONT_RIGHT)
|
|
735
|
+
rear_left = await _find_slot(SLOT_REAR_LEFT)
|
|
736
|
+
belt_cal = BeltCalibrationData(front_left, front_right, rear_left)
|
|
737
|
+
details = belt_cal.check_alignment() # raises error if misaligned
|
|
738
|
+
attitude = solve_attitude(*belt_cal.get_solve_points())
|
|
739
|
+
return attitude, details
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def gripper_pin_offsets_mean(front: Point, rear: Point) -> Point:
|
|
743
|
+
"""
|
|
744
|
+
Get calibration offset of a gripper from its front and rear pin offsets.
|
|
745
|
+
|
|
746
|
+
This function should be used for gripper calibration only.
|
|
747
|
+
|
|
748
|
+
Params
|
|
749
|
+
------
|
|
750
|
+
front: gripper's front pin calibration offset
|
|
751
|
+
rear: gripper's rear pin calibration offset
|
|
752
|
+
|
|
753
|
+
Returns
|
|
754
|
+
-------
|
|
755
|
+
The gripper calibration offset.
|
|
756
|
+
"""
|
|
757
|
+
return 0.5 * (front + rear)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
async def calibrate_gripper_jaw(
|
|
761
|
+
hcapi: OT3HardwareControlAPI,
|
|
762
|
+
probe: GripperProbe,
|
|
763
|
+
slot: int = 5,
|
|
764
|
+
method: CalibrationMethod = CalibrationMethod.BINARY_SEARCH,
|
|
765
|
+
raise_verify_error: bool = True,
|
|
766
|
+
) -> Point:
|
|
767
|
+
"""
|
|
768
|
+
Run automatic calibration for gripper jaw.
|
|
769
|
+
|
|
770
|
+
Before running this function, make sure that the appropriate probe
|
|
771
|
+
has been attached or prepped on the tool (for instance, a capacitive
|
|
772
|
+
tip has been attached, or the conductive probe has been attached,
|
|
773
|
+
or the probe has been lowered). The robot should be homed.
|
|
774
|
+
|
|
775
|
+
This process must be performed on the front
|
|
776
|
+
and rear calibration pins separately. The gripper calibration offset is
|
|
777
|
+
the average of the pin offsets, which can be obtained by passing the
|
|
778
|
+
two offsets into the `gripper_pin_offsets_mean` func.
|
|
779
|
+
"""
|
|
780
|
+
try:
|
|
781
|
+
await hcapi.reset_instrument_offset(OT3Mount.GRIPPER)
|
|
782
|
+
hcapi.add_gripper_probe(probe)
|
|
783
|
+
await hcapi.grip(GRIPPER_GRIP_FORCE)
|
|
784
|
+
offset = await _calibrate_mount(
|
|
785
|
+
hcapi,
|
|
786
|
+
OT3Mount.GRIPPER,
|
|
787
|
+
slot,
|
|
788
|
+
method,
|
|
789
|
+
raise_verify_error,
|
|
790
|
+
probe=probe.to_type(probe),
|
|
791
|
+
)
|
|
792
|
+
LOG.info(f"Gripper {probe.name} probe offset: {offset}")
|
|
793
|
+
return offset
|
|
794
|
+
finally:
|
|
795
|
+
hcapi.remove_gripper_probe()
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
async def calibrate_gripper(
|
|
799
|
+
hcapi: OT3HardwareControlAPI, offset_front: Point, offset_rear: Point
|
|
800
|
+
) -> Point:
|
|
801
|
+
"""Calibrate gripper."""
|
|
802
|
+
offset = gripper_pin_offsets_mean(front=offset_front, rear=offset_rear)
|
|
803
|
+
LOG.info(f"Gripper calibration offset: {offset}")
|
|
804
|
+
await hcapi.save_instrument_offset(OT3Mount.GRIPPER, offset)
|
|
805
|
+
await hcapi.home_gripper_jaw(recalibrate_jaw_width=True)
|
|
806
|
+
return offset
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
async def find_pipette_offset(
|
|
810
|
+
hcapi: OT3HardwareControlAPI,
|
|
811
|
+
mount: Literal[OT3Mount.LEFT, OT3Mount.RIGHT],
|
|
812
|
+
slot: int = 5,
|
|
813
|
+
method: CalibrationMethod = CalibrationMethod.BINARY_SEARCH,
|
|
814
|
+
raise_verify_error: bool = True,
|
|
815
|
+
reset_instrument_offset: bool = True,
|
|
816
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
817
|
+
) -> Point:
|
|
818
|
+
"""
|
|
819
|
+
Run automatic calibration for pipette and only return the calibration point.
|
|
820
|
+
|
|
821
|
+
Before running this function, make sure that the appropriate probe
|
|
822
|
+
has been attached or prepped on the tool (for instance, a capacitive
|
|
823
|
+
tip has been attached, or the conductive probe has been attached,
|
|
824
|
+
or the probe has been lowered).
|
|
825
|
+
|
|
826
|
+
This function should be used in the robot server only.
|
|
827
|
+
"""
|
|
828
|
+
try:
|
|
829
|
+
if reset_instrument_offset:
|
|
830
|
+
await hcapi.reset_instrument_offset(mount)
|
|
831
|
+
hcapi.add_tip(mount, hcapi.config.calibration.probe_length)
|
|
832
|
+
offset = await _calibrate_mount(
|
|
833
|
+
hcapi, mount, slot, method, raise_verify_error, probe=probe
|
|
834
|
+
)
|
|
835
|
+
return offset
|
|
836
|
+
finally:
|
|
837
|
+
hcapi.remove_tip(mount)
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
async def calibrate_pipette(
|
|
841
|
+
hcapi: OT3HardwareControlAPI,
|
|
842
|
+
mount: Literal[OT3Mount.LEFT, OT3Mount.RIGHT],
|
|
843
|
+
slot: int = 5,
|
|
844
|
+
method: CalibrationMethod = CalibrationMethod.BINARY_SEARCH,
|
|
845
|
+
raise_verify_error: bool = True,
|
|
846
|
+
probe: InstrumentProbeType = InstrumentProbeType.PRIMARY,
|
|
847
|
+
) -> Point:
|
|
848
|
+
"""
|
|
849
|
+
Run automatic calibration for pipette and save the offset.
|
|
850
|
+
|
|
851
|
+
Before running this function, make sure that the appropriate probe
|
|
852
|
+
has been attached or prepped on the tool (for instance, a capacitive
|
|
853
|
+
tip has been attached, or the conductive probe has been attached,
|
|
854
|
+
or the probe has been lowered).
|
|
855
|
+
"""
|
|
856
|
+
offset = await find_pipette_offset(
|
|
857
|
+
hcapi, mount, slot, method, raise_verify_error, probe=probe
|
|
858
|
+
)
|
|
859
|
+
await hcapi.save_instrument_offset(mount, offset)
|
|
860
|
+
return offset
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
async def calibrate_module(
|
|
864
|
+
hcapi: OT3HardwareControlAPI,
|
|
865
|
+
mount: OT3Mount,
|
|
866
|
+
slot: str,
|
|
867
|
+
module_id: str,
|
|
868
|
+
nominal_position: Point,
|
|
869
|
+
) -> Point:
|
|
870
|
+
"""
|
|
871
|
+
Run automatic calibration for a module.
|
|
872
|
+
|
|
873
|
+
Before running this function, make sure that the appropriate probe
|
|
874
|
+
has been attached or prepped on the tool (for instance, a capacitive
|
|
875
|
+
tip has been attached, or the conductive probe has been attached,
|
|
876
|
+
or the probe has been lowered). We also need to have the module
|
|
877
|
+
prepped for calibration (for example, not heating, lid closed,
|
|
878
|
+
not shaking, etc) and the corresponding module calibration
|
|
879
|
+
block placed in the module slot.
|
|
880
|
+
|
|
881
|
+
The robot should be homed before calling this function.
|
|
882
|
+
"""
|
|
883
|
+
|
|
884
|
+
try:
|
|
885
|
+
# add the probe depending on the mount
|
|
886
|
+
if mount == OT3Mount.GRIPPER:
|
|
887
|
+
hcapi.add_gripper_probe(GripperProbe.FRONT)
|
|
888
|
+
else:
|
|
889
|
+
hcapi.add_tip(mount, hcapi.config.calibration.probe_length)
|
|
890
|
+
|
|
891
|
+
LOG.info(
|
|
892
|
+
f"Starting module calibration for {module_id} at {nominal_position} using {mount}"
|
|
893
|
+
)
|
|
894
|
+
# FIXME (ba, 2023-04-04): Well B1 of the module adapter definition includes the z prep offset
|
|
895
|
+
# of 13x13mm in the nominial position, but we are still using PREP_OFFSET_DEPTH in
|
|
896
|
+
# find_calibration_structure_height which effectively doubles the offset. We plan
|
|
897
|
+
# on removing PREP_OFFSET_DEPTH in the near future, but for now just subtract PREP_OFFSET_DEPTH
|
|
898
|
+
# from the nominal position so we dont have to alter any other part of the system.
|
|
899
|
+
nominal_position = nominal_position - PREP_OFFSET_DEPTH
|
|
900
|
+
offset = await find_calibration_structure_position(
|
|
901
|
+
hcapi,
|
|
902
|
+
mount,
|
|
903
|
+
nominal_position,
|
|
904
|
+
method=CalibrationMethod.BINARY_SEARCH,
|
|
905
|
+
target=CalibrationTarget.DECK_OBJECT,
|
|
906
|
+
)
|
|
907
|
+
await hcapi.save_module_offset(module_id, mount, slot, offset)
|
|
908
|
+
return offset
|
|
909
|
+
finally:
|
|
910
|
+
# remove probe
|
|
911
|
+
if mount == OT3Mount.GRIPPER:
|
|
912
|
+
hcapi.remove_gripper_probe()
|
|
913
|
+
await hcapi.ungrip()
|
|
914
|
+
else:
|
|
915
|
+
hcapi.remove_tip(mount)
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
async def calibrate_belts(
|
|
919
|
+
hcapi: OT3HardwareControlAPI,
|
|
920
|
+
mount: OT3Mount,
|
|
921
|
+
pipette_id: str,
|
|
922
|
+
) -> Tuple[types.AttitudeMatrix, Dict[str, Any]]:
|
|
923
|
+
"""
|
|
924
|
+
Run automatic calibration for the gantry x and y belts attached to the specified mount.
|
|
925
|
+
|
|
926
|
+
Params
|
|
927
|
+
------
|
|
928
|
+
hcapi: a hardware control api to run commands against
|
|
929
|
+
mount: the mount to calibration
|
|
930
|
+
|
|
931
|
+
Returns
|
|
932
|
+
-------
|
|
933
|
+
A listed matrix of the linear transform in the x and y dimensions that accounts for the stretch of the gantry x and y belts.
|
|
934
|
+
"""
|
|
935
|
+
if mount == OT3Mount.GRIPPER:
|
|
936
|
+
raise RuntimeError("Must use pipette mount, not gripper")
|
|
937
|
+
try:
|
|
938
|
+
hcapi.reset_deck_calibration()
|
|
939
|
+
hcapi.add_tip(mount, hcapi.config.calibration.probe_length)
|
|
940
|
+
belt_attitude, alignment_details = await _determine_transform_matrix(
|
|
941
|
+
hcapi, mount
|
|
942
|
+
)
|
|
943
|
+
save_robot_belt_attitude(belt_attitude, pipette_id)
|
|
944
|
+
return belt_attitude, alignment_details
|
|
945
|
+
finally:
|
|
946
|
+
hcapi.load_deck_calibration()
|
|
947
|
+
hcapi.remove_tip(mount)
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def apply_machine_transform(
|
|
951
|
+
belt_attitude: types.AttitudeMatrix,
|
|
952
|
+
) -> types.AttitudeMatrix:
|
|
953
|
+
"""
|
|
954
|
+
This applies the machine attitude matrix (which happens to be a negative identity) to the belt attitude matrix to form the deck attitude matrix.
|
|
955
|
+
|
|
956
|
+
Param
|
|
957
|
+
-----
|
|
958
|
+
belt_attitude: attitude matrix with regards to belt coordinate system
|
|
959
|
+
|
|
960
|
+
Returns
|
|
961
|
+
-------
|
|
962
|
+
Attitude matrix with regards to machine coordinate system.
|
|
963
|
+
"""
|
|
964
|
+
belt_attitude_arr: DoubleMatrix = np.array(belt_attitude)
|
|
965
|
+
machine_transform_arr: DoubleMatrix = np.array(
|
|
966
|
+
defaults_ot3.DEFAULT_MACHINE_TRANSFORM
|
|
967
|
+
)
|
|
968
|
+
deck_attitude_arr = np.dot(belt_attitude_arr, machine_transform_arr)
|
|
969
|
+
deck_attitude = deck_attitude_arr.round(4).tolist()
|
|
970
|
+
return deck_attitude # type: ignore[no-any-return]
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def load_attitude_matrix(to_default: bool = True) -> DeckCalibration:
|
|
974
|
+
calibration_data = get_robot_belt_attitude()
|
|
975
|
+
|
|
976
|
+
if calibration_data and not to_default:
|
|
977
|
+
return DeckCalibration(
|
|
978
|
+
attitude=apply_machine_transform(calibration_data.attitude),
|
|
979
|
+
source=calibration_data.source,
|
|
980
|
+
status=types.CalibrationStatus(**calibration_data.status.model_dump()),
|
|
981
|
+
belt_attitude=calibration_data.attitude,
|
|
982
|
+
last_modified=calibration_data.lastModified,
|
|
983
|
+
pipette_calibrated_with=calibration_data.pipetteCalibratedWith,
|
|
984
|
+
)
|
|
985
|
+
else:
|
|
986
|
+
# load default if calibration data does not exist
|
|
987
|
+
return DeckCalibration(
|
|
988
|
+
attitude=apply_machine_transform(default_ot3_deck_calibration()),
|
|
989
|
+
source=types.SourceType.default,
|
|
990
|
+
status=types.CalibrationStatus(),
|
|
991
|
+
belt_attitude=default_ot3_deck_calibration(),
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
def validate_attitude_deck_calibration(
|
|
996
|
+
deck_cal: DeckCalibration,
|
|
997
|
+
) -> DeckTransformState:
|
|
998
|
+
"""
|
|
999
|
+
This function determines whether the deck calibration is valid
|
|
1000
|
+
or not based on the following use-cases:
|
|
1001
|
+
|
|
1002
|
+
TODO(pm, 5/9/2023): As with the OT2, expand on this method,
|
|
1003
|
+
or create another method to diagnose bad instrument offset data
|
|
1004
|
+
"""
|
|
1005
|
+
curr_cal: DoubleMatrix = np.array(deck_cal.attitude)
|
|
1006
|
+
row, _ = curr_cal.shape
|
|
1007
|
+
rank: int = np.linalg.matrix_rank(curr_cal)
|
|
1008
|
+
if row != rank:
|
|
1009
|
+
# Check that the matrix is non-singular
|
|
1010
|
+
return DeckTransformState.SINGULARITY
|
|
1011
|
+
elif not deck_cal.last_modified:
|
|
1012
|
+
# Check that the matrix is not an identity
|
|
1013
|
+
return DeckTransformState.IDENTITY
|
|
1014
|
+
else:
|
|
1015
|
+
# Transform as it stands is sufficient.
|
|
1016
|
+
return DeckTransformState.OK
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def delete_belt_calibration_data(hcapi: OT3HardwareControlAPI) -> None:
|
|
1020
|
+
delete_robot_belt_attitude()
|
|
1021
|
+
hcapi.reset_deck_calibration()
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
class OT3RobotCalibrationProvider:
|
|
1025
|
+
"""This class provides the following robot calibration data:
|
|
1026
|
+
deck calibration: transform matrix to account for stretch of x and y belts
|
|
1027
|
+
carriage offset: the vector from the deck origin to the bottom center of the gantry carriage when the gantry is homed
|
|
1028
|
+
mount offset (per mount): the vector from the carriage origin (bottom center) to the mount origin (centered on the top peg of the mount flush with the mating face)
|
|
1029
|
+
"""
|
|
1030
|
+
|
|
1031
|
+
def __init__(self, config: OT3Config) -> None:
|
|
1032
|
+
self._robot_calibration = OT3Transforms(
|
|
1033
|
+
deck_calibration=load_attitude_matrix(to_default=False),
|
|
1034
|
+
carriage_offset=Point(*config.carriage_offset),
|
|
1035
|
+
left_mount_offset=Point(*config.left_mount_offset),
|
|
1036
|
+
right_mount_offset=Point(*config.right_mount_offset),
|
|
1037
|
+
gripper_mount_offset=Point(*config.gripper_mount_offset),
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
@lru_cache(1)
|
|
1041
|
+
def _validate(self) -> DeckTransformState:
|
|
1042
|
+
return validate_attitude_deck_calibration(
|
|
1043
|
+
self._robot_calibration.deck_calibration
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
@property
|
|
1047
|
+
def robot_calibration(self) -> OT3Transforms:
|
|
1048
|
+
return self._robot_calibration
|
|
1049
|
+
|
|
1050
|
+
def reset_robot_calibration(self) -> None:
|
|
1051
|
+
self._validate.cache_clear()
|
|
1052
|
+
self._robot_calibration = OT3Transforms(
|
|
1053
|
+
deck_calibration=load_attitude_matrix(to_default=True),
|
|
1054
|
+
carriage_offset=Point(*defaults_ot3.DEFAULT_CARRIAGE_OFFSET),
|
|
1055
|
+
left_mount_offset=Point(*defaults_ot3.DEFAULT_LEFT_MOUNT_OFFSET),
|
|
1056
|
+
right_mount_offset=Point(*defaults_ot3.DEFAULT_RIGHT_MOUNT_OFFSET),
|
|
1057
|
+
gripper_mount_offset=Point(*defaults_ot3.DEFAULT_GRIPPER_MOUNT_OFFSET),
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
def reset_deck_calibration(self) -> None:
|
|
1061
|
+
self._robot_calibration.deck_calibration = load_attitude_matrix(to_default=True)
|
|
1062
|
+
|
|
1063
|
+
def load_deck_calibration(self) -> None:
|
|
1064
|
+
self._validate.cache_clear()
|
|
1065
|
+
self._robot_calibration.deck_calibration = load_attitude_matrix(
|
|
1066
|
+
to_default=False
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
def set_robot_calibration(self, robot_calibration: OT3Transforms) -> None:
|
|
1070
|
+
self._validate.cache_clear()
|
|
1071
|
+
self._robot_calibration = robot_calibration
|
|
1072
|
+
|
|
1073
|
+
def validate_calibration(self) -> DeckTransformState:
|
|
1074
|
+
"""
|
|
1075
|
+
The lru cache decorator is currently not supported by the
|
|
1076
|
+
ThreadManager. To work around this, we need to wrap the
|
|
1077
|
+
actual function around a dummy outer function.
|
|
1078
|
+
|
|
1079
|
+
Once decorators are more fully supported, we can remove this.
|
|
1080
|
+
"""
|
|
1081
|
+
return self._validate()
|
|
1082
|
+
|
|
1083
|
+
def build_temporary_identity_calibration(self) -> OT3Transforms:
|
|
1084
|
+
"""
|
|
1085
|
+
Get temporary default calibration data suitable for use during
|
|
1086
|
+
calibration
|
|
1087
|
+
"""
|
|
1088
|
+
return OT3Transforms(
|
|
1089
|
+
deck_calibration=load_attitude_matrix(to_default=True),
|
|
1090
|
+
carriage_offset=Point(*defaults_ot3.DEFAULT_CARRIAGE_OFFSET),
|
|
1091
|
+
left_mount_offset=Point(*defaults_ot3.DEFAULT_LEFT_MOUNT_OFFSET),
|
|
1092
|
+
right_mount_offset=Point(*defaults_ot3.DEFAULT_RIGHT_MOUNT_OFFSET),
|
|
1093
|
+
gripper_mount_offset=Point(*defaults_ot3.DEFAULT_GRIPPER_MOUNT_OFFSET),
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
@dataclass
|
|
1098
|
+
class OT3Transforms(RobotCalibration):
|
|
1099
|
+
carriage_offset: Point
|
|
1100
|
+
left_mount_offset: Point
|
|
1101
|
+
right_mount_offset: Point
|
|
1102
|
+
gripper_mount_offset: Point
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def _point_to_tuple(_p: Point) -> Tuple[float, float, float]:
|
|
1106
|
+
return _p.x, _p.y, _p.z
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
class BeltCalibrationData:
|
|
1110
|
+
def __init__(
|
|
1111
|
+
self,
|
|
1112
|
+
slot_front_left: CalibrationSlot,
|
|
1113
|
+
slot_front_right: CalibrationSlot,
|
|
1114
|
+
slot_rear_left: CalibrationSlot,
|
|
1115
|
+
) -> None:
|
|
1116
|
+
self._front_left = slot_front_left
|
|
1117
|
+
self._front_right = slot_front_right
|
|
1118
|
+
self._rear_left = slot_rear_left
|
|
1119
|
+
|
|
1120
|
+
def build_details(self) -> Dict[str, Any]:
|
|
1121
|
+
shift_details = {
|
|
1122
|
+
shift.value: {
|
|
1123
|
+
"spec": MAX_SHIFT[shift],
|
|
1124
|
+
"pass": abs(self._get_shift_mm(shift)) < MAX_SHIFT[shift],
|
|
1125
|
+
"shift": round(self._get_shift_mm(shift), 3),
|
|
1126
|
+
}
|
|
1127
|
+
for shift in AlignmentShift
|
|
1128
|
+
}
|
|
1129
|
+
shift_details["slots"] = {
|
|
1130
|
+
"front_left": _point_to_tuple(self._front_left.actual), # type: ignore[dict-item]
|
|
1131
|
+
"front_right": _point_to_tuple(self._front_right.actual), # type: ignore[dict-item]
|
|
1132
|
+
"rear_left": _point_to_tuple(self._rear_left.actual), # type: ignore[dict-item]
|
|
1133
|
+
}
|
|
1134
|
+
return shift_details
|
|
1135
|
+
|
|
1136
|
+
def check_alignment(self) -> Dict[str, Any]:
|
|
1137
|
+
shift_details = self.build_details()
|
|
1138
|
+
LOG.info(shift_details)
|
|
1139
|
+
failures = [
|
|
1140
|
+
shift for shift in AlignmentShift if not shift_details[shift.value]["pass"]
|
|
1141
|
+
]
|
|
1142
|
+
if failures:
|
|
1143
|
+
raise MisalignedGantryError(shift_details)
|
|
1144
|
+
return shift_details
|
|
1145
|
+
|
|
1146
|
+
def get_solve_points(self) -> Tuple[SolvePoints, SolvePoints]:
|
|
1147
|
+
actual = (
|
|
1148
|
+
_point_to_tuple(self._front_left.actual),
|
|
1149
|
+
_point_to_tuple(self._rear_left.actual),
|
|
1150
|
+
_point_to_tuple(self._front_right.actual),
|
|
1151
|
+
)
|
|
1152
|
+
nominal = (
|
|
1153
|
+
_point_to_tuple(self._front_left.nominal),
|
|
1154
|
+
_point_to_tuple(self._rear_left.nominal),
|
|
1155
|
+
_point_to_tuple(self._front_right.nominal),
|
|
1156
|
+
)
|
|
1157
|
+
return nominal, actual
|
|
1158
|
+
|
|
1159
|
+
def _get_shift_mm(self, shift: AlignmentShift) -> float:
|
|
1160
|
+
# polarity is same as deck coordinates,
|
|
1161
|
+
# so positive values describe shifting towards right/rear/up,
|
|
1162
|
+
# while negative values describe shifting towards left/front/down.
|
|
1163
|
+
if shift == AlignmentShift.FRONT_TO_REAR_X:
|
|
1164
|
+
return self._rear_left.actual.x - self._front_left.actual.x
|
|
1165
|
+
elif shift == AlignmentShift.FRONT_TO_REAR_Z:
|
|
1166
|
+
return self._rear_left.actual.z - self._front_left.actual.z
|
|
1167
|
+
elif shift == AlignmentShift.LEFT_TO_RIGHT_Y:
|
|
1168
|
+
return self._front_right.actual.y - self._front_left.actual.y
|
|
1169
|
+
elif shift == AlignmentShift.LEFT_TO_RIGHT_Z:
|
|
1170
|
+
return self._front_right.actual.z - self._front_left.actual.z
|
|
1171
|
+
raise ValueError(f"unexpected shift: {shift}")
|