def notify_finish_process(): 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)
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): 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): # Define the STEPS of the protocol STEP = 0 STEPS = { # Dictionary with STEP activation, description, and times 1: {'Execute': True, 'description': 'Add samples (300ul)'}, } 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_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 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 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 * math.pi * 4**3 / 3 ) # Sphere Samples.vol_well = 700 ################## # 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): ''' 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 = -5)) 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 ########## # 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( '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']] tips1000 = [ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot, '1000µl filter tiprack') for slot in ['7', '10']] ################################################################################ # 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='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': {p1000: 0}, # p1000: 0}, 'maxes': {p1000: len(tips1000) * 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 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, x_offset = x_offset, pickup_height = 1, rinse = Samples.rinse, disp_height = -10, blow_out = True, touch_tip = True) # Mix the sample AFTER dispensing #custom_mix(p1000, reagent = Samples, location = d, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15) # Drop tip and update counter 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() ############################################################################ # 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 plate (slot 5) to Station C for MMIX addition and qPCR preparation.') 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 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 set_lights(self, button: Optional[bool], rails: Optional[bool]): if opentrons.config.IS_ROBOT: if button is not None: gpio.set_button_light(blue=button) if rails is not None: gpio.set_rail_lights(rails)