def jog(self, instrument, distance, axis): inst = instrument._instrument log.info('Jogging {} by {} in {}'.format(instrument.name, distance, axis)) self._set_state('moving') if instrument._context: try: self._hardware.move_rel(Mount[inst.mount.upper()], Point(**{axis: distance}), check_bounds=MotionChecks.HIGH) except OutOfBoundsMove: log.exception('Out of bounds jog') else: calibration_functions.jog_instrument(instrument=inst, distance=distance, axis=axis, robot=inst.robot) self._set_state('ready')
def remove_supernatant(vol): ctx.comment('\n\n\n~~~~~~~~REMOVING SUPERNATANT~~~~~~~~\n') pip.pick_up_tip() pip.flow_rate.aspirate = 45 for i, col in enumerate(working_cols): side = -1 if i % 2 == 0 else 1 aspirate_loc = col.bottom(z=asp_height).move( Point(x=(col.length / 2 - length_from_side) * side)) pip.transfer(vol, aspirate_loc, waste.bottom(z=25), new_tip='never' # blow_out=True, # blowout_location='destination well' ) # pip.blow_out() pip.flow_rate.aspirate = 92 pip.drop_tip()
def test_module_load_labware(module_name): labware_name = 'corning_96_wellplate_360ul_flat' labware_def = labware.get_labware_definition(labware_name) model = module_geometry.resolve_module_model(module_name) mod = module_geometry.load_module(model, Location(Point(0, 0, 0), 'test')) old_z = mod.highest_z lw = labware.load_from_definition(labware_def, mod.location) mod.add_labware(lw) assert mod.labware == lw assert mod.highest_z ==\ (mod.location.point.z + labware_def['dimensions']['zDimension'] + mod._over_labware) with pytest.raises(AssertionError): mod.add_labware(lw) mod.reset_labware() assert mod.labware is None assert mod.highest_z == old_z
def test_previous_tip(): labware_name = 'opentrons_96_tiprack_300ul' labware_def = labware.get_labware_definition(labware_name) tiprack = labware.Labware(labware_def, Location(Point(0, 0, 0), 'Test Slot')) # If all wells are used, we can't get a previous tip assert tiprack.previous_tip() is None # If one well is empty, wherever it is, we can get a slot tiprack.wells()[5].has_tip = False assert tiprack.previous_tip() is tiprack.wells()[5] # But not if we ask for more slots than are available assert tiprack.previous_tip(2) is None tiprack.wells()[7].has_tip = False # And those available wells have to be contiguous assert tiprack.previous_tip(2) is None # But if they are, we're good tiprack.wells()[6].has_tip = False assert tiprack.previous_tip(3) is tiprack.wells()[5]
def aspirate_wit_scrolling(pip, volume, src, start_height=0, stop_height=0, x_offset_src=0): start_point = src._depth if start_height == 0 else start_height stop_point = 0.0 if stop_height == 0 else stop_height max_asp = volume / pip.min_volume inc_step = (start_point - stop_point) / max_asp for h in reversed(np.arange(stop_point, start_point, inc_step)): s = src.bottom(h).move(Point(x=x_offset_src)) pip.aspirate(volume=pip.min_volume, location=s)
def distribute_custom(pipette, volume, src, dest, waste_pool, pickup_height, extra_dispensal, dest_x_offset, disp_height=0): # Custom distribute function that allows for blow_out in different location and adjustement of touch_tip pipette.aspirate((len(dest) * volume) + extra_dispensal, src.bottom(pickup_height)) pipette.touch_tip(speed=20, v_offset=-5) pipette.move_to(src.top(z=5)) pipette.aspirate(5) # air gap for d in dest: pipette.dispense(5, d.top()) drop = d.top(z = disp_height).move(Point(x = dest_x_offset)) pipette.dispense(volume, drop) pipette.move_to(d.top(z=5)) pipette.aspirate(5) # air gap try: pipette.blow_out(waste_pool.wells()[0].bottom(pickup_height + 3)) except: pipette.blow_out(waste_pool.bottom(pickup_height + 3)) return (len(dest) * volume)
def step1(): for i in range(len(pcr_elution_buffer_rack.columns())): if not m20.hw_pipette['has_tip']: pick_up(m20, tips20) src_pcr = pcr_elution_buffer_rack.columns()[i][0] dest_pcr = [elution_dest_rack.columns()[i][0]] distribute_custom(pip=m20, reagent=pcr_reagent, tube_type=pcr_tube, volume=10, src=src_pcr, dest=dest_pcr, max_volume=20, extra_dispensal=0, touch_tip_aspirate=False, touch_tip_dispense=False) custom_mix(pip=m20, reagent=pcr_reagent, repetitions=2, volume=10, location=elution_dest_rack.columns()[i][0], mix_height=2.5, source_height=2.5) m20.touch_tip(radius=1.0, v_offset=-5, speed=pcr_reagent.touch_tip_dispense_speed) pcr_tube.actual_volume = 5 x = elution_dest_rack.columns()[i][0].top().point.x y = 345.65 z = 120 loc = Location(Point(x, y, z), 'Warning') m20.move_to(location=loc) drop(m20)
def remove_supernatant(vol, park=False): """ `remove_supernatant` will transfer supernatant from the deepwell extraction plate to the liquid waste reservoir. :param vol (float): The amount of volume to aspirate from all deepwell sample wells and dispense in the liquid waste. :param park (boolean): Whether to pick up sample-corresponding tips in the 'parking rack' or to pick up new tips. """ def _waste_track(vol): nonlocal waste_vol if waste_vol + vol >= waste_threshold: ctx.pause('Please empty liquid waste (slot 11) before \ resuming.') ctx.home() # home before continuing with protocol waste_vol = 0 waste_vol += vol m300.flow_rate.aspirate = 30 num_trans = math.ceil(vol / 200) vol_per_trans = vol / num_trans for i, (m, spot) in enumerate(zip(mag_samples_m, parking_spots)): if park: _pick_up(m300, spot) else: _pick_up(m300) side = -1 if i % 2 == 0 else 1 loc = m.bottom(0.5).move(Point(x=side * 2)) for _ in range(num_trans): _waste_track(vol_per_trans * 8) if m300.current_volume > 0: # void air gap if necessary m300.dispense(m300.current_volume, m.top()) m300.move_to(m.center()) m300.transfer(vol_per_trans, loc, waste, new_tip='never', air_gap=20) m300.blow_out(waste) m300.air_gap(20) _drop(m300) m300.flow_rate.aspirate = 150
def test_back_compat(): labware_name = 'corning_96_wellplate_360ul_flat' labware_def = labware.get_labware_definition(labware_name) lw = labware.Labware(labware_def, Location(Point(0, 0, 0), 'Test Slot')) # Note that this test uses the display name of wells to test for equality, # because dimensional parameters could be subject to modification through # calibration, whereas here we are testing for "identity" in a way that is # related to the combination of well name, labware name, and slot name well_a1_name = repr(lw.wells_by_name()['A1']) well_b2_name = repr(lw.wells_by_name()['B2']) well_c3_name = repr(lw.wells_by_name()['C3']) w2 = lw.well(0) assert repr(w2) == well_a1_name w3 = lw.well('A1') assert repr(w3) == well_a1_name w4 = lw.wells('B2') assert repr(w4[0]) == well_b2_name w5 = lw.wells(9, 21, 25, 27) assert len(w5) == 4 assert repr(w5[0]) == well_b2_name w6 = lw.wells('A1', 'B2', 'C3') assert all([ repr(well[0]) == well[1] for well in zip(w6, [well_a1_name, well_b2_name, well_c3_name]) ]) w7 = lw.rows('A') assert len(w7) == 1 assert repr(w7[0][0]) == well_a1_name w8 = lw.rows('A', 'C') assert len(w8) == 2 assert repr(w8[0][0]) == well_a1_name assert repr(w8[1][2]) == well_c3_name w11 = lw.columns('2', '3', '6') assert len(w11) == 3 assert repr(w11[1][2]) == well_c3_name
def stop_reaction(vol, source, mix_reps=6, park=True, resuspend=True): if resuspend and magdeck.status == 'engaged': magdeck.disengage() num_trans = math.ceil(vol / 200) vol_per_trans = vol / num_trans for i, (m, spot) in enumerate(zip(mag_samples_m, parking_spots)): _pick_up(m300) side = 1 if i % 2 == 0 else -1 loc = m.bottom(0.5).move(Point(x=side * 2)) src = source[i // (12 // len(source))] for n in range(num_trans): if m300.current_volume > 0: m300.dispense(m300.current_volume, src.top()) m300.transfer(vol_per_trans, src, m.top(), air_gap=20, new_tip='never') if n < num_trans - 1: # only air_gap if going back to source m300.air_gap(20) if resuspend: #m300.mix(mix_reps, 50, loc) resuspend_pellet(m, m300, 180) m300.blow_out(m.top()) m300.air_gap(20) if park: m300.drop_tip(spot) else: _drop(m300) ctx.pause( 'Incubating for 10 minutes with occasional mixing for stop reaction' ) if magdeck.status == 'disengaged': magdeck.engage(height=MAG_HEIGHT) ctx.delay(minutes=settling_time, msg='Incubating on MagDeck for \ ' + str(settling_time) + ' minutes.') remove_supernatant(vol, park=park)
def _load_from_v2(definition: Dict[str, Any], parent: Location, api_level: APIVersion, configuration: GenericConfiguration) -> ModuleGeometry: """ Load a module geometry from a v2 definition. The definition should be schema checked before being passed to this function; all definitions passed here are assumed to be valid. """ pre_transform = np.array((definition['labwareOffset']['x'], definition['labwareOffset']['y'], 1)) par = parent.labware # this needs to change to look up the current deck type if/when we add # that notion xforms_ser = definition['slotTransforms']\ .get('ot2_standard', {})\ .get(par, {'labwareOffset': [[1, 0, 0], [0, 1, 0], [0, 0, 1]]}) xform_ser = xforms_ser['labwareOffset'] # apply the slot transform if any xform = np.array(xform_ser) xformed = np.dot(xform, pre_transform) opts = { 'parent': parent, 'api_level': api_level, 'offset': Point(xformed[0], xformed[1], definition['labwareOffset']['z']), 'overall_height': definition['dimensions']['bareOverallHeight'], 'height_over_labware': definition['dimensions']['overLabwareHeight'], 'model': module_model_from_string(definition['model']), 'module_type': ModuleType.from_str(definition['moduleType']), 'display_name': definition['displayName'] } if definition['moduleType'] in { ModuleType.MAGNETIC.value, ModuleType.TEMPERATURE.value }: return ModuleGeometry(**opts) elif definition['moduleType'] == ModuleType.THERMOCYCLER.value: return ThermocyclerGeometry( lid_height=definition['dimensions']['lidHeight'], configuration=configuration, **opts) else: raise RuntimeError(f'Unknown module type {definition["moduleType"]}')
def remove_supernatant(vol, park=False): def waste_track(vol): nonlocal waste_vol if waste_vol + vol >= waste_threshold: # Setup for flashing lights notification to empty liquid waste # if not ctx._hw_manager.hardware.is_simulator: # cancellationToken.set_true() # thread = create_thread(ctx, cancellationToken) m300.home() ctx.pause( 'Please empty liquid waste (slot 11) before resuming.') ctx.home() # home before continuing with protocol # cancellationToken.set_false() # stop light flashing after home # thread.join() waste_vol = 0 waste_vol += vol m300.flow_rate.aspirate = 30 num_trans = math.ceil(vol / 200) vol_per_trans = vol / num_trans for m, spot in zip(mag_samples_m, parking_spots): if park: pick_up(m300, spot) else: pick_up(m300) side_ind = int(m.display_name.split(' ')[0][1:]) side = 1 if side_ind % 2 == 0 else -1 loc = m.bottom(0.5).move(Point(x=side * 3)) for _ in range(num_trans): waste_track(vol_per_trans) if m300.current_volume > 0: m300.dispense(m300.current_volume, m.top()) # void air gap if necessary m300.move_to(m.center()) m300.transfer(vol_per_trans, loc, waste, new_tip='never', air_gap=20) m300.blow_out(waste) m300.air_gap(20) drop(m300) m300.flow_rate.aspirate = 150
def wash(vol, source, mix_reps=15, park=True, resuspend=True): if resuspend and magdeck.status == 'engaged': magdeck.disengage() num_trans = math.ceil(vol / 200) vol_per_trans = vol / num_trans for i, (m, spot) in enumerate(zip(mag_samples_m, parking_spots)): _pick_up(m300) side = 1 if i % 2 == 0 else -1 loc = m.bottom(0.5).move(Point(x=side * 2)) src = source[i // (12 // len(source))] for n in range(num_trans): if m300.current_volume > 0: m300.dispense(m300.current_volume, src.top()) m300.transfer(vol_per_trans, src, m.top(), air_gap=20, new_tip='never') if n < num_trans - 1: # only air_gap if going back to source m300.air_gap(20) if resuspend: m300.mix(mix_reps, 150, loc) m300.blow_out(m.top()) m300.air_gap(20) if park: m300.drop_tip(spot) else: _drop(m300) ctx.pause('Mix on bioshake to resuspend') device.exec_cmd('setShakeTargetSpeed800') device.exec_cmd('shakeOn') ctx.delay(minutes=3) device.exec_cmd('shakeOff') ctx.pause('Mix off deck to resuspend pellet') if magdeck.status == 'disengaged': magdeck.engage(height=MAG_HEIGHT) ctx.delay(minutes=settling_time, msg='Incubating on MagDeck for \ ' + str(settling_time) + ' minutes.') remove_supernatant(vol, park=park)
def _drop(pip): nonlocal switch nonlocal drop_count side = 30 if switch else -18 drop_loc = ctx.loaded_labwares[12].wells()[0].top().move(Point(x=side)) # pip.drop_tip(drop_loc) switch = not switch if pip.type == 'multi': drop_count += 8 else: drop_count += 1 if drop_count >= drop_threshold: pip.home() ctx.set_rail_lights(True) ctx.pause('Please empty tips from waste before resuming.') ctx.set_rail_lights(False) ctx.home() # home before continuing with protocol drop_count = 0 return drop_loc
def __init__(self, well_props: 'WellDefinition', parent: Location, display_name: str, has_tip: bool, api_level: APIVersion) -> None: """ Create a well, and track the Point corresponding to the top-center of the well (this Point is in absolute deck coordinates) :param display_name: a string that identifies a well. Used primarily for debug and test purposes. Should be unique and human-readable-- something like "Tip C3 of Opentrons 300ul Tiprack on Slot 5" or "Well D1 of Biorad 96 PCR Plate on Magnetic Module in Slot 1". This is created by the caller and passed in, so here it is just saved and made available. :param well_props: a dict that conforms to the json-schema for a Well :param parent: a :py:class:`.Location` Point representing the absolute position of the parent of the Well (usually the front-left corner of a labware) """ self._api_version = api_level self._display_name = display_name self._position\ = Point(well_props['x'], well_props['y'], well_props['z'] + well_props['depth']) + parent.point if not parent.labware: raise ValueError("Wells must have a parent") self._parent = parent.labware self._has_tip = has_tip self._shape = well_props['shape'] if well_props['shape'] == 'rectangular': self._length: Optional[float] = well_props['xDimension'] self._width: Optional[float] = well_props['yDimension'] self._diameter: Optional[float] = None elif well_props['shape'] == 'circular': self._length = None self._width = None self._diameter = well_props['diameter'] else: raise ValueError('Shape "{}" is not a supported well shape'.format( well_props['shape'])) self.max_volume = well_props['totalLiquidVolume'] self._depth = well_props['depth']
def drop(pip, loc=None): nonlocal switch nonlocal drop_count if not loc: if pip.type == 'multi': drop_count += 8 else: drop_count += 1 if drop_count >= drop_threshold: ctx.home() ctx.pause('Please empty tips from waste before resuming.') drop_count = 0 side = 30 if switch else -18 drop_loc = ctx.loaded_labwares[12].wells()[0].top().move( Point(x=side)) pip.drop_tip(drop_loc) switch = not switch else: pip.drop_tip(loc)
def wash_blot(): for wash, blot in zip(pin_wash_res.wells(), blot_res.wells()): movement_locs = [ wash.center().move(Point(x=side * 5)) for side in [-1, 1] ] m300.move_to(wash.center()) for _ in range(5): for m in movement_locs: m300.move_to(m) m300.move_to(wash.center()) ctx.max_speeds['A'] = slow_speed ctx.max_speeds['Z'] = slow_speed m300.move_to(blot.top()) m300.move_to(blot.bottom()) ctx.delay(seconds=blot_dwell_time) del ctx.max_speeds['A'] del ctx.max_speeds['Z']
def drop(pip): nonlocal switch nonlocal drop_count side = 30 if switch else -18 drop_loc = ctx.loaded_labwares[12].wells()[0].top().move(Point(x=side)) pip.drop_tip(drop_loc) switch = not switch drop_count += 8 if drop_count == drop_threshold: # Setup for flashing lights notification to empty trash if not ctx._hw_manager.hardware.is_simulator: cancellationToken.set_true() thread = create_thread(ctx, cancellationToken) ctx.pause('Please empty tips from waste before resuming.') ctx.home() # home before continuing with protocol cancellationToken.set_false() # stop light flashing after home thread.join() drop_count = 0
def remove_supernatant(vol, pip=m300, park=False, direct=True): """ `remove_supernatant` will transfer supernatant from the deepwell extraction plate to the liquid waste reservoir. :param vol (float): The amount of volume to aspirate from all deepwell sample wells and dispense in the liquid waste. :param park (boolean): Whether to pick up sample-corresponding tips in the 'parking rack' or to pick up new tips. """ def _waste_track(vol): nonlocal waste_vol if waste_vol + vol >= waste_threshold: # Setup for flashing lights notification to empty liquid waste ctx.home() waste_vol = 0 waste_vol += vol for i, (m, spot) in enumerate(zip(mag_samples_m, parking_spots)): if park: _pick_up(pip, spot) else: _pick_up(pip) side = -1 if i % 2 == 0 else 1 loc = m.bottom(0).move( Point(x=side * radius * radial_offset, z=z_offset)) _waste_track(vol) pip.move_to(m.center()) # if pip == m300: # air_gap_vol = 20 # else: # air_gap_vol = pip.max_volume - vol if direct and vol + \ air_gap_vol <= pip.tip_racks[0].wells()[0].max_volume: pip.aspirate(vol, loc) _drop(pip) else: pip.transfer(vol, loc, waste, new_tip='never', air_gap=air_gap_vol) pip.blow_out(waste) _drop(pip)
def test_module_load_labware(): module_names = ['tempdeck', 'magdeck'] labware_name = 'corning_96_wellplate_360ul_flat' labware_def = labware.get_labware_definition(labware_name) for name in module_names: mod = labware.load_module(name, Location(Point(0, 0, 0), 'test')) old_z = mod.highest_z lw = labware.load_from_definition(labware_def, mod.location) mod.add_labware(lw) assert mod.labware == lw assert mod.highest_z ==\ (mod.location.point.z + labware_def['dimensions']['zDimension'] + mod._over_labware) with pytest.raises(AssertionError): mod.add_labware(lw) mod.reset_labware() assert mod.labware is None assert mod.highest_z == old_z
def wash(vol, source, mix_reps=15, park=True, resuspend=True): global whichwash #Defines which wash the protocol is on to log on the app if source == wash1: whichwash = 1 if source == wash2: whichwash = 2 if resuspend and magdeck.status == 'engaged': magdeck.disengage() num_trans = math.ceil(vol/200) vol_per_trans = vol/num_trans for i, (m, spot) in enumerate(zip(mag_samples_m, parking_spots)): _pick_up(m300) side = 1 if i % 2 == 0 else -1 loc = m.bottom(0.5).move(Point(x=side*2)) src = source[i//(12//len(source))] for n in range(num_trans): if m300.current_volume > 0: m300.dispense(m300.current_volume, src.top()) m300.transfer(vol_per_trans, src, m.top(), air_gap=20, new_tip='never') if n < num_trans - 1: # only air_gap if going back to source m300.air_gap(20) if resuspend: resuspend_pellet(m, m300, 180) m300.blow_out(m.top()) m300.air_gap(20) if park: m300.drop_tip(spot) else: _drop(m300) if magdeck.status == 'disengaged': magdeck.engage(height=MAG_HEIGHT) for washi in np.arange(settling_time,0,-0.5): #settling time timer for washes ctx.delay(minutes=0.5, msg='There are ' + str(washi) + ' minutes left in wash ' + str(whichwash) + ' incubation.') remove_supernatant(vol, park=park)
def _load_from_v1(definition: 'ModuleDefinitionV1', parent: Location, api_level: APIVersion) -> ModuleGeometry: """ Load a module geometry from a v1 definition. The definition should be schema checked before being passed to this function; all definitions passed here are assumed to be valid. """ mod_name = definition['loadName'] model_lookup: Mapping[str, ModuleModel] = { 'thermocycler': ThermocyclerModuleModel.THERMOCYCLER_V1, 'magdeck': MagneticModuleModel.MAGNETIC_V1, 'tempdeck': TemperatureModuleModel.TEMPERATURE_V1 } type_lookup = { 'thermocycler': ModuleType.THERMOCYCLER, 'tempdeck': ModuleType.TEMPERATURE, 'magdeck': ModuleType.MAGNETIC } model = model_lookup[mod_name] offset = Point(definition["labwareOffset"]["x"], definition["labwareOffset"]["y"], definition["labwareOffset"]["z"]) overall_height = definition["dimensions"]["bareOverallHeight"]\ height_over_labware = definition["dimensions"]["overLabwareHeight"] if model in ThermocyclerModuleModel: lid_height = definition['dimensions']['lidHeight'] mod: ModuleGeometry = \ ThermocyclerGeometry(definition["displayName"], model, type_lookup[mod_name], offset, overall_height, height_over_labware, lid_height, parent, api_level) else: mod = ModuleGeometry(definition['displayName'], model, type_lookup[mod_name], offset, overall_height, height_over_labware, parent, api_level) return mod
async def _return_tip(self): """ Move pipette with tip to tip rack well, such that the tip is inside the well, but not so deep that the tip rack will block the sheath from ejecting fully. Each pipette config contains a coefficient to apply to an attached tip's length to determine proper z offset """ if self._tip_origin_pt and self._hw_pipette.has_tip: tip_length = self._get_default_tip_length() coeff = self._hw_pipette.config.return_tip_height to_pt = self._tip_origin_pt - Point(0, 0, tip_length * coeff) cp = self._get_critical_point() await self._hardware.move_to(mount=self._mount, abs_position=to_pt, critical_point=cp) await self._hardware.drop_tip(self._mount) self._tip_origin_pt = None
def remove_supernatant(vol): m300.flow_rate.aspirate = 30 num_trans = math.ceil(vol / 270) vol_per_trans = vol / num_trans for i, m in enumerate(mag_samples_m): side = -1 if i < 6 == 0 else 1 loc = m.bottom(0.5).move(Point(x=side * 2)) if not m300.hw_pipette['has_tip']: pick_up(m300) for _ in range(num_trans): m300.move_to(m.center()) m300.transfer(vol_per_trans, loc, waste, new_tip='never', air_gap=30) m300.blow_out(waste) m300.drop_tip() m300.flow_rate.aspirate = 150
def remove_supernatant(): nonlocal waste_vol_ctr nonlocal waste_well_ctr ctx.comment('\n\n\nREMOVING SUPERNATANT\n') for i, col in enumerate(sample_cols): side = -1 if i % 2 == 0 else 1 aspirate_loc = col.bottom(1).move( Point(x=(col.length/2-2)*side)) pick_up() for _ in range(2): m300.aspirate(200 if filter_tips else 300, aspirate_loc, rate=0.6) # noqa E501 m300.dispense(200 if filter_tips else 300, waste_res.wells()[waste_well_ctr]) # noqa E501 m300.blow_out(waste_res.wells()[waste_well_ctr].top()) waste_vol_ctr += 200 if waste_vol_ctr >= 12000: waste_vol_ctr = 0 waste_well_ctr += 1 m300.drop_tip()
def critical_point(self, cp_override: CriticalPoint = None) -> Point: """ The vector from the pipette's origin to its critical point. The critical point for a pipette is the end of the nozzle if no tip is attached, or the end of the tip if a tip is attached. If `cp_override` is specified and valid - so is either :py:attr:`CriticalPoint.NOZZLE` or :py:attr:`CriticalPoint.TIP` when we have a tip, or :py:attr:`CriticalPoint.XY_CENTER` - the specified critical point will be used. """ if not self.has_tip or cp_override == CriticalPoint.NOZZLE: cp_type = CriticalPoint.NOZZLE tip_length = 0.0 else: cp_type = CriticalPoint.TIP tip_length = self.current_tip_length if cp_override == CriticalPoint.XY_CENTER: mod_offset_xy = [0, 0, self.model_offset[2]] cp_type = CriticalPoint.XY_CENTER elif cp_override == CriticalPoint.FRONT_NOZZLE: mod_offset_xy = [0, -self.model_offset[1], self.model_offset[2]] cp_type = CriticalPoint.FRONT_NOZZLE else: mod_offset_xy = self.model_offset mod_and_tip = Point(mod_offset_xy[0], mod_offset_xy[1], mod_offset_xy[2] - tip_length) cp = mod_and_tip + self._instrument_offset._replace(z=0) if self._log.isEnabledFor(logging.DEBUG): mo = 'model offset: {} + '.format(self.model_offset)\ if cp_type != CriticalPoint.XY_CENTER else '' info_str = 'cp: {}{}: {}=({}instr offset xy: {}'\ .format(cp_type, '(from override)' if cp_override else '', cp, mo, self._instrument_offset._replace(z=0)) if cp_type == CriticalPoint.TIP: info_str += '- current_tip_length: {}=(true tip length: {}'\ ' - inst z: {}) (z only)'.format( self.current_tip_length, self._current_tip_length, self._instrument_offset.z) info_str += ')' self._log.debug(info_str) return cp
def test_touch_tip_new_default_args(loop, monkeypatch): ctx = papi.ProtocolContext(loop) ctx.home() lw = ctx.load_labware( 'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap', 1) tiprack = ctx.load_labware('opentrons_96_tiprack_300ul', 3) instr = ctx.load_instrument('p300_single', Mount.RIGHT, tip_racks=[tiprack]) instr.pick_up_tip() total_hw_moves = [] async def fake_hw_move(self, mount, abs_position, speed=None, critical_point=None, max_speeds=None): nonlocal total_hw_moves total_hw_moves.append((abs_position, speed)) instr.aspirate(10, lw.wells()[0]) monkeypatch.setattr(API, 'move_to', fake_hw_move) instr.touch_tip() z_offset = Point(0, 0, 1) # default z offset of 1mm speed = 60 # default speed edges = [ lw.wells()[0]._from_center_cartesian(1, 0, 1) - z_offset, lw.wells()[0]._from_center_cartesian(-1, 0, 1) - z_offset, lw.wells()[0]._from_center_cartesian(0, 0, 1) - z_offset, lw.wells()[0]._from_center_cartesian(0, 1, 1) - z_offset, lw.wells()[0]._from_center_cartesian(0, -1, 1) - z_offset ] for i in range(1, 5): assert total_hw_moves[i] == (edges[i - 1], speed) # Check that the new api version initial well move has the same z height # as the calculated edges. total_hw_moves.clear() instr.touch_tip(v_offset=1) assert total_hw_moves[0][0].z == total_hw_moves[1][0].z
async def test_tip_probe_v2(main_router, model, monkeypatch): def fake_update(mount, new_offset=None, from_tip_probe=None): assert mount == Mount[model.instrument.mount.upper()] if new_offset: assert new_offset == Point(0, 0, 0) elif from_tip_probe: assert from_tip_probe == Point(0, 0, 0) else: assert False, "fake_update called with no args" def fake_move(instrument): assert instrument == model.instrument monkeypatch.setattr(main_router.calibration_manager._hardware._api, 'update_instrument_offset', fake_update) monkeypatch.setattr(main_router.calibration_manager, 'move_to_front', fake_move) tr = labware.load('opentrons_96_tiprack_300ul', Location(Point(), 'test')) model.instrument.tip_racks = [ models.Container(tr, [model.instrument._instrument], model.instrument._context)] def new_fake_locate(mount, tip_length): assert tip_length == pytest.approx(59.3-7.47) return Point(0, 0, 0) monkeypatch.setattr(main_router.calibration_manager._hardware._api, 'locate_tip_probe_center', new_fake_locate) main_router.calibration_manager.tip_probe(model.instrument) await main_router.wait_until(state('ready')) def new_fake_locate2(mount, tip_length): assert tip_length == pytest.approx(59.3-7.47) return Point(0, 0, 0) monkeypatch.setattr(main_router.calibration_manager._hardware._api, 'locate_tip_probe_center', new_fake_locate2) main_router.calibration_manager.tip_probe(model.instrument)
def get_comparisons_by_step( self) -> typing.Dict[CalibrationCheckState, ComparisonStatus]: comparisons = {} for jogged_state, comp in COMPARISON_STATE_MAP.items(): ref_pt = self._saved_points.get(getattr(CalibrationCheckState, comp.reference_state), None) jogged_pt = self._saved_points.get(getattr(CalibrationCheckState, jogged_state), None) if (ref_pt is not None and jogged_pt is not None): diff_magnitude = None if comp.threshold_vector.z == 0.0: diff_magnitude = ref_pt._replace(z=0.0).magnitude_to( jogged_pt._replace(z=0.0)) elif comp.threshold_vector.x == 0.0 and \ comp.threshold_vector.y == 0.0: diff_magnitude = ref_pt._replace( x=0.0, y=0.0).magnitude_to(jogged_pt._replace( x=0.0, y=0.0)) assert diff_magnitude is not None, \ 'step comparisons must check z or (x and y) magnitude' threshold_mag = Point(0, 0, 0).magnitude_to( comp.threshold_vector) exceeds = diff_magnitude > threshold_mag tform_type = DeckCalibrationError.UNKNOWN if exceeds: is_second_pip = jogged_state in [ CalibrationCheckState.joggingSecondPipetteToHeight, CalibrationCheckState.joggingSecondPipetteToPointOne, ] if is_second_pip: tform_type = DeckCalibrationError.BAD_INSTRUMENT_OFFSET elif self.can_distinguish_instr_offset(): tform_type = DeckCalibrationError.BAD_DECK_TRANSFORM comparisons[getattr(CalibrationCheckState, jogged_state)] = \ ComparisonStatus(differenceVector=(jogged_pt - ref_pt), thresholdVector=comp.threshold_vector, exceedsThreshold=exceeds, transformType=tform_type) return comparisons
def test_module_load_labware(loop): ctx = papi.ProtocolContext(loop) labware_name = 'corning_96_wellplate_360ul_flat' # TODO Ian 2019-05-29 load fixtures, not real defs labware_def = json.loads( load_shared_data(f'labware/definitions/2/{labware_name}/1.json')) ctx._hw_manager.hardware._backend._attached_modules = [('mod0', 'tempdeck') ] mod = ctx.load_module('Temperature Module', 1) assert mod.labware is None lw = mod.load_labware(labware_name) lw_offset = Point(labware_def['cornerOffsetFromSlot']['x'], labware_def['cornerOffsetFromSlot']['y'], labware_def['cornerOffsetFromSlot']['z']) assert lw._offset == lw_offset + mod._geometry.location.point assert lw.name == labware_name # Test load with old name mod2 = ctx.load_module('tempdeck', 2) lw2 = mod2.load_labware(labware_name) assert lw2._offset == lw_offset + mod2._geometry.location.point