def test_aspirate_move_to(old_aspiration, robot, instruments): # TODO: it seems like this test is checking that the aspirate point is # TODO: *fully* at the bottom of the well, which isn't the expected # TODO: behavior of aspirate when a location is not specified. This should # TODO: be split into two tests--one for this behavior (specifying a place) # TODO: and another one for the default robot.reset() tip_rack = containers_load(robot, 'tiprack-200ul', '3') p300 = instruments.P300_Single(mount='left', tip_racks=[tip_rack]) p300.pick_up_tip() x, y, z = (161.0, 116.7, 0.0) plate = containers_load(robot, '96-flat', '1') well = plate[0] pos = well.from_center(x=0, y=0, z=-1, reference=plate) location = (plate, pos) robot.poses = p300._move(robot.poses, x=x, y=y, z=z) robot.calibrate_container_with_instrument(plate, p300, False) p300.aspirate(100, location) current_pos = pose_tracker.absolute(robot.poses, p300.instrument_actuator) assert isclose(current_pos, (6.9, 0.0, 0.0)).all() current_pos = pose_tracker.absolute(robot.poses, p300) assert isclose(current_pos, (161, 116.7, 10.5)).all()
def _move_mount(robot, mount, point): """ The carriage moves the mount in the Z axis, and the gantry moves in X and Y Mount movements do not have the same protections calculated in to an existing `move` command like Pipette does, so the safest thing is to home the Z axis, then move in X and Y, then move down to the specified Z height """ carriage = robot._actuators[mount]['carriage'] # Home both carriages, to prevent collisions and to ensure that the other # mount doesn't block the one being moved (mount moves are primarily for # changing pipettes, so we don't want the other pipette blocking access) robot.poses = carriage.home(robot.poses) other_mount = 'left' if mount == 'right' else 'right' robot.poses = robot._actuators[other_mount]['carriage'].home(robot.poses) robot.gantry.move( robot.poses, x=point[0], y=point[1]) robot.poses = carriage.move( robot.poses, z=point[2]) # These x and y values are hard to interpret because of some internals of # pose tracker. It's mostly z that matters for this operation anyway x, y, _ = tuple( pose_tracker.absolute( robot.poses, robot._actuators[mount]['carriage'])) _, _, z = tuple( pose_tracker.absolute( robot.poses, robot.gantry)) new_position = (x, y, z) return "Move complete. New position: {}".format(new_position)
def test_dispense_move_to(old_aspiration): # TODO: same as for aspirate robot.reset() tip_rack = containers_load(robot, 'tiprack-200ul', '3') p300 = instruments.P300_Single( mount='left', tip_racks=[tip_rack]) x, y, z = (161.0, 116.7, 0.0) plate = containers_load(robot, '96-flat', '1') well = plate[0] pos = well.from_center(x=0, y=0, z=-1, reference=plate) location = (plate, pos) robot.poses = p300._move(robot.poses, x=x, y=y, z=z) robot.calibrate_container_with_instrument(plate, p300, False) p300.pick_up_tip() p300.aspirate(100, location) p300.dispense(100, location) current_pos = pose_tracker.absolute( robot.poses, p300.instrument_actuator) assert (current_pos == (1.5, 0.0, 0.0)).all() current_pos = pose_tracker.absolute(robot.poses, p300) assert isclose(current_pos, (161, 116.7, 10.5)).all()
def test_fixed_trash(): robot.reset() p300 = instruments.P300_Single(mount='right') print(pose_tracker.absolute(robot.poses, p300)) p300.move_to(p300.trash_container) print(pose_tracker.absolute(robot.poses, p300)) assert isclose(pose_tracker.absolute(robot.poses, p300), (355.0, 361.43, 85.0)).all()
def _test_offset(x, y, z): robot.config = robot.config._replace(mount_offset=(x, y, z)) robot.reset() left = instruments.P300_Single(mount='left') right = instruments.P300_Single(mount='right') robot.home() left_pos = pose_tracker.absolute(robot.poses, left) right_pos = pose_tracker.absolute(robot.poses, right) assert left_pos[0] == (right_pos[0] + x) assert left_pos[1] == (right_pos[1] + y) assert left_pos[2] == (right_pos[2] + z)
def test_add_tip(self): """ This deals with z accrual behavior during tip add/remove, when +/- get flipped in pose tracking logic """ prior_position = pose_tracker.absolute(self.robot.poses, self.p200) self.p200._add_tip(42) self.p200._remove_tip(42) new_position = pose_tracker.absolute(self.robot.poses, self.p200) assert (new_position == prior_position).all()
def test_add_tip(local_test_pipette, robot): """ This deals with z accrual behavior during tip add/remove, when +/- get flipped in pose tracking logic """ trash, tiprack1, tiprack2, plate, p200 = local_test_pipette prior_position = pose_tracker.absolute(robot.poses, p200) p200._add_tip(42) p200._remove_tip(42) new_position = pose_tracker.absolute(robot.poses, p200) assert (new_position == prior_position).all()
async def test_jog_calibrate_top( dummy_db, main_router, model, monkeypatch): # Check that the old behavior remains the same without the feature flag from numpy import array, isclose from opentrons.trackers import pose_tracker import tempfile temp = tempfile.gettempdir() monkeypatch.setenv('USER_DEFN_ROOT', temp) robot = model.robot container = model.container._container container_coords1 = container.coordinates() pos1 = pose_tracker.absolute(robot.poses, container[0]) coordinates1 = container[0].coordinates() main_router.calibration_manager.move_to(model.instrument, model.container) main_router.calibration_manager.jog(model.instrument, 1, 'x') main_router.calibration_manager.jog(model.instrument, 2, 'y') main_router.calibration_manager.jog(model.instrument, 3, 'z') main_router.calibration_manager.update_container_offset( model.container, model.instrument ) container_coords2 = container.coordinates() pos2 = pose_tracker.absolute(robot.poses, container[0]) coordinates2 = container[0].coordinates() assert isclose( array([*container_coords1]) + (1, 2, 3), array([*container_coords2])).all() assert isclose(pos1 + (1, 2, 3), pos2).all() assert isclose( array([*coordinates1]) + (1, 2, 3), array([*coordinates2])).all() main_router.calibration_manager.pick_up_tip( model.instrument, model.container ) # NOTE: only check XY, as the instrument moves up after tip pickup assert isclose( pose_tracker.absolute(robot.poses, container[0])[:-1], pose_tracker.absolute(robot.poses, model.instrument._instrument)[:-1] ).all()
async def test_jog_calibrate_bottom_v1( dummy_db, main_router, model, calibrate_bottom_flag): # Check that the feature flag correctly implements calibrate to bottom from numpy import array, isclose from opentrons.trackers import pose_tracker robot = model.robot container = model.container._container pos1 = pose_tracker.change_base( robot.poses, src=container[0], dst=robot.deck) coordinates1 = container.coordinates() height = container['A1'].properties['height'] main_router.calibration_manager.move_to(model.instrument, model.container) main_router.calibration_manager.jog(model.instrument, 1, 'x') main_router.calibration_manager.jog(model.instrument, 2, 'y') main_router.calibration_manager.jog(model.instrument, 3, 'z') main_router.calibration_manager.jog(model.instrument, -height, 'z') # Todo: make tests use a tmp dir instead of a real one main_router.calibration_manager.update_container_offset( model.container, model.instrument ) pos2 = pose_tracker.absolute(robot.poses, container[0]) coordinates2 = container.coordinates() assert isclose(pos1 + (1, 2, 3), pos2).all() assert isclose( array([*coordinates1]) + (1, 2, 3), array([*coordinates2])).all() main_router.calibration_manager.pick_up_tip( model.instrument, model.container ) # NOTE: only check XY, as the instrument moves up after tip pickup assert isclose( pose_tracker.absolute(robot.poses, container[0])[:-1], pose_tracker.absolute(robot.poses, model.instrument._instrument)[:-1] ).all()
async def move(request): """ Moves the robot to the specified position as provided by the `control.info` endpoint response Post body must include the following keys: - 'target': either 'mount' or 'pipette' - 'point': a tuple of 3 floats for x, y, z - 'mount': must be 'left' or 'right' If 'target' is 'pipette', body must also contain: - 'model': must be a valid pipette model (as defined in `pipette_config`) """ req = await request.text() data = json.loads(req) target, point, mount, model, message, error = _validate_move_data(data) if error: status = 400 else: status = 200 if target == 'mount': message = _move_mount(mount, point) elif target == 'pipette': pipette = _fetch_or_create_pipette(mount, model) pipette.move_to((robot.deck, point), strategy='arc') new_position = tuple( pose_tracker.absolute(pipette.robot.poses, pipette)) message = "Move complete. New position: {}".format(new_position) return web.json_response({"message": message}, status=status)
def test_retract(robot, instruments): robot.reset() plate = containers_load(robot, '96-flat', '1') p300 = instruments.P300_Single(mount='left') from opentrons.drivers.smoothie_drivers.driver_3_0 import HOMED_POSITION p300.move_to(plate[0].top()) assert p300.previous_placeable == plate[0] current_pos = pose_tracker.absolute(robot.poses, p300) assert current_pos[2] == plate[0].coordinates()[2] p300.retract() assert p300.previous_placeable is None current_pos = pose_tracker.absolute(robot.poses, p300.instrument_mover) assert current_pos[2] == HOMED_POSITION['A']
def test_move_head(robot): robot.move_head(x=100, y=0) assert isclose( pose_tracker.absolute( robot.poses, robot.gantry)[:2], (100, 0, 0)[:2] ).all()
def test_move_head(virtual_smoothie_env): robot.reset() robot.move_head(x=100, y=0) assert isclose( pose_tracker.absolute( robot.poses, robot.gantry)[:2], (100, 0, 0)[:2] ).all()
def move_to(self, location, instrument, strategy='arc', **kwargs): """ Move an instrument to a coordinate, container or a coordinate within a container. Parameters ---------- location : one of the following: 1. :class:`Placeable` (i.e. Container, Deck, Slot, Well) — will move to the origin of a container. 2. :class:`Vector` move to the given coordinate in Deck coordinate system. 3. (:class:`Placeable`, :class:`Vector`) move to a given coordinate within object's coordinate system. instrument : Instrument to move relative to. If ``None``, move relative to the center of a gantry. strategy : {'arc', 'direct'} ``arc`` : move to the point using arc trajectory avoiding obstacles. ``direct`` : move to the point in a straight line. """ placeable, coordinates = containers.unpack_location(location) # because the top position is what is tracked, # this checks if coordinates doesn't equal top offset = subtract(coordinates, placeable.top()[1]) if isinstance(placeable, containers.WellSeries): placeable = placeable[0] target = add(pose_tracker.absolute(self.poses, placeable), offset.coordinates) if self._previous_instrument: if self._previous_instrument != instrument: self._previous_instrument.retract() # because we're switching pipettes, this ensures a large (safe) # Z arc height will be used for the new pipette self._prev_container = None self._previous_instrument = instrument if strategy == 'arc': arc_coords = self._create_arc(instrument, target, placeable) for coord in arc_coords: self.poses = instrument._move(self.poses, **coord) elif strategy == 'direct': position = {'x': target[0], 'y': target[1], 'z': target[2]} self.poses = instrument._move(self.poses, **position) else: raise RuntimeError('Unknown move strategy: {}'.format(strategy))
def test_calibrate_labware(robot, instruments, labware, monkeypatch): import tempfile temp = tempfile.mkdtemp() monkeypatch.setenv('USER_DEFN_ROOT', temp) plate = labware.load('96-flat', '1') pip = instruments.P300_Single(mount='right') old_x, old_y, old_z = pose_tracker.absolute(robot.poses, plate[0]) pip.move_to(plate[0]) robot.poses = pip._jog(robot.poses, 'x', 1) robot.poses = pip._jog(robot.poses, 'y', 2) robot.poses = pip._jog(robot.poses, 'z', -3) robot.calibrate_container_with_instrument(plate, pip, save=False) new_pose = pose_tracker.absolute(robot.poses, plate[0]) assert isclose(new_pose, (old_x + 1, old_y + 2, old_z - 3)).all()
def test_robot_move_to(virtual_smoothie_env): robot.reset() robot.home() p300 = instruments.P300_Single(mount='right') robot.move_to((robot._deck, (100, 0, 0)), p300) assert isclose( pose_tracker.absolute( robot.poses, p300), (100, 0, 0) ).all()
def max_placeable_height_on_deck(self, placeable): """ :param placeable: :return: Calibrated height of container in mm from deck as the reference point """ offset = placeable.top()[1] placeable_coordinate = add( pose_tracker.absolute(self.poses, placeable), offset.coordinates) placeable_tallest_point = pose_tracker.max_z(self.poses, placeable) return placeable_coordinate[2] + placeable_tallest_point
def test_pause_resume(model): """ This test has to use an ugly work-around with the `simulating` member of the driver. When issuing movement commands in test, `simulating` should be True, but when testing whether `pause` actually pauses and `resume` resumes, `simulating` must be False. """ from numpy import isclose from opentrons.trackers import pose_tracker from time import sleep pipette = model.instrument._instrument robot = model.robot robot.home() homed_coords = pose_tracker.absolute(robot.poses, pipette) robot._driver.simulating = False robot.pause() robot._driver.simulating = True def _move_head(): robot.poses = pipette._move(robot.poses, x=100, y=0, z=0) thread = Thread(target=_move_head) thread.start() sleep(0.5) # Check against home coordinates before calling resume to ensure that robot # doesn't move while paused coords = pose_tracker.absolute(robot.poses, pipette) assert isclose(coords, homed_coords).all() robot._driver.simulating = False robot.resume() robot._driver.simulating = True thread.join() coords = pose_tracker.absolute(robot.poses, pipette) expected_coords = (100, 0, 0) assert isclose(coords, expected_coords).all()
def test_trough_move_to(): from opentrons.instruments.pipette_config import Y_OFFSET_MULTI robot.reset() tip_rack = containers_load(robot, 'tiprack-200ul', '3') p300 = instruments.P300_Single(mount='left', tip_racks=[tip_rack]) trough = containers_load(robot, 'trough-12row', '1') p300.pick_up_tip() p300.move_to(trough) current_pos = pose_tracker.absolute(robot.poses, p300) assert isclose(current_pos, (14.34, 7.75 + 35 + Y_OFFSET_MULTI, 40)).all()
def test_trough_move_to(robot, instruments): # TODO: new labware system should center multichannel pipettes within wells # TODO: (correct single-channel position currently corresponds to back- # TODO: most tip of multi-channel), so calculate against that robot.reset() tip_rack = containers_load(robot, 'tiprack-200ul', '3') p300 = instruments.P300_Single(mount='left', tip_racks=[tip_rack]) trough = containers_load(robot, 'trough-12row', '1') p300.pick_up_tip() p300.move_to(trough) current_pos = pose_tracker.absolute(robot.poses, p300) assert isclose(current_pos, (0, 0, 38)).all()
def test_calibrate_labware_new( virtual_smoothie_env, split_labware_def): robot.reset() plate = labware.load('96-flat', '5') pip = instruments.P300_Single(mount='right') old_x, old_y, old_z = pose_tracker.absolute(robot.poses, plate[0]) pip.move_to(plate[0]) robot.poses = pip._jog(robot.poses, 'x', 1) robot.poses = pip._jog(robot.poses, 'y', 2) robot.poses = pip._jog(robot.poses, 'z', -3) robot.calibrate_container_with_instrument(plate, pip, save=True) new_pose = pose_tracker.absolute(robot.poses, plate[0]) assert isclose(new_pose, (old_x + 1, old_y + 2, old_z - 3)).all() # Ensure that slot offset is removed and new offset file is picked up plate2 = labware.load('96-flat', '1') new_pose2 = pose_tracker.absolute(robot.poses, plate2[0]) assert isclose(new_pose2, (15.34, 76.24, 7.5)).all()
def test_pipette_models_reach_max_volume(robot, instruments): for model in pipette_config.config_models: config = pipette_config.load(model) robot.reset() pipette = instruments._create_pipette_from_config(config=config, mount='right', name=model) pipette.tip_attached = True pipette.aspirate(pipette.max_volume) pos = pose_tracker.absolute(robot.poses, pipette.instrument_actuator) assert pos[0] < pipette.plunger_positions['top']
def test_calibrate_multiple(robot, instruments, labware, offsets_tempdir): # Note: labware_name must be a v2 labware definition labware_name = 'agilent_1_reservoir_290ml' reservoir1 = labware.load(labware_name, '1') reservoir2 = labware.load(labware_name, '2') pip = instruments.P300_Single(mount='right') old_x1, old_y1, old_z1 = pose_tracker.absolute(robot.poses, reservoir1[0]) old_x2, old_y2, old_z2 = pose_tracker.absolute(robot.poses, reservoir2[0]) pip.move_to(reservoir1[0]) delta_x1, delta_y1, delta_z1 = (1, 2, -3) robot.poses = pip._jog(robot.poses, 'x', delta_x1) robot.poses = pip._jog(robot.poses, 'y', delta_y1) robot.poses = pip._jog(robot.poses, 'z', delta_z1) robot.calibrate_container_with_instrument(reservoir1, pip, save=True) # Check pose tree, also check data on disk new_pose1 = pose_tracker.absolute(robot.poses, reservoir1[0]) new_pose2 = pose_tracker.absolute(robot.poses, reservoir2[0]) assert isclose(new_pose1, ( old_x1 + delta_x1, old_y1 + delta_y1, old_z1 + delta_z1)).all() assert isclose(new_pose2, ( old_x2 + delta_x1, old_y2 + delta_y1, old_z2 + delta_z1)).all() lw_hash = reservoir1.properties.get('labware_hash') new_offset1 = _look_up_offsets(lw_hash) expected1 = Point(delta_x1, delta_y1, delta_z1) assert isclose(new_offset1, expected1).all() pip.move_to(reservoir2[0]) robot.poses = pip._jog(robot.poses, 'x', -1 * delta_x1) robot.poses = pip._jog(robot.poses, 'y', -1 * delta_y1) robot.poses = pip._jog(robot.poses, 'z', -1 * delta_z1) robot.calibrate_container_with_instrument(reservoir2, pip, save=True) # Check pose tree, also check data on disk new_pose1 = pose_tracker.absolute(robot.poses, reservoir1[0]) new_pose2 = pose_tracker.absolute(robot.poses, reservoir2[0]) assert isclose(new_pose1, ( old_x1, old_y1, old_z1)).all() assert isclose(new_pose2, ( old_x2, old_y2, old_z2)).all() lw_hash = reservoir1.properties.get('labware_hash') new_offset2 = _look_up_offsets(lw_hash) expected2 = Point(0, 0, 0) assert isclose(new_offset2, expected2).all()
def _create_arc(self, inst, destination, placeable=None): """ Returns a list of coordinates to arrive to the destination coordinate """ this_container = None if isinstance(placeable, containers.Well): this_container = placeable.get_parent() elif isinstance(placeable, containers.WellSeries): this_container = placeable.get_parent() elif isinstance(placeable, containers.Container): this_container = placeable if this_container and self._prev_container == this_container: # movements that stay within the same container do not need to # avoid other containers on the deck, so the travel height of # arced movements can be relative to just that one container arc_top = self.max_placeable_height_on_deck(this_container) arc_top += TIP_CLEARANCE_LABWARE elif self._use_safest_height: # bring the pipettes up as high as possible while calibrating arc_top = inst._max_deck_height() else: # bring pipette up above the tallest container currently on deck arc_top = self.max_deck_height() + TIP_CLEARANCE_DECK self._prev_container = this_container # if instrument is currently taller than arc_top, don't move down _, _, pip_z = pose_tracker.absolute(self.poses, inst) arc_top = max(arc_top, destination[2], pip_z) arc_top = min(arc_top, inst._max_deck_height()) strategy = [{ 'z': arc_top }, { 'x': destination[0], 'y': destination[1] }, { 'z': destination[2] }] return strategy
def _move_pipette(robot, mount, model, point): pipette, _ = _fetch_or_create_pipette(robot, mount, model) pipette.move_to((robot.deck, point), strategy='arc') new_position = tuple( pose_tracker.absolute(pipette.robot.poses, pipette)) return "Move complete. New position: {}".format(new_position)
def probe_instrument(instrument, robot, tip_length=None) -> Point: robot.home() tp = robot.config.tip_probe if tip_length is None: tip_length = robot.config.tip_length[instrument.model] instrument._add_tip(tip_length) # probe_center is the point at the center of the switch pcb center = Point(*tp.center) hot_spots = robot_configs.calculate_tip_probe_hotspots(tip_length, tp) # The saved axis positions from limit switch response axis_pos = [] safe_height = _calculate_safeheight(robot, tp.z_clearance.crossover) log.info("Moving to safe z: {}".format(safe_height)) robot.poses = instrument._move(robot.poses, z=safe_height) for hs in hot_spots: x0 = tp.center[0] + hs.x_start_offs y0 = tp.center[1] + hs.y_start_offs z0 = hs.z_start_abs log.info("Moving to {}".format((x0, y0, z0))) robot.poses = instrument._move(robot.poses, x=x0, y=y0) robot.poses = instrument._move(robot.poses, z=z0) axis_index = 'xyz'.index(hs.axis) robot.poses = instrument._probe(robot.poses, hs.axis, hs.probe_distance) # Tip position is stored in accumulator and averaged for each axis # to be used for more accurate positioning for the next axis value = absolute(robot.poses, instrument)[axis_index] axis_pos.append(value) # after probing two points along the same axis # average them out, update center and clear accumulator # except Z, we're only probing that once if hs.axis == 'z': center = center._replace(**{hs.axis: axis_pos[0]}) axis_pos.clear() elif len(axis_pos) == 2: center = center._replace( **{hs.axis: (axis_pos[0] + axis_pos[1]) / 2.0}) axis_pos.clear() log.debug("Current axis positions for {}: {}".format( hs.axis, axis_pos)) # Bounce back to release end stop sgn = hs.probe_distance / abs(hs.probe_distance) bounce = value + (tp.bounce_distance * -sgn) robot.poses = instrument._move(robot.poses, **{hs.axis: bounce}) robot.poses = instrument._move(robot.poses, z=safe_height) log.debug("Updated center point tip probe {}".format(center)) instrument._remove_tip(tip_length) return center
async def test_transform_from_moves(async_client, monkeypatch): test_mount, test_model = ('left', 'p300_multi_v1') # test_mount, test_model = ('right', 'p300_single_v1') def dummy_read_model(mount): if mount == test_mount: return test_model else: return None monkeypatch.setattr(robot._driver, 'read_pipette_model', dummy_read_model) robot.reset() robot.home() # This is difficult to test without the `async_client` because it has to # take an `aiohttp.web.Request` object as a parameter instead of a dict resp = await async_client.post('/calibration/deck/start') start_res = await resp.json() token = start_res.get('token') assert start_res.get('pipette', {}).get('mount') == test_mount assert start_res.get('pipette', {}).get('model') == test_model res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'attach tip', 'tipLength': 51.7 }) assert res.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'move', 'point': 'safeZ' }) assert res.status == 200 jogres = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'jog', 'axis': 'z', 'direction': -1, 'step': 4.5 }) assert jogres.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'save z' }) assert res.status == 200 pipette = endpoints.session.pipettes[test_mount] res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'move', 'point': '1' }) assert res.status == 200 pt1 = endpoints.safe_points().get('1') if 'multi' in test_model: expected1 = (pt1[0], pt1[1] + 2 * Y_OFFSET_MULTI, pt1[2]) else: expected1 = pt1 assert np.isclose(absolute(robot.poses, pipette), expected1).all() # Jog to calculated position for transform x_delta1 = 13.16824337 - dc.endpoints.safe_points()['1'][0] y_delta1 = 8.30855312 - dc.endpoints.safe_points()['1'][1] jogres = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'jog', 'axis': 'x', 'direction': 1, 'step': x_delta1 }) assert jogres.status == 200 jogres = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'jog', 'axis': 'y', 'direction': 1, 'step': y_delta1 }) assert jogres.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'save xy', 'point': '1' }) assert res.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'move', 'point': '2' }) assert res.status == 200 pt2 = endpoints.safe_points().get('2') if 'multi' in test_model: expected2 = (pt2[0], pt2[1] + 2 * Y_OFFSET_MULTI, pt2[2]) else: expected2 = pt2 assert np.isclose(absolute(robot.poses, pipette), expected2).all() # Jog to calculated position for transform x_delta2 = 380.50507635 - dc.endpoints.safe_points()['2'][0] y_delta2 = -23.82925545 - dc.endpoints.safe_points()['2'][1] jogres = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'jog', 'axis': 'x', 'direction': 1, 'step': x_delta2 }) assert jogres.status == 200 jogres = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'jog', 'axis': 'y', 'direction': 1, 'step': y_delta2 }) assert jogres.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'save xy', 'point': '2' }) assert res.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'move', 'point': '3' }) assert res.status == 200 pt3 = endpoints.safe_points().get('3') if 'multi' in test_model: expected3 = (pt3[0], pt3[1] + 2 * Y_OFFSET_MULTI, pt3[2]) else: expected3 = pt3 assert np.isclose(absolute(robot.poses, pipette), expected3).all() # Jog to calculated position for transform x_delta3 = 34.87002331 - dc.endpoints.safe_points()['3'][0] y_delta3 = 256.36103295 - dc.endpoints.safe_points()['3'][1] jogres = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'jog', 'axis': 'x', 'direction': 1, 'step': x_delta3 }) assert jogres.status == 200 jogres = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'jog', 'axis': 'y', 'direction': 1, 'step': y_delta3 }) assert jogres.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'save xy', 'point': '3' }) assert res.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'save transform' }) assert res.status == 200 res = await async_client.post('/calibration/deck', json={ 'token': token, 'command': 'release' }) assert res.status == 200 # This transform represents a 5 degree rotation, with a shift in x, y, & z. # Values for the points and expected transform come from a hand-crafted # transformation matrix and the points that would generate that matrix. cos_5deg_p = 0.99619469809 sin_5deg_p = 0.08715574274 sin_5deg_n = -sin_5deg_p const_zero = 0.0 const_one_ = 1.0 delta_x___ = 0.3 delta_y___ = 0.4 delta_z___ = 0.5 expected_transform = [[cos_5deg_p, sin_5deg_p, const_zero, delta_x___], [sin_5deg_n, cos_5deg_p, const_zero, delta_y___], [const_zero, const_zero, const_one_, delta_z___], [const_zero, const_zero, const_zero, const_one_]] actual_transform = robot.config.gantry_calibration from pprint import pprint print() print("Expected transform [type: {}]:".format(type(expected_transform))) pprint(expected_transform) print() print("Actual transform [type: {}]:".format(type(actual_transform))) pprint(actual_transform) assert np.allclose(actual_transform, expected_transform)
def tip_coords_absolute(self): xyz = pose_tracker.absolute(self.robot.poses, self) return Vector(xyz)
def probe_instrument(instrument, robot, tip_length=None) -> Point: robot.home() if tip_length is None: tip_length = robot.config.tip_length[instrument.name] instrument._add_tip(tip_length) # probe_center is the point at the center of the switch pcb center = Point(*robot.config.probe_center) hot_spots = _calculate_hotspots(robot, tip_length, SWITCH_CLEARANCE, X_SWITCH_OFFSET_MM, Y_SWITCH_OFFSET_MM, Z_SWITCH_OFFSET_MM, Z_DECK_CLEARANCE, Z_PROBE_CLEARANCE, Z_PROBE_START_CLEARANCE) # The saved axis positions from limit switch response axis_pos = [] safe_height = _calculate_safeheight(robot, Z_CROSSOVER_CLEARANCE) log.info("Moving to safe z: {}".format(safe_height)) robot.poses = instrument._move(robot.poses, z=safe_height) for axis, x, y, z, distance in hot_spots: if axis == 'z': x = x + center.x y = y + center.y z = z + center.z else: x = x + center.x y = y + center.y log.info("Moving to {}".format((x, y, z))) robot.poses = instrument._move(robot.poses, x=x, y=y) robot.poses = instrument._move(robot.poses, z=z) axis_index = 'xyz'.index(axis) robot.poses = instrument._probe(robot.poses, axis, distance) # Tip position is stored in accumulator and averaged for each axis # to be used for more accurate positioning for the next axis value = absolute(robot.poses, instrument)[axis_index] axis_pos.append(value) # after probing two points along the same axis # average them out, update center and clear accumulator # except Z, we're only probing that once if axis == 'z': center = center._replace(**{axis: axis_pos[0]}) axis_pos.clear() elif len(axis_pos) == 2: center = center._replace( **{axis: (axis_pos[0] + axis_pos[1]) / 2.0}) axis_pos.clear() log.debug("Current axis positions for {}: {}".format(axis, axis_pos)) # Bounce back to release end stop bounce = value + (BOUNCE_DISTANCE_MM * (-distance / abs(distance))) robot.poses = instrument._move(robot.poses, **{axis: bounce}) robot.poses = instrument._move(robot.poses, z=safe_height) log.debug("Updated center point tip probe {}".format(center)) instrument._remove_tip(tip_length) return center
def position(instrument): return pose_tracker.absolute(robot.poses, instrument._instrument)