def touch_tip(self, location: Optional[Well], radius: float, v_offset: float, speed: float): if location is None: if not self._ctx.location_cache: raise RuntimeError('No valid current location cache present') else: location = self._ctx.location_cache.labware # type: ignore # type checked below if isinstance(location, Well): if 'touchTipDisabled' in quirks_from_any_parent(location): self._log.info(f"Ignoring touch tip on labware {location}") return self if location.parent.is_tiprack: self._log.warning('Touch_tip being performed on a tiprack. ' 'Please re-check your code') move_with_z_offset =\ location.top().point + types.Point(0, 0, v_offset) to_loc = types.Location(move_with_z_offset, location) self.move_to(to_loc) else: # If location is a not a valid well, raise a type error raise TypeError( 'location should be a Well, but it is {}'.format(location)) edges = build_edges(location, v_offset, self._pair_policy.primary, self._ctx._deck_layout, radius) for edge in edges: self._hw_manager.hardware.move_to(self._pair_policy, edge, speed)
def point_and_well(self, location: Union[types.Location, WellV2]): if isinstance(location, WellV2): labware, point = location.top() elif isinstance(location, types.Location): labware, point = location else: raise ValueError("can't unpack") return types.Location(point, labware)
def move_to(self, location: types.Location, force_direct: bool = False, minimum_z_height: Optional[float] = None, speed: Optional[float] = None): if not speed: speed = self.p_instrument.default_speed if self._ctx.location_cache: from_lw = self._ctx.location_cache.labware else: from_lw = None from_center = 'centerMultichannelOnWells'\ in quirks_from_any_parent(from_lw) cp_override = CriticalPoint.XY_CENTER if from_center else None from_loc = types.Location( self._hw_manager.hardware.gantry_position( self._pair_policy.primary, critical_point=cp_override), from_lw) for mod in self._ctx._modules: if isinstance(mod, ThermocyclerContext): mod.flag_unsafe_move(to_loc=location, from_loc=from_loc) primary_height = \ self._hw_manager.hardware.get_instrument_max_height( self._pair_policy.primary) secondary_height = \ self._hw_manager.hardware.get_instrument_max_height( self._pair_policy.secondary) moves = planning.plan_moves(from_loc, location, self._ctx.deck, min(primary_height, secondary_height), force_direct=force_direct, minimum_z_height=minimum_z_height) self._log.debug("move_to: {}->{} via:\n\t{}".format( from_loc, location, moves)) try: for move in moves: self._hw_manager.hardware.move_to( self._pair_policy, move[0], critical_point=move[1], speed=speed, max_speeds=self._ctx.max_speeds.data) except Exception: self._ctx.location_cache = None raise else: self._ctx.location_cache = location return self
def _prepare_for_lid_move(self): loaded_instruments = [ instr for mount, instr in self._ctx.loaded_instruments.items() if instr is not None ] try: instr = loaded_instruments[0] except IndexError: MODULE_LOG.warning( "Cannot assure a safe gantry position to avoid colliding" " with the lid of the Thermocycler Module.") else: self._ctx._hw_manager.hardware.retract(instr._mount) high_point = self._ctx._hw_manager.hardware.current_position( instr._mount) trash_top = self._ctx.fixed_trash.wells()[0].top() safe_point = trash_top.point._replace( z=high_point[Axis.by_mount(instr._mount)]) instr.move_to(types.Location(safe_point, None), force_direct=True)
def run(protocol: protocol_api.ProtocolContext): tiprack = protocol.load_labware(TIPRACK_LOADNAME, TIPRACK_SLOT) pipette = protocol.load_instrument(PIPETTE_NAME, PIPETTE_MOUNT, tip_racks=[tiprack]) test_labware = protocol.load_labware_from_definition( LABWARE_DEF, TEST_LABWARE_SLOT, LABWARE_LABEL, ) num_cols = len(LABWARE_DEF.get('ordering', [[]])) num_rows = len(LABWARE_DEF.get('ordering', [[]])[0]) well_locs = uniq( ['A1', '{}{}'.format(chr(ord('A') + num_rows - 1), str(num_cols))]) pipette.pick_up_tip() def set_speeds(rate): protocol.max_speeds.update({ 'X': (600 * rate), 'Y': (400 * rate), 'Z': (125 * rate), 'A': (125 * rate), }) speed_max = max(protocol.max_speeds.values()) for instr in protocol.loaded_instruments.values(): instr.default_speed = speed_max set_speeds(RATE) for slot in CALIBRATION_CROSS_SLOTS: coordinate = CALIBRATION_CROSS_COORDS[slot] location = types.Location(point=types.Point(**coordinate), labware=None) pipette.move_to(location) protocol.pause( f"Confirm {PIPETTE_MOUNT} pipette is at slot {slot} calibration cross" ) pipette.home() protocol.pause(f"Place your labware in Slot {TEST_LABWARE_SLOT}") for well_loc in well_locs: well = test_labware.well(well_loc) all_4_edges = [[well._from_center_cartesian(x=-1, y=0, z=1), 'left'], [well._from_center_cartesian(x=1, y=0, z=1), 'right'], [well._from_center_cartesian(x=0, y=-1, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back']] set_speeds(RATE) pipette.move_to(well.top()) protocol.pause("Moved to the top of the well") for edge_pos, edge_name in all_4_edges: set_speeds(SLOWER_RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause(f'Moved to {edge_name} edge') set_speeds(RATE) pipette.move_to(well.bottom()) protocol.pause("Moved to the bottom of the well") pipette.blow_out(well) set_speeds(1.0) pipette.return_tip()
def position_for(self, key: types.DeckLocation) -> types.Location: key_int = self._check_name(key) return types.Location(self._positions[key_int], str(key))
def move_to_point(self, coordinates): point = types.Point(*coordinates) self.instr.move_to(location=types.Location(point=point, labware=None), minimum_z_height=SAFE_Z_HEIGHT)
def dispense( self, volume=None, location=None, rate=1.0, # remainder are added params full_dispense: bool = False, top_clearance=None, bottom_clearance=None, manual_liquid_volume_allowance=None): # figure out where we're dispensing to # recapitulate super if isinstance(location, WellV2): if 'fixedTrash' in quirks_from_any_parent(location): loc = location.top() else: point, well = location.bottom() loc = types.Location( point + types.Point(0, 0, self.well_bottom_clearance.dispense), well) self.move_to(loc) elif isinstance(location, types.Location): loc = location self.move_to(location) elif location is not None: raise TypeError( 'location should be a Well or Location, but it is {}'.format( location)) elif self._ctx.location_cache: loc = self._ctx.location_cache else: raise RuntimeError( "If dispense is called without an explicit location, another method that moves to a location (such as move_to or aspirate) must previously have been called so the robot knows where it is." ) location = loc # no need for new variable assert isinstance(location, types.Location) point, well = location if top_clearance is None: if tls.dispense_params_transfer: top_clearance = tls.dispense_params_transfer.top_clearance_transfer if top_clearance is None: top_clearance = self.well_top_clearance.dispense if bottom_clearance is None: if tls.dispense_params_transfer: bottom_clearance = tls.dispense_params_transfer.bottom_clearance_transfer if bottom_clearance is None: bottom_clearance = self.well_bottom_clearance.dispense if manual_liquid_volume_allowance is None: if tls.dispense_params_transfer: manual_liquid_volume_allowance = tls.dispense_params_transfer.manual_manufacture_tolerance_transfer if manual_liquid_volume_allowance is None: manual_liquid_volume_allowance = self.config.dispense.manual_liquid_volume_allowance if is_close(volume, self.current_volume ): # avoid finicky floating-point precision issues volume = self.current_volume location = self._adjust_location_to_liquid_top( location=location, aspirate_volume=None, top_clearance=top_clearance, bottom_clearance=bottom_clearance, manual_liquid_volume_allowance=manual_liquid_volume_allowance) with DispenseParams(): tls.dispense_params.full_dispense_from_dispense = full_dispense def call_super(): super(EnhancedPipette, self).dispense(volume=volume, location=location, rate=rate) self.use_self_while(call_super) if tls.dispense_params.fully_dispensed: assert self.current_volume == 0 if self.current_volume == 0: pass # nothing to do: the next self._position_for_aspirate will reposition for us: 'if pipette is currently empty, ensure the plunger is at "bottom"' else: raise NotImplementedError # track volume well.liquid_volume.dispense(volume)
def aspirate( self, volume: float = None, location: Union[types.Location, EnhancedWellV2] = None, rate: float = 1.0, # remainder are added params pre_wet: bool = None, ms_pause: float = None, top_clearance=None, bottom_clearance=None, manual_liquid_volume_allowance=None): # figure out where we're aspirating from # recapitulate super if isinstance(location, WellV2): point, well = location.bottom() dest = types.Location( point + types.Point(0, 0, self.well_bottom_clearance.aspirate), well) elif isinstance(location, types.Location): dest = location elif location is not None: raise TypeError( 'location should be a Well or Location, but it is {}'.format( location)) elif self._ctx.location_cache: dest = self._ctx.location_cache else: raise RuntimeError( "If aspirate is called without an explicit location, another method that moves to a location (such as move_to or dispense) must previously have been called so the robot knows where it is." ) location = dest # no need for new variable assert isinstance(location, types.Location) point, well = location if top_clearance is None: if tls.aspirate_params_transfer: top_clearance = tls.aspirate_params_transfer.top_clearance_transfer if top_clearance is None: top_clearance = self.well_top_clearance.aspirate if bottom_clearance is None: if tls.aspirate_params_transfer: bottom_clearance = tls.aspirate_params_transfer.bottom_clearance_transfer if bottom_clearance is None: bottom_clearance = self.well_bottom_clearance.aspirate if manual_liquid_volume_allowance is None: if tls.aspirate_params_transfer: manual_liquid_volume_allowance = tls.aspirate_params_transfer.manual_manufacture_tolerance_transfer if manual_liquid_volume_allowance is None: manual_liquid_volume_allowance = self.config.aspirate.manual_liquid_volume_allowance current_liquid_volume = well.liquid_volume.current_volume_min needed_liquid_volume = well.geometry.min_aspiratable_volume + volume if current_liquid_volume < needed_liquid_volume: msg = pretty.format( 'aspirating too much from well={0} have={1:n} need={2:n}', well.get_name(), current_liquid_volume, needed_liquid_volume) warn(msg) self._pre_wet(well, volume, location, rate, pre_wet) location = self._adjust_location_to_liquid_top( location=location, aspirate_volume=volume, top_clearance=top_clearance, bottom_clearance=bottom_clearance, manual_liquid_volume_allowance=manual_liquid_volume_allowance) def call_super(): super(EnhancedPipette, self).aspirate(volume=volume, location=location, rate=rate) self.use_self_while(call_super) self.pause_after_aspirate(ms_pause) # finish up todo: what if we're doing an air gap well.liquid_volume.aspirate(volume) if volume != 0: self.prev_aspirated_well = well
def run(protocol: protocol_api.ProtocolContext): tiprack = protocol.load_labware(TIPRACK_LOADNAME, TIPRACK_SLOT) pipette = protocol.load_instrument( PIPETTE_NAME, PIPETTE_MOUNT, tip_racks=[tiprack]) test_labware = protocol.load_labware_from_definition( LABWARE_DEF, TEST_LABWARE_SLOT, LABWARE_LABEL, ) num_cols = len(LABWARE_DEF.get('ordering', [[]])) num_rows = len(LABWARE_DEF.get('ordering', [[]])[0]) total = num_cols * num_rows pipette.pick_up_tip() def set_speeds(rate): protocol.max_speeds.update({ 'X': (600 * rate), 'Y': (400 * rate), 'Z': (125 * rate), 'A': (125 * rate), }) speed_max = max(protocol.max_speeds.values()) for instr in protocol.loaded_instruments.values(): instr.default_speed = speed_max set_speeds(RATE) pipette.home() if(PIPETTE_NAME == 'p20_single_gen2' or PIPETTE_NAME == 'p300_single_gen2' or PIPETTE_NAME == 'p1000_single_gen2' or PIPETTE_NAME == 'p50_single' or PIPETTE_NAME == 'p10_single' or PIPETTE_NAME == 'p300_single' or PIPETTE_NAME == 'p1000_single'): if(total > 1): #testing with single channel well = test_labware.well('A1') all_4_edges = [ [well._from_center_cartesian(x=-1, y=0, z=1), 'left'], [well._from_center_cartesian(x=1, y=0, z=1), 'right'], [well._from_center_cartesian(x=0, y=-1, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] set_speeds(RATE) pipette.move_to(well.top()) protocol.pause("If the position is accurate click 'resume.'") for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") #last well testing last_well = (num_cols) * (num_rows) well = test_labware.well(last_well-1) all_4_edges = [ [well._from_center_cartesian(x=-1, y=0, z=1), 'left'], [well._from_center_cartesian(x=1, y=0, z=1), 'right'], [well._from_center_cartesian(x=0, y=-1, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") set_speeds(RATE) #test bottom of last well pipette.move_to(well.bottom()) protocol.pause("If the position is accurate click 'resume.'") pipette.blow_out(well) else: #testing with single channel + 1 well labware well = test_labware.well('A1') all_4_edges = [ [well._from_center_cartesian(x=-1, y=0, z=1), 'left'], [well._from_center_cartesian(x=1, y=0, z=1), 'right'], [well._from_center_cartesian(x=0, y=-1, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] set_speeds(RATE) pipette.move_to(well.top()) protocol.pause("If the position is accurate click 'resume.'") for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") #test bottom of first well well = test_labware.well('A1') pipette.move_to(well.bottom()) protocol.pause("If the position is accurate click 'resume.'") pipette.blow_out(well) else: #testing for multichannel if(total == 96 or total == 384): #testing for 96 well plates and 384 first column #test first column well = test_labware.well('A1') all_4_edges = [ [well._from_center_cartesian(x=-1, y=0, z=1), 'left'], [well._from_center_cartesian(x=1, y=0, z=1), 'right'], [well._from_center_cartesian(x=0, y=-1, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] set_speeds(RATE) pipette.move_to(well.top()) protocol.pause("If the position is accurate click 'resume.'") for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") #test last column if(total == 96): last_col = (num_cols * num_rows) - num_rows well = test_labware.well(last_col) all_4_edges = [ [well._from_center_cartesian(x=-1, y=0, z=1), 'left'], [well._from_center_cartesian(x=1, y=0, z=1), 'right'], [well._from_center_cartesian(x=0, y=-1, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") set_speeds(RATE) #test bottom of last column pipette.move_to(well.bottom()) protocol.pause("If the position is accurate click 'resume.'") pipette.blow_out(well) elif(total == 384): #testing for 384 well plates - need to hit well 369, last column well369 = (total) - (num_rows) + 1 well = test_labware.well(well369) pipette.move_to(well.top()) protocol.pause("If the position is accurate click 'resume.'") all_4_edges = [ [well._from_center_cartesian(x=-1, y=0, z=1), 'left'], [well._from_center_cartesian(x=1, y=0, z=1), 'right'], [well._from_center_cartesian(x=0, y=-1, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") set_speeds(RATE) #test bottom of last column pipette.move_to(well.bottom()) protocol.pause("If the position is accurate click 'resume.'") pipette.blow_out(well) elif(num_rows == 1 and total > 1 and LABWARE_DIMENSIONS >= 71.2): #for 1 row reservoirs - ex: 12 well reservoirs well = test_labware.well('A1') all_4_edges = [ [well._from_center_cartesian(x=-1, y=1, z=1), 'left'], [well._from_center_cartesian(x=1, y=1, z=1), 'right'], [well._from_center_cartesian(x=0, y=0.75, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] set_speeds(RATE) pipette.move_to(well.top()) protocol.pause("If the position is accurate click 'resume.'") for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") #test last well well = test_labware.well(-1) all_4_edges = [ [well._from_center_cartesian(x=-1, y=1, z=1), 'left'], [well._from_center_cartesian(x=1, y=1, z=1), 'right'], [well._from_center_cartesian(x=0, y=0.75, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] set_speeds(RATE) for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") #test bottom of first well pipette.move_to(well.bottom()) protocol.pause("If the position is accurate click 'resume.'") pipette.blow_out(well) elif(total == 1 and LABWARE_DIMENSIONS >= 71.2 ): #for 1 well reservoirs well = test_labware.well('A1') all_4_edges = [ [well._from_center_cartesian(x=-1, y=1, z=1), 'left'], [well._from_center_cartesian(x=1, y=1, z=1), 'right'], [well._from_center_cartesian(x=0, y=0.75, z=1), 'front'], [well._from_center_cartesian(x=0, y=1, z=1), 'back'] ] set_speeds(RATE) pipette.move_to(well.top()) protocol.pause("If the position is accurate click 'resume.'") for edge_pos, edge_name in all_4_edges: set_speeds(RATE) edge_location = types.Location(point=edge_pos, labware=None) pipette.move_to(edge_location) protocol.pause("If the position is accurate click 'resume.'") #test bottom of first well pipette.move_to(well.bottom()) protocol.pause("If the position is accurate click 'resume.'") pipette.blow_out(well) else: #for incompatible labwares protocol.pause("labware is incompatible to calibrate with a multichannel pipette") set_speeds(1.0) pipette.return_tip()
def dispense(self, volume: float = None, location: Union[types.Location, Well] = None, rate: float = 1.0) -> PairedInstrumentContext: """ Dispense a volume of liquid (in microliters/uL) using both pipettes into the specified location. You must pass in the location you wish the primary pipette to move to. For example, if your primary pipette is the left pipette and you wish for it to move to well ``A1`` of a 96 SBS format plate you should pass in well ``A1`` of this plate. The right pipette will automatically dispense into well ``A5`` of this plate. If only a volume is passed, the pipette will dispense from its current position. If only a location is passed (as in ``instr.dispense(location=wellplate['A1'])``), all of the liquid aspirated into both pipettes will be dispensed (this volume is accessible through :py:attr:`current_volume`). :param volume: The volume of liquid to dispense, in microliters. If not specified, defaults to :py:attr:`current_volume`. :type volume: int or float :param location: Where to dispense into. If `location` is a :py:class:`.Well`, the robot will dispense into :py:obj:`well_bottom_clearance.dispense` mm above the bottom of the well. If `location` is a :py:class:`.Location` (i.e. the result of :py:meth:`.Well.top` or :py:meth:`.Well.bottom`), the robot will dispense into the exact specified location. If unspecified, the robot will dispense into the current position. :param rate: The relative plunger speed for this dispense. During this dispense, the speed of the plunger will be `rate` * :py:attr:`dispense_speed`. If not specified, defaults to 1.0 (speed will not be modified). :type rate: float :returns: This instance. .. note:: If ``dispense`` is called with a single argument, it will not try to guess whether the argument is a volume or location - it is required to be a volume. If you want to call ``dispense`` with only a location, specify it as a keyword argument: ``instr.dispense(location=wellplate['A1'])`` """ self._log.debug("dispense {} from {} at {}".format( volume, location if location else 'current position', rate)) if isinstance(location, Well): if 'fixedTrash' in quirks_from_any_parent(location): loc = location.top() else: point, well = location.bottom() loc = types.Location( point + types.Point(0, 0, self.well_bottom_clearance.dispense), well) elif location is not None and not isinstance(location, types.Location): raise TypeError( 'location should be a Well or Location, but it is {}'.format( location)) else: loc = location if not volume: c_vol = self._get_minimum_current_volume(self._instruments) else: c_vol = volume instruments = list(self._instruments.values()) primary_loc, dispense_func =\ self.paired_instrument_obj.dispense(volume, loc, rate) if isinstance(primary_loc.labware, Well): labware = primary_loc.labware.parent well = primary_loc.labware locations = [ primary_loc, self._get_secondary_target(labware, well) ] else: locations = [primary_loc] cmds.publish_paired(self.broker, cmds.paired_dispense, 'before', None, instruments, c_vol, locations, rate) dispense_func() cmds.publish_paired(self.broker, cmds.paired_dispense, 'after', self, instruments, c_vol, locations, rate) return self