def add_antibody(protocol: ProtocolContext, p300_multi: InstrumentContext, reservoir: Labware, tuberack: Labware, plate: Labware, wells: [[]]): """ Args: protocol: p300_multi: reservoir: hopefully we will get a 12 well trough tuberack: wells: a column-wise list of lists of wells in use Returns: Nothing as of now """ # move liquid from reservoir to plate with p300 multi for _ in wells: i = 0 p300_multi.transfer(source=reservoir, dest=plate.columns()[i], new_tip='never') i += 1 # pause for 2 hours protocol.delay(minutes=120)
def pick_up(pipette: InstrumentContext): """`pick_up()` will pause the ctx when all tip boxes are out of tips, prompting the user to replace all tip racks. Once tipracks are reset, the ctx will start picking up tips from the first tip box as defined in the slot order when assigning the labware definition for that tip box. `pick_up()` will track tips for both pipettes if applicable. :param pipette: The pipette desired to pick up tip as definited earlier in the ctx (e.g. p300, m20). """ for i, rack in enumerate(tip_map): # Check the flag to see if the rack is empty, then we don't loop # through that rack so that the algorithm executes faster. if rack[-1] is False: if i == len(tip_map) - 1: # All tips are used, time to reset ctx.pause("Replace empty tip racks") # print("Replace empty tip racks") pipette.reset_tipracks() for rack in tip_map: rack[-1] = True # Raise an exception so that we can retry the pick up raise OutOfTipsError( "Tipracks were out of tips and were reset") else: continue for column in rack[:-1]: # skip [-1] index because it's the flag for well in column: if well.has_tip: pipette.pick_up_tip(well) if well.well_name == 'A12': # last tip in the rack rack[-1] = False return
def distribute_mm_to_map(p50s: InstrumentContext, plates: [Labware], tuberack: Labware, tipracks: [Labware], facs: Labware, groups: typing.Generator, platemap: list, protocol: protocol_api.ProtocolContext): tubes = tuberack.wells()[:3] plate = plates[0] next(groups) # for plate in plates: for tube in tubes: current_group = next(groups) map_row_counter = 0 p50s.pick_up_tip() for row in plate.rows(): map_row = platemap[map_row_counter] for i in range(12): if map_row[i] == current_group and map_row[i] != '': p50s.well_bottom_clearance.aspirate = 10 p50s.aspirate(volume=50, location=tube) p50s.well_bottom_clearance.dispense = 10 p50s.dispense(volume=50, location=row[i]) else: pass map_row_counter += 1 p50s.drop_tip()
def distribute_mm_to_map(p50s: InstrumentContext, plates: [Labware], tuberack: Labware, protocol: protocol_api.ProtocolContext, groups: typing.Generator, platemap: list, volume): tubes = tuberack.wells()[:6] # next(groups) for i in range(len(plates)): current_plate = plates[i] if i > len(plates) / 2: p50s.well_bottom_clearance.aspirate = .8 else: p50s.well_bottom_clearance.aspirate = 1 for tube in tubes: current_group = next(groups) map_row_counter = 0 p50s.pick_up_tip() for row in current_plate.rows(): map_row = platemap[map_row_counter] for i in range(12): if map_row[i] == current_group and map_row[i] != '': # p50s.well_bottom_clearance.aspirate = .2 p50s.aspirate(volume=volume, location=tube) # p50s.well_bottom_clearance.dispense = 8 p50s.dispense(volume=volume, location=row[i]) else: pass map_row_counter += 1 p50s.drop_tip() protocol.pause("Remove plate and press Resume to continue")
def wash(pip: InstrumentContext, vol: float, source: VolTracker, dest: list, do_dry_run: bool = False, pip_offset: float = 0, steps: int = 5, do_reuse_tip: bool = False): """ This function is used to aspirate a washing buffer and then dispense it over a well using a moving dispense :param pip: The pipette to use for washing aspirations/dispenses :param vol: The volume to wash with, e.g. 4000 uL :param source: VolTracker tracking a labware source of wash buffer, e.g. a reservoir :param dest: A list of wells to dispense to :param do_dry_run: If this argument is true then pipette tips will be returned to the rack they come from. :param pip_offset: Millimeter offset from the bottom of the well (i.e. the Shandon coverplate mouth) :param do_reuse_tip: Use only one tip for aspirating PBS / Washing each slide well? """ max_vol = pip.max_volume vol_backup = vol for well in dest: if not pip.has_tip: pick_up(pip) while vol > 0: aspiration_vol = vol if vol < max_vol else max_vol pip.aspirate(aspiration_vol, source.track(aspiration_vol)) dispense_while_moving(pip, well, aspiration_vol, steps, verbose, pip_offset) vol -= aspiration_vol if do_dry_run and not do_reuse_tip: pip.return_tip() elif not do_reuse_tip: pip.drop_tip() vol = vol_backup if do_reuse_tip and not do_dry_run: pip.drop_tip() elif do_dry_run and pip.has_tip: pip.return_tip()
def distribute_master_mix(p300m: InstrumentContext, plates: [Labware], tuberack: Labware, tipracks: [Labware]): tubes = tuberack.wells()[0:4] p300m.well_bottom_clearance.dispense = .02 tip_counter = 0 # to_remove is the number of wells to be removed from the final row. all prior rows will be assumed to be filled to_remove = 11 to_remove = 12 - to_remove # last_row is the index of the last row (rows() returns 2D list of rows by well) last_row = 1 plate = plates[0] # for plate in plates: wells = plate.rows()[:2] wells[last_row] = wells[last_row][:to_remove] # for example, pop the last 8 in a row for a # for plate in plates: # # last_row is the index of the last row (rows() returns 2D list of rows by well) # # plate = plates[0] # # for plate in plates: # wells = plate.rows()[:2] # wells[last_row] = wells[last_row][:to_remove] group_counter = 0 tip_gen = next_tip(p300m.tip_racks, 1) for tube in tubes: p300m.well_bottom_clearance.aspirate = .1 print(wells) wells = plate.rows()[:2 + group_counter] group_counter += 2 wells[last_row] = wells[last_row][:to_remove] for row in wells: for well in row: tip = next(tip_gen) print(tip) p300m.pick_up_tip(location=tip, presses=2, increment=.05) print(tube, well) p300m.aspirate(volume=100, location=tube) p300m.dispense(volume=100, location=well) p300m.well_bottom_clearance.aspirate = .02 p300m.mix(volume=60, repetitions=3, location=well) p300m.drop_tip() tip_counter += 1
def dispense_while_moving(pip: InstrumentContext, well: Well, vol: float, steps: int, is_verbose: bool = False, pip_offset: float = 0): """ This function dispenses a partial volume = vol/steps and then moves a distance/steps and repeats """ well_diameter = float(well.diameter) dispense_distance = well_diameter - well_edge_offset dy = dispense_distance / steps # Move a fraction (=steps) of well diatr. dv = vol / steps start_location = well.bottom().move( Point(0, well_diameter / 2 - well_edge_offset, pip_offset)) pip.move_to(start_location) for i in range(steps): loc = start_location.move(Point(0, i * dy, 0)) if is_verbose: ctx.comment("Dispensing at: {}".format(loc)) pip.dispense(dv, loc)
def mix(pipette: InstrumentContext, n_mixes, vol, location): if n_mixes == 0: return else: pipette.mix(n_mixes, vol, location)
def distribute_master_mix_p50(p50s: InstrumentContext, plates: [Labware], tuberack: Labware, tipracks: [Labware], facs: Labware): tubes = tuberack.wells()[:2] # to_remove is the number of wells to be removed from the final row. all prior rows will be assumed to be filled to_remove = 6 to_remove = 12 - to_remove p50s.well_bottom_clearance.dispense = 10 p50s.well_bottom_clearance.aspirate = 3 # last_row is the index of the last row (rows() returns 2D list of rows by well) for plate in plates: start_row = 0 num_rows = 2 # first group @ row 0 (row A) last_row = num_rows for tube in tubes: group = plate.rows()[start_row:last_row] last_group = group[num_rows - 1][:to_remove] group[num_rows - 1] = last_group p50s.pick_up_tip() p50s.transfer( volume=50, source=tube, dest=group, new_tip='never', ) # comment out for actual runs. this is for test p50s.drop_tip() p50s.pick_up_tip() p50s.transfer(volume=50, source=facs['A1'], dest=group, new_tip='never' ) # comment out for actual runs. this is for test p50s.drop_tip() start_row += num_rows last_row += num_rows
def elisa_wash(protocol: protocol_api.ProtocolContext, p300m: InstrumentContext, reservoir: [Labware], plate: Labware, num_washes: int, wash_volume: int): """ TODO: consider splitting into a load function and a remove function? this one is large. Is that modularity useful? # Maybe modularity is useful for multiple plates? Rather than changing this function to accommodate multiple plates # TODO: modularity here is almost certainly useful. Can reuse funcs for FACs prep Args: protocol: From robot p300m: P300 multichannel reservoir: A 12 well trough containing ELISA wash buffer plate: The plate to be washed num_washes: number of plate washes to perform wash_volume: volume of buffer to wash each well with Returns: nothing """ # dispenses per aspiration to minimize movement and counter initialization dispenses_per_load = int(300/wash_volume) # Calculate extra volume well_counter = 0 vol_counter = 0 # Pick up tips # TODO: adjust num presses and increment p300m.pick_up_tip(p300m.tip_racks[0].well('A1'), presses=2, increment=.05) for i1 in range(num_washes): # reset aspiration height index = 0 # TODO: add disposal volume? """ ~~~~~~~~~~~~~~~~~~ Handle adding wash to plate. Calculate max number of dispenses per source load. Loop and increment index appropriately. This calculation would be automatic, but volume removed from source well has to be tracked manually to prevent empty. ~~~~~~~~~~~~~~~~~~ """ for i2 in range(int(12/(dispenses_per_load))): # Establish a source well. well_counter should increment before well runs out of liquid # src must be established within this loop if vol_counter >= 9.7: # If most of the volume from the trough is gone, move to next well and reset volume count well_counter += 1 vol_counter = 0 src = reservoir[0].columns()[well_counter] wells = plate.rows()[0][index: index+dispenses_per_load] # TODO: figure out volume count problem...works but not soon enough. well_counter iterates after two washes # TODO: maybe tips don't go down to bottom of well and that's why? p300m.well_bottom_clearance.aspirate = 5 p300m.well_bottom_clearance.dispense = 10 p300m.distribute(volume=wash_volume, source=src, dest=wells, new_tip='never', disposal_volume=0, blow_out=True) # Increment index by number of dispenses per aspiration index += dispenses_per_load # Count volume consumed from current trough well vol_counter += (.3 * 4) # Tried distribute and keep getting tip already attached or tip not attached error """ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Mix and dispense most of the liquid into the trash. Manual flicking still necessary. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ # protocol.delay(seconds=30) # # Set clearance for tip for aspiration # p300m.well_bottom_clearance.aspirate = 0.1 # # Mix and transfer volume from plate to trash. # p300m.transfer(volume=wash_volume, source=plate.rows()[0], # dest=p300m.trash_container.wells(0), disposal_volume=wash_volume, # new_tip='never',) #mix_before=(2, (wash_volume*.75)), aspirate_speed=5, dispense_speed=12, p300m.home() protocol.pause(msg="{} washes done! Resume washing by clicking the 'Resume' button!".format(i1+1)) protocol.comment("{} washes complete! You may proceed with your protocol.".format(num_washes)) p300m.return_tip() p300m.home()
def elisa_wash(protocol: protocol_api.ProtocolContext, p300m: InstrumentContext, reservoir: [Labware], plate: Labware, wells_to_fill: [[]], num_washes: int, wash_volume: int): """ TODO: consider splitting into a load function and a remove function? this one is large. Is that modularity useful? # Maybe modularity is useful for multiple plates? Rather than changing this function to accommodate multiple plates # TODO: modularity here is almost certainly useful. Can reuse funcs for FACs prep Args: protocol: From robot p300m: P300 multichannel reservoir: A 12 well trough containing ELISA wash buffer plate: The plate to be washed num_washes: number of plate washes to perform wash_volume: volume of buffer to wash each well with Returns: nothing """ # dispenses per aspiration to minimize movement and counter initialization dispenses_per_load = int(300 / wash_volume) # Calculate extra volume well_counter = 0 vol_counter = 0 # Pick up tips # TODO: adjust num presses and increment p300m.pick_up_tip(p300m.tip_racks[0].well('A1'), presses=3, increment=.05) for i1 in range(num_washes): # reset aspiration height p300m.well_bottom_clearance.aspirate = 1 index = 0 # TODO: add disposal volume? """ ~~~~~~~~~~~~~~~~~~ Handle adding wash to plate. Calculate max number of dispenses per source load. Loop and increment index appropriately. This calculation would be automatic, but volume removed from source well has to be tracked manually to prevent empty. ~~~~~~~~~~~~~~~~~~ """ # for i2 in range(int(12/(dispenses_per_load))): # Establish a source well. well_counter should increment before well runs out of liquid # src must be established within this loop src = reservoir[0].columns()[well_counter] # location is a list of pairs defining locations of wells location = wells_to_fill print("location{}".format(location)) # subset to locations in the distribution range (number of dispenses per aspiration) locs = location[index:index + 3] print(locs) # for all lists in locs, take the second (1) index aka the row # rows = plate.wells(*locs[2]) # if multiple rows entered into parentheses, each row returned will be a list # so plate.row(1) yields [[wells B1-B12]] and plate.rows(1,2) yields [[wells B1-12], [C1-12]] """ The logic below should underlie transforming csv files created by the plate mapper into a format to access plate wells """ rows = [] cols = [] for i in location: cols.append(i[0]) if i[1] not in rows: rows.append(i[1]) plate_map = plate.rows(*rows) updated = [] for i in plate_map: # there shouldnt be a +1 for normal list slicing but apparently slicing is noninclusive or something updated.append(i[min(cols):max(cols) + 1]) print(updated) # TODO: figure out volume count problem...works but not soon enough. well_counter iterates after two washes # TODO: maybe tips don't go down to bottom of well and that's why? for i in cols: p300m.distribute(volume=wash_volume, source=src, dest=rows[i], new_tip='never', disposal_volume=0, blow_out=True) # Increment index by number of dispenses per aspiration index += dispenses_per_load # Count volume consumed from current trough well vol_counter += (.3 * 8) if vol_counter >= 9.7: # If most of the volume from the trough is gone, move to next well and reset volume count well_counter += 1 vol_counter = 0 # Tried distribute and keep getting tip already attached or tip not attached error """ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Mix and dispense most of the liquid into the trash. Manual flicking still necessary. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ # Set clearance for tip for aspiration p300m.well_bottom_clearance.aspirate = 0.1 # Mix and transfer volume from plate to trash. p300m.transfer( volume=wash_volume, source=plate.rows()[0], dest=p300m.trash_container.wells(0), disposal_volume=100, new_tip='never', mix_before=(2, 80), aspirate_speed=5, dispense_speed=12, ) p300m.home() protocol.pause( msg="{} washes done! Resume washing by clicking the 'Resume' button!". format(i1 + 1)) protocol.comment( "{} washes complete! You may proceed with your protocol.".format( num_washes)) p300m.return_tip() p300m.home()