예제 #1
0
    def calibrate_container_with_instrument(self, container: Container,
                                            instrument, save: bool):
        '''Calibrates a container using the top or bottom of the first well'''
        well = container[0]

        # Get the relative position of well with respect to instrument
        delta = pose_tracker.change_base(self.poses, src=instrument, dst=well)

        if fflags.calibrate_to_bottom():

            delta_x = delta[0]
            delta_y = delta[1]
            if 'tiprack' in container.get_type():
                delta_z = delta[2]
            else:
                delta_z = delta[2] + well.z_size()
        else:
            delta_x = delta[0]
            delta_y = delta[1]
            delta_z = delta[2]

        self.poses = self._calibrate_container_with_delta(
            self.poses, container, delta_x, delta_y, delta_z, save)

        self.max_deck_height.cache_clear()
예제 #2
0
def calibrate_container_with_delta(pose_tree,
                                   container,
                                   delta_x,
                                   delta_y,
                                   delta_z,
                                   save,
                                   new_container_name=None):

    delta = Point(delta_x, delta_y, delta_z)

    new_coordinates = change_base(
        pose_tree, src=container, dst=container.parent) + delta

    pose_tree = update(pose_tree, container, new_coordinates)

    if ff.split_labware_definitions():
        for well in container.wells():
            well._coordinates = well._coordinates + delta
    else:
        container._coordinates = container._coordinates + delta

    if save and new_container_name:
        database.save_new_container(container, new_container_name)
    elif save:
        database.overwrite_container(container)
    return pose_tree
예제 #3
0
def test_functional(smoothie):
    scale = array([
        [2, 0, 0, -1],
        [0, 2, 0, -2],
        [0, 0, 2, -3],
        [0, 0, 0, 1],
    ])

    back = array([
        [0.5, 0, 0, 0],
        [0, 0.5, 0, 0],
        [0, 0, 0.5, 0],
        [0, 0, 0, 1],
    ])

    left = Mover(driver=smoothie,
                 axis_mapping={'z': 'Z'},
                 src=ROOT,
                 dst=id(scale))
    right = Mover(driver=smoothie,
                  axis_mapping={'z': 'A'},
                  src=ROOT,
                  dst=id(scale))
    gantry = Mover(
        driver=smoothie,
        axis_mapping={
            'x': 'X',
            'y': 'Y'
        },
        src=ROOT,
        dst=id(scale),
    )

    state = init() \
        .add(id(scale), transform=scale) \
        .add(gantry, id(scale)) \
        .add(left, gantry) \
        .add(right, gantry) \
        .add('left', left, transform=back) \
        .add('right', right, transform=back)

    state = gantry.move(state, x=1, y=1)
    state = left.move(state, z=1)

    assert isclose(change_base(state, src='left'), (1, 1, 1)).all()
    assert isclose(change_base(state, src='right'), (1, 1, 1.5)).all()
    state = gantry.move(state, x=2, y=2)
    state = right.move(state, z=3)
    assert isclose(change_base(state, src='left'), (2, 2, 1)).all()
    assert isclose(change_base(state, src='right'), (2, 2, 3)).all()
    state = gantry.jog(state, axis='x', distance=1)
    assert isclose(change_base(state, src='left'), (3, 2, 1)).all()
    assert isclose(change_base(state, src='right'), (3, 2, 3)).all()
    state = right.jog(state, axis='z', distance=1)
    assert isclose(change_base(state, src='left'), (3, 2, 1)).all()
    assert isclose(change_base(state, src='right'), (3, 2, 4)).all()
예제 #4
0
def test_relative(state):
    from math import pi
    assert (change_base(state, src='1-1', dst='2-1') == (24., 28., 32.)).all()
    state = \
        init() \
        .add('1', transform=rotate(pi / 2.0)) \
        .add('1-1', parent='1', point=Point(1, 0, 0)) \
        .add('2', point=Point(1, 0, 0))

    assert isclose(change_base(state, src='1-1', dst='2'),
                   (-1.0, -1.0, 0)).all()
    assert isclose(change_base(state, src='2', dst='1-1'), (0.0, 0.0, 0)).all()

    state = init() \
        .add('1', transform=scale(2, 1, 1)) \
        .add('1-1', parent='1', point=Point(1, 1, 1)) \

    assert isclose(change_base(state, src=ROOT, dst='1-1'),
                   (-2.0, -1.0, -1.0)).all()
    assert isclose(change_base(state, src='1-1', dst=ROOT),
                   (0.5, 1.0, 1.0)).all()
예제 #5
0
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()
예제 #6
0
    def jog(self, pose_tree, axis, distance):
        assert axis in 'xyz', "axis value should be x, y or z"
        assert axis in self._axis_mapping, "mapping is not set for " + axis

        x, y, z = change_base(pose_tree, src=self)

        target = {
            'x': x if 'x' in self._axis_mapping else None,
            'y': y if 'y' in self._axis_mapping else None,
            'z': z if 'z' in self._axis_mapping else None,
        }

        target[axis] += distance

        return self.move(pose_tree, home_flagged_axes=False, **target)
예제 #7
0
    def axis_maximum(self, pose_tree, axis):
        assert axis in 'xyz', "axis value should be x, y or z"
        assert axis in self._axis_mapping, "mapping is not set for " + axis

        d_axis_max = self._driver.homed_position[self._axis_mapping[axis]]
        d_point = {'x': 0, 'y': 0, 'z': 0}
        d_point[axis] = d_axis_max
        x, y, z = change_base(
            pose_tree,
            src=self._dst,
            dst=self._src,
            point=Point(**d_point))
        point = {'x': x, 'y': y, 'z': z}
        self._axis_maximum[axis] = point[axis]
        return self._axis_maximum[axis]
예제 #8
0
def test_update_instrument_config(fixture):
    from opentrons.trackers.pose_tracker import change_base
    from numpy import array
    import json

    robot = fixture.robot
    inst = fixture.instrument

    inst_offset = robot.config.instrument_offset[inst.mount][inst.type]

    cfg = update_instrument_config(instrument=inst,
                                   measured_center=(0.0, 0.0, 105.0))

    new_tip_length = cfg.tip_length[inst.name]
    new_instrument_offset = cfg.instrument_offset[inst.mount][inst.type]

    assert new_tip_length == 55.0
    assert new_instrument_offset == tuple(array(inst_offset) + (5.0, 5.0, 0.0))
    assert tuple(change_base(
        robot.poses,
        src=inst,
        dst=inst.instrument_mover)) == (5.0, 5.0, 0), \
        "Expected instrument position to update relative to mover in pose tree"

    filename = get_config_index().get('robotSettingsFile')
    expected = dict(robot_configs._build_config({}, {})._asdict())
    expected.pop('gantry_calibration')
    expected['instrument_offset']['right']['single'] = [5.0, 5.0, 0.0]
    expected['tip_length']['Pipette'] = 55.0

    with open(filename, 'r') as file:
        actual = json.load(file)

    # from pprint import pprint
    # print('=------> <------=')
    # print("Expected:")
    # pprint(expected)
    # print()
    # print("Actual:")
    # pprint(actual)
    # print()
    assert actual == expected
예제 #9
0
    def _calibrate_container_with_delta(pose_tree,
                                        container,
                                        delta_x,
                                        delta_y,
                                        delta_z,
                                        save,
                                        new_container_name=None):

        delta = pose_tracker.Point(delta_x, delta_y, delta_z)

        # Note: pose tree is updated here, in order to update in-memory state.
        # Separately from that, on-disk state is updated. This is a point of
        # possible dis-unity. Would probably work better to un-load the labware
        # after calibration, and then reload it (would have to figure out where
        # in the call-stack this could be done without raising exceptions due
        # to trying to access old references).

        # Have to update all of the things in the pose tree that have the same
        # load name, otherwise you end up getting the calibration values
        # added together in the on-disk representation
        target_name = container.get_name()
        matching_entries = [
            x for x in list(pose_tree.keys())
            if type(x) == Container and x.get_name() == target_name
        ]
        for entry in matching_entries:
            old_coordinates = pose_tracker.change_base(pose_tree,
                                                       src=entry,
                                                       dst=entry.parent)
            new_coordinates = old_coordinates + delta

            pose_tree = pose_tracker.update(pose_tree, entry, new_coordinates)
            entry._coordinates = entry._coordinates + delta

        if save and container.properties.get('labware_hash'):
            save_new_offsets(container.properties['labware_hash'], delta,
                             container.properties['definition'])
        elif save and new_container_name:
            database.save_new_container(container, new_container_name)
        elif save:
            database.overwrite_container(container)
        return pose_tree
예제 #10
0
    def move(self, pose_tree, x=None, y=None, z=None, home_flagged_axes=True):
        """
        Dispatch move command to the driver changing base of
        x, y and z from source coordinate system to destination.

        Value must be set for each axis that is mapped.

        home_flagged_axes: (default=True)
            This kwarg is passed to the driver. This ensures that any axes
            within this Mover's axis_mapping is homed before moving, if it has
            not yet done so. See driver docstring for details
        """
        def defaults(_x, _y, _z):
            _x = _x if x is not None else 0
            _y = _y if y is not None else 0
            _z = _z if z is not None else 0
            return _x, _y, _z

        dst_x, dst_y, dst_z = change_base(
            pose_tree,
            src=self._src,
            dst=self._dst,
            point=Point(*defaults(x, y, z)))
        driver_target = {}

        if 'x' in self._axis_mapping:
            assert x is not None, "Value must be set for each axis mapped"
            driver_target[self._axis_mapping['x']] = dst_x

        if 'y' in self._axis_mapping:
            assert y is not None, "Value must be set for each axis mapped"
            driver_target[self._axis_mapping['y']] = dst_y

        if 'z' in self._axis_mapping:
            assert z is not None, "Value must be set for each axis mapped"
            driver_target[self._axis_mapping['z']] = dst_z
        self._driver.move(driver_target, home_flagged_axes=home_flagged_axes)

        # Update pose with the new value. Since stepper motors are open loop
        # there is no need to to query diver for position
        return update(pose_tree, self, Point(*defaults(dst_x, dst_y, dst_z)))
예제 #11
0
    def calibrate_container_with_instrument(self, container: Container,
                                            instrument, save: bool):
        '''Calibrates a container using the bottom of the first well'''
        well = container[0]

        # Get the relative position of well with respect to instrument
        delta = pose_tracker.change_base(self.poses, src=instrument, dst=well)

        if fflags.calibrate_to_bottom():

            delta_x = delta[0]
            delta_y = delta[1]
            if 'tiprack' in container.get_type():
                delta_z = delta[2]
            else:
                delta_z = delta[2] + well.z_size()
        else:
            delta_x = delta[0]
            delta_y = delta[1]
            delta_z = delta[2]

        if 'trough' in container.get_type():
            # Rather than calibrating troughs to the center of the well, we
            # calibrate such that a multi-channel would be centered in the
            # well. We don't differentiate between single- and multi-channel
            # pipettes here, and we track the tip of a multi-channel pipette
            # that would go into well A1 of an 8xN plate rather than the axial
            # center, but the axial center of a well is what we track for
            # calibration, so we add Y_OFFSET_MULTI in the calibration `move`
            # command, and then back that value off of the pipette position
            # here (Y_OFFSET_MULTI is the y-distance from the axial center of
            # the pipette to the A1 tip).
            delta_y = delta_y - Y_OFFSET_MULTI

        self.poses = calib.calibrate_container_with_delta(
            self.poses, container, delta_x, delta_y, delta_z, save)

        self.max_deck_height.cache_clear()
예제 #12
0
    def _calibrate_container_with_delta(pose_tree,
                                        container,
                                        delta_x,
                                        delta_y,
                                        delta_z,
                                        save,
                                        new_container_name=None):

        delta = pose_tracker.Point(delta_x, delta_y, delta_z)

        new_coordinates = pose_tracker.change_base(
            pose_tree, src=container, dst=container.parent) + delta

        pose_tree = pose_tracker.update(pose_tree, container, new_coordinates)
        container._coordinates = container._coordinates + delta

        if save and container.properties.get('labware_hash'):
            save_new_offsets(container.properties['labware_hash'], delta)
        elif save and new_container_name:
            database.save_new_container(container, new_container_name)
        elif save:
            database.overwrite_container(container)
        return pose_tree
예제 #13
0
def update_instrument_config(instrument,
                             measured_center) -> Tuple[Point, float]:
    """
    Update config and pose tree with instrument's x and y offsets
    and tip length based on delta between probe center and measured_center,
    persist updated config and return it
    """
    from copy import deepcopy
    from opentrons.trackers.pose_tracker import update

    robot = instrument.robot
    config = robot.config
    instrument_offset = deepcopy(config.instrument_offset)

    dx, dy, dz = array(measured_center) - config.tip_probe.center

    log.debug("This is measured probe center dx {}".format(Point(dx, dy, dz)))
    # any Z offset will adjust the tip length, so instruments have Z=0 offset
    old_x, old_y, _ = instrument_offset[instrument.mount][instrument.type]
    instrument_offset[instrument.mount][instrument.type] = \
        (old_x - dx, old_y - dy, 0.0)
    tip_length = deepcopy(config.tip_length)
    tip_length[instrument.model] = tip_length[instrument.model] + dz

    config = config \
        ._replace(instrument_offset=instrument_offset) \
        ._replace(tip_length=tip_length)
    robot.config = config
    log.debug("Updating config for {} instrument".format(instrument.mount))
    robot_configs.save_robot_settings(config)

    new_coordinates = change_base(
        robot.poses, src=instrument, dst=instrument.instrument_mover) - Point(
            dx, dy, 0.0)
    robot.poses = update(robot.poses, instrument, new_coordinates)

    return robot.config
예제 #14
0
def test_integration(robot):
    left = Pipette(robot, mount='left', ul_per_mm=1000, max_volume=1000)
    robot.home()
    pose = left._move(robot.poses, 1, 1, 1)
    assert isclose(change_base(pose, src=left), (1, 1, 1)).all()
예제 #15
0
def test_integration(robot):
    left = Pipette(robot, mount='left')
    robot.home()
    pose = left._move(robot.poses, 1, 1, 1)
    assert isclose(change_base(pose, src=left), (1, 1, 1)).all()
예제 #16
0
def test_update(state):
    state = update(state, '1-1', Point(0, 0, 0))
    assert (change_base(state, src='1-1-1') == (1, 2, 3)).all()
예제 #17
0
def test_change_base():
    state = init() \
        .add('1', parent=ROOT, transform=scale(2, 2, 2)) \
        .add('1-1', parent='1', point=Point(1, 0, 0))

    assert isclose(change_base(state, src='1-1'), (0.5, 0, 0)).all()
예제 #18
0
def test_absolute(state):
    assert (change_base(state, src='1') == (1, 2, 3)).all()
    assert (change_base(state, src='1-1') == (12, 14, 16)).all()
    assert (change_base(state, src='2') == (-1, -2, -3)).all()
    assert (change_base(state, src='2-1') == (-12, -14, -16)).all()