Example #1
0
def run(protocol: protocol_api.ProtocolContext):
    #######################################################################################################################################
    ## SETUP

    # Load Labware
    temp_mod = protocol.load_module('Temperature Module', '9')
    reaction_plate = protocol.load_labware(
        "biorad_384_wellplate_50ul", "8",
        label="reaction plate")  # where the magic happens
    # tube_rack = protocol.load_labware("opentrons_96_aluminumblock_generic_pcr_strip_200ul", "6", label = "aluminum block") # CBS and substrate stocks
    sample_plate = protocol.load_labware(
        "greiner_96_wellplate_323ul", "6", label="sample plate"
    )  # CBS and dye stocks (!!! protect dye from light !!!)
    library_plate = protocol.load_labware(
        "greiner_96_wellplate_323ul", "5",
        label="library plate")  # compound library

    # Specify tip racks
    tiprack1 = protocol.load_labware("opentrons_96_tiprack_10ul", '2')
    tiprack2 = protocol.load_labware("opentrons_96_tiprack_300ul", '3')

    # Load pipettes and set parameters
    p10 = protocol.load_instrument("p10_multi", "right", tip_racks=[tiprack1])
    p50 = protocol.load_instrument('p50_multi', "left", tip_racks=[tiprack2])

    p10.flow_rate.aspirate = 8
    p10.flow_rate.dispense = 8
    # 384-well depth is 11.56 mm and max volume is 112 uL
    # 20 uL is 2 mm high, tandem (middle) wall is 5.1 mm high
    p50.well_bottom_clearance.dispense = 7

    # Specify target wells (ONLY NEED TO EDIT WELLS HERE)
    samples = ["7", "8", "9"]  # columns on sample plate with protein and dye
    cols_compounds = [6, 7, 8, 9,
                      10]  # library columns to aspirate compounds from
    cols_woSAM = [1, 2, 3, 4,
                  5]  # destination wells in reaction plate (384-tandem)
    cols_wSAM = [
        6, 7, 8, 9, 10
    ]  # destination wells in reaction plate (384-tandem) (30 uM SAM final c)
    wells_reaction_woSAM = ['B' + str(i) for i in cols_woSAM]
    wells_reaction_wSAM = ['B' + str(i) for i in cols_wSAM]
    wells_reaction = ['B' + str(i) for i in (cols_woSAM + cols_wSAM)]

    #######################################################################################################################################
    ## PROCEDURE

    # Distribute 2.5 uL compound from 96-well library plate into empty well
    for i in range(0, len(cols_compounds)
                   ):  # iterate through every column on the compound plate
        lib_col = str(
            cols_compounds[i])  # column of the library plate (single number)
        wells_target = [
            wells_reaction_woSAM[i], wells_reaction_wSAM[i]
        ]  # wells to distribute to (each compound is tested w/ and w/o SAM, so two apart columns)
        p10.distribute(
            2.5,
            library_plate.columns_by_name()[lib_col],
            [reaction_plate.wells_by_name()[j] for j in wells_target],
            new_tip='once')

    # Transfer 5uL fluorescent dye from sample plate to reaction plate
    p50.distribute(5,
                   sample_plate.columns_by_name()[samples[2]],
                   [reaction_plate.wells_by_name()[i] for i in wells_reaction],
                   disposal_volume=0,
                   blow_out=False)

    # Distribute 17.5 protein
    p50.distribute(
        17.5,
        sample_plate.columns_by_name()[samples[0]],
        [reaction_plate.wells_by_name()[i] for i in wells_reaction_woSAM],
        disposal_volume=0,
        blow_out=False,
        new_tip='once')
    p50.distribute(
        17.5,
        sample_plate.columns_by_name()[samples[1]],
        [reaction_plate.wells_by_name()[i] for i in wells_reaction_wSAM],
        disposal_volume=0,
        blow_out=False,
        new_tip='once')


# SAMPLE CODE
# p20.transfer(20,reservoir_plate.wells_by_name()["A1"],mix_plate.wells_by_name()["A1"])
# p20.distribute(10,reservoir_plate.wells_by_name()["A2"],[mix_plate.wells_by_name()[well_name] for well_name in ["B1","C1","E1","G1","H1"]])
# p20.pick_up_tip()
# p20.transfer(10,mix_plate.wells_by_name()["A1"],mix_plate.wells_by_name()["B1"],mix_after=(3,10),new_tip="never")
# p20.transfer(10,mix_plate.wells_by_name()["B1"],mix_plate.wells_by_name()["C1"],mix_after=(3,10),new_tip="never")
# p20.transfer(10,mix_plate.wells_by_name()["C1"],mix_plate.wells_by_name()["D1"],mix_after=(3,10),new_tip="never")
# p20.transfer(10,mix_plate.wells_by_name()["D1"],mix_plate.wells_by_name()["E1"],mix_after=(3,10),new_tip="never")
# p20.drop_tip()
# p20.transfer(20,reservoir_plate.wells_by_name()["A3"],mix_plate.wells_by_name()["F1"])
# p20.pick_up_tip()
# p20.transfer(10,mix_plate.wells_by_name()["F1"],mix_plate.wells_by_name()["G1"],mix_after=(3,10),new_tip="never")
# p20.transfer(10,mix_plate.wells_by_name()["G1"],mix_plate.wells_by_name()["H1"],mix_after=(3,10),new_tip="never")
# p20.drop_tip()

# p10.transfer(4,mix_plate.wells_by_name()["A1"],mix_plate.wells_by_name()["A2"])
# p20.transfer(36,reservoir_plate.wells_by_name()["A4"],mix_plate.columns_by_name()["2"],new_tip='always')
# p10.well_bottom_clearance.dispense = 1
# rows=["A","B","C","D","E","F","G","H"]
# reaction_row_dest = "B"
# p10.transfer(10,mix_plate.columns_by_name()["2"],[reaction_plate.wells_by_name()[well_name] for well_name in ["B2","B3","B4"]])
# # for row in rows:
# # 	p20.distribute(10,mix_plate.wells_by_name()[(row+"2")],[reaction_plate.wells_by_name()[well_name] for well_name in [chr(ord(row)+1)+"2",chr(ord(row)+1)+"3",chr(ord(row)+1)+"4"]])
# p20.transfer(10,reservoir_plate.wells_by_name()["A4"],[reaction_plate.wells_by_name()[well_name] for well_name in ["J2","J3","J4"]])
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()
Example #3
0
def run(protocol: protocol_api.ProtocolContext):
    tiprack = protocol.load_labware(TIPRACK_LOADNAME, TIPRACK_SLOT)
    pipette = protocol.load_instrument(PIPETTE_NAME,
                                       PIPETTE_MOUNT,
                                       tip_racks=[tiprack])

    test_labware = protocol.load_labware_from_definition(
        LABWARE_DEF,
        TEST_LABWARE_SLOT,
        LABWARE_LABEL,
    )

    num_cols = len(LABWARE_DEF.get('ordering', [[]]))
    num_rows = len(LABWARE_DEF.get('ordering', [[]])[0])
    well_locs = uniq(
        ['A1', '{}{}'.format(chr(ord('A') + num_rows - 1), str(num_cols))])

    pipette.pick_up_tip()

    def set_speeds(rate):
        protocol.max_speeds.update({
            'X': (600 * rate),
            'Y': (400 * rate),
            'Z': (125 * rate),
            'A': (125 * rate),
        })

        speed_max = max(protocol.max_speeds.values())

        for instr in protocol.loaded_instruments.values():
            instr.default_speed = speed_max

    set_speeds(RATE)

    for slot in CALIBRATION_CROSS_SLOTS:
        coordinate = CALIBRATION_CROSS_COORDS[slot]
        location = types.Location(point=types.Point(**coordinate),
                                  labware=None)
        pipette.move_to(location)
        protocol.pause(
            f"Confirm {PIPETTE_MOUNT} pipette is at slot {slot} calibration cross"
        )

    pipette.home()
    protocol.pause(f"Place your labware in Slot {TEST_LABWARE_SLOT}")

    for well_loc in well_locs:
        well = test_labware.well(well_loc)
        all_4_edges = [[well._from_center_cartesian(x=-1, y=0, z=1), 'left'],
                       [well._from_center_cartesian(x=1, y=0, z=1), 'right'],
                       [well._from_center_cartesian(x=0, y=-1, z=1), 'front'],
                       [well._from_center_cartesian(x=0, y=1, z=1), 'back']]

        set_speeds(RATE)
        pipette.move_to(well.top())
        protocol.pause("Moved to the top of the well")

        for edge_pos, edge_name in all_4_edges:
            set_speeds(SLOWER_RATE)
            edge_location = types.Location(point=edge_pos, labware=None)
            pipette.move_to(edge_location)
            protocol.pause(f'Moved to {edge_name} edge')

    # go to bottom last. (If there is more than one well, use the last well first
    # because the pipette is already at the last well at this point)
    for well_loc in reversed(well_locs):
        well = test_labware.well(well_loc)
        set_speeds(RATE)
        pipette.move_to(well.bottom())
        protocol.pause("Moved to the bottom of the well")

        pipette.blow_out(well)

    set_speeds(1.0)
    pipette.return_tip()
Example #4
0
def run(protocol: protocol_api.ProtocolContext):
    [_source, _no_plates, _start, _vol_dispense,
     _touch_tip] = get_values(  # noqa: F821
         "source", "no_plates", "start", "vol_dispense", "touch_tip")

    protocol.set_rail_lights(False)
    protocol.set_rail_lights(True)

    # variables: number of plates/mastermixes to be prepared, dispense volume
    no_plates = _no_plates
    vol_dispense = _vol_dispense
    touch_tip = _touch_tip
    start = _start
    source = _source

    # load tiprack
    tiprack_20 = protocol.load_labware('opentrons_96_tiprack_20ul', 6)

    # load pipette
    p10_multi = protocol.load_instrument("p10_multi",
                                         "left",
                                         tip_racks=[tiprack_20])

    # load labware
    master_troughs = protocol.load_labware("nest_12_reservoir_15ml",
                                           2,
                                           label="master trough")

    master_plate = protocol.load_labware("96w_pcr_plate2",
                                         1,
                                         label="master-plate")

    pcr_plate = protocol.load_labware("96w_pcr_plate2", 5, label="pcr-plate")

    # unbound methods
    def slow_tip_withdrawal(self, speed_limit, well_location, to_center=False):
        if self.mount == 'right':
            axis = 'A'
        else:
            axis = 'Z'
        protocol.max_speeds[axis] = speed_limit
        if to_center is False:
            self.move_to(well_location.top())
        else:
            self.move_to(well_location.center())
        protocol.max_speeds[axis] = None

    def delay(self, delay_time):
        protocol.delay(seconds=delay_time)

    # bind methods to pipette
    for pipette_object in [p10_multi]:
        for method in [delay, slow_tip_withdrawal]:
            setattr(pipette_object, method.__name__,
                    MethodType(method, pipette_object))

    # helper functions
    def touch_right(cur_position: Location):
        if touch_tip == "right":
            p10_multi.move_to(cur_position.move(Point(x=2.7, z=-0.75)))
            p10_multi.move_to(cur_position.move(Point(x=-1, z=0.75)))
        else:
            p10_multi.move_to(cur_position.move(Point(x=-2.7, z=-0.75)))
            p10_multi.move_to(cur_position.move(Point(x=1, z=0.75)))

    def dispense_mm(vol, primer_no, mm_source):
        wells = pcr_plate.columns()
        if mm_source == "trough":
            mm = master_troughs.wells()[primer_no - 1]
        else:
            mm = master_plate.columns()[primer_no - 1][0]

        p10_multi.pick_up_tip()

        for col in wells:
            p10_multi.aspirate(vol, mm)
            p10_multi.dispense(vol * 2, col[0].top(-1), rate=1.6)
            touch_right(col[0].top())
            p10_multi.delay(0.2)
            p10_multi.slow_tip_withdrawal(10, col[0])

        p10_multi.drop_tip()

    # protocol: distribute master mixes
    for mm in range(start, no_plates + 1):
        if source == "trough":
            protocol.pause(
                "When you press resume, the master mix will be dispensed \
                into the next plate. Ensure this plate is in position \
                (in slot 5) and that at least {}uL of master mix for this \
                plate has been added to trough {}.".format(
                    (96 * vol_dispense) + 297, mm))
        else:
            protocol.pause(
                "When you press resume, the master mix will be dispensed into \
                the next plate. Ensure this plate is in position (in slot 5) \
                and that at least {}uL of master mix for this plate has been \
                added to each well of column {}.".format(
                    (8 * vol_dispense) + 10, mm))
        dispense_mm(vol_dispense, mm, source)
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)
Example #6
0
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):
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description and times
        1: {
            'Execute':
            True,
            'description':
            'Dispensar Proteinasa K (' + str(PK_VOL_PER_SAMPLE) + 'ul)'
        },
        2: {
            '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,
                     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
    Samples = Reagent(
        name='Samples',
        flow_rate_aspirate=25,
        flow_rate_dispense=100,
        rinse=False,
        delay=0,
        reagent_reservoir_volume=0,
        num_wells=96,
    )

    Pk = Reagent(name='Pk',
                 rinse=False,
                 flow_rate_aspirate=3,
                 flow_rate_dispense=3,
                 reagent_reservoir_volume=1800,
                 num_wells=1,
                 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 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 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')

    pk_rack = ctx.load_labware('opentrons_24_tuberack_nest_1.5ml_screwcap',
                               '3',
                               'opentrons_24_tuberack_nest_1.5ml_screwcap')
    ####################################
    # 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']
    ]
    tips300 = [
        ctx.load_labware('opentrons_96_tiprack_300ul', slot) for slot in ['7']
    ]

    ################################################################################
    # Declare which reagents are in each reservoir as well as deepwell and elution plate
    Pk.reagent_reservoir = pk_rack.rows()[0][0]  # A1

    ################################################################################
    # 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]

    # Divide destination wells in small groups for P300 pipette
    dests = list(divide_destinations(destinations, size_transfer))

    p1000 = ctx.load_instrument('p1000_single_gen2',
                                'left',
                                tip_racks=tips1000)  # load P1000 pipette
    p300 = ctx.load_instrument('p300_single_gen2',
                               mount='right',
                               tip_racks=tips300)

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {
            p1000: 0,
            p300: 0
        },
        'maxes': {
            p1000: 96 * len(p1000.tip_racks),
            p300: 96 * len(p300.tip_racks)
        },  #96 tips per tiprack * number or tipracks in the layout
        'num_refills': {
            p1000: 0,
            p300: 0
        },
        'tips': {
            p1000: [tip for rack in tips1000 for tip in rack.rows()[0]],
            p300: [tip for rack in tips300 for tip in rack.rows()[0]]
        }
    }

    start_run()

    ############################################################################
    # STEP 1: DISPENSAR PK
    ############################################################################
    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:
            ctx.comment(
                'dest length ---------------------------------------------- ' +
                str(len(dest)))
            aspirate_volume = PK_VOL_PER_SAMPLE * len(dest) + extra_dispensal
            used_vol_temp = distribute_custom(p300,
                                              volume=PK_VOL_PER_SAMPLE,
                                              src=Pk.reagent_reservoir,
                                              dest=dest,
                                              touch_tip=False,
                                              waste_pool=Pk.reagent_reservoir,
                                              pickup_height=0.2,
                                              extra_dispensal=extra_dispensal,
                                              dest_x_offset=0,
                                              disp_height=-15,
                                              num_shakes=PK_SAKES)
            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: 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=5,
                                  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):

    # load labware and modules
    tempdeck = ctx.load_module('tempdeck', '1')
    elution_plate = tempdeck.load_labware(
        'opentrons_96_aluminumblock_nest_wellplate_100ul',
        'cooled elution plate')
    reagent_res1 = ctx.load_labware('nest_12_reservoir_15ml', '2',
                                    'reagent reservoir 1')
    magdeck = ctx.load_module('magdeck', '4')
    magplate = magdeck.load_labware('usascientific_96_wellplate_2.4ml_deep')
    reagent_res2 = ctx.load_labware('nest_12_reservoir_15ml', '5',
                                    'reagent reservoir 2')
    waste = ctx.load_labware('nest_1_reservoir_195ml', '7',
                             'waste reservoir').wells()[0].top()
    tips300 = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', slot,
                         '300µl tiprack')
        for slot in ['3', '6', '8', '9', '10', '11']
    ]

    # reagents and samples
    num_cols = math.ceil(NUM_SAMPLES / 8)
    mag_samples_m = [
        well for well in magplate.rows()[0][0::2] + magplate.rows()[0][1::2]
    ][:num_cols]
    elution_samples_m = [
        well
        for well in elution_plate.rows()[0][0::2] + magplate.rows()[0][1::2]
    ][:num_cols]

    viral_dna_rna_buff = reagent_res1.wells()[:3]
    beads = reagent_res1.wells()[3]
    wash_1 = reagent_res1.wells()[4:8]
    wash_2 = reagent_res1.wells()[8:]
    etoh = reagent_res2.wells()[:8]
    water = reagent_res2.wells()[-1]

    # pipettes
    m300 = ctx.load_instrument('p300_multi', 'left', tip_racks=tips300)
    m300.flow_rate.aspirate = 150
    m300.flow_rate.dispense = 300

    tip_counts = {m300: 0}
    tip_maxes = {m300: len(tips300) * 12}

    def pick_up(pip):
        nonlocal tip_counts
        if tip_counts[pip] == tip_maxes[pip]:
            ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
    resuming.')
            pip.reset_tipracks()
            tip_counts[pip] = 0
        tip_counts[pip] += 1
        pip.pick_up_tip()

    def remove_supernatant(vol):
        m300.flow_rate.aspirate = 30
        num_trans = math.ceil(vol / 270)
        vol_per_trans = vol / num_trans
        for i, m in enumerate(mag_samples_m):
            side = -1 if i < 6 == 0 else 1
            loc = m.bottom(0.5).move(Point(x=side * 2))
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for _ in range(num_trans):
                m300.move_to(m.center())
                m300.transfer(vol_per_trans,
                              loc,
                              waste,
                              new_tip='never',
                              air_gap=30)
                m300.blow_out(waste)
            m300.drop_tip()
        m300.flow_rate.aspirate = 150

    # transfer viral DNA/RNA buffer
    for i, m in enumerate(mag_samples_m):
        pick_up(m300)
        m300.transfer(400,
                      viral_dna_rna_buff[i // 4],
                      m.top(),
                      new_tip='never')
        m300.mix(10, 200, m)
        m300.blow_out(m.top(-2))
        m300.drop_tip()

    # premix, transfer, and mix magnetic beads with sample
    for i, m in enumerate(mag_samples_m):
        pick_up(m300)
        if i == 0:
            for _ in range(20):
                m300.aspirate(200, beads.bottom(3))
                m300.dispense(200, beads.bottom(20))
        m300.transfer(20, beads, m, new_tip='never')
        m300.mix(10, 200, m)
        m300.blow_out(m.top(-2))
        m300.drop_tip()

    # incubate on magnet
    magdeck.engage()
    ctx.delay(minutes=3, msg='Incubating on magnet for 3 minutes.')

    # remove supernatant
    remove_supernatant(630)

    magdeck.disengage()

    for wash in [wash_1, wash_2]:
        # transfer and mix wash
        for i, m in enumerate(mag_samples_m):
            pick_up(m300)
            side = 1 if i < 6 == 0 else -1
            loc = m.bottom(0.5).move(Point(x=side * 2))
            m300.transfer(500, wash[i // 3], m.top(), new_tip='never')
            m300.mix(10, 200, loc)
            m300.blow_out(m.top(-2))
            m300.drop_tip()

        # incubate on magnet
        magdeck.engage()
        ctx.delay(minutes=3, msg='Incubating on magnet for 3 minutes.')

        # remove supernatant
        remove_supernatant(510)

        magdeck.disengage()

    # EtOH washes
    for wash in range(2):
        # transfer and mix wash
        etoh_set = etoh[wash * 4:wash * 4 + 4]
        pick_up(m300)
        m300.transfer(500,
                      etoh_set[i // 3], [m.top(3) for m in mag_samples_m],
                      new_tip='never')
        ctx.delay(seconds=30, msg='Incubating in EtOH for 30 seconds.')

        # remove supernatant
        remove_supernatant(510)

        if wash == 1:
            ctx.delay(minutes=10, msg='Airdrying on magnet for 10 minutes.')

        magdeck.disengage()

    # transfer and mix water
    for m in mag_samples_m:
        pick_up(m300)
        side = 1 if i < 6 == 0 else -1
        loc = m.bottom(0.5).move(Point(x=side * 2))
        m300.transfer(50, water, m.top(), new_tip='never')
        m300.mix(10, 30, loc)
        m300.blow_out(m.top(-2))
        m300.drop_tip()

    # incubate on magnet
    magdeck.engage()
    ctx.delay(minutes=3, msg='Incubating on magnet for 3 minutes.')

    # transfer elution to clean plate
    m300.flow_rate.aspirate = 30
    for s, d in zip(mag_samples_m, elution_samples_m):
        pick_up(m300)
        side = -1 if i < 6 == 0 else 1
        loc = s.bottom(0.5).move(Point(x=side * 2))
        m300.transfer(50, loc, d, new_tip='never')
        m300.blow_out(d.top(-2))
        m300.drop_tip()
    m300.flow_rate.aspirate = 150
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)
Example #10
0
def run(ctx: protocol_api.ProtocolContext):

    [
     _num_col,
     _filter_tips,
     _mbl5_vol,
     _mag_height,
     _m300_mount
    ] = get_values(  # noqa: F821 (<--- DO NOT REMOVE!)
        "_num_col",
        "_filter_tips",
        "_mbl5_vol",
        "_mag_height",
        "_m300_mount")

    # VARIABLES
    num_col = _num_col
    filter_tips = _filter_tips
    m300_mount = _m300_mount
    mag_height = _mag_height
    mbl5_vol = int(_mbl5_vol)

    # load module
    mag_mod = ctx.load_module('magnetic module gen2', '1')

    # load labware
    mag_plate = mag_mod.load_labware('96_squarewell_block_macherey_nagel')
    res1 = ctx.load_labware('nest_12_reservoir_15ml', '3')
    res2 = ctx.load_labware('nest_12_reservoir_15ml', '6')
    waste_res = res2 = ctx.load_labware('nest_12_reservoir_15ml', '9')
    elute_plate = ctx.load_labware('abgene_96_wellplate_700ul', '2')

    # load tip_racks
    tipracks = [ctx.load_labware('opentrons_96_filtertiprack_200ul'
                                 if filter_tips
                                 else 'opentrons_96_tiprack_300ul',
                                 slot) for slot in ['4', '5', '7',
                                                    '8', '10', '11']]

    # load instrument
    m300 = ctx.load_instrument('p300_multi_gen2',
                               m300_mount, tip_racks=tipracks)

    def pick_up():
        try:
            m300.pick_up_tip()
        except protocol_api.labware.OutOfTipsError:
            ctx.pause("Replace all tip racks on Slots 7, 8, 10, and 11")
            m300.reset_tipracks()
            m300.pick_up_tip()

    waste_vol_ctr = 0
    waste_well_ctr = 0

    def remove_supernatant():
        nonlocal waste_vol_ctr
        nonlocal waste_well_ctr

        ctx.comment('\n\n\nREMOVING SUPERNATANT\n')
        for i, col in enumerate(sample_cols):
            side = -1 if i % 2 == 0 else 1
            aspirate_loc = col.bottom(1).move(
                    Point(x=(col.length/2-2)*side))
            pick_up()
            for _ in range(2):
                m300.aspirate(200 if filter_tips else 300, aspirate_loc, rate=0.6)  # noqa E501
                m300.dispense(200 if filter_tips else 300, waste_res.wells()[waste_well_ctr])    # noqa E501
                m300.blow_out(waste_res.wells()[waste_well_ctr].top())
                waste_vol_ctr += 200
                if waste_vol_ctr >= 12000:
                    waste_vol_ctr = 0
                    waste_well_ctr += 1
            m300.drop_tip()

    # map liquids
    num_samp = num_col*8
    bead_buffer_wells = math.ceil(num_samp*162.5/10000)
    mbl3_wells = math.ceil(num_samp*800/10000)
    eth_wells = math.ceil(num_samp*400/10000)

    prok = res1.wells()[0]
    bead_buffer = res1.wells()[1:1+bead_buffer_wells]*num_col
    mbl3 = res1.wells()[4:4+mbl3_wells]*num_col
    ethanol = res2.wells()[:eth_wells]*num_col
    mbl5 = res2.wells()[-1]
    sample_cols = mag_plate.rows()[0][:num_col]

    ctx.comment('\n\n\nDISPENSING PRO-K\n')
    for col in sample_cols:
        pick_up()
        m300.aspirate(50, prok)
        m300.dispense(50, col)
        m300.mix(5, 90, col)
        m300.drop_tip()

    ctx.comment('\n\n\nDISPENSING BEAD BUFFER\n')
    airgap = 20
    for reagent_col, col in zip(bead_buffer, sample_cols):
        pick_up()
        m300.mix(5, 200 if filter_tips else 300, reagent_col, rate=2.0)
        m300.aspirate(162.5, reagent_col)
        m300.air_gap(airgap)
        m300.dispense(162.5+airgap, col)
        m300.mix(5, 150, col)
        m300.air_gap(airgap)
        m300.drop_tip()

    mag_mod.engage(height_from_base=mag_height)
    ctx.delay(minutes=3)
    remove_supernatant()
    mag_mod.disengage()

    ctx.comment('\n\n\nDISPENSING MBL3\n')
    for reagent_col, col in zip(mbl3, sample_cols):
        pick_up()
        m300.aspirate(200, reagent_col)
        m300.dispense(200, col.top())
        m300.aspirate(200, reagent_col)
        m300.dispense(200, col)
        m300.mix(5, 200 if filter_tips else 300, col)
        m300.drop_tip()

    mag_mod.engage(height_from_base=mag_height)
    ctx.delay(minutes=3)
    remove_supernatant()
    mag_mod.disengage()

    ctx.comment('\n\n\nDISPENSING ETHANOL\n')
    for reagent_col, col in zip(ethanol, sample_cols):
        pick_up()
        m300.aspirate(200, reagent_col)
        m300.dispense(200, col.top())
        m300.aspirate(200, reagent_col)
        m300.dispense(200, col)
        m300.mix(5, 200 if filter_tips else 300, col)
        m300.drop_tip()

    mag_mod.engage(height_from_base=mag_height)
    ctx.delay(minutes=3)
    remove_supernatant()
    ctx.delay(minutes=20)
    mag_mod.disengage()

    ctx.comment('\n\n\nDISPENSING MBL5\n')
    for col in sample_cols:
        pick_up()
        m300.aspirate(mbl5_vol, mbl5)
        m300.dispense(mbl5_vol, col)
        m300.mix(5, 0.6*mbl5_vol, col)
        m300.drop_tip()

    mag_mod.engage(height_from_base=mag_height)

    for i, (sample_col, elute_col) in enumerate(
                                    zip(sample_cols, elute_plate.rows()[0])):
        side = -1 if i % 2 == 0 else 1
        aspirate_loc = sample_col.bottom(1).move(
                Point(x=(sample_col.length/2-2)*side))
        pick_up()
        m300.aspirate(mbl5_vol, aspirate_loc, rate=0.6)
        m300.dispense(mbl5_vol, elute_col)
        m300.drop_tip()
Example #11
0
def run(protocol: protocol_api.ProtocolContext):

    # LABWARE
    fuge_rack = protocol.load_labware('vwr_24_tuberack_1500ul', '1')
    std_rack = protocol.load_labware('vwr_24_tuberack_1500ul', '2')
    tiprack300 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '8')
    tiprack20 = protocol.load_labware('opentrons_96_filtertiprack_20ul', '9')
    tempdeck = protocol.load_module(
        'tempdeck', '10')  # have this so I don't have to move it off
    holder_1 = protocol.load_labware(
        '8wstriptubesonfilterracks_96_aluminumblock_250ul', '3')
    holder_2 = protocol.load_labware(
        '8wstriptubesonfilterracks_96_aluminumblock_250ul', '6')
    stds_plate = tempdeck.load_labware('abi_96_wellplate_250ul')

    # PIPETTES
    p300 = protocol.load_instrument('p300_single_gen2',
                                    'left',
                                    tip_racks=[tiprack300])
    p20 = protocol.load_instrument('p20_single_gen2',
                                   'right',
                                   tip_racks=[tiprack20])

    # REAGENTS
    std_1 = std_rack['A1']  # 900ul Water
    std_2 = std_rack['A2']  # 900ul water
    std_3 = std_rack['A3']  # 900ul water
    std_4 = std_rack['A4']  # 900ul water
    std_5 = std_rack['A5']  # 900ul water
    std_6 = std_rack['A6']  # 900ul water
    std_7 = std_rack['B1']  # 900ul water
    std_8 = std_rack['B2']  # 900ul water
    std_9 = std_rack['B3']  # 900ul water
    std_10 = std_rack['B4']  # 900ul water
    std_11 = std_rack['B5']  # 900ul water
    std_12 = std_rack['B6']  # 900ul water
    std_13 = std_rack['C1']  # 900ul water
    std_14 = std_rack['C2']  # 900ul water
    std_15 = std_rack['C3']  # 900ul water

    pos_control = fuge_rack['A1']  # 100-1000ul pos control @1uM
    mmp_tube = fuge_rack['A2']  #500 uL of master mixs and 400uL of primers
    waste = fuge_rack['D6']  # waste
    water = fuge_rack['A3']  # 100 uL water

    # LISTS
    std_wells = [std_1, std_2, std_3, std_4, std_5, std_6, std_7, std_8, std_9]
    std_conc = [std_5, std_6, std_7, std_8, std_9]
    cols = [1, 3, 5, 7, 9, 11]
    rows = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']

    ### COMMANDS ######
    # Make std dilution series
    # Make 10nM pos control, std_1
    p300.transfer(
        100,
        pos_control.bottom(2),  #1uM
        std_1.bottom(20),
        mix_after=(3, 200),  # remove residual fluid from tip
        touch_tip=False)

    # serial dilutions in microfuge tubes, 10% diliutions
    for i in range(len(std_wells) - 1):
        h_mix = 20
        p300.pick_up_tip()
        p300.mix(2, 200, std_wells[i].bottom(8))  # mix low
        p300.mix(2, 200, std_wells[i].bottom(14))  # mix mid
        p300.mix(5, 200, std_wells[i].bottom(h_mix))  #mix hi
        p300.aspirate(100, std_wells[i].bottom(h_mix), rate=0.4)
        p300.touch_tip()
        p300.dispense(
            100,
            std_wells[i + 1].bottom(14))  # better mixing with mid dispense
        p300.blow_out(
            std_wells[i + 1].bottom(h_mix))  # blow out just below the surface
        p300.drop_tip()
        if i == len(std_wells) - 2:  # last tube
            p300.pick_up_tip()
            p300.mix(2, 200, std_wells[i + 1].bottom(8))  # mix low
            p300.mix(2, 200, std_wells[i + 1].bottom(14))  # mix mid
            p300.mix(5, 200, std_wells[i + 1].bottom(h_mix))  #mix hi
            p300.blow_out(
                std_wells[i +
                          1].bottom(h_mix))  # blow out just below the surface
    p300.drop_tip()

    #add master mix and primers to PRC tubes
    for col in cols:
        p20.pick_up_tip()
        for row in rows:
            p20.aspirate(18, mmp_tube)
            p20.move_to(mmp_tube.top())
            protocol.delay(seconds=2)
            p20.touch_tip(v_offset=-5)
            p20.dispense(18, holder_1[row + str(col)])
        p20.drop_tip()

    #add first 4 standards to upper half of tubes
    count = 0  # keep track of standard
    for row in rows:
        p20.pick_up_tip()
        p20.aspirate(14, std_conc[count])  #take from standand
        p20.touch_tip()
        for col in cols:
            p20.dispense(2, holder_1[row + str(col)])  # dispense in PCR tubes
            p20.touch_tip()
        p20.dispense(2, waste)
        p20.blow_out(waste.bottom())
        p20.drop_tip()
        count = count + 1
        if count == 4:
            break

    #add first 4 standards to lower half of tubes
    count = 0  #reset count
    for row in rows[4:]:
        p20.pick_up_tip()
        p20.aspirate(8, std_conc[count])
        p20.touch_tip()
        for col in cols[0:3]:
            p20.dispense(2, holder_1[row + str(col)])
            p20.touch_tip()
        p20.dispense(2, waste)
        p20.blow_out(waste.bottom())
        p20.drop_tip()
        count = count + 1
        if count == 4:
            break

    #add final standard
    for row in rows[4:7]:
        p20.pick_up_tip()
        p20.aspirate(20, std_conc[count])
        p20.touch_tip()
        for col in cols[3:]:
            p20.dispense(2, holder_1[row + str(col)])
            p20.touch_tip()
        p20.dispense(2, waste)
        p20.blow_out(waste.bottom())
        p20.drop_tip()

    # add water to last 3 wells
    for row in rows[7:8]:
        p20.pick_up_tip()
        p20.aspirate(8, water)
        p20.touch_tip()
        for col in cols[3:]:
            p20.dispense(2, holder_1[row + str(col)])
            p20.touch_tip()
        p20.dispense(2, waste)
        p20.blow_out(waste.bottom())
        p20.drop_tip()
Example #12
0
def run(protocol: protocol_api.ProtocolContext):
    """
    Pick up 200µL filter tip. Aspirate 200µL from 50mL tube and transfer
    5x to 1x 1.5mL tube (1mL aliquots) - repeat for 24x 1.5mL tubes.
    Drop tip, pick up new tip and repeat.
    """
    # =============================================================================

    # =====================LOADING LABWARE AND PIPETTES============================
    # =============================================================================
    tips_200 = protocol.load_labware(
        'opentrons_96_filtertiprack_200ul',  #labware def
        10,  #deck position
        '200tips')  #custom name
    stock_tubes = protocol.load_labware(
        'opentrons_6_tuberack_falcon_50ml_conical',  #labware def
        7,  #deck position
        '50mL_tubes')  #custom name
    aliquot_tubes_1 = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        4,  #deck position
        'aliquot_tubes_1')  #custom name
    aliquot_tubes_2 = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        1,  #deck position
        'aliquot_tubes_2')  #custom name
    aliquot_tubes_3 = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        11,  #deck position
        'aliquot_tubes_3')  #custom name
    aliquot_tubes_4 = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        8,  #deck position
        'aliquot_tubes_4')  #custom name
    aliquot_tubes_5 = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        5,  #deck position
        'aliquot_tubes_5')  #custom name
    aliquot_tubes_6 = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        2,  #deck position
        'aliquot_tubes_6')  #custom name
    aliquot_tubes_7 = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        6,  #deck position
        'aliquot_tubes_7')  #custom name
    aliquot_tubes_8 = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        3,  #deck position
        'aliquot_tubes_8')  #custom name

    ##### Loading pipettes
    p300 = protocol.load_instrument(
        'p300_single_gen2',  #instrument definition
        'right',  #mount position
        tip_racks=[tips_200])  #assigned tiprack
    # =============================================================================

    # ===================================INITIALIZING==============================
    # =============================================================================
    protocol.set_rail_lights(True)
    p300.starting_tip = tips_200.well('C1')  #!!!
    p300.flow_rate.aspirate = 300
    p300.flow_rate.dispense = 300
    # =============================================================================

    # =============================VARIABLES TO SET#!!!============================
    # =============================================================================
    start_vol = 50000
    ## The start_vol is the volume (ul) that is in the source labware at  ##
    ## the start of the protocol.                                         ##
    source_well_1 = stock_tubes['A1']
    source_well_2 = stock_tubes['A2']
    source_well_3 = stock_tubes['B1']
    source_well_4 = stock_tubes['B2']
    ## Where do you place the 50mL tube
    # =============================================================================

    # ==========================PREDIFINED VARIABLES===============================
    # =============================================================================
    container = 'tube_50mL'
    ## The container variable is needed for the volume tracking module.   ##
    ## It tells the module which dimensions to use for the calculations   ##
    ## of the pipette height. It is the source labware from which liquid  ##
    ## is aliquoted.                                                      ##
    # =============================================================================
    ##### Variables for volume tracking
    start_height = vt.cal_start_height(container, start_vol)
    ## Call start height calculation function from volume tracking module.##
    current_height = start_height
    ## Set the current height to start height at the beginning of the     ##
    ## protocol.                                                          ##
    # =============================================================================

    # ==================================PROTOCOL===================================
    # =============================================================================

    # 1 x 50mL tube================================================================
    p300.pick_up_tip()
    for well in aliquot_tubes_1.wells():
        for i in range(5):
            ## Pipette 5 x 200µL for 1mL aliquots
            current_height, delta_height = vt.volume_tracking(
                container, 200, current_height)
            ## The volume_tracking function needs the arguments container,    ##
            ## dispension_vol and the current_height which we have set in this##
            ## protocol. With those variables, the function updates the       ##
            ## current_height and calculates the delta_height of the liquid   ##
            ## after the next aspiration step. The outcome is stored as tv and##
            ## then the specific variables are updated.                       ##
            pip_height = current_height - 2
            ## Make sure that the pipette tip is always submerged by setting  ##
            ## the current height 2 mm below its actual height                ##
            if current_height - delta_height <= 1:
                protocol.pause("the 50mL tube is empty!")
            else:
                aspiration_location = source_well_1.bottom(pip_height)
                ## Set the location of where to aspirate from. Because we put this##
                ## in the loop, the location will change to the newly calculated  ##
                ## height after each pipetting step.                              ##
                ## If the level of the liquid in the next run of the loop will be ##
                ## smaller than 1 we have reached the bottom of the tube.         ##
                p300.aspirate(200, aspiration_location)
                ## Aspirate 200µL from the set aspiration location                ##
                p300.dispense(200, well.top(z=-2))
        ## Dispense 200µL in the destination well
    p300.drop_tip()

    start_height = current_height

    p300.pick_up_tip()
    for well in aliquot_tubes_2.wells():
        for i in range(5):
            ## Pipette 5 x 200µL for 1mL aliquots
            current_height, delta_height = vt.volume_tracking(
                container, 200, current_height)
            ## The volume_tracking function needs the arguments container,    ##
            ## dispension_vol and the current_height which we have set in this##
            ## protocol. With those variables, the function updates the       ##
            ## current_height and calculates the delta_height of the liquid   ##
            ## after the next aspiration step. The outcome is stored as tv and##
            ## then the specific variables are updated.                       ##
            pip_height = current_height - 2
            ## Make sure that the pipette tip is always submerged by setting  ##
            ## the current height 2 mm below its actual height                ##
            if current_height - delta_height <= 1:
                protocol.pause("the 50mL tube is empty!")
            else:
                aspiration_location = source_well_1.bottom(pip_height)
                ## Set the location of where to aspirate from. Because we put this##
                ## in the loop, the location will change to the newly calculated  ##
                ## height after each pipetting step.                              ##
                ## If the level of the liquid in the next run of the loop will be ##
                ## smaller than 1 we have reached the bottom of the tube.         ##
                p300.aspirate(200, aspiration_location)
                ## Aspirate 200µL from the set aspiration location                ##
                p300.dispense(200, well.top(z=-2))
        ## Dispense 200µL in the destination well
    p300.drop_tip()
    # =============================================================================

    # 2 x 50mL tube================================================================
    # reset variables
    start_vol = start_vol
    start_height = vt.cal_start_height(container, start_vol)
    current_height = start_height

    p300.pick_up_tip()
    for well in aliquot_tubes_3.wells():
        for i in range(5):
            ## Pipette 5 x 200µL for 1mL aliquots
            current_height, delta_height = vt.volume_tracking(
                container, 200, current_height)
            ## The volume_tracking function needs the arguments container,    ##
            ## dispension_vol and the current_height which we have set in this##
            ## protocol. With those variables, the function updates the       ##
            ## current_height and calculates the delta_height of the liquid   ##
            ## after the next aspiration step. The outcome is stored as tv and##
            ## then the specific variables are updated.                       ##
            pip_height = current_height - 2
            ## Make sure that the pipette tip is always submerged by setting  ##
            ## the current height 2 mm below its actual height                ##
            if current_height - delta_height <= 1:
                protocol.pause("the 50mL tube is empty!")
            else:
                aspiration_location = source_well_2.bottom(pip_height)
                ## Set the location of where to aspirate from. Because we put this##
                ## in the loop, the location will change to the newly calculated  ##
                ## height after each pipetting step.                              ##
                ## If the level of the liquid in the next run of the loop will be ##
                ## smaller than 1 we have reached the bottom of the tube.         ##
                p300.aspirate(200, aspiration_location)
                ## Aspirate 200µL from the set aspiration location                ##
                p300.dispense(200, well.top(z=-2))
        ## Dispense 200µL in the destination well
    p300.drop_tip()

    start_height = current_height

    p300.pick_up_tip()
    for well in aliquot_tubes_4.wells():
        for i in range(5):
            ## Pipette 5 x 200µL for 1mL aliquots
            current_height, delta_height = vt.volume_tracking(
                container, 200, current_height)
            ## The volume_tracking function needs the arguments container,    ##
            ## dispension_vol and the current_height which we have set in this##
            ## protocol. With those variables, the function updates the       ##
            ## current_height and calculates the delta_height of the liquid   ##
            ## after the next aspiration step. The outcome is stored as tv and##
            ## then the specific variables are updated.                       ##
            pip_height = current_height - 2
            ## Make sure that the pipette tip is always submerged by setting  ##
            ## the current height 2 mm below its actual height                ##
            if current_height - delta_height <= 1:
                protocol.pause("the 50mL tube is empty!")
            else:
                aspiration_location = source_well_2.bottom(pip_height)
                ## Set the location of where to aspirate from. Because we put this##
                ## in the loop, the location will change to the newly calculated  ##
                ## height after each pipetting step.                              ##
                ## If the level of the liquid in the next run of the loop will be ##
                ## smaller than 1 we have reached the bottom of the tube.         ##
                p300.aspirate(200, aspiration_location)
                ## Aspirate 200µL from the set aspiration location                ##
                p300.dispense(200, well.top(z=-2))
        ## Dispense 200µL in the destination well
    p300.drop_tip()
    # =============================================================================

    # 3 x 50mL tube================================================================
    # reset variables
    start_vol = start_vol
    start_height = vt.cal_start_height(container, start_vol)
    current_height = start_height

    p300.pick_up_tip()
    for well in aliquot_tubes_5.wells():
        for i in range(5):
            ## Pipette 5 x 200µL for 1mL aliquots
            current_height, delta_height = vt.volume_tracking(
                container, 200, current_height)
            ## The volume_tracking function needs the arguments container,    ##
            ## dispension_vol and the current_height which we have set in this##
            ## protocol. With those variables, the function updates the       ##
            ## current_height and calculates the delta_height of the liquid   ##
            ## after the next aspiration step. The outcome is stored as tv and##
            ## then the specific variables are updated.                       ##
            pip_height = current_height - 2
            ## Make sure that the pipette tip is always submerged by setting  ##
            ## the current height 2 mm below its actual height                ##
            if current_height - delta_height <= 1:
                protocol.pause("the 50mL tube is empty!")
            else:
                aspiration_location = source_well_3.bottom(pip_height)
                ## Set the location of where to aspirate from. Because we put this##
                ## in the loop, the location will change to the newly calculated  ##
                ## height after each pipetting step.                              ##
                ## If the level of the liquid in the next run of the loop will be ##
                ## smaller than 1 we have reached the bottom of the tube.         ##
                p300.aspirate(200, aspiration_location)
                ## Aspirate 200µL from the set aspiration location                ##
                p300.dispense(200, well.top(z=-2))
        ## Dispense 200µL in the destination well
    p300.drop_tip()

    start_height = current_height

    p300.pick_up_tip()
    for well in aliquot_tubes_6.wells():
        for i in range(5):
            ## Pipette 5 x 200µL for 1mL aliquots
            current_height, delta_height = vt.volume_tracking(
                container, 200, current_height)
            ## The volume_tracking function needs the arguments container,    ##
            ## dispension_vol and the current_height which we have set in this##
            ## protocol. With those variables, the function updates the       ##
            ## current_height and calculates the delta_height of the liquid   ##
            ## after the next aspiration step. The outcome is stored as tv and##
            ## then the specific variables are updated.                       ##
            pip_height = current_height - 2
            ## Make sure that the pipette tip is always submerged by setting  ##
            ## the current height 2 mm below its actual height                ##
            if current_height - delta_height <= 1:
                protocol.pause("the 50mL tube is empty!")
            else:
                aspiration_location = source_well_3.bottom(pip_height)
                ## Set the location of where to aspirate from. Because we put this##
                ## in the loop, the location will change to the newly calculated  ##
                ## height after each pipetting step.                              ##
                ## If the level of the liquid in the next run of the loop will be ##
                ## smaller than 1 we have reached the bottom of the tube.         ##
                p300.aspirate(200, aspiration_location)
                ## Aspirate 200µL from the set aspiration location                ##
                p300.dispense(200, well.top(z=-2))
        ## Dispense 200µL in the destination well
    p300.drop_tip()
    # =============================================================================

    # 4 x 50mL tube================================================================
    # reset variables
    start_vol = start_vol
    start_height = vt.cal_start_height(container, start_vol)
    current_height = start_height

    p300.pick_up_tip()
    for well in aliquot_tubes_7.wells():
        for i in range(5):
            ## Pipette 5 x 200µL for 1mL aliquots
            current_height, delta_height = vt.volume_tracking(
                container, 200, current_height)
            ## The volume_tracking function needs the arguments container,    ##
            ## dispension_vol and the current_height which we have set in this##
            ## protocol. With those variables, the function updates the       ##
            ## current_height and calculates the delta_height of the liquid   ##
            ## after the next aspiration step. The outcome is stored as tv and##
            ## then the specific variables are updated.                       ##
            pip_height = current_height - 2
            ## Make sure that the pipette tip is always submerged by setting  ##
            ## the current height 2 mm below its actual height                ##
            if current_height - delta_height <= 1:
                protocol.pause("the 50mL tube is empty!")
            else:
                aspiration_location = source_well_4.bottom(pip_height)
                ## Set the location of where to aspirate from. Because we put this##
                ## in the loop, the location will change to the newly calculated  ##
                ## height after each pipetting step.                              ##
                ## If the level of the liquid in the next run of the loop will be ##
                ## smaller than 1 we have reached the bottom of the tube.         ##
                p300.aspirate(200, aspiration_location)
                ## Aspirate 200µL from the set aspiration location                ##
                p300.dispense(200, well.top(z=-2))
        ## Dispense 200µL in the destination well
    p300.drop_tip()

    start_height = current_height

    p300.pick_up_tip()
    for well in aliquot_tubes_8.wells():
        for i in range(5):
            ## Pipette 5 x 200µL for 1mL aliquots
            current_height, delta_height = vt.volume_tracking(
                container, 200, current_height)
            ## The volume_tracking function needs the arguments container,    ##
            ## dispension_vol and the current_height which we have set in this##
            ## protocol. With those variables, the function updates the       ##
            ## current_height and calculates the delta_height of the liquid   ##
            ## after the next aspiration step. The outcome is stored as tv and##
            ## then the specific variables are updated.                       ##
            pip_height = current_height - 2
            ## Make sure that the pipette tip is always submerged by setting  ##
            ## the current height 2 mm below its actual height                ##
            if current_height - delta_height <= 1:
                protocol.pause("the 50mL tube is empty!")
            else:
                aspiration_location = source_well_4.bottom(pip_height)
                ## Set the location of where to aspirate from. Because we put this##
                ## in the loop, the location will change to the newly calculated  ##
                ## height after each pipetting step.                              ##
                ## If the level of the liquid in the next run of the loop will be ##
                ## smaller than 1 we have reached the bottom of the tube.         ##
                p300.aspirate(200, aspiration_location)
                ## Aspirate 200µL from the set aspiration location                ##
                p300.dispense(200, well.top(z=-2))
        ## Dispense 200µL in the destination well
    p300.drop_tip()
    # =============================================================================

    # =============================================================================
    protocol.set_rail_lights(False)


# =============================================================================
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):
        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',
        '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))
Example #14
0
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(
        'nest_12_reservoir_15ml', '11',
        '12-channel reservoir for binding buffer')
    # binding_buffer = ctx.load_labware(
    #     'biorad_96_wellplate_200ul_pcr', '11',
    #     '50ml tuberack for lysis buffer + PK (tube A1)').wells()[1]
    tipracks300 = [
        ctx.load_labware('opentrons_96_tiprack_300ul', slot,
                         '300µ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)
    m300 = ctx.load_instrument('p300_multi_gen2',
                               'right',
                               tip_racks=tipracks300)

    # 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 'tips300' in data:
                    tip_log['count'][m300] = data['tips300']
                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 tipracks300 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 [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 = {binding_buffer: TUBE50_VOlUME * 1}
    # radius = (binding_buffer.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]

    m300.flow_rate.aspirate = 94
    m300.flow_rate.dispense = 94
    m300.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(m300)
    # 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
    #     m300.flow_rate.aspirate = 100
    #     m300.flow_rate.dispense = 100
    #     m300.dispense(500, source.bottom(h+20))
    #     for _ in range(4):
    #         # m300.air_gap(500)
    #         m300.aspirate(500, source.bottom(h))
    #         m300.dispense(500, source.bottom(h+20))

    pick_up(m300)
    num_trans = math.ceil(BB_VOLUME / 210)
    vol_per_trans = BB_VOLUME / num_trans
    for i, m in enumerate(dests_multi):
        source = binding_buffer.wells()[i // 4]
        for i in range(num_trans):
            if i == 0:
                m300.mix(MIX_REPETITIONS, MIX_VOLUME, source)
            m300.transfer(vol_per_trans,
                          source,
                          m,
                          air_gap=20,
                          new_tip='never')
    m300.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 = {
            'tips300': tip_log['count'][m300],
            'tips20': tip_log['count'][s20]
        }
        with open(tip_file_path, 'w') as outfile:
            json.dump(data, outfile)
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()
Example #16
0
def run(ctx: protocol_api.ProtocolContext):
    # ------------------------
    # Load LabWare
    # ------------------------
    # Tip racks
    tips = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot,
                         '20µl filter tiprack') for slot in ['10', '11']
    ]

    # Pipette
    p20 = ctx.load_instrument('p20_single_gen2', 'right', tip_racks=tips)
    m20 = ctx.load_instrument('p20_multi_gen2', 'left', tip_racks=tips)

    # Modules
    tempdeck = ctx.load_module('temperature module', '1')
    tempdeck.set_temperature(4)

    # Mastermix
    mastermix = tempdeck.load_labware(
        'opentrons_24_aluminumblock_generic_2ml_screwcap')
    mastermix_sources = mastermix.wells()[:3]
    mastermix_volumes = [1200, 485, 485]
    mastermix_sources = zip(mastermix_sources, mastermix_volumes)
    mastermix_tube = mastermix.wells()[3]

    # Destination 1 (and source for the 2nd destination)
    dest_plate1 = ctx.load_labware('abi_fast_qpcr_96_alum_opentrons_100ul',
                                   '2', 'PCR plate')
    dest_1 = dest_plate1.columns()[0][:num_rows]
    source_2 = dest_plate1.wells()[0]

    # Destination 2 (destination of 'dest_plate1')
    dest_plate2 = ctx.load_labware('abi_fast_qpcr_96_alum_opentrons_100ul',
                                   '3', 'PCR plate')
    dest_2 = dest_plate2.rows()[0][:num_cols]
    samples_destinations = dest_plate2.wells()[:num_destinations]

    # Muestras
    rack_num = math.ceil(
        num_destinations / NUM_OF_SOURCES_PER_RACK
    ) if num_destinations < MAX_NUM_OF_SOURCES else MIN_NUM_OF_SOURCES
    source_racks = [
        ctx.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', slot,
                         'source tuberack with screwcap' + str(i + 1))
        for i, slot in enumerate(['8', '9', '5', '6'][:rack_num])
    ]
    sample_sources_full = common.generate_source_table(source_racks)
    sample_sources = sample_sources_full[:num_destinations]

    mov = zip(sample_sources, samples_destinations)

    # ------------------
    # Protocol
    # ------------------

    # Hacemos la mastermix

    for s, v in mastermix_sources:
        if not p20.hw_pipette['has_tip']:
            common.pick_up(p20)

        common.move_vol_multichannel(ctx,
                                     p20,
                                     reagent=buffer,
                                     source=s,
                                     dest=mastermix_tube,
                                     vol=v,
                                     air_gap_vol=air_gap_vol_sample,
                                     pickup_height=pickup_height,
                                     disp_height=dispense_height,
                                     x_offset=x_offset,
                                     blow_out=True,
                                     touch_tip=True)

        # Drop pipette tip
        p20.drop_tip()  # TODO: preguntar si hace falta tirar la punta o no

    if not p20.hw_pipette['has_tip']:
        common.pick_up(p20)

    common.custom_mix(p20,
                      reagent=buffer,
                      location=mastermix_tube,
                      vol=vol_to_mix,
                      rounds=rounds,
                      blow_out=True,
                      mix_height=dispense_height,
                      x_offset=x_offset,
                      source_height=dispense_height)

    # Dispensamos mastermix en tira de pcr y con la multi lo propagamos en la placa pcr del slot 3

    if not p20.hw_pipette['has_tip']:
        common.pick_up(p20)

    for d in dest_1:
        common.move_vol_multichannel(ctx,
                                     p20,
                                     reagent=buffer,
                                     source=mastermix_tube,
                                     dest=d,
                                     vol=mastermix_vol_to_move,
                                     air_gap_vol=air_gap_vol_sample,
                                     x_offset=x_offset,
                                     pickup_height=1,
                                     disp_height=-10,
                                     blow_out=True,
                                     touch_tip=False)

    p20.drop_tip()

    if not m20.hw_pipette['has_tip']:
        common.pick_up(m20)

    for d in dest_2:
        common.move_vol_multichannel(ctx,
                                     m20,
                                     reagent=buffer,
                                     source=source_2,
                                     dest=d,
                                     vol=pcr_final_vol,
                                     air_gap_vol=air_gap_vol_sample,
                                     x_offset=x_offset,
                                     pickup_height=1,
                                     disp_height=-10,
                                     blow_out=True,
                                     touch_tip=True)
    m20.drop_tip()

    # Transfer samples from eppendorf to pcr plate

    for s, d in mov:
        if not p20.hw_pipette['has_tip']:
            common.pick_up(p20)

        common.move_vol_multichannel(ctx,
                                     p20,
                                     reagent=buffer,
                                     source=s,
                                     dest=d,
                                     vol=sample_vol_to_move,
                                     air_gap_vol=air_gap_vol_sample,
                                     x_offset=x_offset,
                                     pickup_height=1,
                                     disp_height=-10,
                                     blow_out=True,
                                     touch_tip=True)
        p20.drop_tip()
Example #17
0
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': 'Mix beads'},
        3: {'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/'+run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/Station_KB_sample_prep_pathogen_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 * NUM_SAMPLES,
                     num_wells=96,
                     h_cono=1.95,
                     v_fondo=35)

    Beads = Reagent(name='Magnetic beads and Lysis',
                    flow_rate_aspirate=1,
                    flow_rate_dispense=3,
                    rinse=True,
                    num_wells=math.ceil(NUM_SAMPLES / 32),
                    delay=2,
                    reagent_reservoir_volume=260 * 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, 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):
        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 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)

    ##########
    # 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 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(
        'nest_12_reservoir_15ml', '2', 'Reagent deepwell plate')

    ##################################
    # Elution 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 p300 multi pipette

    m20 = ctx.load_instrument(
        'p20_multi_gen2', 'left', tip_racks=tips20) # load p300 single 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
    #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.5, 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)

    ############################################################################
    # STEP 2: PREMIX BEADS
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:

        start = datetime.now()
        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=180,
                   rounds=10, blow_out=True, mix_height=0, x_offset = x_offset)
        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 3: 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 = [130, 130]  # 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):
                # 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=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=180, rounds=10, blow_out=True, 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 only the first round
                    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 = True, touch_tip=False)
                m300.aspirate(air_gap_vol, work_destinations_cols[i].top(z = -2),
                               rate = Beads.flow_rate_aspirate)
                m300.dispense(air_gap_vol, Beads.reagent_reservoir[Beads.col].top())
            ctx.comment('Mixing MS with beads ')

        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(protocol: protocol_api.ProtocolContext):
    """
    Aliquoting Accustart toughmix with EvaGreen added from a 5 mL tube to a 
     96-wells plate; using volume tracking so that the pipette starts 
    aspirating at the starting height of the liquid and goes down as the 
    volume decreases.
    Adding primers from PCR strips (with 10 uM primer F&R primer mix) or
    1.5mL tubes (with 10 uM F or 10 uM R) to 96-wells plate (with mastermix).
    """
    # =============================================================================

    # ======================LOADING LABWARE AND PIPETTES===========================
    # =============================================================================
    ## For available labware see "labware/list_of_available_labware".       ##
    tips_200 = protocol.load_labware(
        'opentrons_96_filtertiprack_200ul',  #labware definition
        3,  #deck position
        'tips_200')  #custom name
    tips_20_1 = protocol.load_labware(
        'opentrons_96_filtertiprack_20ul',  #labware definition
        10,  #deck position
        'tips_20')  #custom name
    tips_20_2 = protocol.load_labware(
        'opentrons_96_filtertiprack_20ul',  #labware definition
        5,  #deck position
        'tips_20')  #custom name
    plate_96 = protocol.load_labware('biorad_96_wellplate_200ul_pcr', 4,
                                     'plate_96')
    primer_tubes = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        1,  #deck position
        'primer_tubes')  #custom name
    ##### !!! OPTION 1: ROBOT
    tubes_5mL = protocol.load_labware(
        'eppendorfscrewcap_15_tuberack_5000ul',  #labware def
        2,  #deck position
        '5mL_tubes')  #custom name
    primer_strips = protocol.load_labware(
        'pcrstrips_96_wellplate_200ul',  #labware definition
        7,  #deck position
        'primer strips')  #custom name
    # #### !!! OPTION 2: SIMULATOR
    #  with open("labware/eppendorfscrewcap_15_tuberack_5000ul/"
    #            "eppendorfscrewcap_15_tuberack_5000ul.json") as labware_file:
    #          labware_def_5mL = json.load(labware_file)
    #  tubes_5mL = protocol.load_labware_from_definition(
    #          labware_def_5mL,   #variable derived from opening json
    #          2,                 #deck position
    #          '5mL_tubes')       #custom name
    #  with open("labware/pcrstrips_96_wellplate_200ul/"
    #            "pcrstrips_96_wellplate_200ul.json") as labware_file:
    #          labware_def_pcrstrips = json.load(labware_file)
    #  primer_strips = protocol.load_labware_from_definition(
    #      labware_def_pcrstrips, #variable derived from opening json
    #      7,                     #deck position
    #      'primer_strips')       #custom name

    ##### Loading pipettes
    p300 = protocol.load_instrument(
        'p300_single_gen2',  #instrument definition
        'right',  #mount position
        tip_racks=[tips_200])  #assigned tiprack
    p20 = protocol.load_instrument(
        'p20_single_gen2',  #instrument definition
        'left',  #mount position
        tip_racks=[tips_20_1, tips_20_2])  #assigned tiprack
    # =============================================================================

    # ==========================VARIABLES TO SET#!!!===============================
    # =============================================================================
    start_vol = 2226
    ## The start_vol is the volume (ul) that is in the source labware at  ##
    ## the start of the protocol.                                         ##
    dispension_vol = 42
    ## The dispension_vol is the volume (ul) that needs to be aliquoted   ##
    ## into the destination wells/tubes.                                  ##
    primer_vol_label = 3
    ## The primer_vol_label is the volume (ul) of barcoded F&R primer that##
    ## needs to be added to the reactions that get a barcode.             ##
    primer_vol = 1.5
    ## The primer_vol is the volume (ul) of NON barcoded F or R primer    ##
    ## that needs to be added to the reactions that do NOT get a barcode. ##
    p300.starting_tip = tips_200.well('A2')
    p20.starting_tip = tips_20_1.well('A1')
    ## The starting_tip is the location of first pipette tip in the box   ##
    # =============================================================================

    # ==========================PREDIFINED VARIABLES===============================
    # =============================================================================
    container = 'tube_5mL'
    ## The container variable is needed for the volume tracking module.   ##
    ## It tells the module which dimensions to use for the calculations   ##
    ## of the pipette height. It is the source labware from which liquid  ##
    ## is aliquoted.                                                      ##
    ## There are several options to choose from:                          ##
    ## 'tube_1.5ml', 'tube_2mL', 'tube_5mL', 'tube_15mL', 'tube_50mL'   	##
    aspiration_vol = dispension_vol + (dispension_vol / 100 * 2)
    ## The aspiration_vol is the volume (ul) that is aspirated from the   ##
    ## container.                                                         ##
    # ==========================creating mix well list=============================
    ## This is a list with wells in plate_96 that should be filled with mastermix##
    mastermix = []
    ## Create an empty list to append wells to for the mastermix wells.   ##
    mm_columns = ([
        plate_96.columns_by_name()[column_name]
        for column_name in ['1', '2', '3', '4', '5', '6']
    ])
    for column in mm_columns:
        for well in column:
            mastermix.append(well)
    ## Separate the columns into wells and append them to the empty       ##
    ## mastermix wells list                                               ##
# ======================create list for labeled primers========================
## This is a list with the wells in primer_strips where the primers should  ##
## be pipetted from. This list is used to pipette the labeled primers       ##
## to the corresponding wells in plate_96 (automatically first ~3.5 columns)##
    labeled_primers = []
    ## Create an empty list to append wells to for the primer wells.      ##
    labeled_primer_columns = ([
        primer_strips.columns_by_name()[column_name]
        for column_name in ['2', '7']
    ])
    ## Make a list of columns for the primers, this is a list of lists!   ##
    for column in labeled_primer_columns:
        for well in column:
            labeled_primers.append(well)
    ## Separate the columns into wells and append them to the empty primer##
    ## wells list                                                         ##
    labeled_primer_wells = ([
        primer_strips.wells_by_name()[well_name]
        for well_name in ['A11', 'B11', 'C11', 'D11', 'E11']
    ])
    ## Make a list of the remaining primer wells.                         ##
    for well in labeled_primer_wells:
        labeled_primers.append(well)
    ## Append the wells to the list of primer wells.                      ##
# =======================create list for unlabeled primers=====================
## This is a list with the wells in plate_96, that should be filled with    ##
## unlabeled primers (so the fish samples, NTCs and the std dilution series)##
## --> for the wells that do not get labeled primer                         ##
    unlabeled_primer_dest = []
    ## Create an empty list to append the wells where the unlabeled       ##
    ## primers should go.                                                 ##
    unlabeled_primer_wells = ([
        plate_96.wells_by_name()[well_name]
        for well_name in ['F3', 'G3', 'H3']
    ])
    for well in unlabeled_primer_wells:
        unlabeled_primer_dest.append(well)
    unlabeled_primer_columns = ([
        plate_96.columns_by_name()[column_name]
        for column_name in ['4', '5', '6']
    ])
    for column in unlabeled_primer_columns:
        for well in column:
            unlabeled_primer_dest.append(well)
# =============================================================================
##### Variables for volume tracking
    start_height = vt.cal_start_height(container, start_vol)
    ## Call start height calculation function from volume tracking module.##
    current_height = start_height
    ## Set the current height to start height at the beginning of the     ##
    ## protocol.                                                          ##
    # =============================================================================

    # ===============================ALIQUOTING MIX================================
    # =============================================================================
    ## For each column in destination_wells, pick up a tip, than for each   ##
    ## well in these columns pipette mix, and after the+ column drop the tip##
    ## Repeat untill all columns in the list are done.                      ##
    for i, well in enumerate(mastermix):
        ## Name all the wells in the plate 'well', for all these do:            ##
        ## If we are at the first well, start by picking up a tip.          ##
        if i == 0:
            p300.pick_up_tip()
        ## Then, after every 8th well, drop tip and pick up a new one.      ##
        elif i % 8 == 0:
            p300.drop_tip()
            p300.pick_up_tip()
        current_height, pip_height, bottom_reached = vt.volume_tracking(
            container, dispension_vol, current_height)
        ## The volume_tracking function needs the arguments container ##
        ## dispension_vol and the current_height which we have set in ##
        ## this protocol. With those variables, the function updates  ##
        ## the current_height and calculates the delta_height of the  ##
        ## liquid after the next aspiration step.                     ##
        if bottom_reached:
            aspiration_location = tubes_5mL['C1'].bottom(z=1)  #!!!
            protocol.comment("You've reached the bottom!")
        else:
            aspiration_location = tubes_5mL['C1'].bottom(pip_height)  #!!!
        ## If the level of the liquid in the next run of the loop will##
        ## be smaller than 1 we have reached the bottom of the tube.  ##
        ## To prevent the pipette from crashing into the bottom, we   ##
        ## tell it to go home and pause the protocol so that this can ##
        ## never happen. Set the location of where to aspirate from.  ##
        ## Because we put this in the loop, the location will change  ##
        ## to the newly calculated height after each pipetting step.  ##
        p300.aspirate(aspiration_vol, aspiration_location)
        ## Aspirate the amount specified in aspiration_vol from the   ##
        ## location specified in aspiration_location.                 ##
        p300.dispense(dispension_vol, well)
        ## Dispense the amount specified in dispension_vol to the     ##
        ## location specified in well (so a new well every time the   ##
        ## loop restarts)                                             ##
        p300.dispense(10, aspiration_location)
        ## Alternative for blow-out, make sure the tip doesn't fill   ##
        ## completely when using a disposal volume by dispensing some ##
        ## of the volume after each pipetting step. (blow-out to many ##
        ## bubbles)                                                   ##
    p300.drop_tip()
    # =============================================================================

    # ==========================ADDING LABELED PRIMERS=============================
    # =============================================================================
    ## For the columns in both the source (primers) and the destination:        ##
    ## loop trough the wells in those columns.                                  ##
    for primer_tube, mix_tube in zip(labeled_primers, mastermix):
        p20.pick_up_tip()
        p20.aspirate(primer_vol_label, primer_tube)
        ## primer_mix_vol = volume for pipetting up and down                ##
        primer_mix_vol = primer_vol_label + 3
        p20.mix(3, primer_mix_vol, mix_tube)
        ## primer_dispense_vol = volume to dispense that was mixed          ##
        primer_dispense_vol = primer_mix_vol + 3
        p20.dispense(primer_dispense_vol, mix_tube)
        p20.drop_tip()
# =============================================================================

# ==========================ADDING UNLABELED PRIMERS===========================
# =============================================================================
## For the wells in unlabeld_primer_dest (the wells with unlabeled primers) do:
##FORWARD
    for well in unlabeled_primer_dest:
        p20.pick_up_tip()
        p20.aspirate(primer_vol, primer_tubes['A1'])
        ## primer_mix_vol = volume for pipetting up and down            ##
        primer_mix_vol = primer_vol + 3
        p20.mix(3, primer_mix_vol, well)
        ## primer_dispense_vol = volume to dispense that was mixed      ##
        primer_dispense_vol = primer_mix_vol + 3
        p20.dispense(primer_dispense_vol, well)
        p20.drop_tip()
    ##REVERSE
    for well in unlabeled_primer_dest:
        p20.pick_up_tip()
        p20.aspirate(primer_vol, primer_tubes['B1'])
        ## primer_mix_vol = volume for pipetting up and down            ##
        primer_mix_vol = primer_vol + 3
        p20.mix(3, primer_mix_vol, well)
        ## primer_dispense_vol = volume to dispense that was mixed      ##
        primer_dispense_vol = primer_mix_vol + 3
        p20.dispense(primer_dispense_vol, well)
        p20.drop_tip()
Example #19
0
def run(protocol: protocol_api.ProtocolContext):
    """
    Aliquoting Phusion mastermix from a 5 mL tube to an entire 
    96-wells plate; using volume tracking so that the pipette starts 
    aspirating at the starting height of the liquid and goes down as the 
    volume decreases.
    Adding sample (16S positive control) from 1.5 mL tubes (diluted and
    undiluted) in a few places on the plate. 
    """

    # =============================================================================
    ##### Loading labware
    ## For available labware see "labware/list_of_available_labware".       ##
    tips_200 = protocol.load_labware(
        'opentrons_96_filtertiprack_200ul',  #labware definition
        7,  #deck position
        '200tips')  #custom name
    tips_20 = protocol.load_labware(
        'opentrons_96_filtertiprack_20ul',  #labware definition
        10,  #deck position
        '20tips')  #custom name
    plate_96 = protocol.load_labware(
        'biorad_96_wellplate_200ul_pcr',  #labware definition
        9,  #deck position
        '96well_plate')  #custom name
    sample_tubes = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        11,  #deck position
        'sample_tubes')  #custom name
    tubes_5mL = protocol.load_labware(
        'eppendorf_15_tuberack_5000ul',  #labware definition
        8,  #deck position
        '5mL_tubes')  #custom name

    ##### Loading pipettes
    p300 = protocol.load_instrument(
        'p300_single_gen2',  #instrument definition
        'right',  #mount position
        tip_racks=[tips_200])  #assigned tiprack
    p20 = protocol.load_instrument(
        'p20_single_gen2',  #instrument definition
        'left',  #mount position
        tip_racks=[tips_20])  #assigned tiprack
    # =============================================================================

    # =============================================================================
    ##### !!! Variables to set
    start_vol = 2544
    ## The start_vol is the volume (ul) that is in the source labware at  ##
    ## the start of the protocol.                                         ##
    dispension_vol = 24
    ## The dispension_vol is the volume (ul) that needs to be aliquoted   ##
    ## into the destination wells/tubes.                                  ##
    sample_vol = 1
    ## The sample_vol is the volume (ul) of sample added to the PCR       ##
    ## reaction.                                                          ##
    p300.starting_tip = tips_200.well('C8')
    p20.starting_tip = tips_20.well('D9')
    ## The starting_tip is the location of first pipette tip in the box   ##
    # =============================================================================
    # =============================================================================
    ##### Predifined variables
    container = 'tube_5mL'
    ## The container variable is needed for the volume tracking module.   ##
    ## It tells the module which dimensions to use for the calculations   ##
    ## of the pipette height. It is the source labware from which liquid  ##
    ## is aliquoted.                                                      ##
    ## There are several options to choose from:                          ##
    ## 'tube_1.5ml', 'tube_2mL', 'tube_5mL', 'tube_15mL', 'tube_50mL'   	##
    aspiration_vol = dispension_vol + (dispension_vol / 100 * 2)
    ## The aspiration_vol is the volume (ul) that is aspirated from the   ##
    ## container.                                                         ##
    # =============================================================================
    # =============================================================================
    ##### Variables for volume tracking
    start_height = cal_start_height(container, start_vol)
    ## Call start height calculation function from volume tracking module.##
    current_height = start_height
    ## Set the current height to start height at the beginning of the     ##
    ## protocol.                                                          ##
    # =============================================================================

    # =============================================================================
    ##### Aliquoting the mix
    p300.pick_up_tip()
    ## p300 picks up tip from location specified in variable starting_tip ##
    for well in plate_96.wells():
        ## Name all the wells in the plate 'well', for all these do:          ##
        tv = volume_tracking(container, dispension_vol, current_height)
        current_height, delta_height = tv
        ## The volume_tracking function needs the arguments container,    ##
        ## dispension_vol and the current_height which we have set in this##
        ## protocol. With those variables, the function updates the       ##
        ## current_height and calculates the delta_height of the liquid   ##
        ## after the next aspiration step. The outcome is stored as tv and##
        ## then the specific variables are updated.                       ##
        pip_height = current_height - 2
        ## Make sure that the pipette tip is always submerged by setting  ##
        ## the current height 2 mm below its actual height                ##
        if current_height - delta_height <= 1:
            aspiration_location = tubes_5mL['C3'].bottom(z=1)  #!!!
            protocol.comment("You've reached the bottom!")
        else:
            aspiration_location = tubes_5mL['C3'].bottom(pip_height)  #!!!
        ## If the level of the liquid in the next run of the loop will be ##
        ## smaller than 1 we have reached the bottom of the tube. To      ##
        ## prevent the pipette from crashing into the bottom, we tell it  ##
        ## to go home and pause the protocol so that this can never happen##
        ## Set the location of where to aspirate from. Because we put this##
        ## in the loop, the location will change to the newly calculated  ##
        ## height after each pipetting step.                              ##
        well_c = str(
            well)  #set location of the well to str (if takes only str)
        if (well_c == 'A3 of 96well_plate on 9'
                or well_c == 'A5 of 96well_plate on 9'
                or well_c == 'A7 of 96well_plate on 9'
                or well_c == 'A9 of 96well_plate on 9'
                or well_c == 'A11 of 96well_plate on 9'):
            p300.drop_tip()
            p300.pick_up_tip()
        ## Pick up a new tip every two rows.                              ##
        p300.aspirate(aspiration_vol, aspiration_location)
        ## Aspirate the amount specified in aspiration_vol from the        ##
        ## location specified in aspiration_location.                      ##
        p300.dispense(dispension_vol, well)
        ## Dispense the amount specified in dispension_vol to the location##
        ## specified in well (so a new well every time the loop restarts) ##
        p300.dispense(10, aspiration_location)
        ## Alternative for blow-out, make sure the tip doesn't fill       ##
        ## completely when using a disposal volume by dispensing some     ##
        ## of the volume after each pipetting step. (blow-out to many     ##
        ## bubbles)                                                       ##
    p300.drop_tip()
    ## Drop the final tip in the trash bin.                               ##
    # =============================================================================

    # =============================================================================
    ##### Transferring samples
    ## Transfer undiluted sample from specified tube in sample_tubes to     ##
    ## specified well in 96_wells plate.                                    ##
    p20.transfer(sample_vol,
                 sample_tubes['A1'], [
                     plate_96.wells_by_name()[well_name] for well_name in [
                         'A2', 'D4', 'B5', 'F5', 'D6', 'B7', 'F7', 'D8', 'B9',
                         'F9', 'A11'
                     ]
                 ],
                 new_tip='always',
                 blow_out=True,
                 blowout_location='destination well',
                 mix_after=(3, 5),
                 air_gap=1)

    ## Transfer diluted sample from B1-B6 std_tubes to multiple in plate_96 ##
    p20.transfer(sample_vol, [
        sample_tubes.wells_by_name()[well_name]
        for well_name in ['B1', 'B2', 'B3', 'B4', 'B5', 'B6']
    ], [
        plate_96.wells_by_name()[well_name]
        for well_name in ['B2', 'C2', 'D2', 'E2', 'F2', 'G2']
    ],
                 new_tip='always',
                 blow_out=True,
                 blowout_location='destination well',
                 mix_after=(3, 5),
                 air_gap=1)
    p20.transfer(sample_vol, [
        sample_tubes.wells_by_name()[well_name]
        for well_name in ['B1', 'B2', 'B3', 'B4', 'B5', 'B6']
    ], [
        plate_96.wells_by_name()[well_name]
        for well_name in ['B11', 'C11', 'D11', 'E11', 'F11', 'G11']
    ],
                 new_tip='always',
                 blow_out=True,
                 blowout_location='destination well',
                 mix_after=(3, 5),
                 air_gap=1)
Example #20
0
def run(protocol: protocol_api.ProtocolContext, cmdList, deckMap, amtList):
    #global cmdList, deckMap, amtList

    ############ LOAD LABWARES ############
    tipLocs = []
    for i in range(11):
        #load labware
        labware_name = deckMap["labware_" + str(i + 1)]
        if (labware_name != '(empty)' and labware_name != 'TRASH'):
            deck_position = int(list(deckMap.keys())[i].split('_')[1])
            globals()[list(deckMap.keys())[i]] = protocol.load_labware(
                labware_name, deck_position)

            #if labware is a tip rack, assign number to tip location(s)
            if ('tiprack' in labware_name):
                tipLocs.append(globals()[list(deckMap.keys())[i]])

    #load pipettes
    #single-channel
    right_pipette = protocol.load_instrument('p300_single',
                                             'right',
                                             tip_racks=tipLocs)
    right_pipette.flow_rate.aspirate = aspirateSpeed
    right_pipette.flow_rate.dispense = dispenseSpeed
    #multi-channel
    left_pipette = protocol.load_instrument('p300_multi',
                                            'left',
                                            tip_racks=tipLocs)
    left_pipette.flow_rate.aspirate = aspirateSpeed
    left_pipette.flow_rate.dispense = dispenseSpeed

    #Update Amount List
    for i in range(len(amtList)):
        if (float(amtList[i][3]) <= 50):
            amtList[i][3] = float(amtList[i][3]) * 1000
        #get tube type
        if ('50ml' in deckMap[amtList[i][0]]):
            amtList[i] = np.append(amtList[i], '50ml_falcon')
        elif ('15ml' in deckMap[amtList[i][0]]):
            amtList[i] = np.append(amtList[i], '15ml_falcon')
        else:
            amtList[i] = np.append(amtList[i], '1.5ml_eppendorf')

    ############ EXECUTE COMMANDS ############
    #iterate through all command lines
    current_tipID = 0  #initiate tip ID
    for i in range(len(cmdList)):
        #subset
        cmdRow = cmdList[i]
        print(cmdRow)

        #parse all informations
        source_ware = cmdRow[0]
        source_well = cmdRow[1].split(', ')
        target_ware = cmdRow[2]
        target_well = cmdRow[3].split(', ')
        transfer_amt = float(cmdRow[4])  #only one transfer amount is allowed
        if (float(cmdRow[5]) > 0):
            mix_amt = max(float(cmdRow[5]), 300)
        else:
            mix_amt = 0
        tipID = int(cmdRow[6])  #each row is performed using a single tip

        #choose pipette
        if (len(target_well) == 8 and len(source_well) == 8):
            #MULTICHANNEL
            pipette = "left_pipette"

            #pick up tip if needed
            if (tipID != current_tipID):
                left_pipette.pick_up_tip()  #pick up tip if tipID changes
                current_tipID = tipID  #update tip id

            aspHs = []
            dspHs = []
            mixVols = []
            for j in range(1, 7):
                #get mix volumes
                mixVol = GetSrcVolume(amtList, cmdRow,
                                      source_well[j])  #not annotated
                mixVols.append(mixVol)  #not annotated

                #update solutions map
                amtList = Update_Source(amtList, cmdRow, source_well[j])
                amtList = Update_Target(amtList, cmdRow, target_well[j],
                                        deckMap)

                #calculate aspirate and dispense height
                aspH = CalTip_Aspirate(amtList, cmdRow, source_well[j])
                dspH = CalTip_Dispense(amtList, cmdRow, target_well[j])
                aspHs.append(aspH)
                dspHs.append(dspH)

                source_col = source_well[0][1:]
                target_col = target_well[0][1:]

            #selecting aspHs/dspHs
            aspHs = [i for i in aspHs if i > 0]
            dspHs = [i for i in dspHs if i > 0]
            mixVols = [i for i in mixVols if i > 0]  #not annotated

            aspHs_selected = min(aspHs)
            dspHs_selected = min(dspHs)

            if (max(mixVols) == 0):
                mix_amt = 0
            else:
                mixVols.append(300)
                mix_amt = min(mixVols)

            #Main Transfers
            source_well_h = source_well[0]
            target_well_h = target_well[0]
            remV = transfer_amt
            while (remV > 0):
                cur_transfer = min(300, remV)
                if (remV - cur_transfer < 30 and remV - cur_transfer > 0):
                    cur_transfer = cur_transfer / 2
                remV = remV - cur_transfer

                if (mix_amt == 0):
                    #if no mix
                    left_pipette.transfer(
                        cur_transfer,
                        globals()[source_ware].wells_by_name()
                        [source_well_h].bottom(aspHs_selected),
                        globals()[target_ware].wells_by_name()
                        [target_well_h].bottom(dspHs_selected),
                        new_tip='never',
                        disposal_volume=0)
                else:
                    #if mix
                    left_pipette.transfer(
                        cur_transfer,
                        globals()[source_ware].wells_by_name()
                        [source_well_h].bottom(aspHs_selected),
                        globals()[target_ware].wells_by_name()
                        [target_well_h].bottom(dspHs_selected),
                        new_tip='never',
                        mix_before=(2, cur_transfer),
                        disposal_volume=0)

                #blow out on top of the current slot
                left_pipette.blow_out(globals()[target_ware].wells_by_name()
                                      [target_well_h].bottom(dspHs_selected))

            #check if tip need to be trashed afterwards
            if (i == len(cmdList) - 1):
                #if this is the last operation
                left_pipette.drop_tip()
            elif (int(cmdRow[6]) != int(cmdList[i + 1][6])):
                #drop if different tip id is detected
                left_pipette.drop_tip()

        else:
            #SINGLE CHANNEL
            pipette = 'right_pipette'
            cur_source_well = source_well[0]  #select only the first source

            #pick up tip if needed
            if (tipID != current_tipID):
                right_pipette.pick_up_tip()  #pick up tip if tipID changes
                current_tipID = tipID  #update tip id

            #iterate through all target wells
            for j in range(len(target_well)):
                if (mix_amt > 0):
                    mix_amt = min(
                        GetSrcVolume(amtList, cmdRow, cur_source_well), 300)

                #update solutions map
                amtList = Update_Source(amtList, cmdRow, cur_source_well)
                amtList = Update_Target(amtList, cmdRow, target_well[j],
                                        deckMap)

                #calculate aspirate and dispense height
                aspH = CalTip_Aspirate(amtList, cmdRow, cur_source_well)
                dspH = CalTip_Dispense(amtList, cmdRow, target_well[j])

                #Main Transfers
                remV = transfer_amt
                while (remV > 0):
                    cur_transfer = min(300, remV)
                    if (remV - cur_transfer < 30 and remV - cur_transfer > 0):
                        cur_transfer = cur_transfer / 2
                    remV = remV - cur_transfer

                    if (mix_amt == 0):
                        #if no mix
                        right_pipette.transfer(
                            cur_transfer,
                            globals()[source_ware].wells_by_name()
                            [cur_source_well].bottom(aspH),
                            globals()[target_ware].wells_by_name()[
                                target_well[j]].bottom(dspH),
                            new_tip='never',
                            disposal_volume=0)
                    else:
                        #if mix
                        right_pipette.transfer(
                            cur_transfer,
                            globals()[source_ware].wells_by_name()
                            [cur_source_well].bottom(aspH),
                            globals()[target_ware].wells_by_name()[
                                target_well[j]].bottom(dspH),
                            new_tip='never',
                            mix_before=(3, cur_transfer),
                            disposal_volume=0)

                    #blow out on top of the current slot
                    right_pipette.blow_out(
                        globals()[target_ware].wells_by_name()[
                            target_well[j]].bottom(dspH))

            #check if tip need to be trashed afterwards
            if (i == len(cmdList) - 1):
                #if this is the last operation
                right_pipette.drop_tip()
            elif (int(cmdRow[6]) != int(cmdList[i + 1][6])):
                #drop if different tip id is detected
                right_pipette.drop_tip()
Example #21
0
def run(ctx: protocol_api.ProtocolContext):

	# Initial data
	global robot
	global tip_log

	# Set robot as global var
	robot = ctx

	# check if tipcount is being reset
	if RESET_TIPCOUNT:
		reset_tipcount()


	# confirm door is close
	robot.comment(f"Please, close the door")
	confirm_door_is_closed()

	start = start_run()


	# #####################################################
	# Common functions
	# #####################################################
	
	# -----------------------------------------------------
	# Execute step
	# -----------------------------------------------------
	def run_step(step):

		robot.comment(' ')
		robot.comment('###############################################')
		robot.comment('Step ' + str(step) + ': ' + STEPS[step]['Description'])
		robot.comment('===============================================')

		# Execute step?
		if STEPS[step]['Execute']:

			# Get start info
			elapsed = datetime.now()
			for i, key in enumerate(tip_log['used']):
				val = tip_log['used'][key]
				if i == 0: 
					cl = val
				else:
					cr = val

			# Execute function step
			STEPS[step]['Function']()

			# Wait
			if STEPS[step].get('wait_time'):
				robot.comment('===============================================')
				wait = STEPS[step]['wait_time']
				robot.delay(seconds = wait)

			# Get end info
			elapsed = datetime.now() - elapsed
			for i, key in enumerate(tip_log['used']):
				val = tip_log['used'][key]
				if i == 0: 
					cl = val - cl
				else:
					cr = val - cr
			# Stats
			STEPS[step]['Time:'] = str(elapsed)
			robot.comment('===============================================')
			robot.comment('Elapsed time: ' + str(elapsed))
			for i, key in enumerate(tip_log['used']):
				if i == 0: 
					robot.comment('Tips "' + str(key) + '" used: ' + str(cl))
				else:
					robot.comment('Tips "' + str(key) + '" used: ' + str(cr))

		# Dont execute step
		else:
			robot.comment('No ejecutado')

		# End
		robot.comment('###############################################')
		robot.comment(' ')

	# #####################################################
	# 1. Start defining deck
	# #####################################################
	
	# Labware
	# Positions are:
	# 10    11      TRASH
	# 7     8       9
	# 4     5       6
	# 1     2       3


	# -----------------------------------------------------
	# Tips
	# -----------------------------------------------------

	tips20 = [robot.load_labware('opentrons_96_filtertiprack_20ul', slot)
		for slot in ['11']
	]

	# -----------------------------------------------------
	# Pipettes
	# -----------------------------------------------------
	
	p20 = robot.load_instrument('p20_single_gen2', 'right', tip_racks=tips20)

	## retrieve tip_log

	retrieve_tip_info(p20,tips20)
	

	# -----------------------------------------------------
	# Labware
	# -----------------------------------------------------

	primers_rack = ctx.load_labware('opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap', '7',
		'24_tuberack_starsted source rack')

	dest_rack = ctx.load_labware('opentrons_96_aluminumblock_generic_pcr_strip_200ul', '8', 'source tuberack')

	# -----------------------------------------------------
	# Reagens
	# -----------------------------------------------------
	positive_control_reagent = Reagent(name = 'Positive control',
					flow_rate_aspirate = 1,
					flow_rate_dispense = 300,
					flow_rate_aspirate_mix = 1,
					flow_rate_dispense_mix = 300)
					
	# -----------------------------------------------------
	# Tubes
	# -----------------------------------------------------
	starsted_tube = Tube(name = 'Starsted 1.5 Tube',
				actual_volume = 400,
				max_volume = 1500,
				min_height=0.7,
				diameter = 8.7, # avl1.diameter
				base_type = 2,
				height_base = 4) 
				
	

	# #####################################################
	# 2. Steps definition
	# #####################################################

	# -----------------------------------------------------
	# Step n: ....
	# -----------------------------------------------------
	def step1():
		
		if not p20.hw_pipette['has_tip']:
			pick_up(p20,tips20)

		segene_positive_control = primers_rack['A6']
		
		dest = dest_rack.wells()[:50] 

		# transfer buffer to tubes
		distribute_custom(pip = p20,
						reagent = positive_control_reagent,
						tube_type = starsted_tube,
						volume = 10,
						src = segene_positive_control,
						dest = dest,
						extra_dispensal=0,
						disp_height=20,
						touch_tip_aspirate=False,
						touch_tip_dispense=True)
						
		drop(p20)
			
	# -----------------------------------------------------
	# Execution plan
	# -----------------------------------------------------
	STEPS = {
		1:{'Execute': True,  'Function': step1, 'Description': 'Transfer Positive Control 16 first wells/tubes'},
	}

	# #####################################################
	# 3. Execute every step!!
	# #####################################################
	for step in STEPS:
		run_step(step)
   
	# track final used tip
	save_tip_info()

	# -----------------------------------------------------
	# Stats
	# -----------------------------------------------------
	end = finish_run()



	robot.comment('===============================================')
	robot.comment('Start time:   ' + str(start))
	robot.comment('Finish time:  ' + str(end))
	robot.comment('Elapsed time: ' + str(datetime.strptime(end, "%Y/%m/%d %H:%M:%S") - datetime.strptime(start, "%Y/%m/%d %H:%M:%S")))
	for key in tip_log['used']:
		val = tip_log['used'][key]
		robot.comment('Tips "' + str(key) + '" used: ' + str(val))
	robot.comment('===============================================')
Example #22
0
def run(ctx: protocol_api.ProtocolContext):
    [
        input_csv, p20_mount, p300_mount, aspiration_height_plate,
        dispensing_height_plate, aspiration_height_resv, flow_rate_multiplier
    ] = get_values(  # noqa: F821
        "input_csv", "p20_mount", "p300_mount", "aspiration_height_plate",
        "dispensing_height_plate", "aspiration_height_resv",
        "flow_rate_multiplier")

    if 0.1 > aspiration_height_plate:
        raise Exception("Enter a higher source plate aspiration height")

    if 0.1 > dispensing_height_plate:
        raise Exception("Enter a higher destination plate dispensing height")

    if 0.1 > aspiration_height_resv:
        raise Exception("Enter a higher reservoir aspiration height")

    if p20_mount == p300_mount:
        raise Exception("Both pipettes cannot be mounted in the same mount")

    # define all custom variables above here with descriptions:
    sample_plate_lname = 'opentrons_96_aluminumblock_biorad_wellplate_200ul'
    dest_plate_lname = 'opentrons_96_aluminumblock_biorad_wellplate_200ul'
    small_tips_loadname = 'opentrons_96_filtertiprack_20ul'
    large_tips_loadname = 'opentrons_96_filtertiprack_200ul'
    res_loadname = 'nest_12_reservoir_15ml'

    # For validating that wells have the format of <A-H><0-12>
    well_name_validation_regex = re.compile(r'[A-H][0-9][0-2]?')
    # Check that the volumes look like numbers e.g. 12, or 5.25
    volume_validation_regex = re.compile(r'[0-9]+(\.[0-9]+)?')

    # load modules
    '''

    Add your modules here with:

    module_name = ctx.load_module('{module_loadname}', '{slot number}')

    Note: if you are loading a thermocycler, you do not need to specify
    a slot number - thermocyclers will always occupy slots 7, 8, 10, and 11.

    For all other modules, you can load them on slots 1, 3, 4, 6, 7, 9, 10.

    '''

    # load labware
    temp_mod_samples = ctx.load_module('temperature module gen2', '1')
    temp_mod_destination = ctx.load_module('temperature module gen2', '4')
    '''

    Add your labware here with:

    labware_name = ctx.load_labware('{loadname}', '{slot number}')

    If loading labware on a module, you can load with:

    labware_name = module_name.load_labware('{loadname}')
    where module_name is defined above.

    '''
    sample_plate = temp_mod_samples.load_labware(sample_plate_lname)
    destination_plate = temp_mod_destination.load_labware(dest_plate_lname)
    reservoir_12 = ctx.load_labware(res_loadname,
                                    '2',
                                    label="diluent reservoir")

    # load tipracks
    '''

    Add your tipracks here as a list:

    For a single tip rack:

    tiprack_name = [ctx.load_labware('{loadname}', '{slot number}')]

    For multiple tip racks of the same type:

    tiprack_name = [ctx.load_labware('{loadname}', 'slot')
                     for slot in ['1', '2', '3']]

    If two different tipracks are on the deck, use convention:
    tiprack[number of microliters]
    e.g. tiprack10, tiprack20, tiprack200, tiprack300, tiprack1000

    '''
    small_tipracks = [
        ctx.load_labware(small_tips_loadname, slot) for slot in ['7', '10']
    ]

    large_tipracks = [
        ctx.load_labware(large_tips_loadname, slot) for slot in ['5', '8']
    ]

    # load instrument
    '''
    Nomenclature for pipette:

    use 'p'  for single-channel, 'm' for multi-channel,
    followed by number of microliters.

    p20, p300, p1000 (single channel pipettes)
    m20, m300 (multi-channel pipettes)

    If loading pipette, load with:

    ctx.load_instrument(
                        '{pipette api load name}',
                        pipette_mount ("left", or "right"),
                        tip_racks=tiprack
                        )
    '''
    p20 = ctx.load_instrument('p20_single_gen2',
                              p20_mount,
                              tip_racks=small_tipracks)

    p300 = ctx.load_instrument('p300_single_gen2',
                               p300_mount,
                               tip_racks=large_tipracks)

    # pipette functions   # INCLUDE ANY BINDING TO CLASS
    '''

    Define all pipette functions, and class extensions here.
    These may include but are not limited to:

    - Custom pickup functions
    - Custom drop tip functions
    - Custom Tip tracking functions
    - Custom Trash tracking functions
    - Slow tip withdrawal

    For any functions in your protocol, describe the function as well as
    describe the parameters which are to be passed in as a docstring below
    the function (see below).

    def pick_up(pipette):
        """`pick_up()` will pause the protocol when all tip boxes are out of
        tips, prompting the user to replace all tip racks. Once tipracks are
        reset, the protocol will start picking up tips from the first tip
        box as defined in the slot order when assigning the labware definition
        for that tip box. `pick_up()` will track tips for both pipettes if
        applicable.

        :param pipette: The pipette desired to pick up tip
        as definited earlier in the protocol (e.g. p300, m20).
        """
        try:
            pipette.pick_up_tip()
        except protocol_api.labware.OutOfTipsError:
            ctx.pause("Replace empty tip racks")
            pipette.reset_tipracks()
            pipette.pick_up_tip()

    '''

    # helper functions
    '''
    Define any custom helper functions outside of the pipette scope here, using
    the convention seen above.

    e.g.

    def remove_supernatant(vol, index):
        """
        function description

        :param vol:

        :param index:
        """


    '''
    class VolTracker:
        def __init__(self,
                     labware,
                     well_vol,
                     start=0,
                     end=8,
                     mode='reagent',
                     pip_type='single',
                     msg='Reset labware volumes'):
            """
            Voltracker tracks the volume(s) used in a piece of labware

            :param labware: The labware to track
            :param well_vol: The volume of the liquid in the wells
            :param pip_type: The pipette type used 'single' or 'multi'
            :param mode: 'reagent' or 'waste'
            :param start: The starting well
            :param end: The ending well
            :param msg: Message to send to the user when all wells are empty

            """
            self.labware_wells = dict.fromkeys(labware.wells()[start - 1:end],
                                               0)
            self.labware_wells_backup = self.labware_wells.copy()
            self.well_vol = well_vol
            self.pip_type = pip_type
            self.mode = mode
            self.start = start
            self.end = end
            self.msg = msg

            # Parameter error checking
            if not (pip_type == 'single' or pip_type == 'multi'):
                raise Exception('Pipette type must be single or multi')

            if not (mode == 'reagent' or mode == 'waste'):
                raise Exception('mode must be reagent or waste')

        def track(self, vol):
            '''track() will track how much liquid
            was used up per well. If the volume of
            a given well is greater than self.well_vol
            it will remove it from the dictionary and iterate
            to the next well which will act as the reservoir.'''
            well = next(iter(self.labware_wells))
            vol = vol * 8 if self.pip_type == 'multi' else vol
            if self.labware_wells[well] + vol >= self.well_vol:
                del self.labware_wells[well]
                if len(self.labware_wells) < 1:
                    ctx.pause(self.msg)
                    self.labware_wells = self.labware_wells_backup.copy()
                well = next(iter(self.labware_wells))
            self.labware_wells[well] += vol

            if self.mode == 'waste':
                ctx.comment('{}: {} ul of total waste'.format(
                    well, int(self.labware_wells[well])))
            else:
                ctx.comment('{} uL of liquid used from {}'.format(
                    int(self.labware_wells[well]), well))
            return well

    # reagents
    '''
    Define where all reagents are on the deck using the labware defined above.

    e.g.

    water = reservoir12.wells()[-1]
    waste = reservoir.wells()[0]
    samples = plate.rows()[0][0]
    dnase = tuberack.wells_by_name()['A4']

    '''
    diluent = VolTracker(reservoir_12,
                         14 * 10**3,
                         start=1,
                         end=4,
                         mode='reagent',
                         pip_type='single',
                         msg='Refill diluent wells')

    # plate, tube rack maps
    '''
    Define any plate or tube maps here.

    e.g.

    plate_wells_by_row = [well for row in plate.rows() for well in row]

    '''
    sample_wells = sample_plate.wells_by_name()
    dest_wells = destination_plate.wells_by_name()

    # protocol
    '''

    Include header sections as follows for each "section" of your protocol.

    Section can be defined as a step in a bench protocol.

    e.g.

    ctx.comment('\n\nMOVING MASTERMIX TO SAMPLES IN COLUMNS 1-6\n')

    for .... in ...:
        ...
        ...

    ctx.comment('\n\nRUNNING THERMOCYCLER PROFILE\n')

    ...
    ...
    ...


    '''
    # parse
    data = [[val.strip().upper() for val in line.split(',')]
            for line in input_csv.splitlines()[1:]
            if line and line.split(',')[0]]

    # Validate csv input
    i = 1
    for line in data:
        i += 1
        if not len(line) == 4:
            raise Exception(
                "Line #{} \"{}\" has the wrong number of entries".format(
                    i, line))
        # check well formatting
        if well_name_validation_regex.fullmatch(line[0]) is None:
            raise Exception(("Line #{}: The source plate well name \"{}\" "
                             "has the wrong format").format(i, line[0]))

        if well_name_validation_regex.fullmatch(line[1]) is None:
            raise Exception(("Line #{}: The dest. plate well name \"{}\" "
                             "has the wrong format").format(i, line[1]))

        if volume_validation_regex.fullmatch(line[2]) is None:
            raise Exception(("Line #{}: The sample volume \"{}\" " +
                             "has the wrong format").format(i, line[2]))

        if volume_validation_regex.fullmatch(line[3]) is None:
            raise Exception(("Line #{}: The diluent volume \"{}\" " +
                             "has the wrong format").format(i, line[3]))

    # perform normalization - Transfer all the diluent first before
    # transferring any sample: use the same pipette tip
    # Steps 1-4
    # d - dest well,  vol_d - diluent volume
    ctx.comment("\n\nTransferring diluent to the target plate\n")
    for _, d, _, vol_d in data:
        vol_d = float(vol_d)
        d_well = dest_wells[d]

        pip = p300 if vol_d > 20 else p20
        if not pip.has_tip:
            pip.pick_up_tip()
        pip.transfer(vol_d,
                     diluent.track(vol_d).bottom(aspiration_height_resv),
                     d_well.bottom(dispensing_height_plate),
                     new_tip='never')
        pip.blow_out(d_well.top(-2))
        # Debugging info
        # print("diluent reservoir position {}".
        #       format(diluent.track(0).bottom(aspiration_height_resv)))

    # Step 5: drop tips
    ctx.comment("\n\nDiluent transfer complete: Droppping tips")
    for pip in [p20, p300]:
        if pip.has_tip:
            pip.drop_tip()

    # Step 7-10: Transfer samples from the sample plate to the dest. plate
    ctx.comment("\n\nTransferring samples to the target plate\n")
    for s, d, vol_s, _ in data:
        vol_s = float(vol_s)
        s_well = sample_wells[s].bottom(aspiration_height_plate)
        d_well = dest_wells[d].bottom(dispensing_height_plate)
        blow_out_loc = dest_wells[d].top(-2)
        # Debugging info
        # print("Aspiration height s_well: {}".format(s_well))
        # print("Dispensing height d_well: {}".format(d_well))

        # Transfer sample
        pip = p300 if vol_s > 20 else p20
        pip.pick_up_tip()
        pip.aspirate(vol_s, s_well, flow_rate_multiplier)
        pip.dispense(vol_s, d_well, flow_rate_multiplier)
        pip.blow_out(blow_out_loc)
        pip.drop_tip()
Example #23
0
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]))
Example #24
0
def run(ctx: protocol_api.ProtocolContext):
    [
     _samp_cols,  # column numbers containing samples
     _m300_mount  # mount for p300-Multi
    ] = get_values(  # noqa: F821 (<--- DO NOT REMOVE!)
        '_samp_cols',
        '_m300_mount')

    # custom variables
    if type(_samp_cols) is int:
        samp_cols = [_samp_cols]
    else:
        samp_cols = _samp_cols.split(",")
    m300_mount = _m300_mount

    # load modules
    mag_deck = ctx.load_module('magnetic module gen2', '1')

    # load labware
    mag_plate = mag_deck.load_labware(
        'nest_96_wellplate_100ul_pcr_full_skirt',
        'Sample Plate on MagDeck')

    elution_plate = ctx.load_labware(
        'nest_96_wellplate_100ul_pcr_full_skirt', '3',
        'Elution Plate')

    res12 = ctx.load_labware(
        'nest_12_reservoir_15ml', '2', '12-Well Reservoir with Reagents')

    # load tipracks
    tips = [ctx.load_labware(
        'opentrons_96_filtertiprack_200ul', s) for s in ['5', '6']]

    # load instrument
    m300 = ctx.load_instrument('p300_multi_gen2', m300_mount, tip_racks=tips)

    # helper functions
    # to take vol and return estimated liq height
    def liq_height(well):
        if well.diameter is not None:
            radius = well.diameter / 2
            cse = math.pi*(radius**2)
        elif well.length is not None:
            cse = well.length*well.width
        else:
            cse = None
        if cse:
            return well.liq_vol / cse
        else:
            raise Exception("""Labware definition must
                supply well radius or well length and width.""")

    def incubate(min):
        ctx.comment(f'\nIncubating for {min} minutes\n')
        ctx.delay(minutes=min)

    def remove_supernatant(vol):
        ctx.comment(f'\nTransferring {vol}uL from wells to liquid waste\n')
        m300.flow_rate.aspirate = 15
        for col in samp_cols:
            m300.pick_up_tip()
            m300.aspirate(vol, mag_plate['A'+str(col).strip()])
            m300.dispense(vol, waste)
            m300.drop_tip()
        m300.flow_rate.aspirate = 94

    # reagents
    beads = res12['A1']
    beads.liq_vol = 45 * len(samp_cols) * 1.05
    etoh = res12['A3']
    etoh.liq_vol = 350 * len(samp_cols)
    te = res12['A5']
    te.liq_vol = 50 * len(samp_cols) * 1.05
    waste = res12['A11'].top(-2)

    # protocol
    # Transfer Bead Solution Transfer
    ctx.comment(f'\nTransferring 45uL Bead Solution \
    to samples in columns {samp_cols}\n')
    for col in samp_cols:
        m300.pick_up_tip()
        beads.liq_vol -= 45
        bead_ht = liq_height(beads) - 2 if liq_height(beads) - 2 > 1 else 1
        m300.mix(3, 40, beads.bottom(bead_ht))
        m300.aspirate(45, beads.bottom(bead_ht))
        m300.dispense(45, mag_plate['A'+str(col).strip()])
        m300.mix(5, 60, mag_plate['A'+str(col).strip()])
        ctx.delay(seconds=1)
        m300.drop_tip()

    # Incubate for 5 minutes, engage magnet, incubate for 2 minutes
    incubate(5)
    mag_deck.engage()
    incubate(2)

    # remove supernatant
    remove_supernatant(75)
    mag_deck.disengage()

    # Perform 2 ethanol washes
    for i in range(2):
        ctx.comment(f'\nPerforming EtOH Wash {i+1}\n')
        for col in samp_cols:
            m300.pick_up_tip()
            etoh.liq_vol -= 150
            et_ht = liq_height(etoh) - 2 if liq_height(etoh) - 2 > 1 else 1
            m300.aspirate(150, etoh.bottom(et_ht))
            m300.dispense(150, mag_plate['A'+str(col).strip()])
            m300.mix(5, 100, mag_plate['A'+str(col).strip()])
            ctx.delay(seconds=1)
            m300.blow_out()
            m300.drop_tip()

        mag_deck.engage()
        incubate(2)

        remove_supernatant(150)
        mag_deck.disengage()

    incubate(2)

    # Transfer elution buffer and elutes
    ctx.comment(f'\nTransferring 50uL Low TE \
    to samples in columns {samp_cols}\n')
    for col in samp_cols:
        m300.pick_up_tip()
        te.liq_vol -= 50
        te_ht = liq_height(te) - 2 if liq_height(te) - 2 > 1 else 1
        m300.aspirate(50, te.bottom(te_ht))
        m300.dispense(50, mag_plate['A'+str(col).strip()])
        m300.mix(5, 25, mag_plate['A'+str(col).strip()])
        ctx.delay(seconds=1)
        m300.drop_tip()

    incubate(2)
    mag_deck.engage()
    incubate(2)

    ctx.comment('\nTransferring samples to Elution Plate\n')
    m300.flow_rate.aspirate = 30
    for col, dest in zip(samp_cols, elution_plate.rows()[0]):
        m300.pick_up_tip()
        m300.aspirate(50, mag_plate['A'+str(col).strip()])
        m300.dispense(50, dest)
        m300.drop_tip()

    ctx.comment('\nProtocol complete!')
def run(protocol: protocol_api.ProtocolContext):

    # LABWARE
    tiprack300 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '8')
    tiprack20 = protocol.load_labware('opentrons_96_filtertiprack_20ul', '9')
    # tempdeck = protocol.load_module('tempdeck', '4')
    # plate = tempdeck.load_labware('opentrons_96_aluminumblock_generic_pcr_strip_200ul')
    # plate = tempdeck.load_labware('abi_96_wellplate_250ul')
    sectempdeck = protocol.load_module('tempdeck', '10')
    fuge_rack = sectempdeck.load_labware(
        'opentrons_24_aluminumblock_generic_2ml_screwcap')
    holder_1 = protocol.load_labware(
        '8wstriptubesonfilterracks_96_aluminumblock_250ul', '3')
    holder_2 = protocol.load_labware(
        '8wstriptubesonfilterracks_96_aluminumblock_250ul', '6')
    # PIPETTES
    p300 = protocol.load_instrument('p300_single_gen2',
                                    'left',
                                    tip_racks=[tiprack300])
    p20 = protocol.load_instrument('p20_single_gen2',
                                   'right',
                                   tip_racks=[tiprack20])

    # REAGENTS
    SAMP_1mix = fuge_rack['B5']
    SAMP_2mix = fuge_rack['D1']
    SAMP_3mix = fuge_rack['D2']
    SAMP_4mix = fuge_rack['D3']
    SAMP_5mix = fuge_rack['B6']
    SAMP_6mix = fuge_rack['C1']
    SAMP_7mix = fuge_rack['C2']
    SAMP_8mix = fuge_rack['C3']

    # user inputs
    # num_of_sample_reps is another way of stating number of strips
    num_of_sample_reps = 6

    holderList = [holder_1]
    # lists
    # ALL_SAMPs = [samp_1, samp_2, samp_3, samp_4, samp_5, samp_6, samp_7, WATER]
    SAMP_mixes = [
        SAMP_1mix, SAMP_2mix, SAMP_3mix, SAMP_4mix, SAMP_5mix, SAMP_6mix,
        SAMP_7mix, SAMP_8mix
    ]
    # SAMP_mixes = [SAMP_1mix, SAMP_2mix, SAMP_3mix, SAMP_4mix, SAMP_5mix, SAMP_6mix, SAMP_7mix, WATER]
    SAMP_wells = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']

    # #### COMMANDS ######
    # Add sample DNA, mix, distribute to strip tubes
    for i, mixtube in enumerate(SAMP_mixes):
        for y in range(0, len(holderList)):
            p300.pick_up_tip()
            p300.move_to(mixtube.bottom(40))
            p300.aspirate(num_of_sample_reps * 20 * 1.08,
                          mixtube.bottom(2))  # 6*20*1.08 = 130
            protocol.delay(seconds=1)  #equilibrate
            p300.touch_tip(v_offset=-3)
            holderPos = y
            holder = holderList[holderPos]
            start = 6 * y
            stop = num_of_sample_reps if num_of_sample_reps <= 6 * y + 6 else 6 * y + 6  # This is max it can go in cycle; can't go above e.g. A13 !<>
            for x in range(start,
                           stop):  # samples in 1-6, 7-12, 13-18 increments
                # print ("start: ", start, "stop: ", stop)
                row = SAMP_wells[i]
                dest = row + str(
                    2 * x + 1 - 12 * holderPos)  # need +1 offset for col
                p300.move_to(
                    holder[dest].bottom(40))  #move across holder in +4cm pos
                p300.dispense(
                    20, holder[dest].bottom(6),
                    rate=0.75)  # more height so tip doesn't touch pellet
                # p300.move_to(holder[dest].bottom(8))
                # p300.blow_out(holder[dest]. bottom(8))
                p300.touch_tip()
                p300.move_to(holder[dest].top(
                ))  # centers tip so tip doesn't lift tubes after touch
                p300.move_to(
                    holder[dest].bottom(40))  #move across holder in +4cm pos
                # p300.move_to(holder[dest].bottom(40)) #move back holder in +4cm pos
                # p300.move_to(mixtube.bottom(40)) #return to tube at +4cm so no crash into lyo tubes
            p300.drop_tip()
Example #26
0
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(protocol: protocol_api.ProtocolContext):
    #global cmdList, deckMap, amtList
    os.chdir(mainwd)
    amtList, cmdList, deckMap = ReadCSV_Dat(mainInput)
    skip_rows = input('Skip rows ... ')
    skip_rows = int(skip_rows)
    
    ############ LOAD LABWARES ############
    tipLocs = []
    for i in range(11):
        #load labware
        labware_name = deckMap["labware_"+str(i+1)]
        if(('empty' not in labware_name) and labware_name != 'TRASH'):
            deck_position = int(list(deckMap.keys())[i].split('_')[1])
            globals()[list(deckMap.keys())[i]] = protocol.load_labware(labware_name, deck_position)

            #if labware is a tip rack, assign number to tip location(s)
            if('tiprack' in labware_name):
                tipLocs.append(globals()[list(deckMap.keys())[i]])
                
    #load pipettes
        #single-channel
    right_pipette = protocol.load_instrument(
        'p300_single', 'right', tip_racks=tipLocs)
    right_pipette.flow_rate.aspirate=aspirateSpeed
    right_pipette.flow_rate.dispense=dispenseSpeed
        #multi-channel -- deprecated; to be switched to p1000 single channel
    left_pipette = protocol.load_instrument(
        'p1000_single_gen2', 'left', tip_racks=[])
    left_pipette.flow_rate.aspirate=aspirateSpeed
    left_pipette.flow_rate.dispense=dispenseSpeed
    
    #Update Amount List
    for i in range(len(amtList)):
        if(float(amtList[i][3])<=50):
            amtList[i][3] = float(amtList[i][3])*1000
        #get tube type
        if('50ml' in deckMap[amtList[i][0]]):
            amtList[i] = np.append(amtList[i], '50ml_falcon')
        elif('15ml' in deckMap[amtList[i][0]]):
            amtList[i] = np.append(amtList[i], '15ml_falcon')
        else:
            amtList[i] = np.append(amtList[i], '1.5ml_eppendorf')
     
    ############ EXECUTE COMMANDS ############
    #iterate through all command lines
    current_tipID = 0 #initiate tip ID
    iterateMarker = -1
    progressMax = len(cmdList)
    
    for i in range(len(cmdList)):
        #subset
        cmdRow = cmdList[i]
        if(i+1 < skip_rows):
            extraReport = "-- SKIP!"
        else:
            extraReport = ""
            
        print("Progress\t: " + str(int(round(i/progressMax*100))) + "% " + extraReport)
        print(cmdRow)

        #parse all informations
        source_ware = cmdRow[0]
        source_well = cmdRow[1].split(', ')
        target_ware = cmdRow[2]
        target_well = cmdRow[3].split(', ')
        transfer_amt = float(cmdRow[4]) #only one transfer amount is allowed
        if(float(cmdRow[5]) > 0):
            mix_amt = max(float(cmdRow[5]), 300)
        else:
            mix_amt = 0
        tipID = int(cmdRow[6])
        
        #choose pipette -- multichannel block deprecated
        if(len(target_well)==8 and len(source_well)==8):
            #operations for multichannel pipette
            pipette = "left_pipette"
            
            #pick up tip if needed
            if(tipID != current_tipID):
                left_pipette.pick_up_tip() #pick up tip if tipID changes
                current_tipID = tipID #update tip id
            
            #Main Transfers
            source_well_h = source_well[0]
            target_well_h = target_well[0]
            remV = transfer_amt
            
            while(remV>0):
                cur_transfer = min(300, remV)
                if(remV-cur_transfer < 30 and remV-cur_transfer>0):
                    cur_transfer = cur_transfer/2
                remV = remV - cur_transfer
                
                ########### NOT ANNOTATED ############# / BLOCK1 // MULTIPIPETTE HEIGHT
                aspHs = []
                dspHs = []
                mixVols = []
                for j in range(1, 7):
                    #get mix volumes
                    mixVol = GetSrcVolume(amtList, cmdRow, source_well[j]) #not annotated
                    mixVols.append(mixVol)  #not annotated

                    #update solutions map
                    amtList = Update_Source(amtList, cmdRow, source_well[j], cur_transfer)
                    amtList = Update_Target(amtList, cmdRow, target_well[j], deckMap, cur_transfer)

                    #calculate aspirate and dispense height
                    aspH = CalTip_Aspirate(amtList, cmdRow, source_well[j])
                    dspH = CalTip_Dispense(amtList, cmdRow, target_well[j])
                    aspHs.append(aspH)
                    dspHs.append(dspH)

                    source_col = source_well[0][1:]
                    target_col = target_well[0][1:]
                    
                #selecting aspHs/dspHs
                aspHs = [i for i in aspHs if i>0]
                dspHs = [i for i in dspHs if i>0]
                mixVols = [i for i in mixVols if i>0] #not annotated

                aspHs_selected = min(aspHs)
                dspHs_selected = min(dspHs)
                
                ## temporary measure for deep wells AND 96-well plates -- fix well heights
                aspHs_selected = 3 #3 mm from bottom
                dspHs_selected = 15 #15 mm from bottom
            
                #####
                #not annotated
                if(max(mixVols)==0): 
                    mix_amt = 0
                else:
                    mixVols.append(300)
                    mix_amt = min(mixVols)
                ####################################################
                
                
                if(mix_amt==0):
                    #if no mix
                    left_pipette.transfer(cur_transfer,
                                          globals()[source_ware].wells_by_name()[source_well_h].bottom(aspHs_selected),
                                          globals()[target_ware].wells_by_name()[target_well_h].bottom(dspHs_selected),
                                          new_tip='never', disposal_volume=0)
                else:
                    #if mix
                    left_pipette.transfer(cur_transfer,
                                          globals()[source_ware].wells_by_name()[source_well_h].bottom(aspHs_selected),
                                          globals()[target_ware].wells_by_name()[target_well_h].bottom(dspHs_selected),
                                          new_tip='never', mix_before = (2, cur_transfer), disposal_volume=0)
                
                #blow out on top of the current slot
                left_pipette.blow_out(globals()[target_ware].wells_by_name()[target_well_h].bottom(dspHs_selected))
                
            #check if tip need to be trashed afterwards
            if(i == len(cmdList)-1):
                #if this is the last operation
                left_pipette.drop_tip()
            elif(int(cmdRow[6]) != int(cmdList[i+1][6])):
                #drop if different tip id is detected
                left_pipette.drop_tip()

        else:
            #IF NOT MULTI PIPETTE
            pipette = 'right_pipette'
            cur_source_well = source_well[0] #select only the first source
            
            #pick up tip if needed
            if(i+1 >= skip_rows):
                if(tipID != current_tipID or (i+1 == skip_rows)):
                    right_pipette.pick_up_tip() #pick up tip if tipID changes
                    current_tipID = tipID #update tip id

            #iterate through all target wells
            for j in range(len(target_well)):
                if(mix_amt>0):
                    mix_amt = min(GetSrcVolume(amtList, cmdRow, cur_source_well), 300)
                
                #Main Transfers
                remV = transfer_amt
                while(remV>0):
                    #Calculate current transfer amount
                    cur_transfer = min(300, remV)
                    if(remV-cur_transfer < 30 and remV-cur_transfer>0):
                        cur_transfer = cur_transfer/2
                    remV = remV - cur_transfer
                    
                    #update solutions map
                    amtList = Update_Source(amtList, cmdRow, cur_source_well, cur_transfer)
                    amtList = Update_Target(amtList, cmdRow, target_well[j], deckMap, cur_transfer)

                    #calculate aspirate and dispense height
                    aspH = CalTip_Aspirate(amtList, cmdRow, cur_source_well)
                    dspH = CalTip_Dispense(amtList, cmdRow, target_well[j])
                    
                    #Mix boolean
                    if(i+1 >= skip_rows):
                        if(mix_amt==0):
                            #if no mix
                            right_pipette.transfer(cur_transfer,
                                                  globals()[source_ware].wells_by_name()[cur_source_well].bottom(aspH),
                                                  globals()[target_ware].wells_by_name()[target_well[j]].bottom(dspH),
                                                  new_tip='never', disposal_volume=0)
                        else:
                            #if mix
                            right_pipette.transfer(cur_transfer,
                                                  globals()[source_ware].wells_by_name()[cur_source_well].bottom(aspH),
                                                  globals()[target_ware].wells_by_name()[target_well[j]].bottom(dspH),
                                                  new_tip='never', mix_before = (3, cur_transfer), disposal_volume=0)
                        
                        #blow out on top of the current slot
                        right_pipette.blow_out(globals()[target_ware].wells_by_name()[target_well[j]].bottom(dspH))
                
            #check if tip need to be trashed afterwards
            if(i+1 >= skip_rows):
                if(i == len(cmdList)-1):
                    #if this is the last operation
                    right_pipette.drop_tip()
                elif(int(cmdRow[6]) != int(cmdList[i+1][6])):
                    #drop if different tip id is detected
                    right_pipette.drop_tip()
Example #28
0
def run(protocol: protocol_api.ProtocolContext):

    # LABWARE
    fuge_rack = protocol.load_labware('vwr_24_tuberack_1500ul', '1')
    stds_rack = protocol.load_labware('vwr_24_tuberack_1500ul', '2')
    tiprack300 = protocol.load_labware('opentrons_96_filtertiprack_200ul', '8')
    tiprack20 = protocol.load_labware('opentrons_96_filtertiprack_20ul', '9')
    tempdeck = protocol.load_module('tempdeck', '10')
    # plate = tempdeck.load_labware('amplifyt_96_aluminumblock_300ul')
    plate = tempdeck.load_labware('abi_96_wellplate_250ul')
    # PIPETTES
    p300 = protocol.load_instrument('p300_single_gen2',
                                    'left',
                                    tip_racks=[tiprack300])
    p20 = protocol.load_instrument('p20_single_gen2',
                                   'right',
                                   tip_racks=[tiprack20])

    # REAGENTS
    # sds_rack
    sN_mix = stds_rack['D1']  # empty; receives BPW_mix and water for stds
    std_1 = stds_rack['A3']  # 990ul Water
    std_2 = stds_rack['A4']  # 900ul water
    std_3 = stds_rack['A5']  # 900ul water
    std_4 = stds_rack['A6']  # 900ul water
    std_5 = stds_rack['B3']  # 900ul water
    std_6 = stds_rack['B4']  # 900ul water
    std_7 = stds_rack['B5']  # 900ul water
    water = stds_rack['B6']  # 1000ul water
    std_1mix = stds_rack['C3']  # empty
    std_2mix = stds_rack['C4']  # empty
    std_3mix = stds_rack['C5']  # empty
    std_4mix = stds_rack['C6']  # empty
    std_5mix = stds_rack['D3']  # empty
    std_6mix = stds_rack['D4']  # empty
    std_7mix = stds_rack['D5']  # empty
    NTC_mix = stds_rack['D6']  # empty, receives sN_mix and water as NTC

    #fuge_rack
    # bpwd_mix = fuge_rack['A1'] #empty
    # liquid_trash = fuge_rack['B1']
    MIX_bw = fuge_rack['D1']  # see sheet, but gen around 1705 ul; use 2mL tube
    probe_10uM = fuge_rack['C1']
    fwd_10uM = fuge_rack['C2']  # min 300ul
    rev_10uM = fuge_rack['D2']  # min 300ul
    tube_upp = fuge_rack['A4']  # e.g. 0.625uM # empty
    tube_mid = fuge_rack['A5']  # e.g. 1.25uM # empty
    tube_low = fuge_rack['A6']  # e.g. 2.5uM # empty

    probe_mix_1 = fuge_rack['C4']  # e.g. 0.625uM # empty
    probe_mix_2 = fuge_rack['C5']  # e.g. 1.25uM # empty
    probe_mix_3 = fuge_rack['C6']  # e.g. 2.5uM # empty
    probe_mix_4 = fuge_rack['D4']  # e.g. 5.0uM # empty
    probe_mix_5 = fuge_rack['D5']  # e.g. 7.5uM # empty
    probe_mix_6 = fuge_rack['D6']  # e.g. 10uM # empty

    # user inputs
    p300_max_vol = 200
    orig_F_conc = 10  # What is the starting F primer concentration? (in uM)
    orig_R_conc = 10  # What is the starting R primer concentration? (in uM)
    orig_P_conc = 10  # What is probe starting concentration? (in uM)
    P_conc = 300  # What is the probe concentration for the stds?
    P_50 = 50  # Probe at lowest conc.
    P_100 = 100  # Probe at conc
    P_200 = 200  # Probe at conc
    P_400 = 400  # Probe at conc
    P_600 = 600  # Probe at conc
    P_800 = 800  # Probe at highest conc.
    sample_reps = 24  # How many samples at each F, R conc will be run?
    tot_stds = 21  # How many wells of standards will be run? (in # wells)
    tot_NTCs = 3  # How many wells of NTCs will be run? (in # wells)
    tot_samp = 72  # How many samples with varying conditions will be run? (in # wells)
    rxn_base = 8.86  # Everything in PCR buffer (Mg2+, dNTPs, polymerase, enhancers, stabilizers, sugars, etc.). (in uL)
    rxn_base_plus_water = 14.8  # This value set from other experiments.
    tot_rxn_vol = 20  # What is total volume of PCR reaction in plate/tube? (in ul)
    F_upp = 500  # F primer at highest concentration. (in nM)
    F_mid = 400  # What is the constant F primer concentration for standards? This should be guess or from literature or empirically determined. (in nM)
    F_low = 300  # F primer at concentration. (in nM)
    R_upp = 500  # R primer at lowest concentration. (in nM)
    R_mid = 400  # What is the constant R primer concentration for standards? This should be guess or from literature or empirically determined. (in nM)
    R_low = 300  # R primer at concentration. (in nM)
    dna_per_rxn = 2  # How much standard, positive control or NTC to add per well.
    std_NTC_reps = 3  # How many standard and NTC replicates? (int)
    P_reps = 12  # How many wells will use probe primer at particular concentration? (int)
    P_vol_rxn = 1.6  # What vol of probe will be added to reaction? (in ul)
    P_int_vol = 50  # What is the volume of F intermediate primer in new tube? (in ul)
    percent_waste = 0.2  # What percentage waste? (decimal)
    sN_mix_waste_offset = 0.05  # How much percent_waste offset should sN_mix use? This calculated as percent_waste-sN_mix_overage = percent_waste for sN_mix_overage e.g. (20-7=13%) Should not be 0 otherwise offset = percent_waste. (decimal)
    samp_int_mix_waste_offset = 0.05  # How much percent_waste offset should R_mix use? This calculated as percent_waste-R_mix_overage = percent_waste for R_mix_overage e.g. (20-=13%) If 0, then offset = percent_waste. (decimal)
    std_NTC_waste_offset = 0.07  # How much percent_waste offset should std_NTC use? (decimal)
    mix_samp_XFR_well_waste_offset = 0.08  #How much should offset be from each sample tube, upper, mid, low to the plate wells? Will receive P, be mixed and then aliquoted to 3 wells.

    # calcs
    tot_rxns = tot_stds + tot_NTCs + tot_samp  # Calc what is total # rxns. (int)
    tot_stds_NTC = tot_stds + tot_NTCs  # Calc number of standards and nontemplate controls. (int)
    rxn_vol_no_dna = tot_rxn_vol - dna_per_rxn  # Calc what is volume of PCR rxn with no DNA added. (in ul)
    mix_bw_tot = rxn_base_plus_water * tot_rxns * (
        1 + percent_waste)  # Tube with mix containing base and water.
    mix_bw_XFR_mix_sn = rxn_base_plus_water * tot_stds_NTC * (
        1 + percent_waste - sN_mix_waste_offset
    )  # Transfer this amount from MIX_bw to new tube to receive F, R, P for std and NTC prep
    std_vol_F_per_rxn = F_low / 1000 * tot_rxn_vol / orig_F_conc  # Calc adding this much F primer to each std rxn
    std_vol_R_per_rxn = R_low / 1000 * tot_rxn_vol / orig_R_conc  # Calc adding this much R primer to each std rxn
    std_vol_P_per_rxn = P_conc / 1000 * tot_rxn_vol / orig_P_conc  # Calc adding this much Probe to each std rxn
    std_vol_water_per_rxn = 18 - (
        rxn_base_plus_water + std_vol_F_per_rxn + std_vol_R_per_rxn +
        std_vol_P_per_rxn
    )  # How much water is added to the std_ntc reactions. Should be 18ul to receive 2ul DNA. (in ul)
    std_vol_F_mix = std_vol_F_per_rxn * tot_stds_NTC * (
        1 + percent_waste - sN_mix_waste_offset
    )  # How much F primer to add to MIX_sn?
    std_vol_R_mix = std_vol_R_per_rxn * tot_stds_NTC * (
        1 + percent_waste - sN_mix_waste_offset
    )  # How much R primer to add to MIX_sn?
    std_vol_P_mix = std_vol_P_per_rxn * tot_stds_NTC * (
        1 + percent_waste - sN_mix_waste_offset
    )  # How much P primer to add to MIX_sn?
    std_vol_water_mix = std_vol_water_per_rxn * tot_stds_NTC * (
        1 + percent_waste - sN_mix_waste_offset
    )  # How much water  to add to MIX_sn?
    mix_sn_tot = mix_bw_XFR_mix_sn + std_vol_F_mix + std_vol_R_mix + std_vol_P_mix + std_vol_water_mix  # Mix containing base+water+F,R,P. Needs to be aliquoted to std_int tubes
    mix_sn_XFR_to_std_int = std_NTC_reps * rxn_vol_no_dna * (
        1 + percent_waste - std_NTC_waste_offset
    )  # transfer this amount to std_int tubes
    std_dna_XFR_to_std_int = 6.72  #transfer this amount DNA to std_int_tubes to mix and aliquot to 3 wells
    dna_10x_dna_per_rxn = dna_per_rxn / 10  # Dilute DNA vol added by 10x and use std_4 instead of std_5 to save vol for P and F, R at upp
    mix_bw_XFR_samp_int = sample_reps * rxn_base_plus_water * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # transfer this amount from mix_bw to a new sample intermediate tube for diff values of F, R conc. Need to add 2ul from DNA
    dna_XFR_samp_int = sample_reps * dna_10x_dna_per_rxn * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # Add this DNA to a tube and mix before aliquoting to samp_int
    F_mid_rxn = F_mid / 1000 * tot_rxn_vol / orig_F_conc  # How much F @ mid conc to add to rxn.
    F_mid_mix = F_mid_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # How much F to add to mix
    R_mid_rxn = R_mid / 1000 * tot_rxn_vol / orig_R_conc  # How much R @ mid conc to add to rxn.
    R_mid_mix = R_mid_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # How much R to add to mix
    F_low_rxn = F_low / 1000 * tot_rxn_vol / orig_F_conc  # How much F @ low conc to add to rxn.
    F_low_mix = F_low_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # How much F to add to mix
    R_low_rxn = R_low / 1000 * tot_rxn_vol / orig_R_conc  # How much R @ low conc to add to rxn.
    R_low_mix = R_low_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # How much R to add to mix
    F_upp_rxn = F_upp / 1000 * tot_rxn_vol / orig_F_conc  # How much F @ upp conc to add to rxn.
    F_upp_mix = F_upp_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # How much F to add to mix
    R_upp_rxn = R_upp / 1000 * tot_rxn_vol / orig_R_conc  # How much R @ upp conc to add to rxn.
    R_upp_mix = R_upp_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # How much R to add to mix
    water_upp_rxn = tot_rxn_vol - (rxn_base_plus_water + dna_10x_dna_per_rxn +
                                   F_upp_rxn + R_upp_rxn + P_vol_rxn
                                   )  # How much water to add to upp rxn
    water_upp_mix = water_upp_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # How much water to add to upp mix
    water_low_rxn = 20 - (dna_10x_dna_per_rxn + F_low_rxn + R_low_rxn +
                          rxn_base_plus_water + P_vol_rxn
                          )  # How much water to add to low rxn
    water_low_mix = water_low_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # How much water to add to low mix
    water_mid_rxn = 20 - (rxn_base_plus_water + P_vol_rxn +
                          dna_10x_dna_per_rxn + F_mid_rxn + R_mid_rxn
                          )  # how much water to add to mid rxn
    water_mid_mix = water_mid_rxn * sample_reps * (
        1 + percent_waste - samp_int_mix_waste_offset
    )  # how much water to add to mid mix
    MIX_upp_samp_int = mix_bw_XFR_samp_int + dna_XFR_samp_int + F_upp_mix + R_upp_mix + water_upp_mix  # How much in upper intermediate sample tube?
    MIX_mid_samp_int = mix_bw_XFR_samp_int + dna_XFR_samp_int + F_mid_mix + R_mid_mix + water_mid_mix  # How much in middle intermediate sample tube?
    MIX_low_samp_int = mix_bw_XFR_samp_int + dna_XFR_samp_int + F_low_mix + R_low_mix + water_low_mix  # How much in lower intermediate sample tube?
    mix_samp_XFR_to_well = 82.432  # How much to add to plate wells
    P_50_int_conc = P_50 / 1000 * tot_rxn_vol / P_vol_rxn  # What intermediate (int) concentration of  Probe is needed such that by adding 1.6ul I obtain a final concentration of 50, 100, 200…etc in rxn well? (in uM)
    P_100_int_conc = P_100 / 1000 * tot_rxn_vol / P_vol_rxn  # What intermediate (int) concentration of  Probe is needed such that by adding 1.6ul I obtain a final concentration of 50, 100, 200…etc in rxn well? (in uM)
    P_200_int_conc = P_200 / 1000 * tot_rxn_vol / P_vol_rxn  # What intermediate (int) concentration of  Probe is needed such that by adding 1.6ul I obtain a final concentration of 50, 100, 200…etc in rxn well? (in uM)
    P_400_int_conc = P_400 / 1000 * tot_rxn_vol / P_vol_rxn  # What intermediate (int) concentration of  Probe is needed such that by adding 1.6ul I obtain a final concentration of 50, 100, 200…etc in rxn well? (in uM)
    P_600_int_conc = P_600 / 1000 * tot_rxn_vol / P_vol_rxn  # What intermediate (int) concentration of  Probe is needed such that by adding 1.6ul I obtain a final concentration of 50, 100, 200…etc in rxn well? (in uM)
    P_800_int_conc = P_800 / 1000 * tot_rxn_vol / P_vol_rxn  # What intermediate (int) concentration of  Probe is needed such that by adding 1.6ul I obtain a final concentration of 50, 100, 200…etc in rxn well? (in uM)
    P_50_int_primer = P_50_int_conc * P_int_vol / orig_P_conc  # What amount of primer should be added to generate int F primer conc? (in ul)
    P_100_int_primer = P_100_int_conc * P_int_vol / orig_P_conc  # What amount of water should be added to generate int F primer conc? (in ul)
    P_200_int_primer = P_200_int_conc * P_int_vol / orig_P_conc  # What amount of primer should be added to generate int F primer conc? (in ul)
    P_400_int_primer = P_400_int_conc * P_int_vol / orig_P_conc  # What amount of water should be added to generate int F primer conc? (in ul)
    P_600_int_primer = P_600_int_conc * P_int_vol / orig_P_conc  # What amount of primer should be added to generate int F primer conc? (in ul)
    P_800_int_primer = P_800_int_conc * P_int_vol / orig_P_conc  # What amount of water should be added to generate int F primer conc? (in ul)
    P_50_int_water = P_int_vol - P_50_int_primer  # What amount of primer should be added to generate int F primer conc? (in ul)
    P_100_int_water = P_int_vol - P_100_int_primer  # What amount of water should be added to generate int F primer conc? (in ul)
    P_200_int_water = P_int_vol - P_200_int_primer  # What amount of primer should be added to generate int F primer conc? (in ul)
    P_400_int_water = P_int_vol - P_400_int_primer  # What amount of water should be added to generate int F primer conc? (in ul)
    P_600_int_water = P_int_vol - P_600_int_primer  # What amount of primer should be added to generate int F primer conc? (in ul)
    P_800_int_water = P_int_vol - P_800_int_primer  # What amount of water should be added to generate int F primer conc? (in ul)
    p_int_XFR_to_well = P_vol_rxn * 4 * (
        1 + percent_waste - mix_samp_XFR_well_waste_offset
    )  # What amount of probe intermediate to add to bolus in wells?

    #checks
    print("mix_bw_tot", mix_bw_tot)
    print("mix_bw_tot", mix_sn_tot)
    print("mix_bw_XFR_samp_int", mix_bw_XFR_samp_int)
    print("MIX_upp_samp_int", MIX_upp_samp_int)
    print("MIX_low_samp_int", MIX_low_samp_int)
    print("MIX_low_samp_int", MIX_low_samp_int)
    # lists
    # plate_col = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
    F_samp_vols = [F_upp_mix, F_mid_mix, F_low_mix]
    R_samp_vols = [R_upp_mix, R_mid_mix, R_low_mix]
    plate_rows = ['A', 'B', 'C', 'D', 'E', 'F']
    # all_fwd = [fwd_1, fwd_2, fwd_3, fwd_4, fwd_5, fwd_6]
    std_tubes = [std_1, std_2, std_3, std_4, std_5, std_6, std_7, water]
    std_mixes = [
        std_1mix, std_2mix, std_3mix, std_4mix, std_5mix, std_6mix, std_7mix,
        NTC_mix
    ]
    std_wells = ['G1', 'G4', 'G7', 'G10', 'H1', 'H4', 'H7', 'H10']
    samp_tubes = [tube_upp, tube_mid, tube_low]
    probe_mixes = [
        probe_mix_1, probe_mix_2, probe_mix_3, probe_mix_4, probe_mix_5,
        probe_mix_6
    ]
    P_in_mix = [
        P_50_int_primer, P_100_int_primer, P_200_int_primer, P_400_int_primer,
        P_600_int_primer, P_800_int_primer
    ]
    W_in_mix = [
        P_50_int_water, P_100_int_water, P_200_int_water, P_400_int_water,
        P_600_int_water, P_800_int_water
    ]

    # #### COMMANDS ######

    # prepare sN_mix
    # add MIX_bw to sN_mix tube
    p300.pick_up_tip()
    p300.flow_rate.aspirate = 92.86  #default
    p300.flow_rate.dispense = 92.86  #default
    MIX_bw_heights = tip_heights(
        mix_bw_tot, len(split_asp(mix_bw_XFR_mix_sn, p300_max_vol)),
        split_asp(mix_bw_XFR_mix_sn, p300_max_vol)[0])
    p300.mix(3, 200, MIX_bw.bottom(MIX_bw_heights[0]))
    p300.flow_rate.aspirate = 40  #default
    p300.flow_rate.dispense = 40  #default
    for j in range(len(split_asp(mix_bw_XFR_mix_sn, p300_max_vol))):
        amt = split_asp(mix_bw_XFR_mix_sn, p300_max_vol)[j]
        p300.aspirate(amt, MIX_bw.bottom(MIX_bw_heights[j]))
        h = tip_heights(amt + amt * j, 1, 0)[0]
        p300.dispense(amt, sN_mix.bottom(h + 5))
        p300.blow_out(sN_mix.bottom(20))  # want to be above liquid level
        p300.touch_tip()
    p300.drop_tip()
    # transfer water to sN__mix
    p300.flow_rate.aspirate = 40  #default
    p300.flow_rate.dispense = 40  #default
    p300.transfer(
        std_vol_water_mix,  # 27.6ul
        water.bottom(20),
        sN_mix.bottom(tip_heights(mix_bw_XFR_mix_sn, 1, 0)[0]),
        blow_out=True,
        blowout_location='destination well')
    # transfer F primer @ std conditions to sN__mix
    p300.transfer(
        std_vol_F_mix,  #22.08ul
        fwd_10uM.bottom(3),
        sN_mix.bottom(tip_heights(mix_bw_XFR_mix_sn, 1, 0)[0]),
        blow_out=True,
        mix_after=(2, 30),
        blowout_location='destination well')
    # transfer R primer @ std conditions to sN__mix
    p300.transfer(  #some resid fluid on outside
        std_vol_R_mix,  #22.08ul
        rev_10uM.bottom(3),
        sN_mix.bottom(tip_heights(mix_bw_XFR_mix_sn, 1, 0)[0]),
        blow_out=True,
        mix_after=(2, 30),
        blowout_location='destination well')
    # transfer Probe @ std conditions to sN__mix
    p20.flow_rate.aspirate = 4
    p20.flow_rate.dispense = 4
    p20.transfer(  #some resid fluid on outside
        std_vol_P_mix,  #16.56ul
        probe_10uM.bottom(3),
        sN_mix.bottom(tip_heights(mix_bw_XFR_mix_sn, 1, 0)[0]),
        blow_out=True,
        mix_after=(2, 20),
        blowout_location='destination well')
    p300.flow_rate.aspirate = 92.86  #default
    p300.flow_rate.dispense = 92.86  #default
    p20.flow_rate.aspirate = 7.56
    p20.flow_rate.dispense = 7.56

    # transfer sN_mix to intermediate tubes (std_mixes)
    std_mix_heights = tip_heights(mix_sn_tot, len(std_mixes),
                                  mix_sn_XFR_to_std_int)  #[13,11,8,6,4,2,0]
    print(mix_bw_XFR_mix_sn)
    print(mix_sn_tot)
    print(std_mix_heights)
    p300.pick_up_tip()
    p300.mix(7, 200, sN_mix.bottom(std_mix_heights[0]))  #10mm from bottom
    p300.flow_rate.aspirate = 30
    p300.flow_rate.dispense = 40
    # p300.well_bottom_clearance.aspirate = std_mix_heights[0] #mm
    for tube, h in zip(std_mixes, std_mix_heights):
        # p300.well_bottom_clearance.aspirate = h #mm
        p300.aspirate(mix_sn_XFR_to_std_int,
                      sN_mix.bottom(h))  # 18 * 3 * 1.12-0.05= 54 + 6 =60ul
        protocol.delay(seconds=2)  #tip equilibrate
        p300.move_to(sN_mix.bottom(35))  # excess tip fluid condense
        protocol.delay(seconds=3)  #tip droplets slide
        p300.touch_tip()
        p300.dispense(mix_sn_XFR_to_std_int, tube)
    p300.drop_tip()
    p300.flow_rate.aspirate = 92.86  #reset to default
    p300.flow_rate.dispense = 92.86  #reset to default
    p300.well_bottom_clearance.aspirate = 10  #mm default

    # transfer std DNA into intermediate std_mixes tubes and then to plate
    for std, intTube, well in zip(std_tubes, std_mixes, std_wells):
        p20.pick_up_tip()
        p300.pick_up_tip()
        p20.flow_rate.aspirate = 4
        p20.flow_rate.dispense = 4
        p20.aspirate(
            std_dna_XFR_to_std_int, std
        )  #aspirate from std_1 into std_mix (intermediate tube) e.g. 6.42 ul
        protocol.delay(seconds=3)  #equilibrate
        p20.touch_tip()
        p20.dispense(std_dna_XFR_to_std_int, intTube)
        # p20.move_to(intTube.bottom(3))
        p20.flow_rate.aspirate = 7.56
        p20.flow_rate.dispense = 7.56
        p20.mix(2, 20,
                intTube.bottom(3))  #ensure vol in tip in intTube and washed
        p20.blow_out()
        p300.move_to(
            intTube.bottom(40))  #prevent tip from crashing into tube cap
        p300.mix(7, 50, intTube.bottom(1))
        protocol.delay(seconds=2)
        # p300.move_to(intTube.bottom(10)) #prevent air bubbles in mmix during blow out
        p300.blow_out(intTube.bottom(10))
        p20.move_to(intTube.bottom(40))
        p20.flow_rate.aspirate = 4
        p20.flow_rate.dispense = 4
        for x in range(0, 3):  # need int 1, 2, and 3
            p20.aspirate(20, intTube)
            protocol.delay(seconds=2)  #equilibrate
            # find digits in well, G1 and G10 and puts into list
            findNums = [int(i) for i in well.split()[0] if i.isdigit()]
            # joins nums from list [1, 0] -> 10 type = string
            colNum = ''.join(map(str, findNums))
            # this finds row
            row = well.split()[0][0]
            dest = row + str(int(colNum) + x)  # row + neighbor well i.e. 1, 2
            p20.dispense(20, plate[dest].bottom(1))
            p20.move_to(plate[dest].bottom(4))
            p20.blow_out()
            # p20.touch_tip()
        p300.drop_tip()
        p20.drop_tip()
    p20.flow_rate.aspirate = 7.56
    p20.flow_rate.dispense = 7.56
    p20.well_bottom_clearance.dispense = 1
    p20.well_bottom_clearance.aspirate = 1

    # create samp_int tube mixes. sample intermediate
    # first add DNA from a std tube e.g. std_4
    p20.transfer(
        dna_XFR_samp_int * 3,  # ~3 sets of 24 samples
        std_6.bottom(2),
        MIX_bw.bottom(tip_heights(mix_bw_tot - mix_bw_XFR_mix_sn, 1, 0)[0]),
        mix_after=(2, dna_XFR_samp_int * 3),
        blow_out=True,
        blowout_location='destination well')
    # Three tubes created for upper, mid, lower F,R concentrations
    p300.pick_up_tip()
    p300.flow_rate.aspirate = 92.86  #default
    p300.flow_rate.dispense = 92.86  #default
    h = tip_heights(mix_bw_tot - mix_bw_XFR_mix_sn,
                    len(split_asp(mix_bw_XFR_samp_int, p300_max_vol)),
                    408.48)  #split_asp(mix_bw_XFR_samp_int, p300_max_vol)[0])
    p300.mix(2, 200,
             MIX_bw.bottom(4))  # low; need to thoroughly mix DNA in tube
    p300.mix(2, 200,
             MIX_bw.bottom(8))  # mid; need to thoroughly mix DNA in tube
    p300.mix(7, 200,
             MIX_bw.bottom(h[0] - 10))  # need to thoroughly mix DNA in tube
    for tube in samp_tubes:
        for j in range(
                len(split_asp(mix_bw_XFR_samp_int, p300_max_vol))
        ):  # split_asp is a function that returns equally divided aspirations
            p300.flow_rate.aspirate = 40  #default
            p300.flow_rate.dispense = 40  #default
            amt = split_asp(mix_bw_XFR_samp_int, p300_max_vol)[j]
            p300.aspirate(amt, MIX_bw.bottom(2))
            protocol.delay(seconds=2)
            # h = tip_heights(amt+amt*j, 1, 0)[0] # adjust tip height depending on dispenses
            p300.dispense(
                amt,
                tube.bottom(3))  # want tip to be just a little above dispense
            p300.blow_out(tube.bottom(20))  # want to be above liquid level
            p300.touch_tip()
    p300.drop_tip()
    p300.well_bottom_clearance.aspirate = 1  #mm
    p300.well_bottom_clearance.dispense = 1  #mm
    # add F, R primers into samp_int tubes first with upper, mid (p300 vols), then with low (p20 vols)
    for i in range(2):
        h = tip_heights(mix_bw_XFR_samp_int, 1, 0)[0]
        p300.transfer(F_samp_vols[i],
                      fwd_10uM.bottom(2),
                      samp_tubes[i].bottom(h),
                      mix_after=(2, 100),
                      new_tip='always')
        p300.transfer(R_samp_vols[i],
                      rev_10uM.bottom(2),
                      samp_tubes[i].bottom(h),
                      mix_after=(2, R_samp_vols[i]),
                      new_tip='always')
    # F primer to low tube
    p20.transfer(F_samp_vols[-1],
                 fwd_10uM.bottom(2),
                 samp_tubes[-1].bottom(
                     tip_heights(mix_bw_XFR_samp_int, 1, 0)[0]),
                 mix_after=(2, F_samp_vols[-1]),
                 new_tip='always')
    # R primer to low tube
    p20.transfer(R_samp_vols[-1],
                 rev_10uM.bottom(2),
                 samp_tubes[-1].bottom(
                     tip_heights(mix_bw_XFR_samp_int, 1, 0)[0]),
                 mix_after=(2, R_samp_vols[-1]),
                 new_tip='always')

    # add water, dispense samp_int tubes containing F,R, water into plate
    # into each 1st well in plate, A1, B1..F1 then tube_mid into A5, B5..F5
    for i, (tube, wvol) in enumerate(
            zip(samp_tubes, list([water_upp_mix, water_mid_mix,
                                  water_low_mix]))):
        p300.pick_up_tip()
        p300.flow_rate.aspirate = 40  #default
        p300.flow_rate.dispense = 40  #default
        p300.aspirate(wvol, water.bottom(10))
        p300.dispense(wvol, tube.bottom(10))
        p300.flow_rate.aspirate = 92.86  #default
        p300.flow_rate.dispense = 92.86  #default
        p300.mix(2, 200, tube.bottom(4))
        p300.mix(2, 200, tube.bottom(10))
        p300.mix(5, 200, tube.bottom(16.5))
        for j, row in enumerate(plate_rows):
            h = tip_heights(MIX_upp_samp_int, len(plate_rows),
                            mix_samp_XFR_to_well
                            )  #3rd asp is low correct with small tot vol
            print(h)
            dest = row + str(4 * i + 1)
            p300.flow_rate.aspirate = 30
            p300.flow_rate.dispense = 40
            p300.aspirate(mix_samp_XFR_to_well, tube.bottom(h[j]))
            protocol.delay(seconds=2)  # tip equilibrate
            p300.move_to(tube.bottom(25))
            protocol.delay(seconds=2)
            p300.touch_tip(
            )  # bpwd_rxn*row reps (12) * waste = 16.8*12*(1+.12-0.05)=
            p300.dispense(mix_samp_XFR_to_well, plate[dest].bottom(1))
            protocol.delay(seconds=1)
            p300.blow_out(plate[dest].bottom(10))
            p300.touch_tip()
        p300.drop_tip()

    # now that PCR reactions are chillin' at 4C, make Probe int tubes
    # Mix 50ul 'probe' dilutions in tube by adding water and "probe" tube as shown in schedule
    # add water
    p300.pick_up_tip()
    p20.flow_rate.aspirate = 7.56  #default
    p20.flow_rate.dispense = 7.56  #default
    p300.flow_rate.aspirate = 92.86  #default
    p300.flow_rate.dispense = 92.86  #default
    for tube, wvol in zip(probe_mixes[0:4],
                          W_in_mix[0:4]):  # first 4 have vol > 20
        p300.transfer(
            wvol,
            water.bottom(15),
            tube.bottom(1),
            blow_out=False,  #this was creating air bubbles
            blowout_location='destination well',
            new_tip='never')
    p300.drop_tip()
    p20.pick_up_tip()
    # for tube, wvol in probe_mixes[4], W_in_mix[4]:  # 5th item has vol < 20ul
    p20.transfer(W_in_mix[4],
                 water.bottom(15),
                 probe_mixes[4].bottom(1),
                 blow_out=False,
                 blowout_location='destination well',
                 new_tip='never')
    p20.drop_tip()
    # add primer
    p20.pick_up_tip()
    for tube, pvol in zip(probe_mixes[0:3],
                          P_in_mix[0:3]):  # first three tubes have vol < 20ul
        p20.transfer(pvol,
                     probe_10uM.bottom(2),
                     tube.bottom(3),
                     blow_out=False,
                     mix_after=(2, pvol),
                     blowout_location='destination well',
                     new_tip='never')
    p20.drop_tip()
    # next tubes have vol > 20
    p300.pick_up_tip()
    p300.flow_rate.aspirate = 20  #default
    p300.flow_rate.dispense = 40  #default
    for tube, pvol in zip(probe_mixes[3:], P_in_mix[3:]):  # item 3 till end
        p300.transfer(pvol,
                      probe_10uM.bottom(2),
                      tube.bottom(2),
                      blow_out=False,
                      blowout_location='destination well',
                      new_tip='never')
    p300.drop_tip()
    p300.flow_rate.aspirate = 92.86  #default
    p300.flow_rate.dispense = 92.86  #default
    p20.flow_rate.aspirate = 7.56
    p20.flow_rate.dispense = 7.56

    # Add probe to wells. mix, aliquot to adjacent wells
    P_mix_h = tip_heights(P_int_vol, 1, 0)
    for i, (tube, row) in enumerate(zip(probe_mixes, plate_rows)):
        p300.pick_up_tip()
        p20.pick_up_tip()
        p300.flow_rate.aspirate = 60
        p300.flow_rate.dispense = 60  #don't want air bubbles
        p300.mix(3, 30,
                 tube.bottom(P_mix_h[0]))  # mix F primer tube, 100ul in tube
        p300.blow_out(tube.bottom(P_mix_h[0] + 6))
        p300.touch_tip()
        for j in range(2):  # asp and disp into wells
            p20.flow_rate.aspirate = 4
            p20.flow_rate.dispense = 4
            p20.move_to(tube.bottom(40))
            if j == 0:  # wells A1 and A5
                dest = row + str(j + 1)
                nextWell = row + str(j + 5)
                p20.aspirate(
                    p_int_XFR_to_well * 2,
                    tube.bottom(2))  # ~7ul aspirate from P int tube to wells
                protocol.delay(seconds=2)
                p20.move_to(
                    tube.bottom(2))  # relieve pressure if tip against tube
                p20.dispense(p_int_XFR_to_well, plate[dest].bottom(2))
                protocol.delay(seconds=1)
                p20.touch_tip()
                p20.dispense(p_int_XFR_to_well, plate[nextWell].bottom(2))
                protocol.delay(seconds=2)
                p20.blow_out(plate[nextWell].bottom(6))
                p20.touch_tip()
                p20.drop_tip()
                p20.pick_up_tip()
            else:  # well A9
                dest = row + str(j + 8)
                p20.aspirate(
                    p_int_XFR_to_well,
                    tube.bottom(1))  # ~7ul aspirate from P int tube to wells
                p20.move_to(
                    tube.bottom(2))  # relieve pressure if tip against tube
                protocol.delay(seconds=2)
                p20.touch_tip()
                p20.dispense(p_int_XFR_to_well, plate[dest])
                protocol.delay(seconds=2)
                p20.blow_out(plate[dest].bottom(6))
                p20.touch_tip()
        for k in range(
                3):  # need int 0, 1, 2. Looping through bolus in row (3)
            swell = row + str(4 * k + 1)  #source well: A1, A5, A9
            p300.move_to(plate[swell].bottom(30))
            p300.flow_rate.aspirate = 92.86
            p300.flow_rate.dispense = 92.86
            p300.mix(3, 70, plate[swell].bottom(2), rate=0.75)
            p300.flow_rate.aspirate = 40
            p300.flow_rate.dispense = 20
            p300.mix(1, 70, plate[swell].bottom(2),
                     rate=0.6)  #slow mix to avoid/remove bubbles
            p300.blow_out(plate[swell].bottom(10))
            for m in range(1, 4):  # want int 1, 2, 3
                p20.flow_rate.aspirate = 7.56
                p20.flow_rate.dispense = 7.56
                dwell = row + str(
                    4 * k + 1 + m)  # loop through dispensing wells
                p20.move_to(plate[swell].bottom(40))
                p20.aspirate(20, plate[swell].bottom(1))
                protocol.delay(seconds=1)
                p20.dispense(20, plate[dwell].bottom(2))
                protocol.delay(seconds=1)
                p20.move_to(plate[dwell].bottom(6))
                p20.blow_out()
                p20.touch_tip()
        p20.drop_tip()
        p300.drop_tip()
Example #29
0
def run(protocol: protocol_api.ProtocolContext):
    """
    Aliquoting Illumina primers from 1 tube rack filled with 1.5 mL tubes,
    to 3 PCR strips in a BioRad 96-well plate, calibrated with Westburg
    PCR strips.
    """
    # =============================================================================

    # =====================LOADING LABWARE AND PIPETTES============================
    # =============================================================================
    ## For available labware see "labware/list_of_available_labware".       ##
    tips_200_1 = protocol.load_labware(
        'opentrons_96_filtertiprack_200ul',  #labware definition
        10,  #deck position
        '200tips')  #custom name
    tips_200_2 = protocol.load_labware(
        'opentrons_96_filtertiprack_200ul',  #labware definition
        7,  #deck position
        '200tips')  #custom name
    tips_200_3 = protocol.load_labware(
        'opentrons_96_filtertiprack_200ul',  #labware definition
        4,  #deck position
        '200tips')  #custom name
    tips_200_4 = protocol.load_labware(
        'opentrons_96_filtertiprack_200ul',  #labware definition
        1,  #deck position
        '200tips')  #custom name
    primer_tubes = protocol.load_labware(
        'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap',  #labware def
        3,  #deck position
        'primer_tubes')  #custom name
    ##### !!! OPTION 1: ROBOT
    pcr_strips = protocol.load_labware(
        'pcrstrips_96_wellplate_200ul',  #labware definition
        6,  #deck position
        'pcr_strips')  #custom name
    ##### !!! OPTION 2: SIMULATOR
    # with open("labware/pcrstrips_96_wellplate_200ul/"
    #           "pcrstrips_96_wellplate_200ul.json") as labware_file:
    #         labware_def_pcrstrips = json.load(labware_file)
    # pcr_strips = protocol.load_labware_from_definition(
    #         labware_def_pcrstrips, #variable derived from opening json
    #         6,
    #         'pcr_strips')
    #Load the labware using load_labware_from_definition() instead of  ##
    #load_labware(). Then use the variable you just set with the opened##
    #json file to define which labware to use.                         ##

    p300 = protocol.load_instrument(
        'p300_single_gen2',  #instrument definition
        'right',  #mount position
        tip_racks=[tips_200_1, tips_200_2, tips_200_3,
                   tips_200_4])  #as tiprack
    # =============================================================================

    # ===========================VARIABLES TO SET#!!!==============================
    # =============================================================================
    p300.starting_tip = tips_200_1.well('G5')
    primer_volume = 30
    # =============================================================================

    # ============================ALIQUOTING PRIMERS===============================
    # =============================================================================
    protocol.set_rail_lights(True)

    protocol.pause('Put F primers F1 to F47 in slot 3, and '
                   'three empty PCR strips in columns 2, 7, and 11 with the '
                   'caps to the right in slot 6.')
    # =============================================================================
    # F1 to F47 + corresponding R primers==========================================
    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        ## simultanious loop through primer_tubes and PCR_strips           ##
        ## From wells to columns doesn't work, therefore all PCRstrip      ##
        ## wells are given.                                                ##
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        ## air_gap to suck up any liquid that remains in the tip           ##
        p300.drop_tip()
    ## Used aspirate/dipense instead of transfer, to allow for more        ##
    ## customization.  ##
    protocol.pause('Remove F primers and put corresponding'
                   ' R primers on slot 3.')

    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause('Remove R primers and PCR strips, '
                   'put new strips in columns 2, 7 and 11 with caps to '
                   'the right and put F49 to F95 on slot 3. Empty waste bin!!')
    # =============================================================================
    # F49 to F95 + corresponding R primers=========================================
    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause('Remove F primers and put corresponding'
                   ' R primers on slot 3.')

    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause(
        'Remove R primers and PCR strips, '
        'put new strips in columns 2, 7 and 11 with caps to '
        'the right and put F97 to F143 on slot 3. Empty waste bin!!')
    # =============================================================================
    # F97 to F143 + corresponding R primers========================================
    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause('Remove F primers and put corresponding'
                   ' R primers on slot 3.')

    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause(
        'Remove R primers and PCR strips, '
        'put new strips in columns 2, 7 and 11 with caps to '
        'the right and put F145 to F191 on slot 3. Empty waste bin!!')
    # =============================================================================
    # F145 to F191 + corresponding R primers=======================================
    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause('Remove F primers and put corresponding'
                   ' R primers on slot 3.')

    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause(
        'Remove R primers and PCR strips, '
        'put new strips in columns 2, 7 and 11 with caps to '
        'the right and put F193 to F239 on slot 3. Empty waste bin!!')
    # =============================================================================
    # F193 to F239 + corresponding R primers========================================
    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause('Remove F primers and put corresponding'
                   ' R primers on slot 3.')

    for primer_tube, pcr_strip_tube in zip(primer_tubes.wells(), [
            pcr_strips.wells_by_name()[well_name] for well_name in [
                'A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'A7', 'B7',
                'C7', 'D7', 'E7', 'F7', 'G7', 'H7', 'A11', 'B11', 'C11', 'D11',
                'E11', 'F11', 'G11', 'H11'
            ]
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    protocol.pause(
        'Remove R primers and PCR strips, '
        'put new strips in column 7 with caps to '
        'the right and put F241 to F255 in columns 1+2'
        ' + corresponding R primers in columns 5+6 on slot 3. Empty waste bin!!'
    )
    # =============================================================================
    # F241 to F255 + corresponding R primers=============================================================================
    #Forward:
    for primer_tube, pcr_strip_tube in zip([
            primer_tubes.wells_by_name()[well_name]
            for well_name in ['A1', 'B1', 'C1', 'D1', 'A2', 'B2', 'C2', 'D2']
    ], [
            pcr_strips.wells_by_name()[well_name]
            for well_name in ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7']
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
    #Reverse:
    for primer_tube, pcr_strip_tube in zip([
            primer_tubes.wells_by_name()[well_name]
            for well_name in ['A5', 'B5', 'C5', 'D5', 'A6', 'B6', 'C6', 'D6']
    ], [
            pcr_strips.wells_by_name()[well_name]
            for well_name in ['A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7']
    ]):
        p300.pick_up_tip()
        p300.aspirate(primer_volume, primer_tube)
        p300.air_gap(10)
        p300.dispense(primer_volume + 50, pcr_strip_tube)
        p300.air_gap()
        p300.drop_tip()
# =============================================================================
    protocol.set_rail_lights(False)
Example #30
0
def run(ctx: protocol_api.ProtocolContext):
    # ------------------------
    # Load LabWare
    # ------------------------
    # Tip racks
    tips = [
        ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot,
                         '1000µl filter tiprack') for slot in ['10', '11']
    ]

    # Pipette
    p20 = ctx.load_instrument('p20_single_gen2', 'right', tip_racks=tips)

    # Source Samples
    rack_num = math.ceil(
        num_samples / NUM_OF_SOURCES_PER_RACK
    ) if num_samples < MAX_NUM_OF_SOURCES else MIN_NUM_OF_SOURCES
    source_racks = [
        ctx.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', slot,
                         'source tuberack with screwcap' + str(i + 1))
        for i, slot in enumerate(['1', '2', '4', '5'][:rack_num])
    ]
    sample_sources_full = common.generate_source_table(source_racks)
    sample_sources = sample_sources_full[:num_samples]

    # Source TRIS
    tris = ctx.load_labware('opentrons_6_tuberack_falcon_50ml_conical', '6',
                            'Buffer tuberack in Falcon tube')
    tris_phalcon = tris.wells()[0]

    # Destination (in this case 96 well plate)
    dest_plate = ctx.load_labware('abi_fast_qpcr_96_alum_opentrons_100ul', '3',
                                  'PCR final plate')
    destinations = dest_plate.wells()[:num_destinations]

    # ------------------
    # Protocol
    # ------------------
    if not p20.hw_pipette['has_tip']:
        common.pick_up(p20)

    # Dispense 4mM of sample in PCR plate
    for s, d in zip(sample_sources, destinations):
        if not p20.hw_pipette['has_tip']:
            common.pick_up(p20)

        sample_volume_to_be_transferred = vi.pop()

        # Calculate pickup_height based on remaining volume and shape of container
        common.move_vol_multichannel(ctx,
                                     p20,
                                     reagent=sample,
                                     source=s,
                                     dest=d,
                                     vol=sample_volume_to_be_transferred,
                                     air_gap_vol=air_gap_vol_sample,
                                     pickup_height=pickup_height,
                                     disp_height=dispense_height,
                                     x_offset=x_offset,
                                     blow_out=True,
                                     touch_tip=True)
        # Drop pipette tip
        p20.drop_tip()

    # Dispense rest volume of TRIS in each sample of PCR
    for d in destinations:

        if not p20.hw_pipette['has_tip']:
            common.pick_up(p20)

        tris_volume_to_be_transferred = vt.pop()
        common.move_vol_multichannel(ctx,
                                     p20,
                                     reagent=sample,
                                     source=tris_phalcon,
                                     dest=d,
                                     vol=tris_volume_to_be_transferred,
                                     air_gap_vol=air_gap_vol_sample,
                                     pickup_height=pickup_height,
                                     disp_height=dispense_height,
                                     x_offset=x_offset,
                                     blow_out=True,
                                     touch_tip=True)
        # Drop pipette tip
        p20.drop_tip()