def flashing_rail_lights(protocol: protocol_api.ProtocolContext, seconds_per_flash_cycle=1.0): """Flash the rail lights on and off in the background. Source: https://github.com/Opentrons/opentrons/issues/7742 Example usage: # While the robot is doing nothing for 2 minutes, flash lights quickly. with flashing_rail_lights(protocol, seconds_per_flash_cycle=0.25): protocol.delay(minutes=2) When the ``with`` block exits, the rail lights are restored to their original state. Exclusive control of the rail lights is assumed. For example, within the ``with`` block, you must not call `ProtocolContext.set_rail_lights` yourself, inspect `ProtocolContext.rail_lights_on`, or nest additional calls to `flashing_rail_lights`. """ original_light_status = protocol.rail_lights_on stop_flashing_event = threading.Event() def background_loop(): while True: protocol.set_rail_lights(not protocol.rail_lights_on) # Wait until it's time to toggle the lights for the next flash or # we're told to stop flashing entirely, whichever comes first. got_stop_flashing_event = stop_flashing_event.wait( timeout=seconds_per_flash_cycle / 2) if got_stop_flashing_event: break background_thread = threading.Thread( target=background_loop, name="Background thread for flashing rail \ lights") try: if not protocol.is_simulating(): background_thread.start() yield finally: # The ``with`` block might be exiting normally, or it might be exiting # because something inside it raised an exception. # # This accounts for user-issued cancelations because currently # (2021-05-04), the Python Protocol API happens to implement user- # issued cancellations by raising an exception from internal API code. if not protocol.is_simulating(): stop_flashing_event.set() background_thread.join() # This is questionable: it may issue a command to the API while the API # is in an inconsistent state after raising an exception. protocol.set_rail_lights(original_light_status)
def run(ctx: protocol_api.ProtocolContext): global robot robot = ctx # confirm door is close if not ctx.is_simulating(): confirm_door_is_closed() # define tips tips1000 = [ ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot) for slot in ['3', '6'] ] tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', '9')] # define pipettes p1000 = ctx.load_instrument('p1000_single_gen2', 'left', tip_racks=tips1000) p300 = ctx.load_instrument('p300_single_gen2', 'right', tip_racks=tips300) # check buffer labware type if BUFFER_LABWARE not in BUFFER_LW_DICT: raise Exception('Invalid BF_LABWARE. Must be one of the \ following:\nopentrons plastic 50ml tubes') # load mastermix labware buffer_rack = ctx.load_labware(BUFFER_LW_DICT[BUFFER_LABWARE], '10', BUFFER_LABWARE) # check mastermix tube labware type if DESTINATION_LABWARE not in DESTINATION_LW_DICT: raise Exception('Invalid DESTINATION_LABWARE. Must be one of the \ following:\nopentrons plastic 2ml tubes') # load elution labware dest_racks = [ ctx.load_labware(DESTINATION_LW_DICT[DESTINATION_LABWARE], slot, 'Destination tubes labware ' + str(i + 1)) for i, slot in enumerate(['4', '1', '5', '2']) ] # setup sample sources and destinations bf_tubes = buffer_rack.wells()[:4] number_racks = math.ceil(NUM_SAMPLES / len(dest_racks[0].wells())) # dest_sets is a list of lists. Each list is the destination well for each rack # example: [[tube1,tube2,...tube24](first rack),[tube1,tube2(second rack),...] dest_sets = [[tube for rack in dest_racks for tube in rack.wells() ][:NUM_SAMPLES][i * len(dest_racks[0].wells()):(i + 1) * len(dest_racks[0].wells())] for i in range(number_racks)] # transfer buffer to tubes for bf_tube, dests in zip(bf_tubes, dest_sets): transfer_buffer(bf_tube, dests, VOLUME_BUFFER, p1000, tips1000) # track final used tip save_tip_info(p1000) finish_run()
def run(ctx: protocol_api.ProtocolContext): global robot robot = ctx # confirm door is closed if not ctx.is_simulating(): confirm_door_is_closed(ctx) tips1000 = [ ctx.load_labware('opentrons_96_filtertiprack_1000ul', 3, '1000µl tiprack') ] # load pipette p1000 = ctx.load_instrument('p1000_single_gen2', 'left', tip_racks=tips1000) # check source (LYSATE) labware type if LYSATE_LABWARE not in LY_LW_DICT: raise Exception('Invalid LYSATE_LABWARE. Must be one of the \ following:\nopentrons plastic 2ml tubes') # load LYSATE labware if 'plate' in LYSATE_LABWARE: source_racks = ctx.load_labware(LY_LW_DICT[LYSATE_LABWARE], '1', 'RNA LYSATE labware') else: source_racks = [ ctx.load_labware(LY_LW_DICT[LYSATE_LABWARE], slot, 'sample LYSATE labware ' + str(i + 1)) for i, slot in enumerate(['4', '1', '5', '2']) ] # check plate if PLATE_LABWARE not in PL_LW_DICT: raise Exception('Invalid PLATE_LABWARE. Must be one of the \ following:\nopentrons deep generic well plate\nnest deep generic well plate\nvwr deep generic well plate' ) # load pcr plate wells_plate = ctx.load_labware(PL_LW_DICT[PLATE_LABWARE], 10, 'sample LYSATE well plate ') # setup samples #sources, dests = get_source_dest_coordinates(LYSATE_LABWARE, source_racks, wells_plate) sources = [tube for s in source_racks for tube in s.wells()][:NUM_SAMPLES] dests = wells_plate.wells()[:NUM_SAMPLES] # transfer transfer_samples(LYSATE_LABWARE, VOLUME_LYSATE, sources, dests, p1000, tips1000) # track final used tip save_tip_info() finish_run()
def run(ctx: protocol_api.ProtocolContext): # Turn on rail lights and pause program so user can load robot deck. # ctx.set_rail_lights(True) # ctx.pause("Load Labware onto robot deck and click resume when ready to continue") # ctx.home() ctx.set_rail_lights(False) # TSV file location on OT-2 tsv_file_path = "{0}var{0}lib{0}jupyter{0}notebooks{0}ProcedureFile.tsv".format( os.sep) if not os.path.isfile(tsv_file_path): # Temp TSV file location on Win10 Computers for simulation tsv_file_path = "C:{0}Users{0}{1}{0}Documents{0}TempTSV.tsv".format( os.sep, os.getlogin()) sample_parameters, args = Utilities.parse_sample_template(tsv_file_path) labware_dict, left_tiprack_list, right_tiprack_list = Utilities.labware_parsing( args, ctx) # Pipettes left_pipette = ctx.load_instrument(args.LeftPipette, 'left', tip_racks=left_tiprack_list) right_pipette = ctx.load_instrument(args.RightPipette, 'right', tip_racks=right_tiprack_list) # Set the location of the first tip in box. left_pipette.starting_tip = left_tiprack_list[0].wells_by_name()[ args.LeftPipetteFirstTip] right_pipette.starting_tip = right_tiprack_list[0].wells_by_name()[ args.RightPipetteFirstTip] # Make sample dilutions. Calculate sample and water volumes. sample_data_dict, aspirated_water_vol = \ process_samples(args, ctx, sample_parameters, labware_dict, left_pipette, right_pipette) # Dispense Water aspirated_water_vol = \ dispense_water(args, sample_data_dict, labware_dict, left_pipette, right_pipette, aspirated_water_vol) # Dispense Samples aspirated_water_vol = \ dispense_samples(args, sample_data_dict, left_pipette, right_pipette, aspirated_water_vol) # Dispense PCR Reagents. dispense_pcr_reagents(args, labware_dict, left_pipette, right_pipette, aspirated_water_vol, sample_data_dict) if not ctx.is_simulating(): os.remove(tsv_file_path)
def run(ctx: protocol_api.ProtocolContext): ctx.comment("Begin {}".format(metadata['protocolName'])) # Turn on rail lights and pause program so user can load robot deck. # ctx.set_rail_lights(True) # ctx.pause("Load Labware onto robot deck and click resume when ready to continue") # ctx.home() ctx.set_rail_lights(False) # TSV file location on OT-2 tsv_file_path = "{0}var{0}lib{0}jupyter{0}notebooks{0}ProcedureFile.tsv".format( os.sep) if not os.path.isfile(tsv_file_path): # Temp TSV file location on Win10 Computers for simulation tsv_file_path = "C:{0}Users{0}{1}{0}Documents{0}TempTSV.tsv".format( os.sep, os.getlogin()) sample_parameters, args = Utilities.parse_sample_template(tsv_file_path) labware_dict, left_tiprack_list, right_tiprack_list = Utilities.labware_parsing( args, ctx) # Pipettes left_pipette = ctx.load_instrument(args.LeftPipette, 'left', tip_racks=left_tiprack_list) right_pipette = ctx.load_instrument(args.RightPipette, 'right', tip_racks=right_tiprack_list) # Set the location of the first tip in box. with suppress(IndexError): left_pipette.starting_tip = left_tiprack_list[0].wells_by_name()[ args.LeftPipetteFirstTip] with suppress(IndexError): right_pipette.starting_tip = right_tiprack_list[0].wells_by_name()[ args.RightPipetteFirstTip] # Dispense Samples and primers sample_dest_dict = dispense_samples(args, sample_parameters, labware_dict, left_pipette, right_pipette) # Add PCR mix to each destination well. add_pcr_mix(args, labware_dict, sample_dest_dict, left_pipette, right_pipette) if not ctx.is_simulating(): os.remove(tsv_file_path) ctx.comment("Program End")
def run(ctx: protocol_api.ProtocolContext): #Please replace what is between the quotation marks!! NEW_SERIAL_NUMBER = "OT2CEP20201214B12" ctx.comment(f"Setting serial number to {NEW_SERIAL_NUMBER}.") if not ctx.is_simulating(): with open("/var/serial", "w") as serial_number_file: serial_number_file.write(NEW_SERIAL_NUMBER + "\n") with open("/etc/machine-info", "w") as serial_number_file: serial_number_file.write(f"DEPLOYMENT=production\nPRETTY_HOSTNAME={NEW_SERIAL_NUMBER}\n") with open("/etc/hostname", "w") as serial_number_file: serial_number_file.write(NEW_SERIAL_NUMBER + "\n") os.sync() ctx.comment("Done.")
def run(ctx: protocol_api.ProtocolContext): #Please replace what is between the quotation marks!! NEW_SERIAL_NUMBER = "set_your_serial_number_here" ctx.comment(f"Setting serial number to {NEW_SERIAL_NUMBER}.") if not ctx.is_simulating(): with open("/var/serial", "w") as serial_number_file: serial_number_file.write(NEW_SERIAL_NUMBER + "\n") with open("/etc/machine-info", "w") as serial_number_file: serial_number_file.write(f"DEPLOYMENT=production\nPRETTY_HOSTNAME={NEW_SERIAL_NUMBER}\n") with open("/etc/hostname", "w") as serial_number_file: serial_number_file.write(NEW_SERIAL_NUMBER + "\n") os.sync() ctx.comment("Done.")# This script sets an OT-2's serial number. It's meant to be used after you
def run(ctx: protocol_api.ProtocolContext): # load labware s_racks = [ ctx.load_labware('opentrons_15_tuberack_falcon_15ml_conical', '4') ] d_plate = ctx.load_labware('nest_96_wellplate_200ul_flat', '1', '96-wellplate sample plate') tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', '5')] # load pipette p300 = ctx.load_instrument('p300_single_gen2', 'left', tip_racks=tips300) # transfer sample p300.transfer(SAMPLE_VOLUME, s_racks.wells()['A1'], d_plate.columns()['5'], air_gap=20, mix_before=(2, 200), new_tip='never') p300.air_gap(20) p300.drop_tip() ctx.comment('Terminado.') # track final used tip if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) data = {'tips300': tip_log['count'][p300]} with open(tip_file_path, 'w') as outfile: json.dump(data, outfile)
def run(ctx: protocol_api.ProtocolContext): STEP = 0 STEPS = { # Dictionary with STEP activation, description and times 1: {'Execute': True, 'description': 'Mix and move samples ('+str(VOLUME_SAMPLE)+'ul)'} } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time if not ctx.is_simulating(): folder_path = '/var/lib/jupyter/notebooks/'+run_id if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/StationA_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, delay): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.delay = delay # Reagents and their characteristics Samples = Reagent(name = 'Samples', flow_rate_aspirate = 1, flow_rate_dispense = 1, rinse = False, delay = 0 ) ctx.comment(' ') ctx.comment('###############################################') ctx.comment('CONTROL SPACES: ' + str(NUM_CONTROL_SPACES)) ctx.comment('NUM SAMPLES: ' + str(NUM_REAL_SAMPLES)) ctx.comment('###############################################') ctx.comment(' ') ################## # Custom functions def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location = source, vol = vol, rounds = 2, blow_out = True, mix_height = 0, x_offset = x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x = x_offset[0])) pipet.aspirate(vol, s) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z = -2), rate = reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z = disp_height).move(Point(x = x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate = reagent.flow_rate_dispense) # dispense all ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z = -2)) if touch_tip == True: pipet.touch_tip(speed = 20, v_offset = -10) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height = 5): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height <= 0: mix_height = 3 pipet.aspirate(1, location = location.bottom( z = source_height).move(Point(x = x_offset[0])), rate = reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location = location.bottom( z = source_height).move(Point(x = x_offset[0])), rate = reagent.flow_rate_aspirate) pipet.dispense(vol, location = location.bottom( z = mix_height).move(Point(x = x_offset[1])), rate = reagent.flow_rate_dispense) pipet.dispense(1, location = location.bottom( z = mix_height).move(Point(x = x_offset[1])), rate = reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z = -2)) # Blow out def generate_source_table(source): ''' Concatenate the wells frome the different origin racks ''' num_cols = math.ceil(num_samples / 8) s = [] for i in range(num_cols): if i < 6: s += source[0].columns()[i] + source[1].columns()[i] else: s += source[2].columns()[i - 6] + source[3].columns()[i - 6] return s ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 pip.pick_up_tip() #################################### # load labware and modules #################################### # Load Sample racks if num_samples <= 48: rack_num = 2 ctx.comment('Used source racks are ' + str(rack_num)) else: rack_num = 4 source_racks = [ctx.load_labware( 'opentrons_24_tuberack_nest_2ml_snapcap', slot, 'source tuberack with snapcap' + str(i + 1)) for i, slot in enumerate(['5', '2', '6', '3'][:rack_num]) ] ################################## # Destination plate dest_plate = ctx.load_labware( 'nest_96_wellplate_2ml_deep', '1', 'NEST 96 Deepwell Plate 2mL') #################################### # Load tip_racks tips1000 = [ctx.load_labware( 'opentrons_96_filtertiprack_1000ul', slot, '1000µl filter tiprack') for slot in ['7']] ################################################################################ # setup samples and destinations sample_sources_full = generate_source_table(source_racks) sample_sources = sample_sources_full[NUM_CONTROL_SPACES:num_samples] destinations = dest_plate.wells()[NUM_CONTROL_SPACES:num_samples] p1000 = ctx.load_instrument( 'p1000_single_gen2', 'right', tip_racks = tips1000) # load P1000 pipette # used tip counter and set maximum tips available tip_track = { 'counts': {p1000: 0}, 'maxes': {p1000: len(tips1000) * 96} } ############################################################################ # STEP 1: MIX AND MOVE SAMPLES ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') start = datetime.now() for s, d in zip(sample_sources, destinations): if not p1000.hw_pipette['has_tip']: pick_up(p1000) # Mix the sample BEFORE dispensing if NUM_MIXES > 0: custom_mix(p1000, reagent = Samples, location = s, vol = volume_mix, rounds = NUM_MIXES, blow_out = True, mix_height = 15, x_offset = x_offset) move_vol_multichannel(p1000, reagent = Samples, source = s, dest = d, vol = VOLUME_SAMPLE, air_gap_vol = air_gap_vol_sample, x_offset = x_offset, pickup_height = 3, rinse = Samples.rinse, disp_height = -10, blow_out = True, touch_tip = True) p1000.drop_tip(home_after = False) tip_track['counts'][p1000] += 1 # Time statistics end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write('STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program # from opentrons.drivers.rpi_drivers import gpio #if not ctx.is_simulating(): #os.system('mpg123 -f -14000 /var/lib/jupyter/notebooks/final.mp3') if not ctx.is_simulating(): fname = ' /var/lib/jupyter/notebooks/finished_process_esp.mp3' if os.path.isfile(fname) is True: subprocess.run( ['mpg123', fname], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) else: ctx.comment(f"Sound file does not exist. Call the technician") for i in range(3): ctx._hw_manager.hardware.set_lights(rails = False) ctx._hw_manager.hardware.set_lights(button = (1, 0 ,0)) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(rails = True) ctx._hw_manager.hardware.set_lights(button = (0, 0 ,1)) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button = (0, 1 ,0)) ctx.comment('Finished! \nMove deepwell plate (slot 1) to Station B for extraction protocol') ctx.comment('Used p1000 tips in total: ' + str(tip_track['counts'][p1000])) ctx.comment('Used p1000 racks in total: ' + str(tip_track['counts'][p1000] / 96))
def run(ctx: protocol_api.ProtocolContext): from opentrons.drivers.rpi_drivers import gpio gpio.set_rail_lights(False) #Turn off lights (termosensible reagents) ctx.comment('Actual used columns: ' + str(num_cols)) # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: {'Execute': True, 'description': 'Transfer MMIX'}, 2: {'Execute': True, 'description': 'Transfer elution'} } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time folder_path = '/var/lib/jupyter/notebooks/'+run_id' if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/KC_qPCR_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling = 'none'): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused=[] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells # Reagents and their characteristics MMIX = Reagent(name = 'Master Mix', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = volume_mmix_available, num_wells = 2, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) Samples = Reagent(name='Samples', rinse=False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume=50, delay=0, num_wells=num_cols, # num_cols comes from available columns h_cono=0, v_fondo=0 ) MMIX.vol_well = MMIX.vol_well_original Samples.vol_well = Samples.vol_well_original ################## # Custom functions def divide_destinations(l, n): # Divide the list of destinations in size n lists. for i in range(0, len(l), n): yield l[i:i + n] def distribute_custom(pipette, volume, src, dest, waste_pool, pickup_height, extra_dispensal, 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(20) # air gap for d in dest: pipette.dispense(20, d.top()) drop = d.top(z = disp_height) pipette.dispense(volume, drop) pipette.move_to(d.top(z=5)) pipette.aspirate(20) # 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 move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location = source, vol = vol, rounds = 2, blow_out = True, mix_height = 0, x_offset = x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x = x_offset[0])) pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z = -2), rate = reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z = disp_height).move(Point(x = x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate = reagent.flow_rate_dispense) # dispense all ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z = -2)) if touch_tip == True: pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height = 3): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height == 0: mix_height = 3 pipet.aspirate(1, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.5, extra_volume = 50): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if reagent.vol_well < aspirate_volume + extra_volume: reagent.unused.append(reagent.vol_well) ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change #################################### # load labware and modules # 24 well rack tuberack = ctx.load_labware( 'opentrons_24_aluminumblock_generic_2ml_screwcap', '2', 'Bloque Aluminio opentrons 24 screwcaps 2000 µL ') ############################################ # tempdeck tempdeck = ctx.load_module('tempdeck', '4') tempdeck.set_temperature(temperature) ################################## # qPCR plate - final plate, goes to PCR qpcr_plate = tempdeck.load_labware( 'abi_fast_qpcr_96_alum_opentrons_100ul', 'chilled qPCR final plate') ################################## # Sample plate - comes from B source_plate = ctx.load_labware( "kingfisher_std_96_wellplate_550ul", '1', 'chilled KF plate with elutions (alum opentrons)') samples = source_plate.wells()[:NUM_SAMPLES] ################################## # Load Tipracks tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot) for slot in ['5'] ] tips200 = [ ctx.load_labware('opentrons_96_filtertiprack_200ul', slot) for slot in ['6'] ] ################################################################################ # Declare which reagents are in each reservoir as well as deepwell and elution plate MMIX.reagent_reservoir = tuberack.rows()[0][:MMIX.num_wells] # 1 row, 2 columns (first ones) ctx.comment('Wells in: '+ str(tuberack.rows()[0][:MMIX.num_wells]) + ' element: '+str(MMIX.reagent_reservoir[MMIX.col])) # setup up sample sources and destinations samples = source_plate.wells()[:NUM_SAMPLES] samples_multi = source_plate.rows()[0][:num_cols] pcr_wells = qpcr_plate.wells()[:NUM_SAMPLES] pcr_wells_multi = qpcr_plate.rows()[0][:num_cols] # Divide destination wells in small groups for P300 pipette dests = list(divide_destinations(pcr_wells, size_transfer)) # pipettes m20 = ctx.load_instrument( 'p20_multi_gen2', mount='right', tip_racks=tips20) p300 = ctx.load_instrument( 'p300_single_gen2', mount='left', tip_racks=tips200) # used tip counter and set maximum tips available tip_track = { 'counts': {p300: 0, m20: 0} } ############################################################################ # STEP 1: Transfer Master MIX ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() p300.pick_up_tip() used_vol=[] for dest in dests: aspirate_volume=volume_mmix * len(dest) + extra_dispensal [pickup_height,col_change]=calc_height(MMIX, area_section_screwcap, aspirate_volume) used_vol_temp = distribute_custom( p300, volume = volume_mmix, src = MMIX.reagent_reservoir[MMIX.col], dest = dest, waste_pool = MMIX.reagent_reservoir[MMIX.col], pickup_height = pickup_height, extra_dispensal = extra_dispensal) used_vol.append(used_vol_temp) p300.drop_tip() tip_track['counts'][p300]+=1 #MMIX.unused_two = MMIX.vol_well end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2: TRANSFER Samples ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('pcr_wells') #Loop over defined wells for s, d in zip(samples_multi, pcr_wells_multi): m20.pick_up_tip() #Source samples move_vol_multichannel(m20, reagent = Samples, source = s, dest = d, vol = volume_sample, air_gap_vol = air_gap_sample, x_offset = x_offset, pickup_height = 0.2, disp_height = -10, rinse = False, blow_out=True, touch_tip=False) m20.drop_tip() tip_track['counts'][m20]+=8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write('STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program gpio.set_rail_lights(False) time.sleep(2) #os.system('mpg123 -f -8000 /var/lib/jupyter/notebooks/toreador.mp3 &') for i in range(3): gpio.set_rail_lights(False) gpio.set_button_light(1, 0, 0) time.sleep(0.3) gpio.set_rail_lights(True) gpio.set_button_light(0, 0, 1) time.sleep(0.3) gpio.set_rail_lights(False) gpio.set_button_light(0, 1, 0) ctx.comment('Finished! \nMove plate to PCR') if STEPS[1]['Execute'] == True: total_used_vol = np.sum(used_vol) total_needed_volume = total_used_vol ctx.comment('Total Master Mix used volume is: ' + str(total_used_vol) + '\u03BCl.') ctx.comment('Needed Master Mix volume is ' + str(total_needed_volume + extra_dispensal*len(dests)) +'\u03BCl') ctx.comment('Used Master Mix volumes per run are: ' + str(used_vol) + '\u03BCl.') ctx.comment('Master Mix Volume remaining in tubes is: ' + format(np.sum(MMIX.unused)+extra_dispensal*len(dests)+MMIX.vol_well) + '\u03BCl.') ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300])) ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96)) if STEPS[2]['Execute'] == True: ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][m20])) ctx.comment('20 ul Used racks in total: ' + str(tip_track['counts'][m20] / 96))
def run(ctx: protocol_api.ProtocolContext): global robot robot = ctx # confirm door is close if not ctx.is_simulating(): confirm_door_is_closed() # load labware and modules ## ELUTION LABWARE if ELUTION_LABWARE not in ELUTION_LW_DICT: raise Exception('Invalid ELUTION_LABWARE. Must be one of the \ following:\nopentrons aluminum biorad plate\nopentrons aluminum nest plate') elution_plate = ctx.load_labware( ELUTION_LW_DICT[ELUTION_LABWARE], '1', 'elution plate') ## MAGNETIC PLATE LABWARE magdeck = ctx.load_module('magdeck', '10') magdeck.disengage() if MAGPLATE_LABWARE not in MAGPLATE_LW_DICT: raise Exception('Invalid MAGPLATE_LABWARE. Must be one of the \ following:\nopentrons deep generic well plate\nnest deep generic well plate\nvwr deep generic well plate') magplate = magdeck.load_labware(MAGPLATE_LW_DICT[MAGPLATE_LABWARE]) ## WASTE LABWARE if WASTE_LABWARE not in WASTE_LW_DICT: raise Exception('Invalid WASTE_LABWARE. Must be one of the \ following:\nnest 1 reservoir plate') waste = ctx.load_labware( WASTE_LW_DICT[WASTE_LABWARE], '11', 'waste reservoir').wells()[0].top(-10) ## REAGENT RESERVOIR if REAGENT_LABWARE not in REAGENT_LW_DICT: raise Exception('Invalid REAGENT_LABWARE. Must be one of the \ following:\nnest 12 reservoir plate') reagent_res = ctx.load_labware( REAGENT_LW_DICT[REAGENT_LABWARE], '7', 'reagent reservoir') ## TIPS # using standard tip definition despite actually using filter tips # so that the tips can accommodate ~220µl per transfer for efficiency tips300 = [ ctx.load_labware( 'opentrons_96_tiprack_300ul', slot, '200µl filter tiprack') for slot in ['2', '3', '5', '6', '9','4'] ] tips1000 = [ ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot, '1000µl filter tiprack') for slot in ['8'] ] # reagents and samples num_cols = math.ceil(NUM_SAMPLES/8) mag_samples_m = magplate.rows()[0][:num_cols] mag_samples_s = magplate.wells()[:NUM_SAMPLES] elution_samples_m = elution_plate.rows()[0][:num_cols] elution_buffer = reagent_res.wells()[0] bead_buffer = reagent_res.wells()[1:5] wash_sets = [reagent_res.wells()[i:i+2] for i in [5, 7, 9]] # pipettes m300 = ctx.load_instrument('p300_multi_gen2', 'left', tip_racks=tips300) p1000 = ctx.load_instrument('p1000_single_gen2', 'right', tip_racks=tips1000) m300.flow_rate.aspirate = 150 m300.flow_rate.dispense = 300 m300.flow_rate.blow_out = 300 p1000.flow_rate.aspirate = 100 p1000.flow_rate.dispense = 1000 p1000.flow_rate.blow_out = 1000 if(DISPENSE_BEADS): # premix, transfer, and mix magnetic beads with sample ## bead dests depending on number of samples bead_dests = bead_buffer[:math.ceil(num_cols/4)] dispense_beads(bead_dests,mag_samples_m,m300,tips300) else: # Mix bead mix_beads(7, mag_samples_m,m300,tips300) # incubate off and on magnet ctx.delay(minutes=5, msg='Incubating off magnet for 5 minutes.') ## First incubate on magnet. magdeck.engage(height_from_base=22) ctx.delay(minutes=5, msg='Incubating on magnet for 5 minutes.') # remove supernatant with P1000 remove_supernatant(mag_samples_s,waste,p1000,tips1000) # 3x washes wash(wash_sets,mag_samples_m,waste,magdeck,m300,tips300) # elute samples magdeck.disengage() elute_samples(mag_samples_m,elution_samples_m,elution_buffer,magdeck,m300,tips300) # track final used tip save_tip_info() magdeck.disengage() finish_run()
def run(ctx: protocol_api.ProtocolContext): global MM_TYPE # check source (elution) labware type source_plate = ctx.load_labware( 'opentrons_96_aluminumblock_nest_wellplate_100ul', '1', 'chilled elution plate on block from Station B') tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot) for slot in ['3', '6', '8', '9', '10', '11'] ] tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', '2')] tempdeck = ctx.load_module('Temperature Module Gen2', '4') pcr_plate = tempdeck.load_labware( 'opentrons_96_aluminumblock_nest_wellplate_100ul', 'PCR plate') mm_strips = ctx.load_labware( 'opentrons_96_aluminumblock_nest_wellplate_100ul', '7', 'mastermix strips') tempdeck.set_temperature(4) tube_block = ctx.load_labware( 'opentrons_24_aluminumblock_nest_2ml_screwcap', '5', '2ml screw tube aluminum block for mastermix + controls') # pipette m20 = ctx.load_instrument('p20_multi_gen2', 'right', tip_racks=tips20) p300 = ctx.load_instrument('p300_single_gen2', 'left', tip_racks=tips300) # setup up sample sources and destinations num_cols = math.ceil(NUM_SAMPLES/8) sources = source_plate.rows()[0][:num_cols] sample_dests = pcr_plate.rows()[0][:num_cols] tip_log = {'count': {}} folder_path = '/data/C' tip_file_path = folder_path + '/tip_log.json' if TIP_TRACK and not ctx.is_simulating(): if os.path.isfile(tip_file_path): with open(tip_file_path) as json_file: data = json.load(json_file) if 'tips20' in data: tip_log['count'][m20] = data['tips20'] else: tip_log['count'][m20] = 0 if 'tips300' in data: tip_log['count'][p300] = data['tips300'] else: tip_log['count'][p300] = 0 else: tip_log['count'] = {m20: 0, p300: 0} else: tip_log['count'] = {m20: 0, p300: 0} tip_log['tips'] = { m20: [tip for rack in tips20 for tip in rack.rows()[0]], p300: [tip for rack in tips300 for tip in rack.wells()] } tip_log['max'] = { pip: len(tip_log['tips'][pip]) for pip in [m20, p300] } def pick_up(pip): nonlocal tip_log if tip_log['count'][pip] == tip_log['max'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_log['count'][pip] = 0 pip.pick_up_tip(tip_log['tips'][pip][tip_log['count'][pip]]) tip_log['count'][pip] += 1 """ mastermix component maps """ mm_tube = tube_block.wells()[0] mm_dict = { 'volume': 15, 'components': { tube: vol for tube, vol in zip(tube_block.columns()[1][:3], [6.25, 1.25, 7.5]) } } vol_overage = 1.2 if NUM_SAMPLES > 48 else 1.1 # decrease overage for small sample number total_mm_vol = mm_dict['volume']*(NUM_SAMPLES+2)*vol_overage # translate total mastermix volume to starting height r = mm_tube.diameter/2 mm_height = total_mm_vol/(math.pi*(r**2)) - 5 def h_track(vol): nonlocal mm_height dh = 1.1*vol/(math.pi*(r**2)) # compensate for 10% theoretical volume loss mm_height = mm_height - dh if mm_height - dh > 2 else 2 # stop at 2mm above mm tube bottom return mm_tube.bottom(mm_height) if PREPARE_MASTERMIX: vol_overage = 1.2 if NUM_SAMPLES > 48 else 1.1 for i, (tube, vol) in enumerate(mm_dict['components'].items()): comp_vol = vol*(NUM_SAMPLES)*vol_overage pick_up(p300) num_trans = math.ceil(comp_vol/160) vol_per_trans = comp_vol/num_trans for _ in range(num_trans): p300.air_gap(20) p300.aspirate(vol_per_trans, tube) ctx.delay(seconds=3) p300.touch_tip(tube) p300.air_gap(20) p300.dispense(20, mm_tube.top()) # void air gap p300.dispense(vol_per_trans, mm_tube.bottom(2)) p300.dispense(20, mm_tube.top()) # void pre-loaded air gap p300.blow_out(mm_tube.top()) p300.touch_tip(mm_tube) if i < len(mm_dict['components'].items()) - 1: # only keep tip if last component and p300 in use p300.drop_tip() mm_total_vol = mm_dict['volume']*(NUM_SAMPLES)*vol_overage if not p300.hw_pipette['has_tip']: # pickup tip with P300 if necessary for mixing pick_up(p300) mix_vol = mm_total_vol / 2 if mm_total_vol / 2 <= 200 else 200 # mix volume is 1/2 MM total, maxing at 200µl mix_loc = mm_tube.bottom(20) if NUM_SAMPLES > 48 else mm_tube.bottom(5) p300.mix(7, mix_vol, mix_loc) p300.blow_out(mm_tube.top()) p300.touch_tip() # transfer mastermix to strips mm_strip = mm_strips.columns()[0] if not p300.hw_pipette['has_tip']: pick_up(p300) for i, well in enumerate(mm_strip): if NUM_SAMPLES % 8 == 0 or i < NUM_SAMPLES % 8: vol = num_cols*mm_dict['volume']*((vol_overage-1)/2+1) else: vol = (num_cols-1)*mm_dict['volume']*((vol_overage-1)/2+1) p300.transfer(vol, mm_tube, well, new_tip='never') p300.drop_tip() # transfer mastermix to plate mm_vol = mm_dict['volume'] pick_up(m20) m20.transfer(mm_vol, mm_strip[0].bottom(0.5), sample_dests, new_tip='never') m20.drop_tip() # transfer samples to corresponding locations for s, d in zip(sources, sample_dests): pick_up(m20) m20.transfer(SAMPLE_VOL, s.bottom(2), d.bottom(2), new_tip='never') m20.mix(1, 10, d.bottom(2)) m20.blow_out(d.top(-2)) m20.aspirate(5, d.top(2)) # suck in any remaining droplets on way to trash m20.drop_tip() # track final used tip if TIP_TRACK and not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) data = { 'tips20': tip_log['count'][m20], 'tips300': tip_log['count'][p300] } with open(tip_file_path, 'w') as outfile: json.dump(data, outfile)
def run(ctx: protocol_api.ProtocolContext): ctx.comment('Actual used columns: ' + str(num_cols)) # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: {'Execute': True, 'description': 'Add 300 ul Wash Buffer 1 - Round 1'}, 2: {'Execute': True, 'description': 'Add 300 ul Wash Buffer 1 - Round 2'}, 3: {'Execute': True, 'description': 'Add 450 ul Wash Buffer 2 - Round 1'}, 4: {'Execute': True, 'description': 'Add 450 ul Wash Buffer 2 - Round 2'}, 5: {'Execute': True, 'description': 'Add 50 ul Elution Buffer'}, } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 folder_path = '/var/lib/jupyter/notebooks/'+run_id if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/KB_PlateFilling_pathogen_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling = 'none'): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay #Delay of reagent in dispense self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused=[] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells # Reagents and their characteristics WashBuffer1 = Reagent(name='Wash Buffer 1', flow_rate_aspirate=0.75, flow_rate_dispense=1, rinse=True, delay=2, reagent_reservoir_volume=100000, num_wells=1, h_cono=0, v_fondo=0) # Flat surface WashBuffer2 = Reagent(name='Wash Buffer 1', flow_rate_aspirate=0.75, flow_rate_dispense=1, rinse=True, delay=2, reagent_reservoir_volume=100000, num_wells=1, h_cono=0, v_fondo=0) # Flat surface ElutionBuffer = Reagent(name='Elution Buffer', flow_rate_aspirate=1, flow_rate_dispense=1, rinse=False, delay=0, reagent_reservoir_volume=50*NUM_SAMPLES, num_wells=1, h_cono=1.95, v_fondo=695) # Prismatic WashBuffer1.vol_well = WashBuffer1.vol_well_original WashBuffer2.vol_well = WashBuffer2.vol_well_original ElutionBuffer.vol_well = ElutionBuffer.vol_well_original ################## # Custom functions def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location = source, vol = vol, rounds = 2, blow_out = True, mix_height = 0, x_offset = x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x = x_offset[0])) pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z = -2), rate = reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z = disp_height).move(Point(x = x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate = reagent.flow_rate_dispense) # dispense all ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z = -2)) if touch_tip == True: pipet.touch_tip(radius=0.9, speed = 20, v_offset = -5) #radius here is 0.9 def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height = 3): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height == 0: mix_height = 3 pipet.aspirate(1, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.5, extra_volume = 50): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if reagent.vol_well < aspirate_volume + extra_volume: reagent.unused.append(reagent.vol_well) ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 pip.pick_up_tip() ########## def find_side(col): ''' Detects if the current column has the magnet at its left or right side ''' if col % 2 == 0: side = -1 # left else: side = 1 return side #################################### # load labware and modules # 12 well rack #################################### reagent_res = ctx.load_labware( 'nest_12_reservoir_15ml', '3', 'Reservoir 12 channel, column 1') # WashBuffer1 reservoir #################################### WashBuffer1_reservoir = ctx.load_labware( 'nalgene_1_reservoir_300000ul', '2', 'Ethanol 80% reservoir') # WashBuffer2 reservoir #################################### WashBuffer2_reservoir = ctx.load_labware( 'nalgene_1_reservoir_300000ul', '11', 'WashBuffer reservoir') # Wash Buffer 1 300ul Deepwell plate ############################################ WashBuffer1_300ul_plate1 = ctx.load_labware( 'kf_96_wellplate_2400ul', '1', 'Wash Buffer 1 Deepwell plate 1') # Wash Buffer 1 300ul Deepwell plate ############################################ WashBuffer1_300ul_plate2 = ctx.load_labware( 'kf_96_wellplate_2400ul', '4', 'Wash Buffer 1 Deepwell plate 2') # Wash Buffer 2 450ul Deepwell plate ############################################ WashBuffer2_450ul_plate1 = ctx.load_labware( 'kf_96_wellplate_2400ul', '7', 'Wash Buffer 2 Deepwell plate 1') # Wash Buffer 2 450ul Deepwell plate ############################################ WashBuffer2_450ul_plate2 = ctx.load_labware( 'kf_96_wellplate_2400ul', '10', 'Wash Buffer 2 Deepwell plate 2') # Elution Deepwell plate ############################################ ElutionBuffer_50ul_plate = ctx.load_labware( 'kingfisher_std_96_wellplate_550ul', '6', 'Elution Buffer 50 ul STD plate') #################################### # Load tip_racks tips300 = [ctx.load_labware('opentrons_96_tiprack_300ul', slot, '200µl filter tiprack') for slot in ['8']] ################################################################################ # Declare which reagents are in each reservoir as well as deepwell and elution plate WashBuffer1.reagent_reservoir = WashBuffer1_reservoir.wells()[0] WashBuffer2.reagent_reservoir = WashBuffer2_reservoir.wells()[0] ElutionBuffer.reagent_reservoir = reagent_res.rows()[0][0] # columns in destination plates to be filled depending the number of samples wb1plate1_destination = WashBuffer1_300ul_plate1.rows()[0][:num_cols] wb1plate2_destination = WashBuffer1_300ul_plate2.rows()[0][:num_cols] wb2plate1_destination = WashBuffer2_450ul_plate1.rows()[0][:num_cols] wb2plate2_destination = WashBuffer2_450ul_plate2.rows()[0][:num_cols] elutionbuffer_destination = ElutionBuffer_50ul_plate.rows()[0][:num_cols] # pipette m300 = ctx.load_instrument( 'p300_multi_gen2', 'right', tip_racks=tips300) # Load multi pipette # used tip counter and set maximum tips available tip_track = { 'counts': {m300: 0}, 'maxes': {m300: len(tips300)*96} } ############################################################################ # STEP 1 Filling with WashBuffer1 plate 1 ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') wash_buffer_vol = [150, 150] rinse = False # Only first time ######## # Wash buffer dispense for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(wash_buffer_vol): if (i == 0 and j == 0): rinse = True #Rinse only first transfer else: rinse = False move_vol_multichannel(m300, reagent = WashBuffer1, source = WashBuffer1.reagent_reservoir, dest = wb1plate1_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 1, rinse = rinse, disp_height = -2, blow_out = True, touch_tip = True) m300.drop_tip(home_after=True) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2 Filling with WashBuffer1 plate 2 ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') wash_buffer_vol = [150, 150] rinse = False # Only first time ######## # Wash buffer dispense for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(wash_buffer_vol): if (i == 0 and j == 0): rinse = True else: rinse = False move_vol_multichannel(m300, reagent = WashBuffer1, source = WashBuffer1.reagent_reservoir, dest = wb1plate2_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 1, rinse = rinse, disp_height = -2, blow_out = True, touch_tip = True) m300.drop_tip(home_after=True) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 3 Filling with WashBuffer2 plate 1 ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') wash_buffer_vol = [150, 150, 150] rinse = False # Only first time ######## # Wash buffer dispense for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(wash_buffer_vol): if (i == 0 and j == 0): rinse = True else: rinse = False move_vol_multichannel(m300, reagent = WashBuffer2, source = WashBuffer2.reagent_reservoir, dest = wb2plate1_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 1, rinse = rinse, disp_height = -2, blow_out = True, touch_tip = True) m300.drop_tip(home_after=True) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 4 Filling with WashBuffer2 plate 2 ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ethanol_vol = [150, 150, 150] rinse = False # Only first time ######## # Ethanol dispense for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(ethanol_vol): if (i == 0 and j == 0): rinse = True else: rinse = False move_vol_multichannel(m300, reagent = WashBuffer2, source = WashBuffer2.reagent_reservoir, dest = wb2plate2_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 1, rinse = rinse, disp_height = -2, blow_out = True, touch_tip = True) m300.drop_tip(home_after=True) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 5 Transfer Elution buffer ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') # Elution buffer ElutionBuffer_vol = [50] ######## # Water or elution buffer for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for transfer_vol in ElutionBuffer_vol: # Calculate pickup_height based on remaining volume and shape of container [pickup_height, change_col] = calc_height( ElutionBuffer, multi_well_rack_area, transfer_vol * 8) ctx.comment( 'Aspirate from Reservoir column: ' + str(ElutionBuffer.col)) ctx.comment('Pickup height is ' + str(pickup_height)) move_vol_multichannel(m300, reagent = ElutionBuffer, source = ElutionBuffer.reagent_reservoir, dest = elutionbuffer_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol_elutionbuffer, x_offset = x_offset, pickup_height = pickup_height, rinse = False, disp_height = -2, blow_out = True, touch_tip = False) m300.drop_tip(home_after=True) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write('STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program from opentrons.drivers.rpi_drivers import gpio for i in range(3): gpio.set_rail_lights(False) gpio.set_button_light(1, 0, 0) time.sleep(0.3) gpio.set_rail_lights(True) gpio.set_button_light(0, 0, 1) time.sleep(0.3) gpio.set_button_light(0, 1, 0) ctx.comment( 'Finished! \nMove deepwell plates to KingFisher extractor.') ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300])) ctx.comment('Used racks in total: ' + str(tip_track['counts'][m300] / 96))
def run(ctx: protocol_api.ProtocolContext): # Init protocol run run = ProtocolRun(ctx) run.comment("You are about to run %s samples\n STEPS:%s" % (NUM_SAMPLES, steps), add_hash=True) run.pause( "Are you sure the set up is correct? Check the desk before continue") # Define stesp run.add_step( description="Transfer PK A6 - To AW_PLATE Single Slot1 -> Slot2") # 1 run.add_step(description="Transfer MS2 B6 - To AW_PLATE Single 1->2") # 4 run.add_step(description="Transfer Beats 3 - 2 Multi and mix") # 3 # execute avaliaible steps run.init_steps(steps) ################################## # Tube rack tube_rack = ctx.load_labware('opentrons_24_tuberack_nest_1.5ml_screwcap', 4) # Destination plate SLOT 2 if (ctx.is_simulating()): aw_slot = ctx.load_labware( 'opentrons_96_aluminumblock_generic_pcr_strip_200ul', 5) else: aw_slot = ctx.load_labware('axygen_96_wellplate_2000ul', 5) aw_wells = aw_slot.wells()[:NUM_SAMPLES] aw_wells_multi = aw_slot.rows()[0][:num_cols] # Magnetic Beads Pool beads_slot = ctx.load_labware('nest_12_reservoir_15ml', 6) beads_wells_multi = beads_slot.rows()[0][:num_cols] # Mount pippets and set racks # Tipracks20_multi tips20 = ctx.load_labware('opentrons_96_tiprack_20ul', 7) tips300_1 = ctx.load_labware('opentrons_96_filtertiprack_200ul', 8) tips300_2 = ctx.load_labware('opentrons_96_filtertiprack_200ul', 9) run.mount_right_pip('p20_single_gen2', tip_racks=[tips20], capacity=20) run.mount_left_pip('p300_multi_gen2', tip_racks=[tips300_1, tips300_2], capacity=200, multi=True) ############################################################################ # STEP 1: Transfer A6 - To AW_PLATE ############################################################################ if (run.next_step()): run.set_pip("right") # single 20 volumen_move = 5 source = tube_rack.wells("A6")[0] liquid = Reagent( name='Proteinasa K', num_wells=1, # change with num samples flow_rate_aspirate=0.75, # Original 0.5 flow_rate_dispense=3, # Original 1 reagent_reservoir_volume=528, h_cono=4, v_fondo=4 * math.pi * 4**3 / 3) run.pick_up() for dest in aw_wells: [pickup_height, col_change] = run.calc_height(liquid, 4.12 * 4.12 * math.pi, volumen_move) run.move_volume(reagent=liquid, source=source, dest=dest, vol=volumen_move, air_gap_vol=1, pickup_height=pickup_height, disp_height=-10, blow_out=True, post_dispense=True, post_dispense_vol=5) run.drop_tip() run.finish_step() ############################################################################ # STEP 2: Transfer B6 MS2 - To AW_PLATE ############################################################################ if (run.next_step()): run.set_pip("right") # single 20 volumen_move = 5 source = tube_rack.wells("B6")[0] liquid = Reagent( name='MS2', num_wells=1, # change with num samples delay=0, flow_rate_aspirate=3, # Original 0.5 flow_rate_dispense=3, # Original 1 flow_rate_aspirate_mix=15, flow_rate_dispense_mix=25, reagent_reservoir_volume=528, h_cono=4, v_fondo=4 * math.pi * 4**3 / 3) run.pick_up() for dest in aw_wells: [pickup_height, col_change] = run.calc_height(liquid, 4.12 * 4.12 * math.pi, volumen_move) run.move_volume(reagent=liquid, source=source, dest=dest, vol=volumen_move, air_gap_vol=1, pickup_height=pickup_height, disp_height=-10, blow_out=True, post_dispense=True, post_dispense_vol=5) run.drop_tip() run.finish_step() ############################################################################ # STEP 3: Slot 3 -2 beats_PK AW ############################################################################ if (run.next_step()): ############################################################################ # Light flash end of program run.set_pip("left") # p300 multi volume = 275 beads = Reagent(name='Magnetic beads', flow_rate_aspirate=0.5, flow_rate_dispense=0.5, flow_rate_dispense_mix=4, flow_rate_aspirate_mix=4, rinse=True, delay=2, reagent_reservoir_volume=30000, num_wells=3, h_cono=1.95, v_fondo=695, rinse_loops=3) air_gap_vol = 5 disposal_height = -5 pickup_height = 1 beads.reagent_reservoir = beads_slot.rows()[0][0:3] pool_area = 8.3 * 71.1 for destination in aw_wells_multi: run.pick_up() vol = 150 vol_min = 1000 [pickup_height, col_change] = run.calc_height(beads, pool_area, vol * 8, extra_volume=vol_min) run.move_volume(reagent=beads, source=beads.reagent_reservoir[beads.col], dest=destination, vol=vol, air_gap_vol=air_gap_vol, pickup_height=pickup_height, disp_height=disposal_height, rinse=True, blow_out=True) run.change_tip() vol = 125 [pickup_height, col_change] = run.calc_height(beads, pool_area, vol * 8, extra_volume=vol_min) run.move_volume(reagent=beads, source=beads.reagent_reservoir[beads.col], dest=destination, vol=vol, air_gap_vol=air_gap_vol, pickup_height=pickup_height, disp_height=disposal_height, rinse=True, blow_out=True) run.custom_mix(beads, location=destination, vol=150, rounds=3, blow_out=True, mix_height=0) run.drop_tip() run.finish_step() run.log_steps_time() run.blink() for c in robot.commands(): ctx.comment(c) ctx.comment('Finished! \nMove plate to PCR')
def run(ctx: protocol_api.ProtocolContext): STEP = 0 STEPS = { # Dictionary with STEP activation, description and times 1: { 'Execute': True, 'description': 'Add Lysis buffer (' + str(volume_control) + 'ul)' }, 2: { 'Execute': True, 'description': 'Add samples (' + str(volume_sample) + 'ul)' } } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time if not ctx.is_simulating(): folder_path = '/var/lib/jupyter/notebooks/' + run_id if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/StationA_time_log.txt' file_path2 = folder_path + '/StationA_tips_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling='none'): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused = [] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells # Reagents and their characteristics BUFFER = Reagent(name='TNA+Beads+Isopropanol', flow_rate_aspirate=1, flow_rate_dispense=1, rinse=False, delay=0, reagent_reservoir_volume=50000, num_wells=1, h_cono=(v_cone_falcon * 3 / falcon_cross_section_area), v_fondo=v_cone_falcon) Samples = Reagent( name='Samples', flow_rate_aspirate=1, flow_rate_dispense=1, rinse=False, delay=0, reagent_reservoir_volume=700 * 24, num_wells=24, # num_cols comes from available columns h_cono=4, v_fondo=4 * area_section_sample * diameter_sample * 0.5 / 3) # Sphere BUFFER.vol_well = BUFFER.vol_well_original Samples.vol_well = 700 ################## # Custom functions def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location=source, vol=vol, rounds=2, blow_out=True, mix_height=0, x_offset=x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x=x_offset[0])) pipet.aspirate(vol, s) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z=-2), rate=reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z=disp_height).move(Point(x=x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate=reagent.flow_rate_dispense) # dispense all ctx.delay( seconds=reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z=-2)) if touch_tip == True: pipet.touch_tip(speed=20, v_offset=-5) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height=3): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height == 0: mix_height = 3 pipet.aspirate(1, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out def calc_height(reagent, cross_section_area, aspirate_volume, min_height=0.5): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if reagent.vol_well < aspirate_volume: reagent.unused.append(reagent.vol_well) ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change def generate_source_table(source): ''' Concatenate the wells frome the different origin racks ''' for rack_number in range(len(source)): if rack_number == 0: s = source[rack_number].wells() else: s = s + source[rack_number].wells() return s ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 pip.pick_up_tip() #################################### # load labware and modules #################################### # Load Sample racks if NUM_SAMPLES < 96: rack_num = math.ceil(NUM_SAMPLES / 24) ctx.comment('Used source racks are ' + str(rack_num)) samples_last_rack = NUM_SAMPLES - rack_num * 24 else: rack_num = 4 source_racks = [ ctx.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', slot, 'source tuberack with screwcap' + str(i + 1)) for i, slot in enumerate(['4', '1', '6', '3'][:rack_num]) ] ################################## # Destination plate dest_plate = ctx.load_labware('abgene_96_wellplate_800ul', '5', 'ABGENE 96 Well Plate 800 µL') ############################################ # tempdeck #tempdeck = ctx.load_module('tempdeck', '1') #tempdeck.set_temperature(temperature) ################################## # Cooled reagents in tempdeck #reagents = tempdeck.load_labware( #'opentrons_24_aluminumblock_generic_2ml_screwcap', #'cooled reagent tubes') reagents = ctx.load_labware('opentrons_6_tuberack_falcon_50ml_conical', '7', 'Lysis buffer tuberack in Falcon tube') #################################### # Load tip_racks tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack') for slot in ['11'] ] tips1000 = [ ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot, '1000µl filter tiprack') for slot in ['10'] ] ################################################################################ # Declare which reagents are in each reservoir as well as deepwell and elution plate BUFFER.reagent_reservoir = reagents.wells()[0] # setup samples and destinations sample_sources_full = generate_source_table(source_racks) sample_sources = sample_sources_full[:NUM_SAMPLES] destinations = dest_plate.wells()[:NUM_SAMPLES] p20 = ctx.load_instrument('p20_single_gen2', mount='right', tip_racks=tips20) p1000 = ctx.load_instrument('p1000_single_gen2', 'left', tip_racks=tips1000) # load P1000 pipette # used tip counter and set maximum tips available tip_track = { 'counts': { p20: 0, p1000: 0 }, 'maxes': { p20: len(tips20) * 96, p1000: len(tips1000) * 96 } } ############################################################################ # STEP 1: Add TNA ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') # Transfer parameters start = datetime.now() if not p1000.hw_pipette['has_tip']: pick_up(p1000) for d in destinations: # Calculate pickup_height based on remaining volume and shape of container [pickup_height, change_col] = calc_height(BUFFER, falcon_cross_section_area, volume_control) move_vol_multichannel(p1000, reagent=BUFFER, source=BUFFER.reagent_reservoir, dest=d, vol=volume_control, air_gap_vol=air_gap_vol_ci, x_offset=x_offset, pickup_height=pickup_height, rinse=BUFFER.rinse, disp_height=height_control, blow_out=True, touch_tip=True) # Mix the sample AFTER dispensing using 15µl of volume #custom_mix(p20, reagent = Control_I, location = d, vol = 15, rounds = 4, blow_out = True, mix_height = 15) #Do not drop tip as it is not contaminated #p1000.drop_tip() #tip_track['counts'][p20]+=1 #Time statistics end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2: Add Samples ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') # Transfer parameters start = datetime.now() for s, d in zip(sample_sources, destinations): if not p1000.hw_pipette['has_tip']: pick_up(p1000) # Mix the sample BEFORE dispensing #custom_mix(p1000, reagent = Samples, location = s, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15) move_vol_multichannel(p1000, reagent=Samples, source=s, dest=d, vol=volume_sample, air_gap_vol=air_gap_vol_sample, x_offset=x_offset, pickup_height=1, rinse=Samples.rinse, disp_height=-10, blow_out=True, touch_tip=True) # Mix the sample AFTER dispensing using 15µl of volume custom_mix(p1000, reagent=Samples, location=d, vol=800, rounds=2, blow_out=False, mix_height=10) p1000.drop_tip() tip_track['counts'][p1000] += 1 # Time statistics end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write( 'STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() with open(file_path2, 'w') as f2: f2.write('pipette\ttip_count\n') for key in tip_track['counts'].keys(): row = str(key) f.write(str(key) + '\t' + format(tip_track['counts'][key])) f2.close() ############################################################################ # Light flash end of program from opentrons.drivers.rpi_drivers import gpio #if not ctx.is_simulating(): #os.system('mpg123 -f -14000 /var/lib/jupyter/notebooks/lionking.mp3') for i in range(3): gpio.set_rail_lights(False) gpio.set_button_light(1, 0, 0) time.sleep(0.3) gpio.set_rail_lights(True) gpio.set_button_light(0, 0, 1) time.sleep(0.3) gpio.set_button_light(0, 1, 0) ctx.comment( 'Finished! \nMove deepwell plate (slot 5) to Station B for extraction protocol' ) ctx.comment('Used p1000 tips in total: ' + str(tip_track['counts'][p1000])) ctx.comment('Used p1000 racks in total: ' + str(tip_track['counts'][p1000] / 96)) ctx.comment('Used p20 tips in total: ' + str(tip_track['counts'][p20])) ctx.comment('Used p20 racks in total: ' + str(tip_track['counts'][p20] / 96))
def run(ctx: protocol_api.ProtocolContext): STEP = 0 STEPS = { # Dictionary with STEP activation, description and times 1: { 'Execute': True, 'description': 'Mezclar y dispensar muestras (' + str(VOLUME_SAMPLE) + 'ul)' } } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time if not ctx.is_simulating(): folder_path = '/var/lib/jupyter/notebooks/' + run_id if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/StationA_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, delay): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.delay = delay # Reagents and their characteristics Samples = Reagent(name='Samples', flow_rate_aspirate=25, flow_rate_dispense=100, rinse=False, delay=0) ctx.comment(' ') ctx.comment('###############################################') ctx.comment('CONTROLES: ' + str(NUM_CONTROL_SPACES)) ctx.comment('MUESTRAS: ' + str(NUM_REAL_SAMPLES)) ctx.comment('###############################################') ctx.comment(' ') ################## # Custom functions def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip, shakes=0, shake_v_offset=-45): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location=source, vol=vol, rounds=2, blow_out=True, mix_height=0, x_offset=x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x=x_offset[0])) pipet.aspirate(vol, s, rate=reagent.flow_rate_aspirate) # aspirate liquid if shakes > 0: shake_pipet(pipet, v_offset=shake_v_offset) if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z=-2), rate=reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z=disp_height).move(Point(x=x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate=reagent.flow_rate_dispense) # dispense all ctx.delay( seconds=reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z=disp_height)) if touch_tip == True: pipet.touch_tip(speed=20, v_offset=-10) if air_gap_vol != 0: pipet.air_gap(air_gap_vol, height=disp_height) #air gap def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height=5): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height <= 0: mix_height = 3 pipet.aspirate(1, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out def generate_source_table(source): ''' Concatenate the wells frome the different origin racks ''' num_cols = math.ceil(num_samples / 8) s = [] for i in range(num_cols): if i < 6: s += source[0].columns()[i] + source[1].columns()[i] else: s += source[2].columns()[i - 6] + source[3].columns()[i - 6] return s def shake_pipet(pipet, rounds=2, speed=100, v_offset=0): ctx.comment("Shaking " + str(rounds) + " rounds.") for i in range(rounds): pipet.touch_tip(speed=speed, radius=0.1, v_offset=v_offset) ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 pip.pick_up_tip() def run_quiet_process(command): subprocess.check_output('{} &> /dev/null'.format(command), shell=True) def play_sound(filename): print('Speaker') print('Next\t--> CTRL-C') try: run_quiet_process('mpg123 {}'.format(path_sounds + filename + '.mp3')) run_quiet_process('mpg123 {}'.format(path_sounds + sonido_defecto)) run_quiet_process('mpg123 {}'.format(path_sounds + filename + '.mp3')) except KeyboardInterrupt: pass print() def start_run(): ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Empezando protocolo') if PHOTOSENSITIVE == False: ctx._hw_manager.hardware.set_lights(button=True, rails=True) else: ctx._hw_manager.hardware.set_lights(button=True, rails=False) now = datetime.now() # dd/mm/YY H:M:S start_time = now.strftime("%Y/%m/%d %H:%M:%S") return start_time def finish_run(switch_off_lights=False): ctx.comment('###############################################') ctx.comment('Protocolo finalizado') ctx.comment(' ') #Set light color to blue ctx._hw_manager.hardware.set_lights(button=True, rails=False) now = datetime.now() # dd/mm/YY H:M:S finish_time = now.strftime("%Y/%m/%d %H:%M:%S") if PHOTOSENSITIVE == False: for i in range(10): ctx._hw_manager.hardware.set_lights(button=False, rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=True, rails=True) time.sleep(0.3) else: for i in range(10): ctx._hw_manager.hardware.set_lights(button=False, rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=True, rails=False) time.sleep(0.3) if switch_off_lights: ctx._hw_manager.hardware.set_lights(button=True, rails=False) used_tips = tip_track['num_refills'][p1000] * 96 * len( p1000.tip_racks) + tip_track['counts'][p1000] ctx.comment('Puntas de 1000 ul utilizadas: ' + str(used_tips) + ' (' + str(round(used_tips / 96, 2)) + ' caja(s))') ctx.comment('###############################################') if not ctx.is_simulating(): for i in range(SOUND_NUM_PLAYS): if i > 0: time.sleep(60) play_sound('finished_process_esp') return finish_time #################################### # load labware and modules #################################### # Load Sample racks if num_samples <= 48: rack_num = 2 ctx.comment('Used source racks are ' + str(rack_num)) else: rack_num = 4 #source_racks = [ctx.load_labware( # 'pcr_24_wellplate_13200ul', slot, # 'source tuberack with snapcap' + str(i + 1)) for i, slot in enumerate(['4', '1', '5', '2'][:rack_num]) #] source_racks = [ ctx.load_labware('vitrobiocomma_24_tuberack_15000ul_1', '4', 'samples grid 1'), ctx.load_labware('vitrobiocomma_24_tuberack_15000ul_2', '1', 'samples grid 2'), ctx.load_labware('vitrobiocomma_24_tuberack_15000ul_3', '5', 'samples grid 3'), ctx.load_labware('vitrobiocomma_24_tuberack_15000ul_4', '2', 'samples grid 4') ] ################################## # Destination plate dest_plate = ctx.load_labware('nest_96_wellplate_2ml_deep', '6', 'NEST 96 Deepwell Plate 2mL') #################################### # Load tip_racks tips1000 = [ ctx.load_labware( 'opentrons_96_filtertiprack_1000ul' if OPENTRONS_TIPS else 'geb_96_tiprack_1000ul', slot, '1000µl filter tiprack') for slot in ['8'] ] ################################################################################ # setup samples and destinations sample_sources_full = generate_source_table(source_racks) sample_sources = sample_sources_full[NUM_CONTROL_SPACES:num_samples] destinations = dest_plate.wells()[NUM_CONTROL_SPACES:num_samples] p1000 = ctx.load_instrument('p1000_single_gen2', 'left', tip_racks=tips1000) # load P1000 pipette # used tip counter and set maximum tips available tip_track = { 'counts': { p1000: 0 }, 'maxes': { p1000: 96 * len(p1000.tip_racks) }, #96 tips per tiprack * number or tipracks in the layout 'num_refills': { p1000: 0 }, 'tips': { p1000: [tip for rack in tips1000 for tip in rack.rows()[0]] } } start_run() ############################################################################ # STEP 1: MIX AND MOVE SAMPLES ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') start = datetime.now() for s, d in zip(sample_sources, destinations): if not p1000.hw_pipette['has_tip']: pick_up(p1000) # Mix the sample BEFORE dispensing if NUM_MIXES > 0: custom_mix(p1000, reagent=Samples, location=s, vol=volume_mix, rounds=NUM_MIXES, blow_out=True, mix_height=5, x_offset=x_offset) move_vol_multichannel(p1000, reagent=Samples, source=s, dest=d, vol=VOLUME_SAMPLE, air_gap_vol=air_gap_vol_sample, x_offset=x_offset, pickup_height=8, rinse=Samples.rinse, disp_height=-10, blow_out=True, touch_tip=False, shakes=SAMPLE_SAKES) p1000.drop_tip(home_after=False) tip_track['counts'][p1000] += 1 # Time statistics end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write( 'STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program # from opentrons.drivers.rpi_drivers import gpio finish_run(switch_off_lights)
def run(ctx: protocol_api.ProtocolContext): from opentrons.drivers.rpi_drivers import gpio ctx.comment('Actual used columns: ' + str(num_cols)) # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: { 'Execute': True, 'description': 'Add MS2' }, 2: { 'Execute': True, 'description': 'Transfer beads' } } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 folder_path = '/var/lib/jupyter/notebooks' if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/Station_KB_sample_prep_viral_path2_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling='none'): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused = [] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells # Reagents and their characteristics Sample = Reagent(name='Sample', flow_rate_aspirate=1, flow_rate_dispense=1, rinse=True, delay=0, reagent_reservoir_volume=460 * 96, num_wells=96, h_cono=1.95, v_fondo=35) Beads = Reagent(name='Magnetic beads and binding solution', flow_rate_aspirate=0.75, flow_rate_dispense=0.75, rinse=True, num_wells=math.ceil(NUM_SAMPLES / 32), delay=2, reagent_reservoir_volume=550 * 8 * num_cols * 1.1, h_cono=1.95, v_fondo=695) # Prismatic MS = Reagent( name='MS2', flow_rate_aspirate=1, flow_rate_dispense=1, rinse=False, reagent_reservoir_volume=total_MS_volume, num_wells=8, delay=0, h_cono=h_cone, v_fondo=volume_cone # V cono ) # Prismatic) Sample.vol_well = Sample.reagent_reservoir_volume Beads.vol_well = Beads.vol_well_original MS.vol_well = MS.reagent_reservoir_volume def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location=source, vol=vol, rounds=2, blow_out=True, mix_height=0, x_offset=x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x=x_offset[0])) pipet.aspirate(vol, s, rate=reagent.flow_rate_aspirate) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z=-2), rate=reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z=disp_height).move(Point(x=x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate=reagent.flow_rate_dispense) # dispense all ctx.delay( seconds=reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z=-2)) if touch_tip == True: pipet.touch_tip(speed=20, v_offset=-5) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height=3): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height == 0: mix_height = 3 pipet.aspirate(1, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out def calc_height(reagent, cross_section_area, aspirate_volume, min_height=0.5, extra_volume=50): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if reagent.vol_well < aspirate_volume + extra_volume: reagent.unused.append(reagent.vol_well) ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change def divide_destinations(l, n): # Divide the list of destinations in size n lists. for i in range(0, len(l), n): yield l[i:i + n] #################################### # load labware and modules # 12 well rack reagent_res = ctx.load_labware('perkinelmer_12_reservoir_21000ul', '2', 'Reagent deepwell plate') ################################## # Sample prep plate - final plate, goes to Kingfisher sample_plate = ctx.load_labware('kf_96_wellplate_2400ul', '1', 'KF 96 Well 2400ul elution plate') ############################################ # tempdeck tempdeck = ctx.load_module('tempdeck', '4') tempdeck.set_temperature(temperature) ################################## # MS plate - plate with a column containing the internal control MS ms_plate = tempdeck.load_labware('vwr_96_wellplate_200ul_alum_opentrons', 'pcr plate with MS control') #################################### # load labware and modules # 24 well rack aluminium opentrons #tuberack = ctx.load_labware( # 'opentrons_24_aluminumblock_generic_2ml_screwcap', '3', # 'Bloque Aluminio opentrons 24 Screwcaps') ################################## # Load Tipracks tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot) for slot in ['6'] ] tips200 = [ ctx.load_labware('opentrons_96_filtertiprack_200ul', slot) for slot in ['3'] ] # pipettes. P1000 currently deactivated m300 = ctx.load_instrument('p300_multi_gen2', 'right', tip_racks=tips200) # Load multi pipette m20 = ctx.load_instrument('p20_multi_gen2', 'left', tip_racks=tips20) #p20 = ctx.load_instrument( # 'p20_single_gen2', 'left', tip_racks=tips20) # load P1000 pipette tip_track = { 'counts': { m300: 0, m20: 0 }, 'maxes': { m300: len(tips200) * 96, m20: len(tips20) * 96 } } # Divide destination wells in small groups for P300 pipette # Declare which reagents are in each reservoir as well as deepwell and elution plate #destinations = list(divide_destinations(sample_plate.wells()[:NUM_SAMPLES], size_transfer)) Beads.reagent_reservoir = reagent_res.rows( )[0][:Beads.num_wells] # 1 row, 4 columns (first ones) work_destinations = sample_plate.wells()[:NUM_SAMPLES] work_destinations_cols = sample_plate.rows()[0][:num_cols] ms_origins = ms_plate.rows()[0][0] # 1 row, 1 columns ############################################################################ # STEP 1: Transfer MS ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('ms_wells') #Loop over defined wells for d in work_destinations_cols: m20.pick_up_tip() #Source samples move_vol_multichannel(m20, reagent=MS, source=ms_origins, dest=d, vol=MS_vol, air_gap_vol=air_gap_vol_MS, x_offset=x_offset, pickup_height=0.2, disp_height=-35, rinse=False, blow_out=True, touch_tip=True) m20.drop_tip() tip_track['counts'][m20] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write( 'STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # STEP 2: TRANSFER BEADS ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: # Transfer parameters start = datetime.now() ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') beads_transfer_vol = [150, 150, 150, 100] # 4 rounds of different volumes rinse = True for i in range(num_cols): if not m300.hw_pipette['has_tip']: m300.pick_up_tip() for j, transfer_vol in enumerate(beads_transfer_vol): # Calculate pickup_height based on remaining volume and shape of container [pickup_height, change_col] = calc_height(Beads, multi_well_rack_area, transfer_vol * 8, min_height=1) if change_col == True: # If we switch column because there is not enough volume left in current reservoir column we mix new column ctx.comment('Mixing new reservoir column: ' + str(Beads.col)) custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col], vol=170, rounds=10, blow_out=False, mix_height=0, x_offset=x_offset) ctx.comment('Aspirate from reservoir column: ' + str(Beads.col)) ctx.comment('Pickup height is ' + str(pickup_height)) if j != 0: rinse = False move_vol_multichannel( m300, reagent=Beads, source=Beads.reagent_reservoir[Beads.col], dest=work_destinations_cols[i], vol=transfer_vol, air_gap_vol=air_gap_vol, x_offset=x_offset, pickup_height=pickup_height, disp_height=-2, rinse=rinse, blow_out=False, touch_tip=False) m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write( 'STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program gpio.set_rail_lights(False) time.sleep(2) #os.system('mpg123 -f -8000 /var/lib/jupyter/notebooks/toreador.mp3 &') for i in range(3): gpio.set_rail_lights(False) gpio.set_button_light(1, 0, 0) time.sleep(0.3) gpio.set_rail_lights(True) gpio.set_button_light(0, 0, 1) time.sleep(0.3) gpio.set_button_light(0, 1, 0) ctx.comment('Finished! \nMove plate to KingFisher')
def run(ctx: protocol_api.ProtocolContext): import os ctx.comment('Actual used columns: ' + str(num_cols)) # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: {'Execute': False, 'description': 'Make MMIX'}, 2: {'Execute': False, 'description': 'Transfer MMIX with P300'}, 3: {'Execute': True, 'description': 'Transfer MMIX with P20'}, 4: {'Execute': True, 'description': 'Clean up NC and PC wells'}, 5: {'Execute': True, 'description': 'Transfer elution'}, 6: {'Execute': True, 'description': 'Transfer PC'}, 7: {'Execute': True, 'description': 'Transfer NC'} } if STEPS[2]['Execute']==True: STEPS[3]['Execute']=False # just to make sure if P300 is being used for the MMIX, do not load P20 single for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time folder_path = '/var/lib/jupyter/notebooks/'+str(run_id) if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/KC_qPCR_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling = 'none'): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused=[] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells # Reagents and their characteristics MMIX = Reagent(name = 'Master Mix', rinse = False, flow_rate_aspirate = 2, flow_rate_dispense = 4, reagent_reservoir_volume = $MMIX_total_volume, # volume_mmix_available, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) NC = Reagent(name = 'Negative control', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, # volume_mmix_available, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) PC = Reagent(name = 'Positive control', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, # volume_mmix_available, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) Elution = Reagent(name='Elution', rinse=False, flow_rate_aspirate = 1, flow_rate_dispense = 2, reagent_reservoir_volume=50, delay=1, num_wells=num_cols, # num_cols comes from available columns h_cono=0, v_fondo=0 ) tackpath = Reagent(name = 'MMIX_multiplex_tackpath', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) covid_assay = Reagent(name = 'Covid19_assay', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) mmix_water = Reagent(name = 'nuclease_free_water', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) component4 = Reagent(name = 'component4', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) component5 = Reagent(name = 'component5', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) component6 = Reagent(name = 'component6', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) component7 = Reagent(name = 'component7', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) component8 = Reagent(name = 'component8', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 1000, num_wells = 1, #change with num samples delay = 0, h_cono = h_cone, v_fondo = volume_cone # V cono ) MMIX.vol_well = MMIX.vol_well_original PC.vol_well = PC.vol_well_original NC.vol_well = NC.vol_well_original Elution.vol_well = Elution.vol_well_original tackpath.vol_well = tackpath.vol_well_original covid_assay.vol_well = covid_assay.vol_well_original mmix_water.vol_well = mmix_water.vol_well_original component4.vol_well = component4.vol_well_original component5.vol_well = component5.vol_well_original component6.vol_well = component6.vol_well_original component7.vol_well = component7.vol_well_original component8.vol_well = component8.vol_well_original ################## Assign class type reactives to a summary MMIX_components=[tackpath, covid_assay, mmix_water, component4, component5, component6, component7, component8] MMIX_components=MMIX_components[:len(MMIX_make[mmix_selection])] ################## ################## # Custom functions def divide_volume(volume,max_vol): num_transfers=math.ceil(volume/max_vol) vol_roundup=math.ceil(volume/num_transfers) last_vol = volume - vol_roundup*(num_transfers-1) vol_list = [vol_roundup for v in range(1,num_transfers)] vol_list.append(last_vol) return vol_list def divide_destinations(l, n): # Divide the list of destinations in size n lists. a=[] for i in range(0, len(l), n): a.append( l[i:i + n]) return a def distribute_custom(pipette, reagent, volume, src, dest, waste_pool, pickup_height, extra_dispensal, disp_height=0): # Custom distribute function that allows for blow_out in different location and adjustement of touch_tip air_gap=10 pipette.aspirate((len(dest) * volume) + extra_dispensal, src.bottom(pickup_height), rate = reagent.flow_rate_aspirate) pipette.touch_tip(speed=20, v_offset=-5) pipette.move_to(src.top(z=5)) pipette.aspirate(air_gap, rate = reagent.flow_rate_aspirate) # air gap for d in dest: pipette.dispense(air_gap, d.top(), rate = reagent.flow_rate_dispense) drop = d.top(z = disp_height) pipette.dispense(volume, drop, rate = reagent.flow_rate_dispense) ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent pipette.move_to(d.top(z=5)) pipette.aspirate(air_gap,d.top(z=5), rate = reagent.flow_rate_aspirate) # 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 move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip, post_dispense=False, post_dispense_vol=20, post_airgap=False, post_airgap_vol=10): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location = source, vol = vol, rounds = 2, blow_out = True, mix_height = 0, x_offset = x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x = x_offset[0])) pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z = -2), rate = reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z = disp_height).move(Point(x = x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate = reagent.flow_rate_dispense) # dispense all ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z = -2)) if post_dispense == True: pipet.dispense(post_dispense_vol, dest.top(z = -2)) if touch_tip == True: pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9) if post_airgap == True: pipet.aspirate(post_airgap_vol, dest.top(z = 5), rate = 2) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height = 3, post_airgap=False, post_airgap_vol=10, post_dispense=False, post_dispense_vol=20,touch_tip=False): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height == 0: mix_height = 3 pipet.aspirate(1, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out if post_dispense == True: pipet.dispense(post_dispense_vol, location.top(z = -2)) if post_airgap == True: pipet.dispense(post_airgap_vol, location.top(z = 5)) if touch_tip == True: pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9) def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.3, extra_volume = 30): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if reagent.vol_well < aspirate_volume + extra_volume: reagent.unused.append(reagent.vol_well) ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change ############################################ # tempdeck tempdeck = ctx.load_module('tempdeck', '1') #temdeck for qpcr samples tempdeck.set_temperature(temperature) tempdeck_two = ctx.load_module('tempdeck', '4') #tempdeck for MMIX, PC and NC tempdeck_two.set_temperature(temperature) #################################### # load labware and modules # 24 well rack tuberack = tempdeck_two.load_labware( 'opentrons_24_aluminumblock_generic_2ml_screwcap', 'Bloque Aluminio opentrons 24 screwcaps 2000 µL ') ################################## # qPCR plate - final plate, goes to PCR qpcr_plate = tempdeck.load_labware( 'abi_fast_qpcr_96_alum_opentrons_100ul', 'chilled qPCR final plate') ################################## # waste reservoir waste_reservoir = ctx.load_labware( 'nest_1_reservoir_195ml', '9', 'waste reservoir') waste = waste_reservoir.wells()[0] # referenced as reservoir ################################## # Sample plate - comes from B source_plate = ctx.load_labware( "kingfisher_std_96_wellplate_550ul", '2', 'chilled KF plate with elutions (alum opentrons)') samples = source_plate.wells()[:NUM_SAMPLES] ################################## # Load Tipracks tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot) for slot in ['5'] ] tips200 = [ ctx.load_labware('opentrons_96_filtertiprack_200ul', slot) for slot in ['6'] ] # pipettes m20 = ctx.load_instrument( 'p20_multi_gen2', mount='left', tip_racks=tips20) if STEPS[2]['Execute']==True: p300 = ctx.load_instrument( 'p300_single_gen2', mount='right', tip_racks=tips200) # used tips counter tip_track = { 'counts': {p300: 0, m20: 0}, 'maxes': {p300: len(tips200) * 96, m20: len(tips20)*96} } else: tips20mmix = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot) for slot in ['8'] ] p20 = ctx.load_instrument( 'p20_single_gen2', mount='right', tip_racks=tips20mmix) # used tips counter tip_track = { 'counts': {p20: 0, m20: 0}, 'maxes': {p20: len(tips200) * 96, m20: len(tips20)*96} } ################################################################################ # Declare which reagents are in each reservoir as well as deepwell and elution plate MMIX.reagent_reservoir = tuberack.rows()[0][:MMIX.num_wells] # 1 row, 2 columns (first ones) MMIX_components_location=tuberack.wells()[MMIX_make_location:(MMIX_make_location + len(MMIX_make[mmix_selection]))] ctx.comment('Wells in: '+ str(tuberack.rows()[0][:MMIX.num_wells]) + ' element: '+str(MMIX.reagent_reservoir[MMIX.col])) PC.reagent_reservoir = tuberack.rows()[1][:1] NC.reagent_reservoir = tuberack.rows()[2][:1] # setup up sample sources and destinations samples = source_plate.wells()[:NUM_SAMPLES] samples_multi = source_plate.rows()[0][:num_cols] pcr_wells = qpcr_plate.wells()[:(NUM_SAMPLES)] pcr_wells_multi = qpcr_plate.rows()[0][:num_cols] pc_well_old = source_plate.wells()[(NUM_SAMPLES-2):(NUM_SAMPLES-1)][0] nc_well_old = source_plate.wells()[(NUM_SAMPLES-1):(NUM_SAMPLES)][0] pc_well = qpcr_plate.wells()[(NUM_SAMPLES-2):(NUM_SAMPLES-1)][0] nc_well = qpcr_plate.wells()[(NUM_SAMPLES-1):(NUM_SAMPLES)][0] # Divide destination wells in small groups for P300 pipette dests = list(divide_destinations(pcr_wells, size_transfer)) ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 if not pip.hw_pipette['has_tip']: pip.pick_up_tip() ########## ############################################################################ # STEP 1: Make Master MIX ############################################################################ ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() # Check if among the pipettes, p300_single is installed used_vol=[] ctx.comment('#######################################################') ctx.comment('Selected MMIX: '+MMIX_available[mmix_selection]) ctx.comment('#######################################################') for i,[source, vol] in enumerate(zip(MMIX_components_location, MMIX_make[mmix_selection])): pick_up(p300) ctx.comment('#######################################################') ctx.comment('Add component: '+MMIX_components[i].name) ctx.comment('#######################################################') if (vol + air_gap_vol) > pipette_allowed_capacity: # because 200ul is the maximum volume of the tip we will choose 180 # calculate what volume should be transferred in each step vol_list=divide_volume(vol, pipette_allowed_capacity) for vol in vol_list: move_vol_multichannel(p300, reagent=MMIX_components[i], source=source, dest=MMIX.reagent_reservoir[0], vol=vol, air_gap_vol=air_gap_vol, x_offset = x_offset,pickup_height=1, # should be changed with picku_up_height calculation rinse=False, disp_height=-10,blow_out=True, touch_tip=True) else: move_vol_multichannel(p300, reagent=MMIX_components[i], source=source, dest=MMIX.reagent_reservoir[0], vol=vol, air_gap_vol=air_gap_vol, x_offset=x_offset, pickup_height=1, # should be changed with picku_up_height calculation rinse=False, disp_height=-10,blow_out=True, touch_tip=True) if i+1<len(MMIX_components): p300.drop_tip() else: ctx.comment('#######################################################') ctx.comment('Final mix') ctx.comment('#######################################################') custom_mix(p300, reagent = MMIX, location = MMIX.reagent_reservoir[0], vol = 180, rounds = 5, blow_out = True, mix_height = 2, x_offset = x_offset) p300.drop_tip() tip_track['counts'][p300]+=1 end = datetime.now() time_taken = (end - start) ctx.comment('#######################################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) ctx.comment('#######################################################') STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2: Transfer Master MIX with P300 ############################################################################ ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() p300.pick_up_tip() for dest in pcr_wells: [pickup_height, col_change] = calc_height(MMIX, area_section_screwcap, volume_mmix) move_vol_multichannel(p300, reagent = MMIX, source = MMIX.reagent_reservoir[MMIX.col], dest = dest, vol = volume_mmix, air_gap_vol = air_gap_mmix, x_offset = x_offset, pickup_height = pickup_height, disp_height = -10, rinse = False, blow_out=True, touch_tip=False) #used_vol.append(used_vol_temp) p300.drop_tip() tip_track['counts'][p300]+=1 #MMIX.unused_two = MMIX.vol_well end = datetime.now() time_taken = (end - start) ctx.comment('#######################################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) ctx.comment('#######################################################') STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 3: Transfer Master MIX with P20 ############################################################################ ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() p20.pick_up_tip() for dest in pcr_wells: [pickup_height, col_change] = calc_height(MMIX, area_section_screwcap, volume_mmix) move_vol_multichannel(p20, reagent = MMIX, source = MMIX.reagent_reservoir[MMIX.col], dest = dest, vol = volume_mmix, air_gap_vol = 0, x_offset = x_offset, pickup_height = pickup_height, disp_height = -10, rinse = False, blow_out=True, touch_tip=True) #used_vol.append(used_vol_temp) p20.drop_tip() tip_track['counts'][p20]+=1 #MMIX.unused_two = MMIX.vol_well end = datetime.now() time_taken = (end - start) ctx.comment('#######################################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) ctx.comment('#######################################################') STEPS[STEP]['Time:'] = str(time_taken) ctx.pause('Put samples please') ############################################################################ # STEP 3: Clean up PC and NC well ############################################################################ ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() clean_up_wells=[pc_well_old,nc_well_old] p20.pick_up_tip() for src in clean_up_wells: for i in range(3): move_vol_multichannel(p20, reagent = PC, source = src, dest = waste, vol = 20, air_gap_vol = 0, x_offset = [0,0], pickup_height = 0.2, disp_height = 5, rinse = False, blow_out=True, touch_tip=False,post_airgap=False, post_airgap_vol=0) p20.drop_tip() tip_track['counts'][p20]+=1 #MMIX.unused_two = MMIX.vol_well end = datetime.now() time_taken = (end - start) ctx.comment('#######################################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) ctx.comment('#######################################################') STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 4: TRANSFER Samples ############################################################################ ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('pcr_wells') #Loop over defined wells for s, d in zip(samples_multi, pcr_wells_multi): m20.pick_up_tip() #Source samples move_vol_multichannel(m20, reagent = Elution, source = s, dest = d, vol = volume_sample, air_gap_vol = air_gap_sample, x_offset = x_offset, pickup_height = 0.3, disp_height = -10, rinse = False, blow_out=True, touch_tip=False, post_airgap=False) # Mixing custom_mix(m20, Elution, d, vol=5, rounds=2, blow_out=True, mix_height=6, post_dispense=True, source_height=0.5, x_offset=[0,0],touch_tip=True) m20.drop_tip() tip_track['counts'][m20]+=8 end = datetime.now() time_taken = (end - start) ctx.comment('#######################################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) ctx.comment('#######################################################') STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 5: Transfer PC with P20 ############################################################################ ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() p20.pick_up_tip() #[pickup_height, col_change] = calc_height(PC, area_section_screwcap, volume_mmix) move_vol_multichannel(p20, reagent = PC, source = PC.reagent_reservoir[PC.col], dest = pc_well, vol = volume_pc, air_gap_vol = 0, x_offset = x_offset, pickup_height = 0.2, disp_height = -10, rinse = False, blow_out=True, touch_tip=True) p20.drop_tip(home_after=False) tip_track['counts'][p20]+=1 end = datetime.now() time_taken = (end - start) ctx.comment('#######################################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) ctx.comment('#######################################################') STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 6: Transfer NC with P20 ############################################################################ ctx._hw_manager.hardware.set_lights(rails=False) # set lights off when using MMIX STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() p20.pick_up_tip() move_vol_multichannel(p20, reagent = NC, source = NC.reagent_reservoir[NC.col], dest = nc_well, vol = volume_nc, air_gap_vol = 0, x_offset = x_offset, pickup_height = 0.2, disp_height = -10, rinse = False, blow_out=True, touch_tip=True) p20.drop_tip(home_after=False) tip_track['counts'][p20]+=1 #MMIX.unused_two = MMIX.vol_well end = datetime.now() time_taken = (end - start) ctx.comment('#######################################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) ctx.comment('#######################################################') STEPS[STEP]['Time:'] = str(time_taken) tempdeck.deactivate() tempdeck_two.deactivate() ############################################################################ # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write('STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program ctx.home() time.sleep(2) import os #os.system('mpg123 -f -8000 /etc/audio/speaker-test.mp3 &') '''if STEPS[1]['Execute'] == True: total_used_vol = np.sum(used_vol) total_needed_volume = total_used_vol ctx.comment('Total Master Mix used volume is: ' + str(total_used_vol) + '\u03BCl.') ctx.comment('Needed Master Mix volume is ' + str(total_needed_volume + extra_dispensal*len(dests)) +'\u03BCl') ctx.comment('Used Master Mix volumes per run are: ' + str(used_vol) + '\u03BCl.') ctx.comment('Master Mix Volume remaining in tubes is: ' + format(np.sum(MMIX.unused)+extra_dispensal*len(dests)+MMIX.vol_well) + '\u03BCl.') ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300])) ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96))''' if (STEPS[3]['Execute'] == True & STEPS[4]['Execute'] == True): ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][m20]+tip_track['counts'][p20])) ctx.comment('20 ul Used racks in total: ' + str((tip_track['counts'][m20]+tip_track['counts'][p20]) / 96)) if (STEPS[2]['Execute'] == True & STEPS[4]['Execute'] == True): ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300])) ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96)) ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][m20])) ctx.comment('20 ul Used racks in total: ' + str((tip_track['counts'][m20] / 96))) if (STEPS[2]['Execute'] == True & STEPS[5]['Execute'] == False): ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300])) ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96)) if (STEPS[3]['Execute'] == False & STEPS[4]['Execute'] == True): ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][m20])) ctx.comment('20 ul Used racks in total: ' + str((tip_track['counts'][m20] / 96))) ctx.comment('Finished! \nMove plate to PCR')
def run(ctx: protocol_api.ProtocolContext): ctx.comment('Actual used columns: ' + str(num_cols)) # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: { 'Execute': True, 'description': 'Transfer Mmix' }, 2: { 'Execute': True, 'description': 'Transfer samples' }, 3: { 'Execute': True, 'description': 'Transfer negative control' }, 4: { 'Execute': True, 'description': 'Transfer positive control' } } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time folder_path = '/var/lib/jupyter/notebooks' + run_id if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/Station_C_Vitro_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling='none'): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused = [] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells # Reagents and their characteristics Mmix = Reagent( name='Mmix', rinse=False, flow_rate_aspirate=3, flow_rate_dispense=3, reagent_reservoir_volume=1800, num_wells=1, #change with num samples delay=0, h_cono=h_cone, v_fondo=volume_cone # V cono ) Samples = Reagent( name='Samples', rinse=False, flow_rate_aspirate=1, flow_rate_dispense=1, reagent_reservoir_volume=50, delay=0, num_wells=num_cols, # num_cols comes from available columns h_cono=0, v_fondo=0) Mmix.vol_well = Mmix.vol_well_original Samples.vol_well = Samples.vol_well_original ################## # Custom functions def divide_volume(volume, max_vol): num_transfers = math.ceil(volume / max_vol) vol_roundup = math.ceil(volume / num_transfers) last_vol = volume - vol_roundup * (num_transfers - 1) vol_list = [vol_roundup for v in range(1, num_transfers)] vol_list.append(last_vol) return vol_list def divide_destinations(l, n): # Divide the list of destinations in size n lists. for i in range(0, len(l), n): yield l[i:i + n] 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 move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location=source, vol=vol, rounds=2, blow_out=True, mix_height=0, x_offset=x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x=x_offset[0])) pipet.aspirate(vol, s) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z=-2), rate=reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z=disp_height).move(Point(x=x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate=reagent.flow_rate_dispense) # dispense all ctx.delay( seconds=reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z=-2)) if touch_tip == True: pipet.touch_tip(speed=20, v_offset=-5, radius=0.5) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height=3): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height <= 0: mix_height = 3 pipet.aspirate(1, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out def calc_height(reagent, cross_section_area, aspirate_volume, min_height=0.5): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if reagent.vol_well < aspirate_volume: reagent.unused.append(reagent.vol_well) ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change #################################### # load labware and modules # 24 well rack tuberack = ctx.load_labware( 'opentrons_24_aluminumblock_generic_2ml_screwcap', '2', 'Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap') ############################################ # tempdecks tempdeck_orig = ctx.load_module('Temperature Module Gen2', '4') tempdeck_dest = ctx.load_module('Temperature Module Gen2', '1') if SET_TEMP_ON_SLOT_4: tempdeck_orig.set_temperature(TEMPERATURE_SLOT_4) if SET_TEMP_ON_SLOT_1: tempdeck_dest.set_temperature(TEMPERATURE_SLOT_1) ################################## # Sample plate - comes from B source_plate = tempdeck_orig.load_labware( 'kingfisher_96_aluminumblock_200ul', 'Kingfisher 96 Aluminum Block 200 uL') samples = source_plate.wells()[:NUM_SAMPLES] ################################## # qPCR plate - final plate, goes to PCR qpcr_plate = tempdeck_dest.load_labware( 'opentrons_96_aluminumblock_nest_wellplate_100ul', 'Opentrons 96 Well Aluminum Block with NEST Well Plate 100 uL') ################################## # Load Tipracks tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot) for slot in ['5'] ] tips200 = [ ctx.load_labware('opentrons_96_filtertiprack_200ul', slot) for slot in ['3'] ] ################################################################################ # Declare which reagents are in each reservoir as well as deepwell and elution plate Mmix.reagent_reservoir = tuberack.rows()[0][0] # A1 # setup up sample sources and destinations samples = source_plate.wells()[2:NUM_SAMPLES] pcr_wells = qpcr_plate.wells()[:NUM_SAMPLES] pcr_wells_samples = qpcr_plate.wells()[2:NUM_SAMPLES] # Divide destination wells in small groups for P300 pipette dests = list(divide_destinations(pcr_wells, size_transfer)) # pipettes p20 = ctx.load_instrument('p20_single_gen2', mount='right', tip_racks=tips20) p300 = ctx.load_instrument('p300_single_gen2', mount='left', tip_racks=tips200) # used tip counter and set maximum tips available tip_track = { 'counts': { p300: 0, p20: 0 }, 'maxes': { p300: 96 * len(p300.tip_racks), p20: 96 * len(p20.tip_racks) } } ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 if not pip.hw_pipette['has_tip']: pip.pick_up_tip() ########## ############################################################################ # STEP 1: TRANSFER MMIX ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') pick_up(p300) used_vol = [] for dest in dests: aspirate_volume = MMIX_VOL_PER_SAMPLE * len(dest) + extra_dispensal used_vol_temp = distribute_custom( p300, volume=MMIX_VOL_PER_SAMPLE, src=Mmix.reagent_reservoir, dest=dest, waste_pool=Mmix.reagent_reservoir, pickup_height=0.2, extra_dispensal=extra_dispensal, dest_x_offset=2, disp_height=-1) used_vol.append(used_vol_temp) p300.drop_tip(home_after=False) tip_track['counts'][p300] += 1 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2: TRANSFER SAMPLES ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') for s, d in zip(samples, pcr_wells_samples): pick_up(p20) move_vol_multichannel(p20, reagent=Samples, source=s, dest=d, vol=VOLUME_SAMPLE, air_gap_vol=air_gap_sample, x_offset=x_offset, pickup_height=0.2, disp_height=0, rinse=False, blow_out=True, touch_tip=True) p20.drop_tip(home_after=False) tip_track['counts'][p20] += 1 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 3: TRANSFER NEGATIVE CONTROL ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') pick_up(p20) s = tuberack.rows()[0][1] # A2 d = qpcr_plate.wells()[1] # B1 move_vol_multichannel(p20, reagent=Samples, source=s, dest=d, vol=VOLUME_SAMPLE, air_gap_vol=air_gap_sample, x_offset=x_offset, pickup_height=0.2, disp_height=0, rinse=False, blow_out=True, touch_tip=True) p20.drop_tip(home_after=False) tip_track['counts'][p20] += 1 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 4: TRANSFER POSITIVE CONTROL ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') pick_up(p20) s = tuberack.rows()[0][2] # A3 d = qpcr_plate.wells()[0] # A1 move_vol_multichannel(p20, reagent=Samples, source=s, dest=d, vol=VOLUME_SAMPLE, air_gap_vol=air_gap_sample, x_offset=x_offset, pickup_height=0.2, disp_height=0, rinse=False, blow_out=True, touch_tip=True) p20.drop_tip(home_after=False) tip_track['counts'][p20] += 1 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write( 'STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program for i in range(3): #ctx._hw_manager.hardware.set_lights(rails=False) ctx._hw_manager.hardware.set_lights(button=(1, 0, 0)) time.sleep(0.3) #ctx._hw_manager.hardware.set_lights(rails=True) ctx._hw_manager.hardware.set_lights(button=(0, 0, 1)) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=(0, 1, 0)) ctx.comment('Finished! \nMove plate to PCR') total_used_vol = np.sum(used_vol) total_needed_volume = total_used_vol ctx.comment('Total Mmix used volume is: ' + str(total_used_vol) + '\u03BCl.') ctx.comment('Needed Mmix volume is ' + str(total_needed_volume + extra_dispensal * len(dests)) + '\u03BCl') ctx.comment('Mmix remaining in tubes is: ' + format( np.sum(Mmix.unused) + extra_dispensal * len(dests) + Mmix.vol_well) + '\u03BCl.') ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300])) ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96)) ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][p20])) ctx.comment('20 ul Used racks in total: ' + str(tip_track['counts'][p20] / 96))
def run(ctx: protocol_api.ProtocolContext): ctx.comment('Columnas a utilizar: ' + str(num_cols)) # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: { 'Execute': True, 'description': 'Transferir muestras a la placa PCR' } } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time folder_path = '/var/lib/jupyter/notebooks' + run_id if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/Station_C_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling='none'): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused = [] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells # Reagents and their characteristics Samples = Reagent( name='Samples', rinse=False, flow_rate_aspirate=sample_aspirate_rate, flow_rate_dispense=sample_dispense_rate, reagent_reservoir_volume=50, delay=0, num_wells=num_cols, # num_cols comes from available columns h_cono=0, v_fondo=0) Samples.vol_well = Samples.vol_well_original ################## # Custom functions def log_parameters(): ctx.comment(' ') ctx.comment('###############################################') ctx.comment('VALORES DE VARIABLES') ctx.comment(' ') ctx.comment('Número de muestras: ' + str(NUM_SAMPLES)) ctx.comment('Volumen a transferir a la placa PCR: ' + str(VOLUME_PCR_SAMPLE) + ' ul') ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE)) ctx.comment('###############################################') ctx.comment(' ') def start_run(): ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Empezando protocolo') if PHOTOSENSITIVE == False: ctx._hw_manager.hardware.set_lights(button=True, rails=True) else: ctx._hw_manager.hardware.set_lights(button=True, rails=False) now = datetime.now() # dd/mm/YY H:M:S start_time = now.strftime("%Y/%m/%d %H:%M:%S") return start_time def finish_run(): ctx.comment('###############################################') ctx.comment('Protocolo finalizado') ctx.comment(' ') #Set light color to blue ctx._hw_manager.hardware.set_lights(button=True, rails=False) now = datetime.now() # dd/mm/YY H:M:S finish_time = now.strftime("%Y/%m/%d %H:%M:%S") if PHOTOSENSITIVE == False: for i in range(10): ctx._hw_manager.hardware.set_lights(button=False, rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=True, rails=True) time.sleep(0.3) else: for i in range(10): ctx._hw_manager.hardware.set_lights(button=False, rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=True, rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=True, rails=False) ctx.comment('Puntas de 20 ul utilizadas: ' + str(tip_track['counts'][p20]) + ' (' + str(round(tip_track['counts'][p20] / 96, 2)) + ' caja(s))') ctx.comment('###############################################') return finish_time def log_step_start(): ctx.comment(' ') ctx.comment('###############################################') ctx.comment('PASO ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') return datetime.now() def log_step_end(start): end = datetime.now() time_taken = (end - start) STEPS[STEP]['Time:'] = str(time_taken) ctx.comment(' ') ctx.comment('Paso ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' hizo un tiempo de ' + str(time_taken)) ctx.comment(' ') def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip, v_offset=-5, radius=0.5): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location=source, vol=vol, rounds=2, blow_out=True, mix_height=0, x_offset=x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x=x_offset[0])) pipet.aspirate(vol, s) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z=-2), rate=reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z=disp_height).move(Point(x=x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate=reagent.flow_rate_dispense) # dispense all ctx.delay( seconds=reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z=-2)) if touch_tip == True: pipet.touch_tip(speed=20, v_offset=v_offset, radius=radius) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height=3): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height <= 0: mix_height = 3 pipet.aspirate(1, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom(z=source_height).move( Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom(z=mix_height).move( Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out #################################### # load labware and modules ################################## # Sample plate - comes from B #source_plate1 = ctx.load_labware( # 'nest_96_wellplate_100ul_pcr_full_skirt', '1', # 'NEST 96 Well Plate 100 uL PCR Full Skirt') source_plate1 = ctx.load_labware( 'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '1', 'Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 µL') source_plate2 = ctx.load_labware( 'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '2', 'Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 µL') samples_source1 = source_plate1.columns()[ 0::2] # Select odd columns from source plate samples_source2 = source_plate2.columns()[ 0::2] # Select odd columns from source plate samples_source1 = [ well for columns in samples_source1 for well in columns ] # list of lists to list samples_source2 = [well for columns in samples_source2 for well in columns] samples = samples_source1 + samples_source2 samples = samples[:NUM_SAMPLES] ################################## # qPCR plate - final plate, goes to PCR qpcr_plate = ctx.load_labware( 'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '4', 'Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 µL') ################################## # Load Tipracks tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot) for slot in ['7'] ] ################################################################################ # setup up sample sources and destinations pcr_wells = qpcr_plate.wells()[pcr_plate_well_offset:NUM_SAMPLES + pcr_plate_well_offset] # pipettes p20 = ctx.load_instrument('p20_single_gen2', mount='right', tip_racks=tips20) # used tip counter and set maximum tips available tip_track = {'counts': {p20: 0}, 'maxes': {p20: 96 * len(p20.tip_racks)}} ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Reemplaza las cajas de puntas de ' + str(pip.max_volume) + 'µl antes \ de continuar.') pip.reset_tipracks() tip_track['counts'][pip] = 0 if not pip.hw_pipette['has_tip']: pip.pick_up_tip() ########## log_parameters() start_run() ############################################################################ # STEP 1: TRANSFER SAMPLES ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = log_step_start() for source, dest in zip(samples, pcr_wells): pick_up(p20) move_vol_multichannel(p20, reagent=Samples, source=source, dest=dest, vol=VOLUME_PCR_SAMPLE + 5, air_gap_vol=air_gap_pcr_sample, x_offset=x_offset, pickup_height=0.1, disp_height=pcr_disp_height, v_offset=pcr_disp_height, rinse=False, blow_out=True, touch_tip=dispense_touch_tip, radius=1) if dispense_touch_tip == False: p20.aspirate(air_gap_vol) if recycle_tip: p20.return_tip() else: p20.drop_tip(home_after=False) tip_track['counts'][p20] += 1 log_step_end(start) ############################################################################ finish_run()
def run(ctx: protocol_api.ProtocolContext): import os # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: {'Execute': True, 'description': 'Add samples ('+str(volume_sample)+'ul)'}, 2: {'Execute': False, 'description': 'Add internal control one by one (10ul)'} # We won't use it as we will do it in KB with the multichannel pipette } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 if not ctx.is_simulating(): # Folder and file_path for log time folder_path = '/var/lib/jupyter/notebooks/'+run_id if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/KA_SampleSetup_viral_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling = 'none',rinse_loops = 2): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused=[] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells self.rinse_loops = rinse_loops Samples = Reagent(name = 'Samples', flow_rate_aspirate = 1, flow_rate_dispense = 1, rinse = False, delay = 0, reagent_reservoir_volume = 100 * 24, num_wells = 24, # num_cols comes from available columns h_cono = h_cone, v_fondo = volume_cone ) # cone IC = Reagent(name = 'Internal control', flow_rate_aspirate = 1, flow_rate_dispense = 3, rinse = False, delay = 0, reagent_reservoir_volume = $IC_total_volume, num_wells = $IC_wells, # num_cols comes from available columns h_cono = h_cone, v_fondo = volume_cone ) # cone Samples.vol_well = Samples.vol_well_original IC.vol_well = IC.vol_well_original ################## # Custom functions def generate_source_table(source): ''' Concatenate the wells from the different origin racks ''' for rack_number in range(len(source)): if rack_number == 0: s = source[rack_number].wells() else: s = s + source[rack_number].wells() return s def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip, post_dispense=False, post_dispense_vol=20, post_airgap=False, post_airgap_vol=10): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location = source, vol = vol, rounds = 2, blow_out = True, mix_height = 0, x_offset = x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x = x_offset[0])) pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z = -2), rate = reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z = disp_height).move(Point(x = x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate = reagent.flow_rate_dispense) # dispense all ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z = -2)) if post_dispense == True: pipet.dispense(post_dispense_vol, dest.top(z = -2)) if touch_tip == True: pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9) if post_airgap == True: pipet.aspirate(post_airgap_vol, dest.top(z = 5)) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height = 3, post_airgap=False, post_airgap_vol=10, post_dispense=False, post_dispense_vol=20,): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height == 0: mix_height = 3 pipet.aspirate(1, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate) pipet.dispense(vol, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) pipet.dispense(1, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out if post_dispense == True: pipet.dispense(post_dispense_vol, location.top(z = -2)) if post_airgap == True: pipet.dispense(post_airgap_vol, location.top(z = 5)) def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.4, extra_volume = 50): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if reagent.vol_well < aspirate_volume + extra_volume: reagent.unused.append(reagent.vol_well) ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 pip.pick_up_tip() #################################### # load labware and modules #################################### # Load Sample racks if NUM_SAMPLES < 96: rack_num = math.ceil(NUM_SAMPLES / 24) ctx.comment('Used source racks are ' + str(rack_num)) samples_last_rack = NUM_SAMPLES - rack_num * 24 else: rack_num = 4 source_tube_types={'Screwcap 2ml': ['opentrons_24_tuberack_generic_2ml_screwcap','source tuberack with screwcap'], 'Eppendorf 1.5ml': ['opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap','source tuberack with eppendorf'], } source_racks = [ctx.load_labware( source_tube_types[source_type][0], slot, source_tube_types[source_type][1] + str(i + 1)) for i, slot in enumerate(['4', '1', '6', '3'][:rack_num]) ] ic_source_rack = ctx.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', '9', 'Internal control source') ic_source = ic_source_rack.wells()[0] # internal control comes from 1 bottle ################################## # Destination plate dest_plate = ctx.load_labware( 'kf_96_wellplate_2400ul', '5', 'KF 96well destination plate') #################################### # Load tip_racks # tips20 = [ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack') # for slot in ['2', '8']] tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', slot, '200µl filter tiprack') for slot in ['10', '11']] tips20 = [ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack') for slot in ['8']] ################################################################################ # Declare which reagents are in each reservoir as well as deepwell and elution plate # setup samples and destinations sample_sources_full = generate_source_table(source_racks) sample_sources = sample_sources_full[:NUM_SAMPLES] destinations = dest_plate.wells()[:NUM_SAMPLES] p20 = ctx.load_instrument( 'p20_single_gen2', mount='left', tip_racks=tips20) p300 = ctx.load_instrument( 'p300_single_gen2', mount='right', tip_racks=tips300) # load P1000 pipette # used tip counter and set maximum tips available tip_track = { 'counts': {p300: 0, p20: 0}, # p1000: 0}, 'maxes': {p300: len(tips300) * 96, p20: len(tips20)*96} # ,p20: len(tips20)*96, } ############################################################################ # STEP 1: Add Samples ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') # Transfer parameters start = datetime.now() for s, d in zip(sample_sources, destinations): if not p300.hw_pipette['has_tip']: pick_up(p300) # Mix the sample BEFORE dispensing #custom_mix(p1000, reagent = Samples, location = s, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15) move_vol_multichannel(p300, reagent = Samples, source = s, dest = d, vol=volume_sample, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 0.3, rinse = Samples.rinse, disp_height = -10, blow_out = True, touch_tip = True) # Mix the sample AFTER dispensing #custom_mix(p300, reagent = Samples, location = d, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15) # Drop tip and update counter p300.drop_tip(home_after=False) tip_track['counts'][p300] += 1 # Time statistics end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2: Add internal control ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') #BEWARE, everything with the same tip, dispensed from top! # Transfer parameters start = datetime.now() for d in destinations: if not p20.hw_pipette['has_tip']: pick_up(p20) # Mix the sample BEFORE dispensing #custom_mix(p1000, reagent = Samples, location = s, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15) move_vol_multichannel(p20, reagent = IC, source = ic_source, dest = d, vol = ic_volume, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 0.4, rinse = IC.rinse, disp_height = -8, blow_out = True, touch_tip = False) # Mix the sample AFTER dispensing #custom_mix(p20, reagent = Samples, location = d, vol = 10, rounds = 2, #blow_out = True, mix_height = 2, x_offset = x_offset) # Drop tip and update counter p20.drop_tip() tip_track['counts'][p20] += 1 # Time statistics end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write('STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program '''if not ctx.is_simulating(): os.system('mpg123 -f -8000 /etc/audio/speaker-test.mp3 &')''' for i in range(3): ctx._hw_manager.hardware.set_lights(rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(rails=True) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(rails=False) ctx.home() ctx.comment( 'Finished! \nMove deepwell plate to Station B.') ctx.comment('Used 200µl tips in total: ' + str(tip_track['counts'][p300])) ctx.comment('Used 200ul racks in total: '+str(tip_track['counts'][p300] / 96)) ctx.comment('Used p20 tips in total: ' + str(tip_track['counts'][p20])) ctx.comment('Used p20 racks in total: ' + str(tip_track['counts'][p20] / 96))
def run(ctx: protocol_api.ProtocolContext): ctx.comment('Actual used columns: ' + str(num_cols)) #from opentrons.drivers.rpi_drivers import gpio # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: {'Execute': True, 'description': 'Add 50 ul Elution Buffer'}, 2: {'Execute': True, 'description': 'Add 100 ul Wash Buffer 1 - Round 1'}, 3: {'Execute': True, 'description': 'Add 100 ul Wash Buffer 2 - Round 1 and stop until plate comes from A'}, 4: {'Execute': True, 'description': 'Add 100 ul Lysis Buffer and then move to station A'}, 5: {'Execute': False, 'description': 'Transfer IC'}, 6: {'Execute': True, 'description': 'Transfer ICtwo'}, 7: {'Execute': False, 'description': 'Mix beads'}, 8: {'Execute': False, 'description': 'Transfer beads'}, 9: {'Execute': True, 'description': 'Mix beadstwo'}, 10: {'Execute': True, 'description': 'Transfer beadstwo'} } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 folder_path = '/var/lib/jupyter/notebooks/'+str(run_id) if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/KB_station_viral_time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo, tip_recycling = 'none', rinse_loops = 3, flow_rate_dispense_mix = 4, flow_rate_aspirate_mix = 4): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.flow_rate_aspirate_mix = flow_rate_aspirate_mix self.flow_rate_dispense_mix = flow_rate_dispense_mix self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay #Delay of reagent in dispense self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.unused=[] self.tip_recycling = tip_recycling self.vol_well_original = reagent_reservoir_volume / num_wells self.rinse_loops = rinse_loops # Reagents and their characteristics WashBuffer1 = Reagent(name='Wash Buffer 1', flow_rate_aspirate=0.75, flow_rate_dispense=1, rinse=True, rinse_loops=6, delay=3, reagent_reservoir_volume=$Wone_total_volume, #100*NUM_SAMPLES, num_wells=$Wone_wells, h_cono=1.95, v_fondo=695) # Flat surface WashBuffer2 = Reagent(name='Wash Buffer 2', flow_rate_aspirate=0.75, flow_rate_dispense=1, rinse=True, delay=3, reagent_reservoir_volume=$Wtwo_total_volume, #100*NUM_SAMPLES, num_wells=$Wtwo_wells, h_cono=1.95, v_fondo=695) # Flat surface Lysis = Reagent(name='Lysis Buffer', flow_rate_aspirate=0.75, flow_rate_dispense=0.5, rinse=False, rinse_loops=6, delay=2, reagent_reservoir_volume=$Lysis_total_volume, #100*NUM_SAMPLES, num_wells=1, h_cono=1.95, v_fondo=695) # Flat surface ElutionBuffer = Reagent(name='Elution Buffer', flow_rate_aspirate=1, flow_rate_dispense=1, rinse=False, delay=0, reagent_reservoir_volume=$Elution_total_volume,#50*NUM_SAMPLES, num_wells=$Elution_wells, h_cono=1.95, v_fondo=695) # Prismatic IC = Reagent(name='Magnetic beads and Lysis', flow_rate_aspirate=1, flow_rate_dispense=3, rinse=False, num_wells=$IC_wells, delay=2, reagent_reservoir_volume=$IC_total_volume,#20 * NUM_SAMPLES * 1.1, h_cono=1.95, v_fondo=695) # Prismatic ICtwo = Reagent(name='Internal control 2', flow_rate_aspirate=1, flow_rate_dispense=3, rinse=False, num_wells=1, delay=2, reagent_reservoir_volume=$IC_total_volume,#20 * NUM_SAMPLES * 1.1, h_cono=1, v_fondo=10) # Prismatic Beads = Reagent(name='Magnetic beads and Lysis', flow_rate_aspirate=0.5, flow_rate_dispense=0.5, rinse=True, rinse_loops=6, num_wells=$Beads_wells, delay=3, reagent_reservoir_volume=$Beads_total_volume,#20 * NUM_SAMPLES * 1.1, h_cono=1.95, v_fondo=695) # Prismatic Beadstwo = Reagent(name='Magnetic beads 2', flow_rate_aspirate=0.5, flow_rate_dispense=0.5, rinse=True, rinse_loops=4, num_wells=2, delay=3, reagent_reservoir_volume=$Beads_total_volume,#20 * NUM_SAMPLES * 1.1, h_cono=1.95, v_fondo=695) # Prismatic Beads.vol_well = Beads.vol_well_original IC.vol_well = IC.vol_well_original WashBuffer1.vol_well = WashBuffer1.vol_well_original WashBuffer2.vol_well = WashBuffer2.vol_well_original ElutionBuffer.vol_well = ElutionBuffer.vol_well_original Lysis.vol_well = Lysis.vol_well_original Beadstwo.vol_well = Beadstwo.vol_well_original ICtwo.vol_well = ICtwo.vol_well_original ################## # Custom functions def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip, post_dispense=False, post_dispense_vol=20, post_airgap=True, post_airgap_vol=10): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location = source, vol = vol, rounds = reagent.rinse_loops, blow_out = True, mix_height = 0, x_offset = x_offset, post_airgap=False,post_dispense=False, post_dispense_vol=20,post_airgap_vol=10) # SOURCE s = source.bottom(pickup_height).move(Point(x = x_offset[0])) pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z = -2), rate = reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z = disp_height).move(Point(x = x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate = reagent.flow_rate_dispense) # dispense all ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z = -5)) if post_airgap == True: pipet.aspirate(post_airgap_vol, dest.top(z = -2)) if post_dispense == True: pipet.dispense(post_dispense_vol, dest.top(z = -2)) if touch_tip == True: pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, source_height = 3, post_airgap=True, post_airgap_vol=10, post_dispense=False, post_dispense_vol=20,x_offset = x_offset): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height == 0: mix_height = 3 pipet.aspirate(1, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate_mix) for _ in range(rounds): pipet.aspirate(vol, location=location.bottom( z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate_mix) pipet.dispense(vol, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense_mix) pipet.dispense(1, location=location.bottom( z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense_mix) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out if post_dispense == True: pipet.dispense(post_dispense_vol, location.top(z = -2)) if post_airgap == True: pipet.aspirate(post_airgap_vol, location.top(z = 5)) def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.2, extra_volume = 50): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if reagent.vol_well < aspirate_volume + extra_volume: reagent.unused.append(reagent.vol_well) ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 pip.pick_up_tip() ########## def find_side(col): ''' Detects if the current column has the magnet at its left or right side ''' if col % 2 == 0: side = -1 # left else: side = 1 return side #################################### # load labware and modules # IC well rack #################################### #tempdeck = ctx.load_module('tempdeck', '3') ic_reservoir = ctx.load_labware( 'nest_96_wellplate_100ul_pcr_full_skirt','3', 'Wellplate with Beads and IC') # 12 well rack #################################### reagent_res = ctx.load_labware( 'nest_12_reservoir_15ml', '5', 'Reservoir 12 channel, column 1') # Wash Buffer 1 100ul Deepwell plate ############################################ WashBuffer1_100ul_plate1 = ctx.load_labware( 'kf_96_wellplate_2400ul', '1', 'Wash Buffer 1 Deepwell plate 1') # Wash Buffer 2 100ul Deepwell plate ############################################ WashBuffer2_100ul_plate1 = ctx.load_labware( 'kf_96_wellplate_2400ul', '4', 'Wash Buffer 2 Deepwell plate 1') # Lysis buffer 100ul Deepwell plate and later will be the plate with samples ############################################ kf_plate = ctx.load_labware( 'kf_96_wellplate_2400ul', '2', 'Sample Deepwell plate 1') # Elution Deepwell plate ############################################ ElutionBuffer_50ul_plate = ctx.load_labware( 'kingfisher_std_96_wellplate_550ul', '7', 'Elution Buffer 50 ul STD plate') #################################### # Load tip_racks tips300 = [ctx.load_labware('opentrons_96_tiprack_300ul', slot, '200µl filter tiprack') for slot in ['8']] tips20 = [ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack') for slot in ['6','9']] ################################################################################ # Declare which reagents are in each reservoir as well as deepwell and elution plate WashBuffer1.reagent_reservoir = reagent_res.rows( )[0][:1] # position 1 WashBuffer2.reagent_reservoir = reagent_res.rows( )[0][1:2] #position 2 ElutionBuffer.reagent_reservoir = reagent_res.rows( )[0][9:10] #position 10 Lysis.reagent_reservoir = reagent_res.rows( )[0][2:3] #position 3 IC.reagent_reservoir = reagent_res.rows( )[0][10:11] #position 4 Beads.reagent_reservoir = reagent_res.rows( )[0][11:] #position 4 ICtwo.reagent_reservoir = ic_reservoir.rows( )[0][:1] #position 1 Beadstwo.reagent_reservoir = ic_reservoir.rows( )[0][1:3] #position 2 to 3 # columns in destination plates to be filled depending the number of samples wb1plate1_destination = WashBuffer1_100ul_plate1.rows()[0][:num_cols] wb2plate1_destination = WashBuffer2_100ul_plate1.rows()[0][:num_cols] elutionbuffer_destination = ElutionBuffer_50ul_plate.rows()[0][:num_cols] kf_destination = kf_plate.rows()[0][:num_cols] # pipette m300 = ctx.load_instrument( 'p300_multi_gen2', 'right', tip_racks=tips300) # Load multi pipette # pipettes. P1000 currently deactivated m20 = ctx.load_instrument( 'p20_multi_gen2', 'left', tip_racks=tips20) # Load p300 multi pipette # used tip counter and set maximum tips available tip_track = { 'counts': {m300: 0, m20: 0}, 'maxes': {m300: len(tips300)*96, m20: len(tips20)*96} } ############################################################################ # STEP 1: Transfer Elution buffer ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') # Elution buffer ElutionBuffer_vol = [Elution_vol] ######## # Water or elution buffer for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for transfer_vol in ElutionBuffer_vol: # Calculate pickup_height based on remaining volume and shape of container move_vol_multichannel(m300, reagent = ElutionBuffer, source = ElutionBuffer.reagent_reservoir[ElutionBuffer.col], dest = elutionbuffer_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol_elutionbuffer, x_offset = x_offset, pickup_height = 0.2, rinse = False, disp_height = -2, blow_out = True, touch_tip = True, post_airgap=True) m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2: Filling with WashBuffer1 ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') WB1 = [WBone_vol] rinse = False # Only first time ######## # Wash buffer dispense for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(WB1): if (i == 0 and j == 0): rinse = True #Rinse only first transfer else: rinse = False move_vol_multichannel(m300, reagent = WashBuffer1, source = WashBuffer1.reagent_reservoir[WashBuffer1.col], dest = wb1plate1_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 0.3, rinse = rinse, disp_height = -2, blow_out = True, touch_tip = False, post_airgap=True) m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 3: Filling with WashBuffer2 ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') WB2 = [WBtwo_vol] rinse = False # Only first time ######## # Wash buffer dispense for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(WB2): if (i == 0 and j == 0): rinse = True else: rinse = False move_vol_multichannel(m300, reagent = WashBuffer2, source = WashBuffer2.reagent_reservoir[WashBuffer2.col], dest = wb2plate1_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 0.2, rinse = rinse, disp_height = -2, blow_out = True, touch_tip = False, post_airgap=True) m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 ctx.pause('Bring plate from A in position 2 and click continue. Put IC and Beads in position 3') end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 4: Filling with Lysis ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') lysis_vol = [100] rinse = False # Only first time ######## # Wash buffer dispense for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(lysis_vol): if (i == 0 and j == 0): rinse = True #Rinse only first transfer else: rinse = False move_vol_multichannel(m300, reagent = Lysis, source = Lysis.reagent_reservoir[Lysis.col], dest = kf_destination[i], vol = transfer_vol, air_gap_vol = air_gap_vol, x_offset = x_offset, pickup_height = 0.2, rinse = rinse, disp_height = -2, blow_out = True, touch_tip = False, post_airgap=True,post_dispense=True) m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 5: TRANSFER IC ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: # Transfer parameters start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') IC_transfer_vol = [ic_vol] rinse = True for i in range(num_cols): if not m20.hw_pipette['has_tip']: pick_up(m20) for j, transfer_vol in enumerate(IC_transfer_vol): move_vol_multichannel(m20, reagent=IC, source=IC.reagent_reservoir[IC.col], dest=kf_destination[i], vol=transfer_vol, air_gap_vol=air_gap_ic, x_offset=[0,0], pickup_height=0.2, disp_height = -40.7, rinse=IC.rinse, blow_out = True, touch_tip=False, post_airgap=True) m20.drop_tip(home_after=False) tip_track['counts'][m20] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 6: TRANSFER IC2 ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: # Transfer parameters start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') IC_transfer_vol = [ic_vol] rinse = True for i in range(num_cols): if not m20.hw_pipette['has_tip']: pick_up(m20) for j, transfer_vol in enumerate(IC_transfer_vol): move_vol_multichannel(m20, reagent=ICtwo, source=ICtwo.reagent_reservoir[IC.col], dest=kf_destination[i], vol=transfer_vol, air_gap_vol=air_gap_ic, x_offset=[0,0], pickup_height=0.5, disp_height = -40.7, rinse=ICtwo.rinse, blow_out = True, touch_tip=False, post_airgap=True) m20.drop_tip(home_after=False) tip_track['counts'][m20] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 7: PREMIX BEADS ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') if not m300.hw_pipette['has_tip']: pick_up(m300) ctx.comment('Tip picked up') ctx.comment('Mixing ' + Beads.name) # Mixing custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col], vol=120, rounds=10, blow_out=True, mix_height=10, post_dispense=True, source_height=0.3) ctx.comment('Finished premixing!') ctx.comment('Now, reagents will be transferred to deepwell plate.') end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 8: TRANSFER BEADS ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: # Transfer parameters start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') beads_transfer_vol = [beads_vol] # Two rounds of 130 rinse = True for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(beads_transfer_vol): if (i == 0 and j == 0): rinse = True else: rinse = False # Calculate pickup_height based on remaining volume and shape of container [pickup_height, change_col] = calc_height( reagent = Beads, cross_section_area = multi_well_rack_area, aspirate_volume = transfer_vol * 8, min_height=0.3, extra_volume=10) if change_col == True: # If we switch column because there is not enough volume left in current reservoir column we mix new column ctx.comment( 'Mixing new reservoir column: ' + str(Beads.col)) custom_mix(m300, Beads, Beads.reagent_reservoir[Beads.col], vol=120, rounds=10, blow_out=True, mix_height=1, post_dispense=True) ctx.comment( 'Aspirate from reservoir column: ' + str(Beads.col)) ctx.comment('Pickup height is ' + str(pickup_height)) move_vol_multichannel(m300, reagent=Beads, source=Beads.reagent_reservoir[Beads.col], dest=kf_destination[i], vol=transfer_vol, air_gap_vol=air_gap_vol, x_offset=x_offset, pickup_height=0.2, disp_height = -8, rinse=rinse, blow_out = True, touch_tip=False, post_airgap=True) '''custom_mix(m300, Beads, work_destinations_cols[i] , vol=70, rounds=10, blow_out=True, mix_height=8, x_offset = x_offset, source_height=0.5, post_dispense=True) m300.drop_tip(home_after=False) ''' m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 9: PREMIX BEADStwo ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') if not m300.hw_pipette['has_tip']: pick_up(m300) ctx.comment('Tip picked up') ctx.comment('Mixing ' + Beadstwo.name) # Mixing custom_mix(m300, Beadstwo, Beadstwo.reagent_reservoir[Beadstwo.col], vol=80, rounds=10, blow_out=True, mix_height=5, post_dispense=True, source_height=0.7) ctx.comment('Finished premixing!') ctx.comment('Now, reagents will be transferred to deepwell plate.') end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 10: TRANSFER BEADStwo ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: # Transfer parameters start = datetime.now() ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') beads_transfer_vol = [beads_vol] # Two rounds of 130 rinse = True for i in range(num_cols): if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(beads_transfer_vol): if (i == 0 and j == 0): rinse = True else: rinse = False # Calculate pickup_height based on remaining volume and shape of container [pickup_height, change_col] = calc_height( reagent = Beadstwo, cross_section_area = 15, aspirate_volume = transfer_vol , min_height=0.7, extra_volume=0) # I will consider only aspiration from one well pickup_height=0.7 if change_col == True: # If we switch column because there is not enough volume left in current reservoir column we mix new column ctx.comment( 'Mixing new reservoir column: ' + str(Beadstwo.col)) custom_mix(m300, Beadstwo, Beadstwo.reagent_reservoir[Beadstwo.col], vol=100, rounds=10, blow_out=True, mix_height=0.7, post_dispense=True) ctx.comment( 'Aspirate from reservoir column: ' + str(Beadstwo.col)) ctx.comment('Pickup height is ' + str(pickup_height)) move_vol_multichannel(m300, reagent=Beadstwo, source=Beadstwo.reagent_reservoir[Beadstwo.col], dest=kf_destination[i], vol=transfer_vol, air_gap_vol=air_gap_vol, x_offset=x_offset, pickup_height=pickup_height, disp_height = -8, rinse=rinse, blow_out = True, touch_tip=False, post_airgap=True) '''custom_mix(m300, Beads, work_destinations_cols[i] , vol=70, rounds=10, blow_out=True, mix_height=8, x_offset = x_offset, source_height=0.5, post_dispense=True) m300.drop_tip(home_after=False) ''' m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write('STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ # Light flash end of program if not ctx.is_simulating(): for i in range(3): ctx._hw_manager.hardware.set_lights(rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(rails=True) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(rails=False) ctx.home() ctx.comment( 'Finished! \nMove deepwell plates to KingFisher extractor.') ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300])) ctx.comment('Used racks in total: ' + str(tip_track['counts'][m300] / 96))
def run(ctx: protocol_api.ProtocolContext): # load labware dest_plate = ctx.load_labware('nest_96_wellplate_2ml_deep', '2', '96-deepwell sample plate') tipracks1000 = [ ctx.load_labware('opentrons_96_filtertiprack_1000ul', '1', '1000µl filter tiprack') ] # load pipette p1000 = ctx.load_instrument('p1000_single_gen2', 'right', tip_racks=tipracks1000) tip_log = {'count': {}} folder_path = '/data/A' tip_file_path = folder_path + '/tip_log.json' if TIP_TRACK and not ctx.is_simulating(): if os.path.isfile(tip_file_path): with open(tip_file_path) as json_file: data = json.load(json_file) if 'tips1000' in data: tip_log['count'][p1000] = data['tips1000'] else: tip_log['count'][p1000] = 0 else: tip_log['count'] = {p1000: 0} tip_log['tips'] = { p1000: [tip for rack in tipracks1000 for tip in rack.wells()] } tip_log['max'] = {pip: len(tip_log['tips'][pip]) for pip in [p1000]} def pick_up(pip): nonlocal tip_log if tip_log['count'][pip] == tip_log['max'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_log['count'][pip] = 0 pip.pick_up_tip(tip_log['tips'][pip][tip_log['count'][pip]]) tip_log['count'][pip] += 1 # pool samples for i in range(math.ceil(NUM_SAMPLES / 2)): if NUM_SAMPLES % 2 != 0 and i == math.ceil(NUM_SAMPLES / 2) - 1: pool_source_set = [dest_plate.wells()[NUM_SAMPLES]] vol = SAMPLE_VOLUME * 2 else: pool_source_set = dest_plate.wells()[i * 2:i * 2 + 2] vol = SAMPLE_VOLUME for s in pool_source_set: pick_up(p1000) p1000.transfer(vol, s, dest_plate.wells()[i + 64], air_gap=20, new_tip='never') p1000.air_gap(100) p1000.drop_tip() ctx.comment('Move deepwell plate (slot 2) to Station B for RNA \ extraction.') # track final used tip if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) data = {'tips1000': tip_log['count'][p1000]} with open(tip_file_path, 'w') as outfile: json.dump(data, outfile)
def run(ctx: protocol_api.ProtocolContext): #Change light to red ctx._hw_manager.hardware.set_lights(button=(1, 0, 0)) ctx.comment('Actual used columns: ' + str(num_cols)) STEP = 0 STEPS = { #Dictionary with STEP activation, description, and times 1: { 'Execute': True, 'description': 'Transfer beads + PK + binding' }, 2: { 'Execute': True, 'description': 'Transfer wash' }, 3: { 'Execute': True, 'description': 'Transfer ethanol' }, 4: { 'Execute': True, 'description': 'Transfer elution' } } #Folder and file_path for log time import os folder_path = '/var/lib/jupyter/notebooks' + run_id if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/Station_B_Preparacion_Kingfisher_time_log.txt' #Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, flow_rate_aspirate_mix, flow_rate_dispense_mix, air_gap_vol_bottom, air_gap_vol_top, disposal_volume, rinse, max_volume_allowed, reagent_volume, reagent_reservoir_volume, num_wells, h_cono, v_fondo, tip_recycling='none', dead_vol=700): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.flow_rate_aspirate_mix = flow_rate_aspirate_mix self.flow_rate_dispense_mix = flow_rate_dispense_mix self.air_gap_vol_bottom = air_gap_vol_bottom self.air_gap_vol_top = air_gap_vol_top self.disposal_volume = disposal_volume self.rinse = bool(rinse) self.max_volume_allowed = max_volume_allowed self.reagent_volume = reagent_volume self.reagent_reservoir_volume = reagent_reservoir_volume self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.tip_recycling = tip_recycling self.dead_vol = dead_vol self.vol_well_original = (reagent_reservoir_volume / num_wells ) + dead_vol if num_wells > 0 else 0 #Reagents and their characteristics Wash = Reagent( name='Wash', flow_rate_aspirate=5, # Original = 0.5 flow_rate_dispense=5, # Original = 1 flow_rate_aspirate_mix= 1, # Liquid density very high, needs slow aspiration flow_rate_dispense_mix= 1, # Liquid density very high, needs slow dispensation air_gap_vol_bottom=5, air_gap_vol_top=0, disposal_volume=1, rinse=True, max_volume_allowed=180, reagent_volume= WASH_VOLUME_PER_SAMPLE, # reagent volume needed per sample reagent_reservoir_volume=(NUM_SAMPLES + 5) * WASH_VOLUME_PER_SAMPLE, #70000, #51648 num_wells=1, h_cono=1.95, v_fondo=695) #1.95 * multi_well_rack_area / 2, #Prismatic Ethanol = Reagent( name='Ethanol', flow_rate_aspirate=5, flow_rate_dispense=5, flow_rate_aspirate_mix=1, flow_rate_dispense_mix=1, air_gap_vol_bottom=5, air_gap_vol_top=0, disposal_volume=1, rinse=True, max_volume_allowed=180, reagent_volume=ETHANOL_VOLUME_PER_SAMPLE, reagent_reservoir_volume=(NUM_SAMPLES + 5) * ETHANOL_VOLUME_PER_SAMPLE, num_wells=1, h_cono=1.95, v_fondo=695) #1.95 * multi_well_rack_area / 2, #Prismatic Beads_PK_Binding = Reagent( name='Magnetic beads + PK + Binding', flow_rate_aspirate=0.5, flow_rate_dispense=0.5, flow_rate_aspirate_mix=0.5, flow_rate_dispense_mix=0.5, air_gap_vol_bottom=5, air_gap_vol_top=0, disposal_volume=1, rinse=True, max_volume_allowed=180, reagent_volume=BEADS_VOLUME_PER_SAMPLE, reagent_reservoir_volume=NUM_SAMPLES * BEADS_VOLUME_PER_SAMPLE * 1.1, num_wells=math.ceil(NUM_SAMPLES * BEADS_VOLUME_PER_SAMPLE * 1.1 / 11500), h_cono=1.95, v_fondo=695) #1.95 * multi_well_rack_area / 2, #Prismatic Elution = Reagent( name='Elution', flow_rate_aspirate=3, # Original 0.5 flow_rate_dispense=3, # Original 1 flow_rate_aspirate_mix=15, flow_rate_dispense_mix=25, air_gap_vol_bottom=5, air_gap_vol_top=0, disposal_volume=1, rinse=False, max_volume_allowed=180, reagent_volume=ELUTION_VOLUME_PER_SAMPLE, reagent_reservoir_volume=(NUM_SAMPLES + 5) * ELUTION_VOLUME_PER_SAMPLE, num_wells=math.ceil( (NUM_SAMPLES + 5) * ELUTION_VOLUME_PER_SAMPLE / 13000), h_cono=1.95, v_fondo=695) #1.95 * multi_well_rack_area / 2, #Prismatic Sample = Reagent( name='Sample', flow_rate_aspirate=3, # Original 0.5 flow_rate_dispense=3, # Original 1 flow_rate_aspirate_mix=15, flow_rate_dispense_mix=25, air_gap_vol_bottom=5, air_gap_vol_top=0, disposal_volume=1, rinse=False, max_volume_allowed=150, reagent_volume=50, reagent_reservoir_volume=(NUM_SAMPLES + 5) * 50, #14800, num_wells=num_cols, #num_cols comes from available columns h_cono=4, v_fondo=4 * math.pi * 4**3 / 3) #Sphere Wash.vol_well = Wash.vol_well_original Ethanol.vol_well = Ethanol.vol_well_original Beads_PK_Binding.vol_well = Beads_PK_Binding.vol_well_original Elution.vol_well = Elution.vol_well_original Sample.vol_well = 350 # Arbitrary value def str_rounded(num): return str(int(num + 0.5)) ctx.comment(' ') ctx.comment('###############################################') ctx.comment('VOLUMES FOR ' + str(NUM_SAMPLES) + ' SAMPLES') ctx.comment(' ') ctx.comment('Beads + PK + Binding: ' + str(Beads_PK_Binding.num_wells) + ' wells from well 2 in multi reservoir with volume ' + str_rounded(Beads_PK_Binding.vol_well_original) + ' uL each one') ctx.comment('Elution: ' + str(Elution.num_wells) + ' wells from well 7 in multi reservoir with volume ' + str_rounded(Elution.vol_well_original) + ' uL each one') ctx.comment('Wash: in reservoir 1 with volume ' + str_rounded(Wash.vol_well_original) + ' uL') ctx.comment('Etanol: in reservoir 2 with volume ' + str_rounded(Ethanol.vol_well_original) + ' uL') ctx.comment('###############################################') ctx.comment(' ') ################### #Custom functions def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, offset, wait_time=0, drop_height=-1, two_thirds_mix_bottom=False): ''' Function for mix in the same location a certain number of rounds. Blow out optional. Offset can set to 0 or a higher/lower value which indicates the lateral movement ''' if mix_height <= 0: mix_height = 1 pipet.aspirate(1, location=location.bottom(z=mix_height), rate=reagent.flow_rate_aspirate_mix) for i in range(rounds): pipet.aspirate(vol, location=location.bottom(z=mix_height), rate=reagent.flow_rate_aspirate_mix) if two_thirds_mix_bottom and i < ((rounds / 3) * 2): pipet.dispense(vol, location=location.bottom(z=5).move( Point(x=offset)), rate=reagent.flow_rate_dispense_mix) else: pipet.dispense(vol, location=location.top(z=drop_height).move( Point(x=offset)), rate=reagent.flow_rate_dispense_mix) pipet.dispense(1, location=location.bottom(z=mix_height), rate=reagent.flow_rate_dispense_mix) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out if wait_time != 0: ctx.delay(seconds=wait_time, msg='Waiting for ' + str(wait_time) + ' seconds.') def calc_height(reagent, cross_section_area, aspirate_volume, min_height=0.4): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if (reagent.vol_well - reagent.dead_vol) < aspirate_volume: ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change def move_vol_multi(pipet, reagent, source, dest, vol, x_offset_source, x_offset_dest, pickup_height, rinse, avoid_droplet, wait_time, blow_out, touch_tip=False, drop_height=-5): # Rinse before aspirating if rinse == True: #pipet.aspirate(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap custom_mix(pipet, reagent, location=source, vol=vol, rounds=20, blow_out=False, mix_height=3, offset=0) #pipet.dispense(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_dispense) # SOURCE if reagent.air_gap_vol_top != 0: #If there is air_gap_vol, switch pipette to slow speed pipet.move_to(source.top(z=0)) pipet.air_gap(reagent.air_gap_vol_top) #air gap #pipet.aspirate(reagent.air_gap_vol_top, source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap s = source.bottom(pickup_height).move(Point(x=x_offset_source)) pipet.aspirate(vol, s, rate=reagent.flow_rate_aspirate) # aspirate liquid if reagent.air_gap_vol_bottom != 0: #If there is air_gap_vol, switch pipette to slow speed pipet.move_to(source.top(z=0)) pipet.air_gap(reagent.air_gap_vol_bottom) #air gap #pipet.aspirate(air_gap_vol_bottom, source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap if wait_time != 0: ctx.delay(seconds=wait_time, msg='Waiting for ' + str(wait_time) + ' seconds.') if avoid_droplet == True: # Touch the liquid surface to avoid droplets ctx.comment("Moving to: " + str(round(pickup_height, 2)) + ' mm') pipet.move_to(source.bottom(pickup_height)) # GO TO DESTINATION d = dest.top(z=drop_height).move(Point(x=x_offset_dest)) pipet.dispense(vol - reagent.disposal_volume + reagent.air_gap_vol_bottom, d, rate=reagent.flow_rate_dispense) if wait_time != 0: ctx.delay(seconds=wait_time, msg='Waiting for ' + str(wait_time) + ' seconds.') if reagent.air_gap_vol_top != 0: pipet.dispense(reagent.air_gap_vol_top, dest.top(z=0), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(dest.top(z=-5)) if touch_tip == True: pipet.touch_tip(speed=20, v_offset=-10, radius=0.7) #if reagent.air_gap_vol_bottom != 0: #pipet.move_to(dest.top(z = 0)) #pipet.air_gap(reagent.air_gap_vol_bottom) #air gap #pipet.aspirate(air_gap_vol_bottom, dest.top(z = 0),rate = reagent.flow_rate_aspirate) #air gap ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track #if not ctx.is_simulating(): if tip_track['counts'][pip] >= tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 pip.pick_up_tip() ########## def find_side(col): if col % 2 == 0: side = -1 # left else: side = 1 # right return side # load labware and modules ############################################ ######## Sample plate - comes from A deepwell_plate_samples = ctx.load_labware('nest_96_wellplate_2ml_deep', '1', 'NEST 96 Deepwell Plate 2mL 1') deepwell_plate_wash = ctx.load_labware('nest_96_wellplate_2ml_deep', '5', 'NEST 96 Deepwell Plate 2mL 2') deepwell_plate_ethanol = ctx.load_labware('nest_96_wellplate_2ml_deep', '6', 'NEST 96 Deepwell Plate 2mL 3') deepwell_plate_elution = ctx.load_labware( 'biorad_96_wellplate_200ul_pcr', '7', 'Bio-Rad 96 Well Plate 200 µL PCR') #################################### ######## 12 well rack reagent_multi_res = ctx.load_labware('nest_12_reservoir_15ml', '4', 'Reagent deepwell plate') #################################### ######## Single reservoirs reagent_res_1 = ctx.load_labware('nest_1_reservoir_195ml', '2', 'Single reagent reservoir 1') res_1 = reagent_res_1.wells()[0] reagent_res_2 = ctx.load_labware('nest_1_reservoir_195ml', '3', 'Single reagent reservoir 2') res_2 = reagent_res_2.wells()[0] #################################### ######### Load tip_racks tips300 = [ ctx.load_labware('opentrons_96_tiprack_300ul', slot, '200µl filter tiprack') for slot in ['8', '9'] ] ############################################################################### #Declare which reagents are in each reservoir as well as deepwell and sample plate Wash.reagent_reservoir = res_1 Ethanol.reagent_reservoir = res_2 Beads_PK_Binding.reagent_reservoir = reagent_multi_res.rows()[0][1:5] Elution.reagent_reservoir = reagent_multi_res.rows()[0][6:7] work_destinations = deepwell_plate_samples.rows()[0][:Sample.num_wells] wash_destinations = deepwell_plate_wash.rows()[0][:Sample.num_wells] ethanol_destinations = deepwell_plate_ethanol.rows()[0][:Sample.num_wells] elution_destinations = deepwell_plate_elution.rows()[0][:Sample.num_wells] # pipettes. m300 = ctx.load_instrument('p300_multi_gen2', 'right', tip_racks=tips300) # Load multi pipette #### used tip counter and set maximum tips available tip_track = { 'counts': { m300: 0 }, 'maxes': { m300: 96 * len(m300.tip_racks) } #96 tips per tiprack * number or tipracks in the layout } ############################################################################### ############################################################################### ############################################################################### # STEP 1 TRANSFER BEADS + PK + Binding ######## STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') beads_trips = math.ceil(Beads_PK_Binding.reagent_volume / Beads_PK_Binding.max_volume_allowed) beads_volume = Beads_PK_Binding.reagent_volume / beads_trips #136.66 beads_transfer_vol = [] for i in range(beads_trips): beads_transfer_vol.append(beads_volume + Beads_PK_Binding.disposal_volume) x_offset_source = 0 x_offset_dest = 0 rinse = False # Original: True first_mix_done = False for i in range(num_cols): ctx.comment("Column: " + str(i)) if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(beads_transfer_vol): #Calculate pickup_height based on remaining volume and shape of container [pickup_height, change_col] = calc_height(Beads_PK_Binding, multi_well_rack_area, transfer_vol * 8) if change_col == True or not first_mix_done: #If we switch column because there is not enough volume left in current reservoir column we mix new column ctx.comment('Mixing new reservoir column: ' + str(Beads_PK_Binding.col)) custom_mix(m300, Beads_PK_Binding, Beads_PK_Binding.reagent_reservoir[ Beads_PK_Binding.col], vol=Beads_PK_Binding.max_volume_allowed, rounds=BEADS_WELL_FIRST_TIME_NUM_MIXES, blow_out=False, mix_height=0.5, offset=0) first_mix_done = True else: ctx.comment('Mixing reservoir column: ' + str(Beads_PK_Binding.col)) custom_mix(m300, Beads_PK_Binding, Beads_PK_Binding.reagent_reservoir[ Beads_PK_Binding.col], vol=Beads_PK_Binding.max_volume_allowed, rounds=BEADS_WELL_NUM_MIXES, blow_out=False, mix_height=0.5, offset=0) ctx.comment('Aspirate from reservoir column: ' + str(Beads_PK_Binding.col)) ctx.comment('Pickup height is ' + str(round(pickup_height, 2)) + ' mm') #if j!=0: # rinse = False move_vol_multi(m300, reagent=Beads_PK_Binding, source=Beads_PK_Binding.reagent_reservoir[ Beads_PK_Binding.col], dest=work_destinations[i], vol=transfer_vol, x_offset_source=x_offset_source, x_offset_dest=x_offset_dest, pickup_height=pickup_height, rinse=rinse, avoid_droplet=False, wait_time=2, blow_out=True, touch_tip=True, drop_height=-1) ctx.comment(' ') ctx.comment('Mixing sample ') custom_mix(m300, Beads_PK_Binding, location=work_destinations[i], vol=Beads_PK_Binding.max_volume_allowed, rounds=BEADS_NUM_MIXES, blow_out=False, mix_height=0, offset=0, wait_time=2, two_thirds_mix_bottom=True) # m300.move_to(work_destinations[i].top(0)) # m300.air_gap(Beads_PK_Binding.air_gap_vol_bottom) #air gap if recycle_tip == True: m300.return_tip() else: m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300])) ############################################################################### # STEP 1 TRANSFER BEADS + PK + Binding ######## ############################################################################### # STEP 2 TRANSFER WASH ######## STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') wash_trips = math.ceil(Wash.reagent_volume / Wash.max_volume_allowed) wash_volume = Wash.reagent_volume / wash_trips #136.66 wash_transfer_vol = [] for i in range(wash_trips): wash_transfer_vol.append(wash_volume + Wash.disposal_volume) x_offset_source = 0 x_offset_dest = 0 rinse = False for i in range(num_cols): ctx.comment("Column: " + str(i)) if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(wash_transfer_vol): ctx.comment('Aspirate from reservoir 1') #if j!=0: # rinse = False move_vol_multi(m300, reagent=Wash, source=Wash.reagent_reservoir, dest=wash_destinations[i], vol=transfer_vol, x_offset_source=x_offset_source, x_offset_dest=x_offset_dest, pickup_height=2, rinse=rinse, avoid_droplet=False, wait_time=0, blow_out=True, touch_tip=True) ctx.comment(' ') if recycle_tip == True: m300.return_tip() else: m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300])) ############################################################################### # STEP 2 TRANSFER WASH ######## ############################################################################### # STEP 3 TRANSFER ETHANOL ######## STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') ethanol_trips = math.ceil(Ethanol.reagent_volume / Ethanol.max_volume_allowed) ethanol_volume = Ethanol.reagent_volume / ethanol_trips #136.66 ethanol_transfer_vol = [] for i in range(ethanol_trips): ethanol_transfer_vol.append(ethanol_volume + Ethanol.disposal_volume) x_offset_source = 0 x_offset_dest = 0 rinse = False for i in range(num_cols): ctx.comment("Column: " + str(i)) if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(ethanol_transfer_vol): ctx.comment('Aspirate from reservoir 2') #if j!=0: # rinse = False move_vol_multi(m300, reagent=Ethanol, source=Ethanol.reagent_reservoir, dest=ethanol_destinations[i], vol=transfer_vol, x_offset_source=x_offset_source, x_offset_dest=x_offset_dest, pickup_height=2, rinse=rinse, avoid_droplet=False, wait_time=0, blow_out=True, touch_tip=True) if recycle_tip == True: m300.return_tip() else: m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300])) ############################################################################### # STEP 3 TRANSFER ETHANOL ######## ############################################################################### # STEP 4 TRANSFER ELUTION ######## STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') elution_trips = math.ceil(Elution.reagent_volume / Elution.max_volume_allowed) elution_volume = Elution.reagent_volume / elution_trips #136.66 elution_transfer_vol = [] for i in range(elution_trips): elution_transfer_vol.append(elution_volume + Elution.disposal_volume) x_offset_source = 0 x_offset_dest = 0 rinse = False for i in range(num_cols): ctx.comment("Column: " + str(i)) if not m300.hw_pipette['has_tip']: pick_up(m300) for j, transfer_vol in enumerate(elution_transfer_vol): #Calculate pickup_height based on remaining volume and shape of container [pickup_height, change_col] = calc_height(Elution, multi_well_rack_area, transfer_vol * 8) ctx.comment('Aspirate from reservoir column: ' + str(Elution.col)) ctx.comment('Pickup height is ' + str(round(pickup_height, 2)) + ' mm') #if j!=0: # rinse = False move_vol_multi(m300, reagent=Elution, source=Elution.reagent_reservoir[Elution.col], dest=elution_destinations[i], vol=transfer_vol, x_offset_source=x_offset_source, x_offset_dest=x_offset_dest, pickup_height=pickup_height, rinse=rinse, avoid_droplet=False, wait_time=0, blow_out=False, touch_tip=True) ctx.comment(' ') if recycle_tip == True: m300.return_tip() else: m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300])) ############################################################################### # STEP 4 TRANSFER ELUTION ######## ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Homing robot') ctx.comment('###############################################') ctx.comment(' ') ctx.home() ############################################################################### # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write( 'STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() # Light flash end of program import os #os.system('mpg123 /etc/audio/speaker-test.mp3') for i in range(3): ctx._hw_manager.hardware.set_lights(rails=False) ctx._hw_manager.hardware.set_lights(button=(1, 0, 0)) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(rails=True) ctx._hw_manager.hardware.set_lights(button=(0, 0, 1)) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=(0, 1, 0)) ctx.comment('Finished! \nMove deepwell plates Kingfisher robot.') ctx.comment('Used tips 200uL in total: ' + str(tip_track['counts'][m300])) ctx.comment('Used racks 200uL in total: ' + str(tip_track['counts'][m300] / 96)) ctx.comment('Available 200uL tips: ' + str(tip_track['maxes'][m300]))
def run(ctx: protocol_api.ProtocolContext): # Init protocol run run = ProtocolRun(ctx) # Define stesp run.add_step( description="Transfer PK+MS2 A6 - To AW_PLATE Single Slot1 -> Slot2") # 1 run.add_step(description="Transfer Beats 3 - 2 Multi and mix") # 2 # execute avaliaible steps run.init_steps(steps) ################################## # Tube rack tube_rack = ctx.load_labware( 'opentrons_24_tuberack_nest_1.5ml_screwcap', 4) # Destination plate SLOT 2 if(ctx.is_simulating()): aw_slot = ctx.load_labware( 'opentrons_96_aluminumblock_generic_pcr_strip_200ul', 5) else: aw_slot = ctx.load_labware( 'axygen_96_wellplate_2000ul', 5) aw_wells = aw_slot.wells()[:NUM_SAMPLES] # Mount pippets and set racks # Tipracks20_multi tips20_1 = ctx.load_labware('opentrons_96_tiprack_20ul', 7) tips20_2 = ctx.load_labware('opentrons_96_tiprack_20ul', 8) run.mount_right_pip('p20_single_gen2', tip_racks=[tips20_1,tips20_2], capacity=20) tips300 = ctx.load_labware('opentrons_96_filtertiprack_200ul', 7) run.mount_right_pip('p20_single_gen2', tip_racks=[tips20_1,tips20_2], capacity=20) ############################################################################ # STEP 1: Transfer A6 - To AW_PLATE ############################################################################ if (run.next_step()): run.set_pip("right") # single 20 volumen_move = 5 source = tube_rack.wells("A6")[0] pkms2 = Reagent( name='PK + MS2', num_wells=1, # change with num samples flow_rate_aspirate=0.75, # Original 0.5 flow_rate_dispense=3, # Original 1 reagent_reservoir_volume=vol_pkms2*(NUM_SAMPLES+1), h_cono=4, v_fondo=4 * math.pi * 4 ** 3 / 3 ) pkms2.set_positions([tube_rack.wells("A5"),tube_rack.wells("B5"),tube_rack.wells("B6")]) run.comment(pkms2.get_volumes_fill_print(),add_hash=True) run.pick_up() for dest in aw_wells: pickup_height = run.calc_height( liquid, 4.12*4.12*math.pi, vol_pkms2) run.move_volume(reagent=liquid, source=source, dest=pkms2.get_current_position(), vol=vol_pkms2, air_gap_vol=1, pickup_height=pickup_height, disp_height=-10, blow_out=True, post_dispense=True, post_dispense_vol=5) run.drop_tip() run.finish_step() ############################################################################ # STEP 2: Slot 3 -2 beats_PK AW ############################################################################ if (run.next_step()): ############################################################################ # Light flash end of program run.set_pip("left") # p300 multi volume = 275 beads = Reagent(name='Magnetic beads', flow_rate_aspirate=0.5, flow_rate_dispense=0.5, flow_rate_dispense_mix=4, flow_rate_aspirate_mix=4, rinse=True, delay=2, reagent_reservoir_volume=vol_beads*(NUM_SAMPLES+1), h_cono=1.95, v_fondo=695, rinse_loops=3) beads.set_positions([tube_rack.wells("A6"),tube_rack.wells("B6")]) air_gap_vol = 5 disposal_height = -5 for destination in aw_wells: volumes = beads.divide_volume(vol_beads,180) for vol in volumes: run.pick_up() pickup_height = beads.calc_height( beads, pool_area, vol, extra_volume=vol_min) run.move_volume(reagent=beads, source=beads.get_current_position(), dest=destination, vol=vol, air_gap_vol=air_gap_vol, pickup_height=pickup_height, disp_height=disposal_height, rinse=True, blow_out=True) run.custom_mix(beads, location=destination, vol=vol/2, rounds=3, blow_out=True, mix_height=0) run.drop_tip() run.finish_step() run.log_steps_time() run.blink() for c in robot.commands(): ctx.comment(c) ctx.comment('Finished! \nMove plate to PCR')
def run(ctx: protocol_api.ProtocolContext): STEP = 0 STEPS = { # Dictionary with STEP activation, description and times 1: { 'Execute': True, 'description': 'Dispensar Antibioticos' }, 2: { 'Execute': True, 'description': 'Dispensar caldo y mezclar ' } } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time if not ctx.is_simulating(): folder_path = '/var/lib/jupyter/notebooks/' + run_id if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/time_log.txt' # Define Reagents as objects with their properties #Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, flow_rate_aspirate_mix, flow_rate_dispense_mix, air_gap_vol_bottom, air_gap_vol_top, disposal_volume, rinse, max_volume_allowed, reagent_volume, reagent_reservoir_volume, num_wells, h_cono, v_fondo, tip_recycling='none', dead_vol=DEFAULT_DEAD_VOL): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.flow_rate_aspirate_mix = flow_rate_aspirate_mix self.flow_rate_dispense_mix = flow_rate_dispense_mix self.air_gap_vol_bottom = air_gap_vol_bottom self.air_gap_vol_top = air_gap_vol_top self.disposal_volume = disposal_volume self.rinse = bool(rinse) self.max_volume_allowed = max_volume_allowed self.reagent_volume = reagent_volume self.reagent_reservoir_volume = reagent_reservoir_volume self.num_wells = num_wells self.col = 0 self.vol_well = 0 self.h_cono = h_cono self.v_cono = v_fondo self.tip_recycling = tip_recycling self.dead_vol = dead_vol self.vol_well_original = (reagent_reservoir_volume / num_wells ) + dead_vol if num_wells > 0 else 0 # Reagents and their characteristics Samples = Reagent( name='Antibioticos', flow_rate_aspirate=3, flow_rate_dispense=3, flow_rate_aspirate_mix=1, flow_rate_dispense_mix=1, air_gap_vol_bottom=5, air_gap_vol_top=0, disposal_volume=0, rinse=True, max_volume_allowed=180, reagent_volume=VOLUME_SAMPLE, reagent_reservoir_volume=NUM_REAL_SAMPLES * VOLUME_SAMPLE * 1.1, num_wells=math.ceil(NUM_REAL_SAMPLES * VOLUME_SAMPLE * 1.1 / 11500), h_cono=1.95, v_fondo=695) #1.95 * multi_well_rack_area / 2, #Prismatic Caldo = Reagent( name='Caldo ', flow_rate_aspirate=2, flow_rate_dispense=2, flow_rate_aspirate_mix=1, flow_rate_dispense_mix=1, air_gap_vol_bottom=6, air_gap_vol_top=0, disposal_volume=0, rinse=True, max_volume_allowed=180, reagent_volume=VOLUME_SAMPLE, reagent_reservoir_volume=NUM_REAL_SAMPLES * VOLUME_SAMPLE * 1.1, num_wells=math.ceil(NUM_REAL_SAMPLES * VOLUME_SAMPLE * 1.1 / 11500), h_cono=1.95, v_fondo=695) #1.95 * multi_well_rack_area / 2, #Prismatic ctx.comment(' ') ctx.comment('###############################################') ctx.comment('VALORES DE VARIABLES') ctx.comment(' ') ctx.comment('Número Antibióticos: ' + str(NUM_SAMPLE_ANTIBIOTIC) + ' (' + str(NUM_FINAL_PLATES) + ' placas)') ctx.comment('Número de muestras: ' + str(NUM_REAL_SAMPLES) + ' (' + str(math.ceil(NUM_REAL_SAMPLES / 8)) + ' columnas)') ctx.comment('Número de mezclas: ' + str(NUM_DILUTION_MIXES)) ctx.comment(' ') ctx.comment('Volumen de muestra a mover al deepwell: ' + str(VOLUME_SAMPLE) + ' ul') ctx.comment(' ') ctx.comment('Número de mezclas en la muestra: ' + str(TUBE_NUM_MIXES)) ctx.comment(' ') ctx.comment('Repeticiones del sonido final: ' + str(SOUND_NUM_PLAYS)) ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE)) ctx.comment(' ') ################## # Custom functions def move_multichanel_caldo(dest): if not m300.hw_pipette['has_tip']: pick_up_multi(m300) caldo_trips = math.floor(Caldo.max_volume_allowed / Caldo.reagent_volume) caldo_volume = Caldo.reagent_volume * caldo_trips #136.66 Caldo.max_volume_allowed = caldo_volume caldo_transfer_vol = [] x_offset_source = 0 x_offset_dest = 0 rinse = False # Original: True first_mix_done = False actual_pipette_vol = 0 for i in range(num_cols_caldo): ctx.comment("Column: " + str(i)) #Calculate pickup_height based on remaining volume and shape of container ctx.comment('Aspirate from reservoir column: ' + str(Caldo.col)) if actual_pipette_vol >= VOLUME_SAMPLE: recharge = False else: actual_pipette_vol = Caldo.max_volume_allowed recharge = True move_vol_multi(m300, reagent=Caldo, source=Caldo.reagent_reservoir, dest=dest[i], vol=Caldo.reagent_volume, x_offset_source=x_offset_source, x_offset_dest=x_offset_dest, pickup_height=0, rinse=rinse, wait_time=0.5, blow_out=False, touch_tip=True, drop_height=-3, recharge=recharge) actual_pipette_vol = actual_pipette_vol - VOLUME_SAMPLE m300.blow_out(Caldo.reagent_reservoir.top(z=0)) ctx.delay(seconds=2, msg='Waiting for ' + str(1) + ' seconds.') m300.move_to(Caldo.reagent_reservoir.bottom(2)) m300.blow_out(Caldo.reagent_reservoir.top(z=0)) def move_multichanel_mezcla(origin, dest, pickup_height=2, air_gap_vol=5): x_offset_source = 0 x_offset_dest = 0 first_mix_done = False if not m300.hw_pipette['has_tip']: pick_up(m300) for i in range(num_cols_caldo): ctx.comment("Column: " + str(i)) ctx.comment('Aspirate from reservoir column: ' + str(Caldo.col)) move_vol_multi(m300, reagent=Caldo, source=origin[i], dest=dest[i], vol=Caldo.reagent_volume, multitripCount=1, x_offset_source=x_offset_source, x_offset_dest=x_offset_dest, pickup_height=pickup_height, rinse=False, rinseEnd=True, wait_time=0.5, blow_out=False, touch_tip=False, drop_height=-1, rate_multiplier=0.1) # Aspirar 50 ul de la última columna para igualar volúmenes m300.aspirate(Caldo.reagent_volume, location=dest[num_cols_caldo - 1].bottom(z=pickup_height), rate=Caldo.flow_rate_aspirate) if air_gap_vol > 0: m300.aspirate(air_gap_vol, dest[num_cols_caldo - 1].top(z=0), rate=Caldo.flow_rate_aspirate) # air gap m300.drop_tip(home_after=False) tip_track['counts'][m300] += 8 def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # SOURCE s = source.bottom(pickup_height).move(Point(x=x_offset[0])) pipet.aspirate(vol, s, rate=reagent.flow_rate_aspirate) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z=-2), rate=reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z=disp_height).move(Point(x=x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate=reagent.flow_rate_dispense) # dispense all #ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent if blow_out == True: pipet.blow_out(dest.top(z=disp_height)) if touch_tip == True: pipet.touch_tip(speed=20, v_offset=-10) if air_gap_vol != 0: pipet.air_gap(air_gap_vol, height=disp_height) #air gap def move_vol_multi(pipet, reagent, source, dest, vol, x_offset_source, x_offset_dest, pickup_height, rinse, wait_time, blow_out, touch_tip=False, drop_height=-5, dispense_bottom_air_gap_before=False, rinseEnd=False, recharge=True, multitripCount=3, rate_multiplier=1): # Rinse before aspirating if rinse == True: #pipet.aspirate(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap custom_mix(pipet, reagent, location=source, vol=vol * .6, rounds=NUM_DILUTION_MIXES, blow_out=False, mix_height=pickup_height, offset=0) #pipet.dispense(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_dispense) # SOURCE if recharge == True: #if dispense_bottom_air_gap_before and reagent.air_gap_vol_bottom: # pipet.dispense(reagent.air_gap_vol_bottom, source.top(z = -2), rate = reagent.flow_rate_dispense) if reagent.air_gap_vol_top != 0: #If there is air_gap_vol, switch pipette to slow speed pipet.move_to(source.top(z=0)) pipet.air_gap(reagent.air_gap_vol_top) #air gap #pipet.aspirate(reagent.air_gap_vol_top, source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap pipet.blow_out(source.top(z=drop_height)) s = source.bottom(pickup_height).move(Point(x=x_offset_source)) pipet.aspirate(vol * multitripCount, s, rate=reagent.flow_rate_aspirate * rate_multiplier) # aspirate liquid if reagent.air_gap_vol_bottom != 0: #If there is air_gap_vol, switch pipette to slow speed pipet.air_gap(reagent.air_gap_vol_bottom, height=0) #air gap if wait_time != 0: ctx.delay(seconds=wait_time, msg='Waiting for ' + str(wait_time) + ' seconds.') # GO TO DESTINATION d = dest.top(z=drop_height).move(Point(x=x_offset_dest)) pipet.dispense(vol + reagent.air_gap_vol_bottom, d, rate=reagent.flow_rate_dispense * rate_multiplier) if wait_time != 0: ctx.delay(seconds=wait_time, msg='Waiting for ' + str(wait_time) + ' seconds.') if reagent.air_gap_vol_top != 0: pipet.dispense(reagent.air_gap_vol_top, dest.top(z=0), rate=reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(dest.top(z=drop_height)) if touch_tip == True: pipet.touch_tip(speed=20, v_offset=-3, radius=0.7) # Rinse after aspirating if rinseEnd == True: #pipet.aspirate(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap custom_mix(pipet, reagent, location=dest, vol=vol * .6, rounds=2, blow_out=False, mix_height=pickup_height, offset=0) pipet.blow_out(dest.top(z=drop_height)) #pipet.dispense(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_dispense) if reagent.air_gap_vol_bottom != 0 and rinseEnd == False: pipet.move_to(dest.top(z=0)) pipet.air_gap(reagent.air_gap_vol_bottom) #air gap #pipet.aspirate(air_gap_vol_bottom, dest.top(z = 0),rate = reagent.flow_rate_aspirate) #air gap def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, offset, wait_time=0, drop_height=-1, two_thirds_mix_bottom=False): ''' Function for mix in the same location a certain number of rounds. Blow out optional. Offset can set to 0 or a higher/lower value which indicates the lateral movement ''' if mix_height <= 0: mix_height = 1 pipet.aspirate(1, location=location.bottom(z=mix_height), rate=reagent.flow_rate_aspirate_mix) for i in range(rounds): pipet.aspirate(vol, location=location.bottom(z=mix_height), rate=reagent.flow_rate_aspirate_mix) if two_thirds_mix_bottom and i < ((rounds / 3) * 2): pipet.dispense(vol, location=location.bottom(z=5).move( Point(x=offset)), rate=reagent.flow_rate_dispense_mix) else: pipet.dispense(vol, location=location.top(z=drop_height).move( Point(x=offset)), rate=reagent.flow_rate_dispense_mix) pipet.dispense(1, location=location.bottom(z=mix_height), rate=reagent.flow_rate_dispense_mix) if blow_out == True: pipet.blow_out(location.top(z=-2)) # Blow out if wait_time != 0: ctx.delay(seconds=wait_time, msg='Waiting for ' + str(wait_time) + ' seconds.') def generate_atibiotic_source(source_plate): result = [] ''' for i in range (NUM_FINAL_PLATES): result += source_plate.rows()[i][0] ''' result = source_plate.rows()[:NUM_FINAL_PLATES][0] return result def generate_antibiotic_dest(dest_plates): ''' Concatenate cols from all destination plates ''' result = [] #result = dest_plates[0].columns()[0][0] + dest_plates[1].columns()[0] + dest_plates[2].columns()[0] for i in range(NUM_FINAL_PLATES): result += [dest_plates[i].columns()[0][0]] return result def calc_height(reagent, cross_section_area, aspirate_volume, min_height=0.4): nonlocal ctx ctx.comment('Remaining volume ' + str(reagent.vol_well) + '< needed volume ' + str(aspirate_volume) + '?') if (reagent.vol_well - reagent.dead_vol) < aspirate_volume: ctx.comment('Next column should be picked') ctx.comment('Previous to change: ' + str(reagent.col)) # column selector position; intialize to required number reagent.col = reagent.col + 1 ctx.comment(str('After change: ' + str(reagent.col))) reagent.vol_well = reagent.vol_well_original ctx.comment('New volume:' + str(reagent.vol_well)) height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Remaining volume:' + str(reagent.vol_well)) if height < min_height: height = min_height col_change = True else: height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area reagent.vol_well = reagent.vol_well - aspirate_volume ctx.comment('Calculated height is ' + str(height)) if height < min_height: height = min_height ctx.comment('Used height is ' + str(height)) col_change = False return height, col_change ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 pip.pick_up_tip() def pick_up_multi(pip): nonlocal tip_track #if not ctx.is_simulating(): if tip_track['counts'][pip] >= tip_track['maxes'][pip]: for i in range(3): ctx._hw_manager.hardware.set_lights(rails=False) ctx._hw_manager.hardware.set_lights(button=(1, 0, 0)) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(rails=True) ctx._hw_manager.hardware.set_lights(button=(0, 0, 1)) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=(0, 1, 0)) ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 tip_track['num_refills'][pip] += 1 pip.pick_up_tip() def run_quiet_process(command): subprocess.check_output('{} &> /dev/null'.format(command), shell=True) def play_sound(filename): print('Speaker') print('Next\t--> CTRL-C') try: run_quiet_process('mpg123 {}'.format(path_sounds + filename + '.mp3')) run_quiet_process('mpg123 {}'.format(path_sounds + sonido_defecto)) run_quiet_process('mpg123 {}'.format(path_sounds + filename + '.mp3')) except KeyboardInterrupt: pass print() def start_run(): ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Empezando protocolo') if PHOTOSENSITIVE == False: ctx._hw_manager.hardware.set_lights(button=True, rails=True) else: ctx._hw_manager.hardware.set_lights(button=True, rails=False) now = datetime.now() # dd/mm/YY H:M:S start_time = now.strftime("%Y/%m/%d %H:%M:%S") return start_time def finish_run(switch_off_lights=False): ctx.comment('###############################################') ctx.comment('Protocolo finalizado') ctx.comment(' ') #Set light color to blue ctx._hw_manager.hardware.set_lights(button=True, rails=False) now = datetime.now() # dd/mm/YY H:M:S finish_time = now.strftime("%Y/%m/%d %H:%M:%S") if PHOTOSENSITIVE == False: for i in range(10): ctx._hw_manager.hardware.set_lights(button=False, rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=True, rails=True) time.sleep(0.3) else: for i in range(10): ctx._hw_manager.hardware.set_lights(button=False, rails=False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button=True, rails=False) time.sleep(0.3) if switch_off_lights: ctx._hw_manager.hardware.set_lights(button=True, rails=False) used_tips_300 = tip_track['num_refills'][m300] * 96 * len( m300.tip_racks) + tip_track['counts'][m300] ctx.comment('Puntas de 300 ul utilizadas: ' + str(used_tips_300) + ' (' + str(round(used_tips_300 / 96, 2)) + ' caja(s))') ctx.comment('###############################################') if not ctx.is_simulating(): for i in range(SOUND_NUM_PLAYS): if i > 0: time.sleep(60) play_sound('finished_process_esp') return finish_time def validate_parameters(): result = True return result #################################### # load labware and modules #################################### # Load Sample racks source_antibiotic = ctx.load_labware('nest_96_wellplate_2ml_deep', '8', 'NEST 96 Deepwell Plate 2mL') ''' ctx.load_labware( 'opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap', '8', 'Opentrons 24 Tuberack Eppendorf 2ml Safelock Snapcap') ''' source_caldo = ctx.load_labware('nest_1_reservoir_195ml', '1', 'NEST 1 Reservoir 195ul') ################################## # Destination plate # Destination dest_plate1 = ctx.load_labware('nest_96_wellplate_200ul_flat', '3', 'NEST 96 Well Plate 200ul 1') dest_plate2 = ctx.load_labware('nest_96_wellplate_200ul_flat', '5', 'NEST 96 Well Plate 200ul 2') dest_plate3 = ctx.load_labware('nest_96_wellplate_200ul_flat', '7', 'NEST 96 Well Plate 200ul 3') #################################### # Load tip_racks tips200 = [ ctx.load_labware('opentrons_96_filtertiprack_200ul', slot) for slot in ['10'] ] ################################################################################ # setup samples and destinations num_destination_plates = NUM_FINAL_PLATES #sample_antibiotic = source_antibiotic.wells()[:NUM_SAMPLE_ANTIBIOTIC] sample_antibiotic = generate_atibiotic_source(source_antibiotic) Caldo.reagent_reservoir = source_caldo.wells()[0] destinations_antibiotic = generate_antibiotic_dest( [dest_plate1, dest_plate2, dest_plate3]) #[:NUM_SAMPLE_ANTIBIOTIC] destinations1 = dest_plate1.rows()[0][1:] destinations2 = dest_plate2.rows()[0][1:] destinations3 = dest_plate3.rows()[0][1:] sourceMix1 = dest_plate1.rows()[0][:11] sourceMix2 = dest_plate2.rows()[0][:11] sourceMix3 = dest_plate3.rows()[0][:11] destinationsMix1 = dest_plate1.rows()[0][1:12] destinationsMix2 = dest_plate2.rows()[0][1:12] destinationsMix3 = dest_plate3.rows()[0][1:12] m300 = ctx.load_instrument('p300_multi_gen2', 'right', tip_racks=tips200) # load P300 pipette # used tip counter and set maximum tips available tip_track = { 'counts': { m300: 0 }, 'maxes': { m300: 96 * len(m300.tip_racks) }, 'num_refills': { m300: 0 }, 'tips': { m300: [tip for rack in tips200 for tip in rack.rows()[0]] } } if validate_parameters(): start_run() ############################################################################ # STEP 1: Dispensación de Atnibiótico ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') start = datetime.now() for s, d in zip(sample_antibiotic, destinations_antibiotic): # Mix the sample BEFORE dispensing if not m300.hw_pipette['has_tip']: pick_up(m300) move_vol_multichannel(m300, reagent=Samples, source=s, dest=d, vol=VOLUME_ANTBIOTIC, air_gap_vol=air_gap_vol_sample, x_offset=x_offset, pickup_height=3, rinse=Samples.rinse, disp_height=0, blow_out=True, touch_tip=False) m300.drop_tip(home_after=False) tip_track['counts'][m300] += 1 # Time statistics end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2: Dilución de antibiótico sobre el caldo ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description']) ctx.comment('###############################################') start = datetime.now() if num_destination_plates >= 1: move_multichanel_caldo(destinations1) move_multichanel_mezcla(sourceMix1, destinationsMix1) if num_destination_plates >= 2: move_multichanel_caldo(destinations2) move_multichanel_mezcla(sourceMix2, destinationsMix2) if num_destination_plates >= 3: move_multichanel_caldo(destinations3) move_multichanel_mezcla(sourceMix3, destinationsMix3) # Time statistics end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write( 'STEP\texecution\tdescription\twait_time\texecution_time\n' ) for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ finish_run(switch_off_lights)
def run(ctx: protocol_api.ProtocolContext): # load labware ic_pk = ctx.load_labware( 'opentrons_24_aluminumblock_nest_2ml_snapcap', '9', 'chilled tubeblock for internal control and proteinase K (strip 1)' ).wells()[0] source_racks = [ ctx.load_labware( 'opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap', slot, 'source tuberack ' + str(i + 1)) for i, slot in enumerate(['1', '2', '3', '4']) ] dest_plate = ctx.load_labware('nest_96_wellplate_2ml_deep', '8', '96-deepwell sample plate') binding_buffer = ctx.load_labware( 'opentrons_6_tuberack_falcon_50ml_conical', '11', '50ml tuberack for binding buffer (tube B1)').wells('B1') # binding_buffer = ctx.load_labware( # 'biorad_96_wellplate_200ul_pcr', '7', # '50ml tuberack for lysis buffer + PK (tube A1)').wells()[:1] tipracks1000 = [ ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot, '1000µl filter tiprack') for slot in ['10', '7'] ] tipracks20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', '6', '20µl filter tiprack') ] # load pipette s20 = ctx.load_instrument('p20_single_gen2', 'left', tip_racks=tipracks20) p1000 = ctx.load_instrument('p1000_single_gen2', 'right', tip_racks=tipracks1000) # setup samples sources = [well for rack in source_racks for well in rack.wells()][:NUM_SAMPLES] dests_single = dest_plate.wells()[:NUM_SAMPLES] num_cols = math.ceil(NUM_SAMPLES / 8) dests_multi = dest_plate.rows()[0][:num_cols] tip_log = {'count': {}} folder_path = '/data/A' tip_file_path = folder_path + '/tip_log.json' if TIP_TRACK and not ctx.is_simulating(): if os.path.isfile(tip_file_path): with open(tip_file_path) as json_file: data = json.load(json_file) if 'tips1000' in data: tip_log['count'][p1000] = data['tips1000'] else: tip_log['count'][p1000] = 0 if 'tips20' in data: tip_log['count'][s20] = data['tips20'] else: tip_log['count'][s20] = 0 else: tip_log['count'] = {p1000: 0, s20: 0} tip_log['tips'] = { p1000: [tip for rack in tipracks1000 for tip in rack.wells()], #s20: [tip for rack in tipracks20 for tip in rack.rows()[0]] s20: [tip for rack in tipracks20 for tip in rack.wells()] } tip_log['max'] = {pip: len(tip_log['tips'][pip]) for pip in [p1000, s20]} def pick_up(pip): nonlocal tip_log if tip_log['count'][pip] == tip_log['max'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_log['count'][pip] = 0 pip.pick_up_tip(tip_log['tips'][pip][tip_log['count'][pip]]) tip_log['count'][pip] += 1 heights = {tube: TUBE50_VOlUME * 1 for tube in binding_buffer} radius = (binding_buffer[0].diameter) / 2 min_h = 5 def h_track(vol, tube): nonlocal heights dh = vol / (math.pi * (radius**2)) if heights[tube] - dh > min_h: heights[tube] = heights[tube] - dh else: heights[tube] = min_h # stop 5mm short of the bottom return heights[tube] p1000.flow_rate.aspirate = 50 p1000.flow_rate.dispense = 60 p1000.flow_rate.blow_out = 100 # transfer internal control + proteinase K pick_up(s20) for d in dests_single: s20.dispense(10, ic_pk.bottom(2)) s20.transfer(ICPK_VOlUME, ic_pk.bottom(2), d.bottom(2), air_gap=5, new_tip='never') s20.air_gap(5) s20.drop_tip() # transfer binding buffer and mix pick_up(p1000) for i, (s, d) in enumerate(zip(sources, dests_single)): source = binding_buffer[ i // 96] # 1 tube of binding buffer can accommodate all samples here h = h_track(275, source) # custom mix p1000.flow_rate.aspirate = 100 p1000.flow_rate.dispense = 100 p1000.dispense(500, source.bottom(h + 20)) for _ in range(4): # p1000.air_gap(500) p1000.aspirate(500, source.bottom(h)) p1000.dispense(500, source.bottom(h + 20)) # p1000.transfer(BB_VOLUME, source.bottom(h), d.bottom(5), air_gap=100, # new_tip='never') p1000.flow_rate.aspirate = 50 p1000.flow_rate.dispense = 100 p1000.aspirate(BB_VOLUME, source.bottom(h)) p1000.air_gap(10) p1000.dispense(BB_VOLUME + 100, d.bottom(10)) p1000.air_gap(10) p1000.drop_tip() ctx.comment('Move deepwell plate (slot 4) to Station B for RNA \ extraction.') # track final used tip if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) data = { 'tips1000': tip_log['count'][p1000], 'tips20': tip_log['count'][s20] } with open(tip_file_path, 'w') as outfile: json.dump(data, outfile)
def run(ctx: protocol_api.ProtocolContext): # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: {'Execute': HYDRATATE, 'description': 'Hidratate'}, 2: {'Execute': True, 'description': 'Transfer samples'} } for s in STEPS: # Create an empty wait_time if 'wait_time' not in STEPS[s]: STEPS[s]['wait_time'] = 0 #Folder and file_path for log time folder_path = '/var/lib/jupyter/notebooks' + run_id if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) file_path = folder_path + '/time_log.txt' # Define Reagents as objects with their properties class Reagent: def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, reagent_reservoir_volume, delay, num_wells): self.name = name self.flow_rate_aspirate = flow_rate_aspirate self.flow_rate_dispense = flow_rate_dispense self.rinse = bool(rinse) self.reagent_reservoir_volume = reagent_reservoir_volume self.delay = delay self.num_wells = num_wells self.vol_well = 0 self.unused = [] self.vol_well_original = reagent_reservoir_volume / num_wells # Reagents and their characteristics Hydr = Reagent(name = 'Hydr', rinse = False, flow_rate_aspirate = 3, flow_rate_dispense = 3, reagent_reservoir_volume = 1800, num_wells = 1, delay = 0 ) Samples = Reagent(name = 'Samples', rinse = False, flow_rate_aspirate = 1, flow_rate_dispense = 1, reagent_reservoir_volume = 50, delay = 0, num_wells = NUM_SAMPLES ) Hydr.vol_well = Hydr.vol_well_original Samples.vol_well = Samples.vol_well_original ctx.comment(' ') ctx.comment('###############################################') ctx.comment('VALORES DE VARIABLES') ctx.comment(' ') ctx.comment('Número de muestras: ' + str(NUM_SAMPLES) + ' las dos primeras son controles.') ctx.comment(' ') ctx.comment('Hidratar muestras: ' + str(HYDRATATE)) if HYDRATATE: ctx.comment('Volumen de Hidratante por muestra: ' + str(HYDR_VOL_PER_SAMPLE) + ' uL') ctx.comment(' ') ctx.comment('Volumen de muestra: ' + str(VOLUME_SAMPLE) + ' uL') ctx.comment(' ') ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE)) ctx.comment('Repeticiones del sonido final: ' + str(SOUND_NUM_PLAYS)) ctx.comment(' ') ################## # Custom functions def divide_destinations(l, n): # Divide the list of destinations in size n lists. for i in range(0, len(l), n): yield l[i:i + n] def distribute_custom(pipette, volume, src, dest, waste_pool, pickup_height, extra_dispensal, dest_x_offset, disp_height = 0, touch_tip = False, num_shakes = 0): pipette.aspirate((len(dest) * volume) + extra_dispensal, src.bottom(pickup_height)) if touch_tip : pipette.touch_tip(speed = 20, v_offset = -5) for d in dest: drop = d.top(z = disp_height) pipette.dispense(volume, drop) shake_pipet(pipette, rounds = num_shakes, v_offset = disp_height) 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 shake_pipet (pipet, rounds = 2, speed = 100, v_offset = 0): ctx.comment("Shaking " + str(rounds) + " rounds.") for i in range(rounds): pipet.touch_tip(speed = speed, radius = 0.1, v_offset = v_offset) def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset, pickup_height, rinse, disp_height, blow_out, touch_tip, num_shakes = 0): ''' x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1] pickup_height: height from bottom where volume rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered blow_out, touch_tip: if True they will be done after dispensing ''' # Rinse before aspirating if rinse == True: custom_mix(pipet, reagent, location = source, vol = vol, rounds = 2, blow_out = True, mix_height = 0, x_offset = x_offset) # SOURCE s = source.bottom(pickup_height).move(Point(x = x_offset[0])) pipet.aspirate(vol, s) # aspirate liquid if air_gap_vol != 0: # If there is air_gap_vol, switch pipette to slow speed pipet.aspirate(air_gap_vol, source.top(z = -2), rate = reagent.flow_rate_aspirate) # air gap # GO TO DESTINATION drop = dest.top(z = disp_height).move(Point(x = x_offset[1])) pipet.dispense(vol + air_gap_vol, drop, rate = reagent.flow_rate_dispense) # dispense all ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent shake_pipet(pipet, rounds = num_shakes, v_offset = disp_height) if blow_out == True: pipet.blow_out(dest.top(z = -10)) if touch_tip == True: pipet.touch_tip(speed = 20, v_offset = -10, radius = 0.5) def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height, x_offset, source_height = 3): ''' Function for mixing a given [vol] in the same [location] a x number of [rounds]. blow_out: Blow out optional [True,False] x_offset = [source, destination] source_height: height from bottom to aspirate mix_height: height from bottom to dispense ''' if mix_height <= 0: mix_height = 3 pipet.aspirate(1, location = location.bottom( z = source_height).move(Point(x = x_offset[0])), rate = reagent.flow_rate_aspirate) for _ in range(rounds): pipet.aspirate(vol, location = location.bottom( z = source_height).move(Point(x = x_offset[0])), rate = reagent.flow_rate_aspirate) pipet.dispense(vol, location = location.bottom( z = mix_height).move(Point(x = x_offset[1])), rate = reagent.flow_rate_dispense) pipet.dispense(1, location = location.bottom( z = mix_height).move(Point(x = x_offset[1])), rate = reagent.flow_rate_dispense) if blow_out == True: pipet.blow_out(location.top(z = -2)) # Blow out def run_quiet_process(command): subprocess.check_output('{} &> /dev/null'.format(command), shell=True) def play_sound(filename): print('Speaker') print('Next\t--> CTRL-C') try: run_quiet_process('mpg123 {}'.format(path_sounds + filename + '.mp3')) except KeyboardInterrupt: pass print() def finish_run(switch_off_lights = False): ctx.comment('###############################################') ctx.comment('Protocolo finalizado') ctx.comment(' ') #Set light color to blue ctx._hw_manager.hardware.set_lights(button = True, rails = False) now = datetime.now() # dd/mm/YY H:M:S finish_time = now.strftime("%Y/%m/%d %H:%M:%S") if PHOTOSENSITIVE==False: for i in range(10): ctx._hw_manager.hardware.set_lights(button = False, rails = False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button = True, rails = True) time.sleep(0.3) else: for i in range(10): ctx._hw_manager.hardware.set_lights(button = False, rails = False) time.sleep(0.3) ctx._hw_manager.hardware.set_lights(button = True, rails = False) time.sleep(0.3) if switch_off_lights: ctx._hw_manager.hardware.set_lights(button = True, rails = False) ctx.comment('Puntas de 20 uL utilizadas: ' + str(tip_track['counts'][m20]) + ' (' + str(round(tip_track['counts'][m20] / 96, 2)) + ' caja(s))') ctx.comment('###############################################') if not ctx.is_simulating(): for i in range(SOUND_NUM_PLAYS): if i > 0: time.sleep(60) play_sound('finalizado') return finish_time #################################### # load labware and modules #################################### #################################### # 24 well rack tuberack = ctx.load_labware( 'opentrons_24_aluminumblock_generic_2ml_screwcap', '8', 'Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap') ################################## # Sample plate - comes from B source_plate = ctx.load_labware( 'biorad_96_wellplate_200ul_pcr', '3', 'Bio-Rad 96 Well Plate 200 µL PCR') ################################## # qPCR plate - final plate, goes to PCR qpcr_plate = ctx.load_labware( 'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '6', 'Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 µL') ################################## # Load Tipracks tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', slot) for slot in ['2'] ] tips200 = [ ctx.load_labware('opentrons_96_filtertiprack_200ul', slot) for slot in ['5'] ] ################################################################################ # Declare which reagents are in each reservoir as well as deepwell and elution plate Hydr.reagent_reservoir = tuberack.rows()[0][0] # A1 # setup up sample sources and destinations samples = source_plate.rows()[0][:num_cols] pcr_wells = qpcr_plate.wells()[:NUM_SAMPLES] pcr_wells_samples = qpcr_plate.rows()[0][:num_cols] tipCols = tips20[0].rows()[0][:num_cols] # Divide destination wells in small groups for P300 pipette dests = list(divide_destinations(pcr_wells, size_transfer)) # pipettes m20 = ctx.load_instrument( 'p20_multi_gen2', mount = 'right', tip_racks = tips20) # load m20 pipette p300 = ctx.load_instrument( 'p300_single_gen2', mount = 'left', tip_racks = tips200) # used tip counter and set maximum tips available tip_track = { 'counts': {p300: 0, m20: 0}, 'maxes': {p300: 96 * len(p300.tip_racks), m20: 96 * len(m20.tip_racks)} } ########## # pick up tip and if there is none left, prompt user for a new rack def pick_up(pip): nonlocal tip_track if not ctx.is_simulating(): if tip_track['counts'][pip] == tip_track['maxes'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_track['counts'][pip] = 0 if not pip.hw_pipette['has_tip']: pip.pick_up_tip() ########## ############################################################################ # STEP 1: HIDRATATE ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step '+str(STEP)+': '+STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') pick_up(p300) used_vol = [] for dest in dests: aspirate_volume = HYDR_VOL_PER_SAMPLE * len(dest) + extra_dispensal used_vol_temp = distribute_custom(p300, volume = HYDR_VOL_PER_SAMPLE, src = Hydr.reagent_reservoir, dest = dest, touch_tip = False, waste_pool = Hydr.reagent_reservoir, pickup_height = 0.2, extra_dispensal = extra_dispensal, dest_x_offset = 0, disp_height = -15, num_shakes = 1) used_vol.append(used_vol_temp) p300.drop_tip(home_after = False) tip_track['counts'][p300] += 1 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) ############################################################################ # STEP 2: TRANSFER SAMPLES ############################################################################ STEP += 1 if STEPS[STEP]['Execute'] == True: start = datetime.now() ctx.comment(' ') ctx.comment('###############################################') ctx.comment('Step '+str(STEP)+': '+STEPS[STEP]['description']) ctx.comment('###############################################') ctx.comment(' ') i = 0 for s, d in zip(samples, pcr_wells_samples): pick_up(m20) move_vol_multichannel(m20, reagent = Samples, source = s, dest = d, vol = VOLUME_SAMPLE, air_gap_vol = air_gap_sample, x_offset = x_offset, pickup_height = 0.2, disp_height = -10, rinse = False, blow_out=True, touch_tip=False) m20.drop_tip(home_after = False) tip_track['counts'][m20] += 8 i = i + 1 end = datetime.now() time_taken = (end - start) ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] + ' took ' + str(time_taken)) STEPS[STEP]['Time:'] = str(time_taken) # Export the time log to a tsv file if not ctx.is_simulating(): with open(file_path, 'w') as f: f.write('STEP\texecution\tdescription\twait_time\texecution_time\n') for key in STEPS.keys(): row = str(key) for key2 in STEPS[key].keys(): row += '\t' + format(STEPS[key][key2]) f.write(row + '\n') f.close() ############################################################################ finish_run()
def run(ctx: protocol_api.ProtocolContext): # load labware ic_pk = ctx.load_labware( 'opentrons_96_aluminumblock_nest_wellplate_100ul', '9', 'chilled tubeblock for internal control and proteinase K (strip 1)' ).wells()[0] bb = ctx.load_labware('nest_12_reservoir_15ml', '8', 'reagent reservoir Binding Buffer') binding_buffer = bb.wells()[:2] dest_plate = ctx.load_labware('nest_96_wellplate_2ml_deep', '11', '96-deepwell sample plate') # load tips tips300 = [ ctx.load_labware('opentrons_96_filtertiprack_200ul', '5', '200µl filter tiprack') ] tips20 = [ ctx.load_labware('opentrons_96_filtertiprack_20ul', '6', '20µl filter tiprack') ] # load pipette m300 = ctx.load_instrument('p300_multi_gen2', 'right', tip_racks=tips300) s20 = ctx.load_instrument('p20_single_gen2', 'left', tip_racks=tips20) m300.flow_rate.aspirate = 50 m300.flow_rate.dispense = 150 m300.flow_rate.blow_out = 300 s20.flow_rate.aspirate = 50 s20.flow_rate.dispense = 100 s20.flow_rate.blow_out = 300 # setup samples num_cols = math.ceil(NUM_SAMPLES / 8) bbs = bb.wells()[:2] dests_single = dest_plate.wells()[:NUM_SAMPLES] dests_multi = dest_plate.rows()[0][:num_cols] tip_log = {'count': {}} folder_path = '/data/A' tip_file_path = folder_path + '/tip_log.json' if TIP_TRACK and not ctx.is_simulating(): if os.path.isfile(tip_file_path): with open(tip_file_path) as json_file: data = json.load(json_file) if 'tips1000' in data: tip_log['count'][m300] = data['tips1000'] else: tip_log['count'][m300] = 0 if 'tips20' in data: tip_log['count'][s20] = data['tips20'] else: tip_log['count'][s20] = 0 else: tip_log['count'] = {m300: 0, s20: 0} tip_log['tips'] = { m300: [tip for rack in tips300 for tip in rack.wells()], s20: [tip for rack in tips20 for tip in rack.rows()[0]] } tip_log['max'] = {pip: len(tip_log['tips'][pip]) for pip in [m300, s20]} def pick_up(pip): nonlocal tip_log if tip_log['count'][pip] == tip_log['max'][pip]: ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \ resuming.') pip.reset_tipracks() tip_log['count'][pip] = 0 pip.pick_up_tip(tip_log['tips'][pip][tip_log['count'][pip]]) tip_log['count'][pip] += 1 heights = {tube: 20 for tube in binding_buffer} # radius = (binding_buffer[0].diameter)/2 min_h = 5 def h_track(vol, tube): nonlocal heights dh = vol / (math.pi * (radius**2)) if heights[tube] - dh > min_h: heights[tube] = heights[tube] - dh else: heights[tube] = min_h # stop 5mm short of the bottom return heights[tube] # transfer internal control + proteinase K for d in dests_single: pick_up(s20) s20.transfer(ICPK_VOlUME, ic_pk.bottom(2), d.bottom(2), air_gap=5, new_tip='never') s20.air_gap(5) s20.drop_tip() # transfer binding buffer for i, e in enumerate(dests_multi): pick_up(m300) m300.transfer(BB_VOLUME, bbs[i // 6].bottom(2), e.bottom(10), air_gap=5, mix_after=(5, 100), new_tip='never') m300.air_gap(100) m300.drop_tip() ctx.comment('Terminado.') # track final used tip if not ctx.is_simulating(): if not os.path.isdir(folder_path): os.mkdir(folder_path) data = {'tips300': tip_log['count'][m300]} with open(tip_file_path, 'w') as outfile: json.dump(data, outfile)
def run(ctx: protocol_api.ProtocolContext): [csv, m300_mount, p300_mount, temp_mod_temp, asp_rate_step1, pbs_dispense_rate, incubation_time, first_media_x, second_media_y, track_tips ] = get_values( # noqa: F821 "csv", "m300_mount", "p300_mount", "temp_mod_temp", "asp_rate_step1", "pbs_dispense_rate", "incubation_time", "first_media_x", "second_media_y", "track_tips") # LABWARE temp_mod = ctx.load_module('temperature module gen2', '10') reagents = ctx.load_labware('nest_12_reservoir_15ml', '11') waste_res = ctx.load_labware('nest_12_reservoir_15ml', '7') plate = temp_mod.load_labware( 'corning_96_wellplate_360ul_flat', '10') # TIPRACKS tipracks = [ctx.load_labware('opentrons_96_filtertiprack_200ul', slot) for slot in ['4', '5', '6']] # INSTRUMENTS p300 = ctx.load_instrument('p300_single_gen2', p300_mount, tip_racks=tipracks) m300 = ctx.load_instrument('p300_multi_gen2', m300_mount, tip_racks=tipracks) tips_by_col = [tip for rack in tipracks for col in rack.columns() for tip in col[::-1]] tip_cols = [tips_by_col[i:i+8] for i in range(0, len(tips_by_col), 8)] """ TIP-TRACKING BETWEEN RUNS. """ total_tip_cols = 36 file_path = '/data/csv/tiptracking.json' file_dir = os.path.dirname(file_path) tips_by_col = [tip for rack in tipracks for col in rack.columns() for tip in col[::-1]] tip_cols = [tips_by_col[i:i+8] for i in range(0, len(tips_by_col), 8)] if track_tips and not ctx.is_simulating(): # check for file directory if not os.path.exists(file_dir): os.makedirs(file_dir) # if no file, then use standard tip_chunks definition, and the # end of the code will write to updated tip_chunks list to # the file created. This if statement handles the case in which # tip tracking is selected for the first time. if not os.path.isfile(file_path): tip_chunks = [tips_by_col[i:i+8] for i in range(0, len(tips_by_col), 8)] else: # grab nested list tip_chunks from file. source = open(file_path, 'rb').read() # see below for conversion of tip_chunks nested list to bools. # in order to dump the well objects in tip_chunks to a json file, # they had to be serializable (int, string, bool). The end of the # protocol does this conversion. tip_bool_chunks = json.loads(source) # convert bools back to well objects to use. tip_chunks = [[] for _ in range(total_tip_cols)] for i, (bool_chunk, tip_chunk) in enumerate(zip(tip_bool_chunks, tip_cols)): if len(bool_chunk) == 0: continue for true_tip, tip_loc in zip(bool_chunk, tip_chunk): if true_tip: tip_chunks[i].append(tip_loc) else: continue else: # standard definition of tip_chunks if not tracking tips. tip_chunks = [tips_by_col[i:i+8] for i in range(0, len(tips_by_col), 8)] """PROTOCOL BEGINS """ csv_rows = [[val.strip() for val in line.split(',')] for line in csv.splitlines() if line.split(',')[0].strip()][1:] """FIND INVOLVED WELLS""" values_from_csv = [] wells_from_csv = [] for row in csv_rows: well, value = row[:2] value = int(value) values_from_csv.append(value) wells_from_csv.append(well) # create nested list of all values in csv (by column). value_chunk_cols = [values_from_csv[i:i+8] for i in range(0, len(values_from_csv), 8)] list_well_tips = [] """CREATE A LIST OF # TIPS FOR EACH WELL""" start_point = 0 tip_count = 0 for i, chunk in enumerate(value_chunk_cols): start_point = 0 # check for the values in each column. # if we find a well with a value of 85 or greater, # use that index (j) as the starting point and see how many values # after that are also greater than 85. Once we don't find one, # break. for j, value in enumerate(chunk[start_point:]): if value >= 85: for check_values in chunk[j:]: if check_values >= 85: tip_count += 1 else: break list_well_tips.append(tip_count) tip_count = 0 continue else: list_well_tips.append(0) # create a dictionary which says how many tips go to each well. # For example, if the entire first column has values higher than 85, # the first value in the dictionary will be "A1: 8". If first four wells # in column 2 are greater than 85, "B1:4". dict_tips_per_well = {} tip_ctr = 0 for j, (well, num_tips) in enumerate(zip(wells_from_csv, list_well_tips)): if tip_ctr > 0: tip_ctr -= 1 continue if num_tips > 0: tip_ctr = num_tips - 1 dict_tips_per_well[well] = num_tips # print('\n\n', dict_tips_per_well, '\n\n') """PICKUP FUNCTION""" def pick_up(num_channels_per_pickup): nonlocal tip_chunks if num_channels_per_pickup > 1: pip = m300 else: pip = p300 try: col = 0 # based on the demand of the next well (1-8 tips), this for loop # will find the first available column having adequate number # of tips starting from the first. If the tip pick up order is: # 6, 8, 2 for the first 3 pick ups, then 6 tips will be taken from # column 1, 8 tips from column 2, and 2 tips back from column 1. # efficient tip pick up instead of "throwing away" a whole column # after pick up. for _ in range(36): if num_channels_per_pickup <= len(tip_chunks[col]): break else: col += 1 pip.pick_up_tip(tip_chunks[col][num_channels_per_pickup-1]) # remove as many tips as we picked up in that column # from the 0 index. for _ in range(num_channels_per_pickup): tip_chunks[col].pop(0) # replace tip exception except IndexError: ctx.pause("Replace empty tip racks on slots 4, 5, and 6") pip.reset_tipracks() tip_chunks = [tips_by_col[i:i+8] for i in range(0, len(tips_by_col), 8)] col = 0 for _ in range(36): if num_channels_per_pickup <= len(tip_chunks[col]): break else: col += 1 pip.pick_up_tip(tip_chunks[col][num_channels_per_pickup-1]) for _ in range(num_channels_per_pickup): tip_chunks[col].pop(0) if len(tip_chunks[col]) == 0: tip_chunks.remove(tip_chunks[col]) # DUMP WASTE vol_ctr = 0 waste_well = 0 # move to next well in reservoir once we fill one. def check_waste_vol(vol): nonlocal vol_ctr nonlocal waste_well vol_ctr += vol if vol_ctr > 12000: waste_well += 1 vol_ctr = 0 waste = waste_res.wells()[waste_well] temp_mod.set_temperature(temp_mod_temp) ctx.pause(""" Ensure temperature module is at correct temperature, then, select "Resume" on the Opentrons app. """) # REAGENTS pbs = reagents.wells()[0] trypsin = reagents.wells()[1] media = reagents.wells()[-1] airgap = 20 ctx.comment("MOVING INCLUDED WELLS TO WASTE") for i, well in enumerate(dict_tips_per_well): num_tips = dict_tips_per_well[well] plate_well = plate.wells_by_name()[well] if num_tips > 1: pip = m300 else: pip = p300 pick_up(num_tips) # aspirate from side so as to not disturb cell culture. pip.aspirate(200, plate_well.bottom(z=1).move( Point(x=(plate_well.diameter/2-2))), rate=asp_rate_step1) pip.dispense(200, waste) check_waste_vol(200) pip.air_gap(airgap) pip.drop_tip() ctx.comment('\n') ctx.comment("\n\n\nMOVING PBS TO PLATE") for i, well in enumerate(dict_tips_per_well): num_tips = dict_tips_per_well[well] plate_well = plate.wells_by_name()[well] if num_tips > 1: pip = m300 else: pip = p300 pick_up(num_tips) pip.aspirate(150, pbs, rate=pbs_dispense_rate) pip.dispense(150, plate_well.bottom(z=1).move( Point(x=(plate_well.diameter/2-2)))) pip.air_gap(airgap) pip.drop_tip() ctx.comment('\n') ctx.comment("\n\n\nREMOVING PBS FROM PLATE") for i, well in enumerate(dict_tips_per_well): num_tips = dict_tips_per_well[well] plate_well = plate.wells_by_name()[well] if num_tips > 1: pip = m300 else: pip = p300 pick_up(num_tips) pip.aspirate(175, plate_well.bottom(z=1).move( Point(x=(plate_well.diameter/2-2)))) pip.dispense(175, waste) pip.air_gap(airgap) pip.drop_tip() ctx.comment('\n') ctx.comment("\n\n\nMOVING TRYPSIN TO PLATE") for i, well in enumerate(dict_tips_per_well): num_tips = dict_tips_per_well[well] plate_well = plate.wells_by_name()[well] if num_tips > 1: pip = m300 else: pip = p300 pick_up(num_tips) pip.aspirate(25, trypsin) pip.dispense(25, plate_well) pip.blow_out() pip.touch_tip() pip.air_gap(airgap) pip.drop_tip() ctx.comment('\n') ctx.delay(minutes=incubation_time) ctx.comment("\n\n\nMOVING MEDIA TO PLATE") for i, well in enumerate(dict_tips_per_well): num_tips = dict_tips_per_well[well] plate_well = plate.wells_by_name()[well] if num_tips > 1: pip = m300 else: pip = p300 pick_up(num_tips) pip.aspirate(140, media) pip.dispense(140, plate_well) pip.blow_out() pip.touch_tip() pip.air_gap(airgap) pip.drop_tip() ctx.comment('\n') ctx.comment("\n\n\nASPIRATE FIRST MEDIA FROM PLATE") for i, well in enumerate(dict_tips_per_well): num_tips = dict_tips_per_well[well] plate_well = plate.wells_by_name()[well] if num_tips > 1: pip = m300 else: pip = p300 pick_up(num_tips) pip.aspirate(first_media_x, plate_well.bottom(z=1).move( Point(x=(plate_well.diameter/2-2)))) pip.dispense(first_media_x, waste) pip.air_gap(airgap) pip.drop_tip() ctx.comment('\n') ctx.comment("\n\n\nDISPENSE SECOND MEDIA TO PLATE") for i, well in enumerate(dict_tips_per_well): num_tips = dict_tips_per_well[well] plate_well = plate.wells_by_name()[well] if num_tips > 1: pip = m300 else: pip = p300 pick_up(num_tips) pip.aspirate(second_media_y, media) pip.dispense(second_media_y, plate_well) pip.air_gap(airgap) pip.drop_tip() ctx.comment('\n') tip_data = [] for i, chunk in enumerate(tip_chunks): tip_data.append([]) if len(chunk) > 0: for value in chunk: tip_data[i].append(True) else: continue # write to the ot-2 no matter what in case the user would like to start # tracking tips for the next run if not ctx.is_simulating(): with open(file_path, 'w') as outfile: outfile.write(json.dumps(tip_data))