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()
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
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()
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()
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()
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)
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]
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
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
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)))
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()
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
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
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()
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()
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()
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()
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()