opentrons 8.6.0__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.

Files changed (601) hide show
  1. opentrons/__init__.py +150 -0
  2. opentrons/_version.py +34 -0
  3. opentrons/calibration_storage/__init__.py +54 -0
  4. opentrons/calibration_storage/deck_configuration.py +62 -0
  5. opentrons/calibration_storage/encoder_decoder.py +31 -0
  6. opentrons/calibration_storage/file_operators.py +142 -0
  7. opentrons/calibration_storage/helpers.py +103 -0
  8. opentrons/calibration_storage/ot2/__init__.py +34 -0
  9. opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
  10. opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
  11. opentrons/calibration_storage/ot2/models/__init__.py +0 -0
  12. opentrons/calibration_storage/ot2/models/v1.py +149 -0
  13. opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
  14. opentrons/calibration_storage/ot2/tip_length.py +281 -0
  15. opentrons/calibration_storage/ot3/__init__.py +31 -0
  16. opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
  17. opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
  18. opentrons/calibration_storage/ot3/models/__init__.py +0 -0
  19. opentrons/calibration_storage/ot3/models/v1.py +122 -0
  20. opentrons/calibration_storage/ot3/module_offset.py +138 -0
  21. opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
  22. opentrons/calibration_storage/types.py +45 -0
  23. opentrons/cli/__init__.py +21 -0
  24. opentrons/cli/__main__.py +5 -0
  25. opentrons/cli/analyze.py +557 -0
  26. opentrons/config/__init__.py +631 -0
  27. opentrons/config/advanced_settings.py +871 -0
  28. opentrons/config/defaults_ot2.py +214 -0
  29. opentrons/config/defaults_ot3.py +499 -0
  30. opentrons/config/feature_flags.py +86 -0
  31. opentrons/config/gripper_config.py +55 -0
  32. opentrons/config/reset.py +203 -0
  33. opentrons/config/robot_configs.py +187 -0
  34. opentrons/config/types.py +183 -0
  35. opentrons/drivers/__init__.py +0 -0
  36. opentrons/drivers/absorbance_reader/__init__.py +11 -0
  37. opentrons/drivers/absorbance_reader/abstract.py +72 -0
  38. opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
  39. opentrons/drivers/absorbance_reader/driver.py +81 -0
  40. opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
  41. opentrons/drivers/absorbance_reader/simulator.py +84 -0
  42. opentrons/drivers/asyncio/__init__.py +0 -0
  43. opentrons/drivers/asyncio/communication/__init__.py +22 -0
  44. opentrons/drivers/asyncio/communication/async_serial.py +187 -0
  45. opentrons/drivers/asyncio/communication/errors.py +88 -0
  46. opentrons/drivers/asyncio/communication/serial_connection.py +557 -0
  47. opentrons/drivers/command_builder.py +102 -0
  48. opentrons/drivers/flex_stacker/__init__.py +13 -0
  49. opentrons/drivers/flex_stacker/abstract.py +214 -0
  50. opentrons/drivers/flex_stacker/driver.py +768 -0
  51. opentrons/drivers/flex_stacker/errors.py +68 -0
  52. opentrons/drivers/flex_stacker/simulator.py +309 -0
  53. opentrons/drivers/flex_stacker/types.py +367 -0
  54. opentrons/drivers/flex_stacker/utils.py +19 -0
  55. opentrons/drivers/heater_shaker/__init__.py +5 -0
  56. opentrons/drivers/heater_shaker/abstract.py +76 -0
  57. opentrons/drivers/heater_shaker/driver.py +204 -0
  58. opentrons/drivers/heater_shaker/simulator.py +94 -0
  59. opentrons/drivers/mag_deck/__init__.py +6 -0
  60. opentrons/drivers/mag_deck/abstract.py +44 -0
  61. opentrons/drivers/mag_deck/driver.py +208 -0
  62. opentrons/drivers/mag_deck/simulator.py +63 -0
  63. opentrons/drivers/rpi_drivers/__init__.py +33 -0
  64. opentrons/drivers/rpi_drivers/dev_types.py +94 -0
  65. opentrons/drivers/rpi_drivers/gpio.py +282 -0
  66. opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
  67. opentrons/drivers/rpi_drivers/interfaces.py +15 -0
  68. opentrons/drivers/rpi_drivers/types.py +364 -0
  69. opentrons/drivers/rpi_drivers/usb.py +102 -0
  70. opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
  71. opentrons/drivers/serial_communication.py +151 -0
  72. opentrons/drivers/smoothie_drivers/__init__.py +4 -0
  73. opentrons/drivers/smoothie_drivers/connection.py +51 -0
  74. opentrons/drivers/smoothie_drivers/constants.py +121 -0
  75. opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
  76. opentrons/drivers/smoothie_drivers/errors.py +49 -0
  77. opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
  78. opentrons/drivers/smoothie_drivers/simulator.py +99 -0
  79. opentrons/drivers/smoothie_drivers/types.py +16 -0
  80. opentrons/drivers/temp_deck/__init__.py +10 -0
  81. opentrons/drivers/temp_deck/abstract.py +54 -0
  82. opentrons/drivers/temp_deck/driver.py +197 -0
  83. opentrons/drivers/temp_deck/simulator.py +57 -0
  84. opentrons/drivers/thermocycler/__init__.py +12 -0
  85. opentrons/drivers/thermocycler/abstract.py +99 -0
  86. opentrons/drivers/thermocycler/driver.py +395 -0
  87. opentrons/drivers/thermocycler/simulator.py +126 -0
  88. opentrons/drivers/types.py +107 -0
  89. opentrons/drivers/utils.py +222 -0
  90. opentrons/execute.py +742 -0
  91. opentrons/hardware_control/__init__.py +65 -0
  92. opentrons/hardware_control/__main__.py +77 -0
  93. opentrons/hardware_control/adapters.py +98 -0
  94. opentrons/hardware_control/api.py +1347 -0
  95. opentrons/hardware_control/backends/__init__.py +7 -0
  96. opentrons/hardware_control/backends/controller.py +400 -0
  97. opentrons/hardware_control/backends/errors.py +9 -0
  98. opentrons/hardware_control/backends/estop_state.py +164 -0
  99. opentrons/hardware_control/backends/flex_protocol.py +497 -0
  100. opentrons/hardware_control/backends/ot3controller.py +1930 -0
  101. opentrons/hardware_control/backends/ot3simulator.py +900 -0
  102. opentrons/hardware_control/backends/ot3utils.py +664 -0
  103. opentrons/hardware_control/backends/simulator.py +442 -0
  104. opentrons/hardware_control/backends/status_bar_state.py +240 -0
  105. opentrons/hardware_control/backends/subsystem_manager.py +431 -0
  106. opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
  107. opentrons/hardware_control/backends/types.py +14 -0
  108. opentrons/hardware_control/constants.py +6 -0
  109. opentrons/hardware_control/dev_types.py +125 -0
  110. opentrons/hardware_control/emulation/__init__.py +0 -0
  111. opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
  112. opentrons/hardware_control/emulation/app.py +56 -0
  113. opentrons/hardware_control/emulation/connection_handler.py +38 -0
  114. opentrons/hardware_control/emulation/heater_shaker.py +150 -0
  115. opentrons/hardware_control/emulation/magdeck.py +60 -0
  116. opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
  117. opentrons/hardware_control/emulation/module_server/client.py +78 -0
  118. opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
  119. opentrons/hardware_control/emulation/module_server/models.py +31 -0
  120. opentrons/hardware_control/emulation/module_server/server.py +110 -0
  121. opentrons/hardware_control/emulation/parser.py +74 -0
  122. opentrons/hardware_control/emulation/proxy.py +241 -0
  123. opentrons/hardware_control/emulation/run_emulator.py +68 -0
  124. opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
  125. opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
  126. opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
  127. opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
  128. opentrons/hardware_control/emulation/settings.py +119 -0
  129. opentrons/hardware_control/emulation/simulations.py +133 -0
  130. opentrons/hardware_control/emulation/smoothie.py +192 -0
  131. opentrons/hardware_control/emulation/tempdeck.py +69 -0
  132. opentrons/hardware_control/emulation/thermocycler.py +128 -0
  133. opentrons/hardware_control/emulation/types.py +10 -0
  134. opentrons/hardware_control/emulation/util.py +38 -0
  135. opentrons/hardware_control/errors.py +43 -0
  136. opentrons/hardware_control/execution_manager.py +164 -0
  137. opentrons/hardware_control/instruments/__init__.py +5 -0
  138. opentrons/hardware_control/instruments/instrument_abc.py +39 -0
  139. opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
  140. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
  141. opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
  142. opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
  143. opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
  144. opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
  145. opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
  146. opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
  147. opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
  148. opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
  149. opentrons/hardware_control/module_control.py +332 -0
  150. opentrons/hardware_control/modules/__init__.py +69 -0
  151. opentrons/hardware_control/modules/absorbance_reader.py +373 -0
  152. opentrons/hardware_control/modules/errors.py +7 -0
  153. opentrons/hardware_control/modules/flex_stacker.py +948 -0
  154. opentrons/hardware_control/modules/heater_shaker.py +426 -0
  155. opentrons/hardware_control/modules/lid_temp_status.py +35 -0
  156. opentrons/hardware_control/modules/magdeck.py +233 -0
  157. opentrons/hardware_control/modules/mod_abc.py +245 -0
  158. opentrons/hardware_control/modules/module_calibration.py +93 -0
  159. opentrons/hardware_control/modules/plate_temp_status.py +61 -0
  160. opentrons/hardware_control/modules/tempdeck.py +299 -0
  161. opentrons/hardware_control/modules/thermocycler.py +731 -0
  162. opentrons/hardware_control/modules/types.py +417 -0
  163. opentrons/hardware_control/modules/update.py +255 -0
  164. opentrons/hardware_control/modules/utils.py +73 -0
  165. opentrons/hardware_control/motion_utilities.py +318 -0
  166. opentrons/hardware_control/nozzle_manager.py +422 -0
  167. opentrons/hardware_control/ot3_calibration.py +1171 -0
  168. opentrons/hardware_control/ot3api.py +3227 -0
  169. opentrons/hardware_control/pause_manager.py +31 -0
  170. opentrons/hardware_control/poller.py +112 -0
  171. opentrons/hardware_control/protocols/__init__.py +106 -0
  172. opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
  173. opentrons/hardware_control/protocols/calibratable.py +45 -0
  174. opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
  175. opentrons/hardware_control/protocols/configurable.py +48 -0
  176. opentrons/hardware_control/protocols/event_sourcer.py +18 -0
  177. opentrons/hardware_control/protocols/execution_controllable.py +33 -0
  178. opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
  179. opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
  180. opentrons/hardware_control/protocols/gripper_controller.py +55 -0
  181. opentrons/hardware_control/protocols/hardware_manager.py +51 -0
  182. opentrons/hardware_control/protocols/identifiable.py +16 -0
  183. opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
  184. opentrons/hardware_control/protocols/liquid_handler.py +266 -0
  185. opentrons/hardware_control/protocols/module_provider.py +16 -0
  186. opentrons/hardware_control/protocols/motion_controller.py +243 -0
  187. opentrons/hardware_control/protocols/position_estimator.py +45 -0
  188. opentrons/hardware_control/protocols/simulatable.py +10 -0
  189. opentrons/hardware_control/protocols/stoppable.py +9 -0
  190. opentrons/hardware_control/protocols/types.py +27 -0
  191. opentrons/hardware_control/robot_calibration.py +224 -0
  192. opentrons/hardware_control/scripts/README.md +28 -0
  193. opentrons/hardware_control/scripts/__init__.py +1 -0
  194. opentrons/hardware_control/scripts/gripper_control.py +208 -0
  195. opentrons/hardware_control/scripts/ot3gripper +7 -0
  196. opentrons/hardware_control/scripts/ot3repl +7 -0
  197. opentrons/hardware_control/scripts/repl.py +187 -0
  198. opentrons/hardware_control/scripts/tc_control.py +97 -0
  199. opentrons/hardware_control/scripts/update_module_fw.py +274 -0
  200. opentrons/hardware_control/simulator_setup.py +260 -0
  201. opentrons/hardware_control/thread_manager.py +431 -0
  202. opentrons/hardware_control/threaded_async_lock.py +97 -0
  203. opentrons/hardware_control/types.py +792 -0
  204. opentrons/hardware_control/util.py +234 -0
  205. opentrons/legacy_broker.py +53 -0
  206. opentrons/legacy_commands/__init__.py +1 -0
  207. opentrons/legacy_commands/commands.py +483 -0
  208. opentrons/legacy_commands/helpers.py +153 -0
  209. opentrons/legacy_commands/module_commands.py +276 -0
  210. opentrons/legacy_commands/protocol_commands.py +54 -0
  211. opentrons/legacy_commands/publisher.py +155 -0
  212. opentrons/legacy_commands/robot_commands.py +51 -0
  213. opentrons/legacy_commands/types.py +1186 -0
  214. opentrons/motion_planning/__init__.py +32 -0
  215. opentrons/motion_planning/adjacent_slots_getters.py +168 -0
  216. opentrons/motion_planning/deck_conflict.py +501 -0
  217. opentrons/motion_planning/errors.py +35 -0
  218. opentrons/motion_planning/types.py +42 -0
  219. opentrons/motion_planning/waypoints.py +218 -0
  220. opentrons/ordered_set.py +138 -0
  221. opentrons/protocol_api/__init__.py +105 -0
  222. opentrons/protocol_api/_liquid.py +157 -0
  223. opentrons/protocol_api/_liquid_properties.py +814 -0
  224. opentrons/protocol_api/_nozzle_layout.py +31 -0
  225. opentrons/protocol_api/_parameter_context.py +300 -0
  226. opentrons/protocol_api/_parameters.py +31 -0
  227. opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
  228. opentrons/protocol_api/_types.py +43 -0
  229. opentrons/protocol_api/config.py +23 -0
  230. opentrons/protocol_api/core/__init__.py +23 -0
  231. opentrons/protocol_api/core/common.py +33 -0
  232. opentrons/protocol_api/core/core_map.py +74 -0
  233. opentrons/protocol_api/core/engine/__init__.py +22 -0
  234. opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
  235. opentrons/protocol_api/core/engine/deck_conflict.py +400 -0
  236. opentrons/protocol_api/core/engine/exceptions.py +19 -0
  237. opentrons/protocol_api/core/engine/instrument.py +2391 -0
  238. opentrons/protocol_api/core/engine/labware.py +238 -0
  239. opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
  240. opentrons/protocol_api/core/engine/module_core.py +1027 -0
  241. opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
  242. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
  243. opentrons/protocol_api/core/engine/point_calculations.py +64 -0
  244. opentrons/protocol_api/core/engine/protocol.py +1153 -0
  245. opentrons/protocol_api/core/engine/robot.py +139 -0
  246. opentrons/protocol_api/core/engine/stringify.py +74 -0
  247. opentrons/protocol_api/core/engine/transfer_components_executor.py +1006 -0
  248. opentrons/protocol_api/core/engine/well.py +241 -0
  249. opentrons/protocol_api/core/instrument.py +459 -0
  250. opentrons/protocol_api/core/labware.py +151 -0
  251. opentrons/protocol_api/core/legacy/__init__.py +11 -0
  252. opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
  253. opentrons/protocol_api/core/legacy/deck.py +369 -0
  254. opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
  255. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
  256. opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
  257. opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
  258. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
  259. opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
  260. opentrons/protocol_api/core/legacy/load_info.py +67 -0
  261. opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
  262. opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
  263. opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
  264. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
  265. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
  266. opentrons/protocol_api/core/module.py +484 -0
  267. opentrons/protocol_api/core/protocol.py +311 -0
  268. opentrons/protocol_api/core/robot.py +51 -0
  269. opentrons/protocol_api/core/well.py +116 -0
  270. opentrons/protocol_api/core/well_grid.py +45 -0
  271. opentrons/protocol_api/create_protocol_context.py +177 -0
  272. opentrons/protocol_api/deck.py +223 -0
  273. opentrons/protocol_api/disposal_locations.py +244 -0
  274. opentrons/protocol_api/instrument_context.py +3272 -0
  275. opentrons/protocol_api/labware.py +1579 -0
  276. opentrons/protocol_api/module_contexts.py +1447 -0
  277. opentrons/protocol_api/module_validation_and_errors.py +61 -0
  278. opentrons/protocol_api/protocol_context.py +1688 -0
  279. opentrons/protocol_api/robot_context.py +303 -0
  280. opentrons/protocol_api/validation.py +761 -0
  281. opentrons/protocol_engine/__init__.py +155 -0
  282. opentrons/protocol_engine/actions/__init__.py +65 -0
  283. opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
  284. opentrons/protocol_engine/actions/action_handler.py +13 -0
  285. opentrons/protocol_engine/actions/actions.py +302 -0
  286. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  287. opentrons/protocol_engine/clients/__init__.py +5 -0
  288. opentrons/protocol_engine/clients/sync_client.py +174 -0
  289. opentrons/protocol_engine/clients/transports.py +197 -0
  290. opentrons/protocol_engine/commands/__init__.py +757 -0
  291. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
  292. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
  293. opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
  294. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
  295. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
  296. opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
  297. opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
  298. opentrons/protocol_engine/commands/aspirate.py +244 -0
  299. opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
  300. opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
  301. opentrons/protocol_engine/commands/blow_out.py +146 -0
  302. opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
  303. opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
  304. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
  305. opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
  306. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
  307. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
  308. opentrons/protocol_engine/commands/command.py +308 -0
  309. opentrons/protocol_engine/commands/command_unions.py +974 -0
  310. opentrons/protocol_engine/commands/comment.py +57 -0
  311. opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
  312. opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
  313. opentrons/protocol_engine/commands/custom.py +67 -0
  314. opentrons/protocol_engine/commands/dispense.py +194 -0
  315. opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
  316. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  317. opentrons/protocol_engine/commands/drop_tip.py +232 -0
  318. opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
  319. opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
  320. opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
  321. opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
  322. opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
  323. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
  324. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
  325. opentrons/protocol_engine/commands/flex_stacker/store.py +339 -0
  326. opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
  327. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  328. opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
  329. opentrons/protocol_engine/commands/hash_command_params.py +38 -0
  330. opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
  331. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
  332. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
  333. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
  334. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
  335. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
  336. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
  337. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
  338. opentrons/protocol_engine/commands/home.py +100 -0
  339. opentrons/protocol_engine/commands/identify_module.py +86 -0
  340. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  341. opentrons/protocol_engine/commands/liquid_probe.py +464 -0
  342. opentrons/protocol_engine/commands/load_labware.py +210 -0
  343. opentrons/protocol_engine/commands/load_lid.py +154 -0
  344. opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
  345. opentrons/protocol_engine/commands/load_liquid.py +95 -0
  346. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  347. opentrons/protocol_engine/commands/load_module.py +223 -0
  348. opentrons/protocol_engine/commands/load_pipette.py +167 -0
  349. opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
  350. opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
  351. opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
  352. opentrons/protocol_engine/commands/move_labware.py +546 -0
  353. opentrons/protocol_engine/commands/move_relative.py +102 -0
  354. opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
  355. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
  356. opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
  357. opentrons/protocol_engine/commands/move_to_well.py +119 -0
  358. opentrons/protocol_engine/commands/movement_common.py +338 -0
  359. opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
  360. opentrons/protocol_engine/commands/pipetting_common.py +443 -0
  361. opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
  362. opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
  363. opentrons/protocol_engine/commands/reload_labware.py +90 -0
  364. opentrons/protocol_engine/commands/retract_axis.py +75 -0
  365. opentrons/protocol_engine/commands/robot/__init__.py +70 -0
  366. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
  367. opentrons/protocol_engine/commands/robot/common.py +18 -0
  368. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  369. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  370. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  371. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
  372. opentrons/protocol_engine/commands/save_position.py +109 -0
  373. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
  374. opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
  375. opentrons/protocol_engine/commands/set_status_bar.py +89 -0
  376. opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
  377. opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
  378. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
  379. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
  380. opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
  381. opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
  382. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
  383. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
  384. opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
  385. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
  386. opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
  387. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
  388. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
  389. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
  390. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
  391. opentrons/protocol_engine/commands/touch_tip.py +189 -0
  392. opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
  393. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
  394. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
  395. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
  396. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  397. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
  398. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
  399. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
  400. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
  401. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  402. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
  403. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
  404. opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
  405. opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
  406. opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
  407. opentrons/protocol_engine/create_protocol_engine.py +193 -0
  408. opentrons/protocol_engine/engine_support.py +28 -0
  409. opentrons/protocol_engine/error_recovery_policy.py +81 -0
  410. opentrons/protocol_engine/errors/__init__.py +191 -0
  411. opentrons/protocol_engine/errors/error_occurrence.py +182 -0
  412. opentrons/protocol_engine/errors/exceptions.py +1308 -0
  413. opentrons/protocol_engine/execution/__init__.py +50 -0
  414. opentrons/protocol_engine/execution/command_executor.py +216 -0
  415. opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
  416. opentrons/protocol_engine/execution/door_watcher.py +119 -0
  417. opentrons/protocol_engine/execution/equipment.py +819 -0
  418. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  419. opentrons/protocol_engine/execution/gantry_mover.py +686 -0
  420. opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
  421. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
  422. opentrons/protocol_engine/execution/labware_movement.py +297 -0
  423. opentrons/protocol_engine/execution/movement.py +350 -0
  424. opentrons/protocol_engine/execution/pipetting.py +607 -0
  425. opentrons/protocol_engine/execution/queue_worker.py +86 -0
  426. opentrons/protocol_engine/execution/rail_lights.py +25 -0
  427. opentrons/protocol_engine/execution/run_control.py +33 -0
  428. opentrons/protocol_engine/execution/status_bar.py +34 -0
  429. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
  430. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
  431. opentrons/protocol_engine/execution/tip_handler.py +550 -0
  432. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  433. opentrons/protocol_engine/notes/__init__.py +17 -0
  434. opentrons/protocol_engine/notes/notes.py +59 -0
  435. opentrons/protocol_engine/plugins.py +104 -0
  436. opentrons/protocol_engine/protocol_engine.py +683 -0
  437. opentrons/protocol_engine/resources/__init__.py +26 -0
  438. opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
  439. opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
  440. opentrons/protocol_engine/resources/file_provider.py +161 -0
  441. opentrons/protocol_engine/resources/fixture_validation.py +68 -0
  442. opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
  443. opentrons/protocol_engine/resources/labware_validation.py +73 -0
  444. opentrons/protocol_engine/resources/model_utils.py +32 -0
  445. opentrons/protocol_engine/resources/module_data_provider.py +44 -0
  446. opentrons/protocol_engine/resources/ot3_validation.py +21 -0
  447. opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
  448. opentrons/protocol_engine/slot_standardization.py +128 -0
  449. opentrons/protocol_engine/state/__init__.py +1 -0
  450. opentrons/protocol_engine/state/_abstract_store.py +27 -0
  451. opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
  452. opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
  453. opentrons/protocol_engine/state/_move_types.py +83 -0
  454. opentrons/protocol_engine/state/_well_math.py +193 -0
  455. opentrons/protocol_engine/state/addressable_areas.py +699 -0
  456. opentrons/protocol_engine/state/command_history.py +309 -0
  457. opentrons/protocol_engine/state/commands.py +1164 -0
  458. opentrons/protocol_engine/state/config.py +39 -0
  459. opentrons/protocol_engine/state/files.py +57 -0
  460. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  461. opentrons/protocol_engine/state/geometry.py +2408 -0
  462. opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
  463. opentrons/protocol_engine/state/labware.py +1432 -0
  464. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  465. opentrons/protocol_engine/state/liquids.py +73 -0
  466. opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
  467. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
  468. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
  469. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
  470. opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
  471. opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
  472. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
  473. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
  474. opentrons/protocol_engine/state/modules.py +1515 -0
  475. opentrons/protocol_engine/state/motion.py +373 -0
  476. opentrons/protocol_engine/state/pipettes.py +905 -0
  477. opentrons/protocol_engine/state/state.py +421 -0
  478. opentrons/protocol_engine/state/state_summary.py +36 -0
  479. opentrons/protocol_engine/state/tips.py +420 -0
  480. opentrons/protocol_engine/state/update_types.py +904 -0
  481. opentrons/protocol_engine/state/wells.py +290 -0
  482. opentrons/protocol_engine/types/__init__.py +310 -0
  483. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  484. opentrons/protocol_engine/types/command_annotations.py +53 -0
  485. opentrons/protocol_engine/types/deck_configuration.py +81 -0
  486. opentrons/protocol_engine/types/execution.py +96 -0
  487. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  488. opentrons/protocol_engine/types/instrument.py +47 -0
  489. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  490. opentrons/protocol_engine/types/labware.py +131 -0
  491. opentrons/protocol_engine/types/labware_movement.py +22 -0
  492. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  493. opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
  494. opentrons/protocol_engine/types/liquid.py +40 -0
  495. opentrons/protocol_engine/types/liquid_class.py +59 -0
  496. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  497. opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
  498. opentrons/protocol_engine/types/location.py +194 -0
  499. opentrons/protocol_engine/types/module.py +310 -0
  500. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  501. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  502. opentrons/protocol_engine/types/tip.py +18 -0
  503. opentrons/protocol_engine/types/util.py +21 -0
  504. opentrons/protocol_engine/types/well_position.py +124 -0
  505. opentrons/protocol_reader/__init__.py +37 -0
  506. opentrons/protocol_reader/extract_labware_definitions.py +66 -0
  507. opentrons/protocol_reader/file_format_validator.py +152 -0
  508. opentrons/protocol_reader/file_hasher.py +27 -0
  509. opentrons/protocol_reader/file_identifier.py +284 -0
  510. opentrons/protocol_reader/file_reader_writer.py +90 -0
  511. opentrons/protocol_reader/input_file.py +16 -0
  512. opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
  513. opentrons/protocol_reader/protocol_reader.py +188 -0
  514. opentrons/protocol_reader/protocol_source.py +124 -0
  515. opentrons/protocol_reader/role_analyzer.py +86 -0
  516. opentrons/protocol_runner/__init__.py +26 -0
  517. opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
  518. opentrons/protocol_runner/json_file_reader.py +55 -0
  519. opentrons/protocol_runner/json_translator.py +314 -0
  520. opentrons/protocol_runner/legacy_command_mapper.py +852 -0
  521. opentrons/protocol_runner/legacy_context_plugin.py +116 -0
  522. opentrons/protocol_runner/protocol_runner.py +530 -0
  523. opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
  524. opentrons/protocol_runner/run_orchestrator.py +496 -0
  525. opentrons/protocol_runner/task_queue.py +95 -0
  526. opentrons/protocols/__init__.py +6 -0
  527. opentrons/protocols/advanced_control/__init__.py +0 -0
  528. opentrons/protocols/advanced_control/common.py +38 -0
  529. opentrons/protocols/advanced_control/mix.py +60 -0
  530. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  531. opentrons/protocols/advanced_control/transfers/common.py +180 -0
  532. opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
  533. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
  534. opentrons/protocols/api_support/__init__.py +0 -0
  535. opentrons/protocols/api_support/constants.py +8 -0
  536. opentrons/protocols/api_support/deck_type.py +110 -0
  537. opentrons/protocols/api_support/definitions.py +18 -0
  538. opentrons/protocols/api_support/instrument.py +151 -0
  539. opentrons/protocols/api_support/labware_like.py +233 -0
  540. opentrons/protocols/api_support/tip_tracker.py +175 -0
  541. opentrons/protocols/api_support/types.py +32 -0
  542. opentrons/protocols/api_support/util.py +403 -0
  543. opentrons/protocols/bundle.py +89 -0
  544. opentrons/protocols/duration/__init__.py +4 -0
  545. opentrons/protocols/duration/errors.py +5 -0
  546. opentrons/protocols/duration/estimator.py +628 -0
  547. opentrons/protocols/execution/__init__.py +0 -0
  548. opentrons/protocols/execution/dev_types.py +181 -0
  549. opentrons/protocols/execution/errors.py +40 -0
  550. opentrons/protocols/execution/execute.py +84 -0
  551. opentrons/protocols/execution/execute_json_v3.py +275 -0
  552. opentrons/protocols/execution/execute_json_v4.py +359 -0
  553. opentrons/protocols/execution/execute_json_v5.py +28 -0
  554. opentrons/protocols/execution/execute_python.py +169 -0
  555. opentrons/protocols/execution/json_dispatchers.py +87 -0
  556. opentrons/protocols/execution/types.py +7 -0
  557. opentrons/protocols/geometry/__init__.py +0 -0
  558. opentrons/protocols/geometry/planning.py +297 -0
  559. opentrons/protocols/labware.py +312 -0
  560. opentrons/protocols/models/__init__.py +0 -0
  561. opentrons/protocols/models/json_protocol.py +679 -0
  562. opentrons/protocols/parameters/__init__.py +0 -0
  563. opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
  564. opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
  565. opentrons/protocols/parameters/exceptions.py +34 -0
  566. opentrons/protocols/parameters/parameter_definition.py +272 -0
  567. opentrons/protocols/parameters/types.py +17 -0
  568. opentrons/protocols/parameters/validation.py +267 -0
  569. opentrons/protocols/parse.py +671 -0
  570. opentrons/protocols/types.py +159 -0
  571. opentrons/py.typed +0 -0
  572. opentrons/resources/scripts/lpc21isp +0 -0
  573. opentrons/resources/smoothie-edge-8414642.hex +23010 -0
  574. opentrons/simulate.py +1065 -0
  575. opentrons/system/__init__.py +6 -0
  576. opentrons/system/camera.py +51 -0
  577. opentrons/system/log_control.py +59 -0
  578. opentrons/system/nmcli.py +856 -0
  579. opentrons/system/resin.py +24 -0
  580. opentrons/system/smoothie_update.py +15 -0
  581. opentrons/system/wifi.py +204 -0
  582. opentrons/tools/__init__.py +0 -0
  583. opentrons/tools/args_handler.py +22 -0
  584. opentrons/tools/write_pipette_memory.py +157 -0
  585. opentrons/types.py +618 -0
  586. opentrons/util/__init__.py +1 -0
  587. opentrons/util/async_helpers.py +166 -0
  588. opentrons/util/broker.py +84 -0
  589. opentrons/util/change_notifier.py +47 -0
  590. opentrons/util/entrypoint_util.py +278 -0
  591. opentrons/util/get_union_elements.py +26 -0
  592. opentrons/util/helpers.py +6 -0
  593. opentrons/util/linal.py +178 -0
  594. opentrons/util/logging_config.py +265 -0
  595. opentrons/util/logging_queue_handler.py +61 -0
  596. opentrons/util/performance_helpers.py +157 -0
  597. opentrons-8.6.0.dist-info/METADATA +37 -0
  598. opentrons-8.6.0.dist-info/RECORD +601 -0
  599. opentrons-8.6.0.dist-info/WHEEL +4 -0
  600. opentrons-8.6.0.dist-info/entry_points.txt +3 -0
  601. opentrons-8.6.0.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}")