def _get_plunger_position(self, position): """ Returns the calibrated coordinate of a given plunger position Raises exception if the position has not been calibrated yet """ try: value = self.positions[position] if helpers.is_number(value): return value else: raise RuntimeError( 'Plunger position "{}" not yet calibrated'.format( position)) except KeyError: raise RuntimeError( 'Plunger position "{}" does not exist'.format(position))
def load(robot, container_name, slot, label=None, share=False): """ Examples -------- >>> from opentrons import containers >>> containers.load('96-flat', '1') <Deck>/<Slot 1>/<Container 96-flat> >>> containers.load('96-flat', '4', 'plate') <Deck>/<Slot 4>/<Container plate> >>> containers.load('non-existent-type', '4') # doctest: +ELLIPSIS Exception: Container type "non-existent-type" not found in file ... """ # OT-One users specify columns in the A1, B3 fashion # below methods help convert to the 1, 2, etc integer names def is_ot_one_slot_name(s): return isinstance(s, str) and len(s) is 2 and s[0] in 'ABCD' def convert_ot_one_slot_names(s): col = 'ABCD'.index(slot[0]) row = int(slot[1]) - 1 slot_number = col + (row * robot.get_max_robot_cols()) + 1 warnings.warn('Changing deprecated slot name "{}" to "{}"'.format( slot, slot_number)) return slot_number if isinstance(slot, str): # convert to integer try: slot = int(slot) except (ValueError, TypeError): if is_ot_one_slot_name(slot): slot = convert_ot_one_slot_names(slot) if helpers.is_number(slot): # test that it is within correct range if not (1 <= slot <= len(robot.deck)): raise ValueError('Unknown slot: {}'.format(slot)) slot = str(slot) return robot.add_container(container_name, slot, label, share)
def dispense(self, volume=None, location=None, rate=1.0, full_dispense: bool = False, top_clearance=None, bottom_clearance=None, manual_liquid_volume_allowance=None): if not helpers.is_number(volume): # recapitulate super if volume and not location: location = volume volume = self._working_volume - self.current_volume location = location if location else self.previous_placeable well, _ = well_vector(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.config.dispense.top_clearance 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.config.dispense.bottom_clearance 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._update_pose_tree_in_place(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, __ = well_vector(location) well.liquid_volume.dispense(volume)
def aspirate(self, volume=None, location=None, rate: float = 1.0, pre_wet: bool = None, ms_pause: float = None, top_clearance=None, bottom_clearance=None, manual_liquid_volume_allowance=None): if not helpers.is_number(volume): # recapitulate super if volume and not location: location = volume volume = self._working_volume - self.current_volume location = location if location else self.previous_placeable well, _ = well_vector(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.config.aspirate.top_clearance 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.config.aspirate.bottom_clearance 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._update_pose_tree_in_place(call_super) self.pause_after_aspirate(ms_pause) # finish up todo: what if we're doing an air gap well, __ = well_vector(location) well.liquid_volume.aspirate(volume) if volume != 0: self.prev_aspirated_well = well
def pick_up_tip(self, location=None, presses=3): """ Pick up a tip for the Pipette to run liquid-handling commands with Notes ----- A tip can be manually set by passing a `location`. If no location is passed, the Pipette will pick up the next available tip in it's `tip_racks` list (see :any:`Pipette`) Parameters ---------- location : :any:`Placeable` or tuple(:any:`Placeable`, :any:`Vector`) The :any:`Placeable` (:any:`Well`) to perform the pick_up_tip. Can also be a tuple with first item :any:`Placeable`, second item relative :any:`Vector` Returns ------- This instance of :class:`Pipette`. Examples -------- .. >>> robot.reset() # doctest: +ELLIPSIS <opentrons.robot.robot.Robot object at ...> >>> tiprack = containers.load('tiprack-200ul', 'A1') >>> p200 = instruments.Pipette(axis='a', tip_racks=[tiprack]) >>> p200.pick_up_tip(tiprack[0]) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> p200.return_tip() # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> # `pick_up_tip` will automatically go to tiprack[1] >>> p200.pick_up_tip() # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> p200.return_tip() # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> """ if not location: location = self.get_next_tip() self.current_tip(None) if location: placeable, _ = unpack_location(location) self.current_tip(placeable) if isinstance(location, Placeable): location = location.bottom() _description = "Picking up tip {0}".format( ('from ' + humanize_location(location) if location else '')) # NOQA self.robot.add_command(_description) presses = (1 if not helpers.is_number(presses) else presses) self.motor.move(self._get_plunger_position('bottom')) self.current_volume = 0 if location: self.move_to(location, strategy='arc') tip_plunge = 6 for i in range(int(presses) - 1): self.robot.move_head(z=tip_plunge, mode='relative') self.robot.move_head(z=-tip_plunge, mode='relative') return self
def touch_tip(self, location=None, radius=1.0): """ Touch the :any:`Pipette` tip to the sides of a well, with the intent of removing left-over droplets Notes ----- If no `location` is passed, the pipette will touch_tip from it's current position. Parameters ---------- location : :any:`Placeable` or tuple(:any:`Placeable`, :any:`Vector`) The :any:`Placeable` (:any:`Well`) to perform the touch_tip. Can also be a tuple with first item :any:`Placeable`, second item relative :any:`Vector` radius : float Radius is a floating point number between 0.0 and 1.0, describing the percentage of a well's radius. When radius=1.0, :any:`touch_tip()` will move to 100% of the wells radius. When radius=0.5, :any:`touch_tip()` will move to 50% of the wells radius. Returns ------- This instance of :class:`Pipette`. Examples -------- .. >>> p200 = instruments.Pipette(name='p200', axis='a', max_volume=200) >>> p200.aspirate(50, plate[0]) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> p200.dispense(plate[1]).touch_tip() # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> """ _description = 'Touching tip' self.robot.add_command(_description) height_offset = 0 if helpers.is_number(location): height_offset = location location = None # if no location specified, use the previously # associated placeable to get Well dimensions if location: self.move_to(location, strategy='arc') else: location = self.previous_placeable v_offset = (0, 0, height_offset) well_edges = [ location.from_center(x=radius, y=0, z=1), # right edge location.from_center(x=radius * -1, y=0, z=1), # left edge location.from_center(x=0, y=radius, z=1), # back edge location.from_center(x=0, y=radius * -1, z=1) # front edge ] # Apply vertical offset to well edges well_edges = map(lambda x: x + v_offset, well_edges) [self.move_to((location, e), strategy='direct') for e in well_edges] return self
def dispense(self, volume=None, location=None, rate=1.0): """ Dispense a volume of liquid (in microliters/uL) using this pipette Notes ----- If no `location` is passed, the pipette will dispense from it's current position. If no `volume` is passed, `dispense` will default to it's `current_volume` Parameters ---------- volume : int or float The number of microliters to dispense (Default: self.current_volume) location : :any:`Placeable` or tuple(:any:`Placeable`, :any:`Vector`) The :any:`Placeable` (:any:`Well`) to perform the dispense. Can also be a tuple with first item :any:`Placeable`, second item relative :any:`Vector` rate : float Set plunger speed for this dispense, where speed = rate * dispense_speed (see :meth:`set_speed`) Returns ------- This instance of :class:`Pipette`. Examples -------- .. >>> p200 = instruments.Pipette(name='p200', axis='a', max_volume=200) >>> # fill the pipette with liquid (200uL) >>> p200.aspirate(plate[0]) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> # dispense 50uL to a Well >>> p200.dispense(50, plate[0]) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> # dispense 50uL to the center of a well >>> relative_vector = plate[1].center() >>> p200.dispense(50, (plate[1], relative_vector)) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> # dispense 20uL in place, at half the speed >>> p200.dispense(20, rate=0.5) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> # dispense the pipette's remaining volume (80uL) to a Well >>> p200.dispense(plate[2]) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> """ if not helpers.is_number(volume): if volume and not location: location = volume volume = self.current_volume # Ensure we don't dispense more than the current volume volume = min(self.current_volume, volume) # if volume is specified as 0uL, then do nothing if volume == 0: return self _description = "Dispensing {0} {1}".format( volume, ('at ' + humanize_location(location) if location else '')) # NOQA self.robot.add_command(_description) self.move_to(location, strategy='arc') # position robot above location # TODO(ahmed): revisit this distance = self._plunge_distance(self.current_volume - volume) bottom = self._get_plunger_position('bottom') destination = bottom - distance speed = self.speeds['dispense'] * rate self.motor.speed(speed) self.motor.move(destination) self.current_volume -= volume # update after actual dispense return self
def aspirate(self, volume=None, location=None, rate=1.0): """ Aspirate a volume of liquid (in microliters/uL) using this pipette Notes ----- If no `location` is passed, the pipette will aspirate from it's current position. If no `volume` is passed, `aspirate` will default to it's `max_volume` Parameters ---------- volume : int or float The number of microliters to aspirate (Default: self.max_volume) location : :any:`Placeable` or tuple(:any:`Placeable`, :any:`Vector`) The :any:`Placeable` (:any:`Well`) to perform the aspirate. Can also be a tuple with first item :any:`Placeable`, second item relative :any:`Vector` rate : float Set plunger speed for this aspirate, where speed = rate * aspirate_speed (see :meth:`set_speed`) Returns ------- This instance of :class:`Pipette`. Examples -------- .. >>> p200 = instruments.Pipette( ... name='p200', axis='a', max_volume=200) >>> # aspirate 50uL from a Well >>> p200.aspirate(50, plate[0]) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> # aspirate 50uL from the center of a well >>> p200.aspirate(50, plate[1].bottom()) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> # aspirate 20uL in place, twice as fast >>> p200.aspirate(20, rate=2.0) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> >>> # aspirate the pipette's remaining volume (80uL) from a Well >>> p200.aspirate(plate[2]) # doctest: +ELLIPSIS <opentrons.instruments.pipette.Pipette object at ...> """ # Note: volume positional argument may not be passed. if it isn't then # assume the first positional argument is the location if not helpers.is_number(volume): if volume and not location: location = volume volume = self.max_volume - self.current_volume # if volume is specified as 0uL, then do nothing if volume == 0: return self if self.current_volume + volume > self.max_volume: raise RuntimeWarning( 'Pipette with max volume of {0} cannot hold volume {1}'.format( self.max_volume, self.current_volume + volume)) distance = self._plunge_distance(self.current_volume + volume) bottom = self._get_plunger_position('bottom') destination = bottom - distance speed = self.speeds['aspirate'] * rate _description = "Aspirating {0} {1}".format( volume, ('at ' + humanize_location(location) if location else '')) # NOQA self.robot.add_command(_description) self._position_for_aspirate(location) self.motor.speed(speed) self.motor.move(destination) self.current_volume += volume # update after actual aspirate return self