Example #1
0
def flashing_rail_lights(protocol: protocol_api.ProtocolContext,
                         seconds_per_flash_cycle=1.0):
    """Flash the rail lights on and off in the background.

    Source: https://github.com/Opentrons/opentrons/issues/7742

    Example usage:

        # While the robot is doing nothing for 2 minutes, flash lights quickly.
        with flashing_rail_lights(protocol, seconds_per_flash_cycle=0.25):
            protocol.delay(minutes=2)

    When the ``with`` block exits, the rail lights are restored to their
    original state.

    Exclusive control of the rail lights is assumed. For example, within the
    ``with`` block, you must not call `ProtocolContext.set_rail_lights`
    yourself, inspect `ProtocolContext.rail_lights_on`, or nest additional
    calls to `flashing_rail_lights`.
    """
    original_light_status = protocol.rail_lights_on

    stop_flashing_event = threading.Event()

    def background_loop():
        while True:
            protocol.set_rail_lights(not protocol.rail_lights_on)
            # Wait until it's time to toggle the lights for the next flash or
            # we're told to stop flashing entirely, whichever comes first.
            got_stop_flashing_event = stop_flashing_event.wait(
                timeout=seconds_per_flash_cycle / 2)
            if got_stop_flashing_event:
                break

    background_thread = threading.Thread(
        target=background_loop,
        name="Background thread for flashing rail \
lights")

    try:
        if not protocol.is_simulating():
            background_thread.start()
        yield

    finally:
        # The ``with`` block might be exiting normally, or it might be exiting
        # because something inside it raised an exception.
        #
        # This accounts for user-issued cancelations because currently
        # (2021-05-04), the Python Protocol API happens to implement user-
        # issued cancellations by raising an exception from internal API code.
        if not protocol.is_simulating():
            stop_flashing_event.set()
            background_thread.join()

        # This is questionable: it may issue a command to the API while the API
        # is in an inconsistent state after raising an exception.
        protocol.set_rail_lights(original_light_status)
def run(ctx: protocol_api.ProtocolContext):
    global robot
    robot = ctx
    # confirm door is close
    if not ctx.is_simulating():
        confirm_door_is_closed()

    # define tips
    tips1000 = [
        ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot)
        for slot in ['3', '6']
    ]
    tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', '9')]

    # define pipettes
    p1000 = ctx.load_instrument('p1000_single_gen2',
                                'left',
                                tip_racks=tips1000)
    p300 = ctx.load_instrument('p300_single_gen2', 'right', tip_racks=tips300)

    # check buffer labware type
    if BUFFER_LABWARE not in BUFFER_LW_DICT:
        raise Exception('Invalid BF_LABWARE. Must be one of the \
following:\nopentrons plastic 50ml tubes')

    # load mastermix labware
    buffer_rack = ctx.load_labware(BUFFER_LW_DICT[BUFFER_LABWARE], '10',
                                   BUFFER_LABWARE)

    # check mastermix tube labware type
    if DESTINATION_LABWARE not in DESTINATION_LW_DICT:
        raise Exception('Invalid DESTINATION_LABWARE. Must be one of the \
    following:\nopentrons plastic 2ml tubes')

    # load elution labware
    dest_racks = [
        ctx.load_labware(DESTINATION_LW_DICT[DESTINATION_LABWARE], slot,
                         'Destination tubes labware ' + str(i + 1))
        for i, slot in enumerate(['4', '1', '5', '2'])
    ]

    # setup sample sources and destinations
    bf_tubes = buffer_rack.wells()[:4]
    number_racks = math.ceil(NUM_SAMPLES / len(dest_racks[0].wells()))

    # dest_sets is a list of lists. Each list is the destination well for each rack
    # example: [[tube1,tube2,...tube24](first rack),[tube1,tube2(second rack),...]
    dest_sets = [[tube for rack in dest_racks for tube in rack.wells()
                  ][:NUM_SAMPLES][i * len(dest_racks[0].wells()):(i + 1) *
                                  len(dest_racks[0].wells())]
                 for i in range(number_racks)]

    # transfer buffer to tubes
    for bf_tube, dests in zip(bf_tubes, dest_sets):
        transfer_buffer(bf_tube, dests, VOLUME_BUFFER, p1000, tips1000)

    # track final used tip
    save_tip_info(p1000)

    finish_run()
def run(ctx: protocol_api.ProtocolContext):
    global robot
    robot = ctx
    # confirm door is closed
    if not ctx.is_simulating():
        confirm_door_is_closed(ctx)

    tips1000 = [
        ctx.load_labware('opentrons_96_filtertiprack_1000ul', 3,
                         '1000µl tiprack')
    ]

    # load pipette
    p1000 = ctx.load_instrument('p1000_single_gen2',
                                'left',
                                tip_racks=tips1000)

    # check source (LYSATE) labware type
    if LYSATE_LABWARE not in LY_LW_DICT:
        raise Exception('Invalid LYSATE_LABWARE. Must be one of the \
following:\nopentrons plastic 2ml tubes')
    # load LYSATE labware
    if 'plate' in LYSATE_LABWARE:
        source_racks = ctx.load_labware(LY_LW_DICT[LYSATE_LABWARE], '1',
                                        'RNA LYSATE labware')
    else:
        source_racks = [
            ctx.load_labware(LY_LW_DICT[LYSATE_LABWARE], slot,
                             'sample LYSATE labware ' + str(i + 1))
            for i, slot in enumerate(['4', '1', '5', '2'])
        ]

    # check plate
    if PLATE_LABWARE not in PL_LW_DICT:
        raise Exception('Invalid PLATE_LABWARE. Must be one of the \
following:\nopentrons deep generic well plate\nnest deep generic well plate\nvwr deep generic well plate'
                        )

    # load pcr plate
    wells_plate = ctx.load_labware(PL_LW_DICT[PLATE_LABWARE], 10,
                                   'sample LYSATE well plate ')

    # setup samples
    #sources, dests = get_source_dest_coordinates(LYSATE_LABWARE, source_racks, wells_plate)
    sources = [tube for s in source_racks for tube in s.wells()][:NUM_SAMPLES]
    dests = wells_plate.wells()[:NUM_SAMPLES]

    # transfer
    transfer_samples(LYSATE_LABWARE, VOLUME_LYSATE, sources, dests, p1000,
                     tips1000)

    # track final used tip
    save_tip_info()

    finish_run()
def run(ctx: protocol_api.ProtocolContext):

    # Turn on rail lights and pause program so user can load robot deck.
    # ctx.set_rail_lights(True)
    # ctx.pause("Load Labware onto robot deck and click resume when ready to continue")
    # ctx.home()
    ctx.set_rail_lights(False)

    # TSV file location on OT-2
    tsv_file_path = "{0}var{0}lib{0}jupyter{0}notebooks{0}ProcedureFile.tsv".format(
        os.sep)
    if not os.path.isfile(tsv_file_path):
        # Temp TSV file location on Win10 Computers for simulation
        tsv_file_path = "C:{0}Users{0}{1}{0}Documents{0}TempTSV.tsv".format(
            os.sep, os.getlogin())

    sample_parameters, args = Utilities.parse_sample_template(tsv_file_path)
    labware_dict, left_tiprack_list, right_tiprack_list = Utilities.labware_parsing(
        args, ctx)

    # Pipettes
    left_pipette = ctx.load_instrument(args.LeftPipette,
                                       'left',
                                       tip_racks=left_tiprack_list)
    right_pipette = ctx.load_instrument(args.RightPipette,
                                        'right',
                                        tip_racks=right_tiprack_list)

    # Set the location of the first tip in box.
    left_pipette.starting_tip = left_tiprack_list[0].wells_by_name()[
        args.LeftPipetteFirstTip]
    right_pipette.starting_tip = right_tiprack_list[0].wells_by_name()[
        args.RightPipetteFirstTip]

    # Make sample dilutions.  Calculate sample and water volumes.
    sample_data_dict, aspirated_water_vol = \
        process_samples(args, ctx, sample_parameters, labware_dict, left_pipette, right_pipette)

    # Dispense Water
    aspirated_water_vol = \
        dispense_water(args, sample_data_dict, labware_dict, left_pipette, right_pipette, aspirated_water_vol)

    # Dispense Samples
    aspirated_water_vol = \
        dispense_samples(args, sample_data_dict, left_pipette, right_pipette, aspirated_water_vol)

    # Dispense PCR Reagents.
    dispense_pcr_reagents(args, labware_dict, left_pipette, right_pipette,
                          aspirated_water_vol, sample_data_dict)

    if not ctx.is_simulating():
        os.remove(tsv_file_path)
def run(ctx: protocol_api.ProtocolContext):
    ctx.comment("Begin {}".format(metadata['protocolName']))

    # Turn on rail lights and pause program so user can load robot deck.
    # ctx.set_rail_lights(True)
    # ctx.pause("Load Labware onto robot deck and click resume when ready to continue")
    # ctx.home()
    ctx.set_rail_lights(False)

    # TSV file location on OT-2
    tsv_file_path = "{0}var{0}lib{0}jupyter{0}notebooks{0}ProcedureFile.tsv".format(
        os.sep)
    if not os.path.isfile(tsv_file_path):
        # Temp TSV file location on Win10 Computers for simulation
        tsv_file_path = "C:{0}Users{0}{1}{0}Documents{0}TempTSV.tsv".format(
            os.sep, os.getlogin())

    sample_parameters, args = Utilities.parse_sample_template(tsv_file_path)
    labware_dict, left_tiprack_list, right_tiprack_list = Utilities.labware_parsing(
        args, ctx)

    # Pipettes
    left_pipette = ctx.load_instrument(args.LeftPipette,
                                       'left',
                                       tip_racks=left_tiprack_list)
    right_pipette = ctx.load_instrument(args.RightPipette,
                                        'right',
                                        tip_racks=right_tiprack_list)

    # Set the location of the first tip in box.
    with suppress(IndexError):
        left_pipette.starting_tip = left_tiprack_list[0].wells_by_name()[
            args.LeftPipetteFirstTip]
    with suppress(IndexError):
        right_pipette.starting_tip = right_tiprack_list[0].wells_by_name()[
            args.RightPipetteFirstTip]

    # Dispense Samples and primers
    sample_dest_dict = dispense_samples(args, sample_parameters, labware_dict,
                                        left_pipette, right_pipette)

    # Add PCR mix to each destination well.
    add_pcr_mix(args, labware_dict, sample_dest_dict, left_pipette,
                right_pipette)

    if not ctx.is_simulating():
        os.remove(tsv_file_path)

    ctx.comment("Program End")
Example #6
0
def run(ctx: protocol_api.ProtocolContext):

    #Please replace what is between the quotation marks!!

    NEW_SERIAL_NUMBER = "OT2CEP20201214B12"


    ctx.comment(f"Setting serial number to {NEW_SERIAL_NUMBER}.")

    if not ctx.is_simulating():
        with open("/var/serial", "w") as serial_number_file:
            serial_number_file.write(NEW_SERIAL_NUMBER + "\n")
        with open("/etc/machine-info", "w") as serial_number_file:
            serial_number_file.write(f"DEPLOYMENT=production\nPRETTY_HOSTNAME={NEW_SERIAL_NUMBER}\n")
        with open("/etc/hostname", "w") as serial_number_file:
            serial_number_file.write(NEW_SERIAL_NUMBER + "\n")
        
        os.sync()
        
        
        ctx.comment("Done.")
Example #7
0
def run(ctx: protocol_api.ProtocolContext):

    #Please replace what is between the quotation marks!!

    NEW_SERIAL_NUMBER = "set_your_serial_number_here"


    ctx.comment(f"Setting serial number to {NEW_SERIAL_NUMBER}.")

    if not ctx.is_simulating():
        with open("/var/serial", "w") as serial_number_file:
            serial_number_file.write(NEW_SERIAL_NUMBER + "\n")
        with open("/etc/machine-info", "w") as serial_number_file:
            serial_number_file.write(f"DEPLOYMENT=production\nPRETTY_HOSTNAME={NEW_SERIAL_NUMBER}\n")
        with open("/etc/hostname", "w") as serial_number_file:
            serial_number_file.write(NEW_SERIAL_NUMBER + "\n")
        
        os.sync()
        
        
        ctx.comment("Done.")# This script sets an OT-2's serial number.  It's meant to be used after you
Example #8
0
def run(ctx: protocol_api.ProtocolContext):

    # load labware

    s_racks = [
        ctx.load_labware('opentrons_15_tuberack_falcon_15ml_conical', '4')
    ]
    d_plate = ctx.load_labware('nest_96_wellplate_200ul_flat', '1',
                               '96-wellplate sample plate')

    tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', '5')]

    # load pipette

    p300 = ctx.load_instrument('p300_single_gen2', 'left', tip_racks=tips300)

    # transfer sample

    p300.transfer(SAMPLE_VOLUME,
                  s_racks.wells()['A1'],
                  d_plate.columns()['5'],
                  air_gap=20,
                  mix_before=(2, 200),
                  new_tip='never')
    p300.air_gap(20)
    p300.drop_tip()

    ctx.comment('Terminado.')

    # track final used tip
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        data = {'tips300': tip_log['count'][p300]}
        with open(tip_file_path, 'w') as outfile:
            json.dump(data, outfile)
Example #9
0
def run(ctx: protocol_api.ProtocolContext):
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description and times
        1: {'Execute': True, 'description': 'Mix and move samples ('+str(VOLUME_SAMPLE)+'ul)'}
    }
    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    if not ctx.is_simulating():
        folder_path = '/var/lib/jupyter/notebooks/'+run_id
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/StationA_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse, delay):
            self.name               = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse              = bool(rinse)
            self.delay              = delay 

    # Reagents and their characteristics
    Samples = Reagent(name                  = 'Samples',
                      flow_rate_aspirate    = 1,
                      flow_rate_dispense    = 1,
                      rinse                 = False,
                      delay                 = 0
                      ) 

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('CONTROL SPACES: ' + str(NUM_CONTROL_SPACES))  
    ctx.comment('NUM SAMPLES: ' + str(NUM_REAL_SAMPLES)) 
    ctx.comment('###############################################')
    ctx.comment(' ')

    ##################
    # Custom functions
    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset,
                       pickup_height, rinse, disp_height, blow_out, touch_tip):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol,
                       rounds = 2, blow_out = True, mix_height = 0,
                       x_offset = x_offset)

        # SOURCE
        s = source.bottom(pickup_height).move(Point(x = x_offset[0]))
        pipet.aspirate(vol, s)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol, source.top(z = -2),
                           rate = reagent.flow_rate_aspirate)  # air gap

        # GO TO DESTINATION
        drop = dest.top(z = disp_height).move(Point(x = x_offset[1]))
        pipet.dispense(vol + air_gap_vol, drop,
                       rate = reagent.flow_rate_dispense)  # dispense all

        ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent

        if blow_out == True:
            pipet.blow_out(dest.top(z = -2))
        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = -10)

    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height,
    x_offset, source_height = 5):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height <= 0:
            mix_height = 3

        pipet.aspirate(1, location = location.bottom(
                        z = source_height).move(Point(x = x_offset[0])), rate = reagent.flow_rate_aspirate)

        for _ in range(rounds):
            pipet.aspirate(vol, location = location.bottom(
                z = source_height).move(Point(x = x_offset[0])), rate = reagent.flow_rate_aspirate)
            pipet.dispense(vol, location = location.bottom(
                z = mix_height).move(Point(x = x_offset[1])), rate = reagent.flow_rate_dispense)

        pipet.dispense(1, location = location.bottom(
            z = mix_height).move(Point(x = x_offset[1])), rate = reagent.flow_rate_dispense)

        if blow_out == True:
            pipet.blow_out(location.top(z = -2))  # Blow out

    def generate_source_table(source):
        '''
        Concatenate the wells frome the different origin racks
        '''
        num_cols = math.ceil(num_samples / 8)
        s = []
        for i  in range(num_cols):
            if i < 6:
                s += source[0].columns()[i] + source[1].columns()[i]
            else:
                s += source[2].columns()[i - 6] + source[3].columns()[i - 6]
        return s

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
        pip.pick_up_tip()

    ####################################
    # load labware and modules

    ####################################
    # Load Sample racks
    if num_samples <= 48:
        rack_num = 2
        ctx.comment('Used source racks are ' + str(rack_num))
    else:
        rack_num = 4

    source_racks = [ctx.load_labware(
        'opentrons_24_tuberack_nest_2ml_snapcap', slot,
        'source tuberack with snapcap' + str(i + 1)) for i, slot in enumerate(['5', '2', '6', '3'][:rack_num])
    ]

    ##################################
    # Destination plate
    dest_plate = ctx.load_labware(
        'nest_96_wellplate_2ml_deep', '1',
        'NEST 96 Deepwell Plate 2mL')

    ####################################
    # Load tip_racks
    tips1000 = [ctx.load_labware(
        'opentrons_96_filtertiprack_1000ul', slot, 
        '1000µl filter tiprack') for slot in ['7']]

    ################################################################################
    # setup samples and destinations
    sample_sources_full = generate_source_table(source_racks)
    sample_sources      = sample_sources_full[NUM_CONTROL_SPACES:num_samples]
    destinations        = dest_plate.wells()[NUM_CONTROL_SPACES:num_samples]

    p1000 = ctx.load_instrument(
        'p1000_single_gen2', 'right', 
        tip_racks = tips1000) # load P1000 pipette

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {p1000: 0},
        'maxes': {p1000: len(tips1000) * 96}
    }

    ############################################################################
    # STEP 1: MIX AND MOVE SAMPLES
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        start = datetime.now()
        for s, d in zip(sample_sources, destinations):
            if not p1000.hw_pipette['has_tip']:
                pick_up(p1000)

            # Mix the sample BEFORE dispensing
            if NUM_MIXES > 0:
                custom_mix(p1000, reagent = Samples, location = s, vol = volume_mix, 
                    rounds = NUM_MIXES, blow_out = True, mix_height = 15, x_offset = x_offset)

            move_vol_multichannel(p1000, reagent = Samples, source = s, dest = d,
                vol = VOLUME_SAMPLE, air_gap_vol = air_gap_vol_sample, x_offset = x_offset,
                pickup_height = 3, rinse = Samples.rinse, disp_height = -10,
                blow_out = True, touch_tip = True)

            p1000.drop_tip(home_after = False)
            tip_track['counts'][p1000] += 1

        # Time statistics
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)


    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program
    # from opentrons.drivers.rpi_drivers import gpio
    #if not ctx.is_simulating():
        #os.system('mpg123 -f -14000 /var/lib/jupyter/notebooks/final.mp3')
    if not ctx.is_simulating():
        fname = ' /var/lib/jupyter/notebooks/finished_process_esp.mp3'
        if os.path.isfile(fname) is True:
                subprocess.run(
                    ['mpg123', fname],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE
                )
        else:
            ctx.comment(f"Sound file does not exist. Call the technician")
    for i in range(3):
        ctx._hw_manager.hardware.set_lights(rails = False)
        ctx._hw_manager.hardware.set_lights(button = (1, 0 ,0))
        time.sleep(0.3)
        ctx._hw_manager.hardware.set_lights(rails = True)
        ctx._hw_manager.hardware.set_lights(button = (0, 0 ,1))
        time.sleep(0.3)
    ctx._hw_manager.hardware.set_lights(button = (0, 1 ,0))
    ctx.comment('Finished! \nMove deepwell plate (slot 1) to Station B for extraction protocol')
    ctx.comment('Used p1000 tips in total: ' + str(tip_track['counts'][p1000]))
    ctx.comment('Used p1000 racks in total: ' + str(tip_track['counts'][p1000] / 96))
def run(ctx: protocol_api.ProtocolContext):
    from opentrons.drivers.rpi_drivers import gpio
    gpio.set_rail_lights(False) #Turn off lights (termosensible reagents)
    ctx.comment('Actual used columns: ' + str(num_cols))

    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times
        1: {'Execute': True, 'description': 'Transfer MMIX'},
        2: {'Execute': True, 'description': 'Transfer elution'}
    }

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    folder_path = '/var/lib/jupyter/notebooks/'+run_id'
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/KC_qPCR_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse,
                     reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo,
                      tip_recycling = 'none'):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused=[]
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells

    # Reagents and their characteristics
    MMIX = Reagent(name = 'Master Mix',
                      rinse = False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume = volume_mmix_available,
                      num_wells = 2, #change with num samples
                      delay = 0,
                      h_cono = h_cone,
                      v_fondo = volume_cone  # V cono
                      )

    Samples = Reagent(name='Samples',
                      rinse=False,
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      reagent_reservoir_volume=50,
                      delay=0,
                      num_wells=num_cols,  # num_cols comes from available columns
                      h_cono=0,
                      v_fondo=0
                      )

    MMIX.vol_well = MMIX.vol_well_original
    Samples.vol_well = Samples.vol_well_original

    ##################
    # Custom functions


    def divide_destinations(l, n):
        # Divide the list of destinations in size n lists.
        for i in range(0, len(l), n):
            yield l[i:i + n]

    def distribute_custom(pipette, volume, src, dest, waste_pool, pickup_height, extra_dispensal, disp_height=0):
        # Custom distribute function that allows for blow_out in different location and adjustement of touch_tip
        pipette.aspirate((len(dest) * volume) +
                         extra_dispensal, src.bottom(pickup_height))
        pipette.touch_tip(speed=20, v_offset=-5)
        pipette.move_to(src.top(z=5))
        pipette.aspirate(20)  # air gap
        for d in dest:
            pipette.dispense(20, d.top())
            drop = d.top(z = disp_height)
            pipette.dispense(volume, drop)
            pipette.move_to(d.top(z=5))
            pipette.aspirate(20)  # air gap
        try:
            pipette.blow_out(waste_pool.wells()[0].bottom(pickup_height + 3))
        except:
            pipette.blow_out(waste_pool.bottom(pickup_height + 3))
        return (len(dest) * volume)

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset,
                       pickup_height, rinse, disp_height, blow_out, touch_tip):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol,
                       rounds = 2, blow_out = True, mix_height = 0,
                       x_offset = x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x = x_offset[0]))
        pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol, source.top(z = -2),
                           rate = reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z = disp_height).move(Point(x = x_offset[1]))
        pipet.dispense(vol + air_gap_vol, drop,
                       rate = reagent.flow_rate_dispense)  # dispense all
        ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z = -2))
        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9)


    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height,
    x_offset, source_height = 3):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height == 0:
            mix_height = 3
        pipet.aspirate(1, location=location.bottom(
            z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol, location=location.bottom(
                z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol, location=location.bottom(
                z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        pipet.dispense(1, location=location.bottom(
            z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out

    def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.5, extra_volume = 50):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume + extra_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
                    #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    ####################################
    # load labware and modules
    # 24 well rack
    tuberack = ctx.load_labware(
        'opentrons_24_aluminumblock_generic_2ml_screwcap', '2',
        'Bloque Aluminio opentrons 24 screwcaps 2000 µL ')

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

    ##################################
    # qPCR plate - final plate, goes to PCR
    qpcr_plate = tempdeck.load_labware(
        'abi_fast_qpcr_96_alum_opentrons_100ul',
        'chilled qPCR final plate')

    ##################################
    # Sample plate - comes from B
    source_plate = ctx.load_labware(
        "kingfisher_std_96_wellplate_550ul", '1',
        'chilled KF plate with elutions (alum opentrons)')
    samples = source_plate.wells()[:NUM_SAMPLES]

    ##################################
    # Load Tipracks
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot)
        for slot in ['5']
    ]

    tips200 = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', slot)
        for slot in ['6']
    ]

    ################################################################################
    # Declare which reagents are in each reservoir as well as deepwell and elution plate
    MMIX.reagent_reservoir = tuberack.rows()[0][:MMIX.num_wells] # 1 row, 2 columns (first ones)
    ctx.comment('Wells in: '+ str(tuberack.rows()[0][:MMIX.num_wells]) + ' element: '+str(MMIX.reagent_reservoir[MMIX.col]))
    # setup up sample sources and destinations
    samples = source_plate.wells()[:NUM_SAMPLES]
    samples_multi = source_plate.rows()[0][:num_cols]
    pcr_wells = qpcr_plate.wells()[:NUM_SAMPLES]
    pcr_wells_multi = qpcr_plate.rows()[0][:num_cols]
    # Divide destination wells in small groups for P300 pipette
    dests = list(divide_destinations(pcr_wells, size_transfer))


    # pipettes
    m20 = ctx.load_instrument(
        'p20_multi_gen2', mount='right', tip_racks=tips20)
    p300 = ctx.load_instrument(
        'p300_single_gen2', mount='left', tip_racks=tips200)

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {p300: 0,
                   m20: 0}
    }

    ############################################################################
    # STEP 1: Transfer Master MIX
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        p300.pick_up_tip()

        used_vol=[]
        for dest in dests:
            aspirate_volume=volume_mmix * len(dest) + extra_dispensal
            [pickup_height,col_change]=calc_height(MMIX, area_section_screwcap, aspirate_volume)
            used_vol_temp = distribute_custom(
            p300, volume = volume_mmix, src = MMIX.reagent_reservoir[MMIX.col], dest = dest,
            waste_pool = MMIX.reagent_reservoir[MMIX.col], pickup_height = pickup_height,
            extra_dispensal = extra_dispensal)
            used_vol.append(used_vol_temp)
        p300.drop_tip()
        tip_track['counts'][p300]+=1
        #MMIX.unused_two = MMIX.vol_well

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 2: TRANSFER Samples
    ############################################################################

    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment('pcr_wells')
        #Loop over defined wells
        for s, d in zip(samples_multi, pcr_wells_multi):
            m20.pick_up_tip()
            #Source samples
            move_vol_multichannel(m20, reagent = Samples, source = s, dest = d,
            vol = volume_sample, air_gap_vol = air_gap_sample, x_offset = x_offset,
                   pickup_height = 0.2, disp_height = -10, rinse = False,
                   blow_out=True, touch_tip=False)
            m20.drop_tip()
            tip_track['counts'][m20]+=8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program
    gpio.set_rail_lights(False)
    time.sleep(2)
    #os.system('mpg123 -f -8000 /var/lib/jupyter/notebooks/toreador.mp3 &')
    for i in range(3):
        gpio.set_rail_lights(False)
        gpio.set_button_light(1, 0, 0)
        time.sleep(0.3)
        gpio.set_rail_lights(True)
        gpio.set_button_light(0, 0, 1)
        time.sleep(0.3)
        gpio.set_rail_lights(False)
    gpio.set_button_light(0, 1, 0)
    ctx.comment('Finished! \nMove plate to PCR')

    if STEPS[1]['Execute'] == True:
        total_used_vol = np.sum(used_vol)
        total_needed_volume = total_used_vol
        ctx.comment('Total Master Mix used volume is: ' + str(total_used_vol) + '\u03BCl.')
        ctx.comment('Needed Master Mix volume is ' +
                    str(total_needed_volume + extra_dispensal*len(dests)) +'\u03BCl')
        ctx.comment('Used Master Mix volumes per run are: ' + str(used_vol) + '\u03BCl.')
        ctx.comment('Master Mix Volume remaining in tubes is: ' +
                    format(np.sum(MMIX.unused)+extra_dispensal*len(dests)+MMIX.vol_well) + '\u03BCl.')
        ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300]))
        ctx.comment('200 ul Used racks in total: ' + str(tip_track['counts'][p300] / 96))

    if STEPS[2]['Execute'] == True:
        ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][m20]))
        ctx.comment('20 ul Used racks in total: ' + str(tip_track['counts'][m20] / 96))
def run(ctx: protocol_api.ProtocolContext):
    global robot
    robot = ctx

    # confirm door is close
    if not ctx.is_simulating():
        confirm_door_is_closed()

    # load labware and modules
    ## ELUTION LABWARE
    if ELUTION_LABWARE not in ELUTION_LW_DICT:
        raise Exception('Invalid ELUTION_LABWARE. Must be one of the \
    following:\nopentrons aluminum biorad plate\nopentrons aluminum nest plate')

    elution_plate = ctx.load_labware(
        ELUTION_LW_DICT[ELUTION_LABWARE], '1',
        'elution plate')

    ## MAGNETIC PLATE LABWARE
    magdeck = ctx.load_module('magdeck', '10')
    magdeck.disengage()

    if MAGPLATE_LABWARE not in MAGPLATE_LW_DICT:
        raise Exception('Invalid MAGPLATE_LABWARE. Must be one of the \
following:\nopentrons deep generic well plate\nnest deep generic well plate\nvwr deep generic well plate')

    magplate = magdeck.load_labware(MAGPLATE_LW_DICT[MAGPLATE_LABWARE])

    ## WASTE LABWARE
    if WASTE_LABWARE not in WASTE_LW_DICT:
        raise Exception('Invalid WASTE_LABWARE. Must be one of the \
    following:\nnest 1 reservoir plate')

    waste = ctx.load_labware(
        WASTE_LW_DICT[WASTE_LABWARE], '11', 'waste reservoir').wells()[0].top(-10)

    ## REAGENT RESERVOIR
    if REAGENT_LABWARE not in REAGENT_LW_DICT:
        raise Exception('Invalid REAGENT_LABWARE. Must be one of the \
    following:\nnest 12 reservoir plate')

    reagent_res = ctx.load_labware(
        REAGENT_LW_DICT[REAGENT_LABWARE], '7', 'reagent reservoir')

    ## TIPS
    # using standard tip definition despite actually using filter tips
    # so that the tips can accommodate ~220µl per transfer for efficiency
    tips300 = [
        ctx.load_labware(
            'opentrons_96_tiprack_300ul', slot, '200µl filter tiprack')
        for slot in ['2', '3', '5', '6', '9','4']
    ]
    tips1000 = [
        ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot,
                         '1000µl filter tiprack')
        for slot in ['8']
    ]

    # reagents and samples
    num_cols = math.ceil(NUM_SAMPLES/8)
    mag_samples_m = magplate.rows()[0][:num_cols]
    mag_samples_s = magplate.wells()[:NUM_SAMPLES]
    elution_samples_m = elution_plate.rows()[0][:num_cols]
    elution_buffer = reagent_res.wells()[0]
    bead_buffer = reagent_res.wells()[1:5]
    wash_sets = [reagent_res.wells()[i:i+2] for i in [5, 7, 9]]

    # pipettes
    m300 = ctx.load_instrument('p300_multi_gen2', 'left', tip_racks=tips300)
    p1000 = ctx.load_instrument('p1000_single_gen2', 'right',
                                tip_racks=tips1000)

    m300.flow_rate.aspirate = 150
    m300.flow_rate.dispense = 300
    m300.flow_rate.blow_out = 300
    p1000.flow_rate.aspirate = 100
    p1000.flow_rate.dispense = 1000
    p1000.flow_rate.blow_out = 1000

    if(DISPENSE_BEADS):
        # premix, transfer, and mix magnetic beads with sample
        ## bead dests depending on number of samples
        bead_dests = bead_buffer[:math.ceil(num_cols/4)]
        dispense_beads(bead_dests,mag_samples_m,m300,tips300)
    else:
        # Mix bead
        mix_beads(7, mag_samples_m,m300,tips300)

    # incubate off and on magnet
    ctx.delay(minutes=5, msg='Incubating off magnet for 5 minutes.')

    ## First incubate on magnet.
    magdeck.engage(height_from_base=22)
    ctx.delay(minutes=5, msg='Incubating on magnet for 5 minutes.')

    # remove supernatant with P1000
    remove_supernatant(mag_samples_s,waste,p1000,tips1000)

    # 3x washes
    wash(wash_sets,mag_samples_m,waste,magdeck,m300,tips300)

    # elute samples
    magdeck.disengage()
    elute_samples(mag_samples_m,elution_samples_m,elution_buffer,magdeck,m300,tips300)

    # track final used tip
    save_tip_info()
    magdeck.disengage()
    finish_run()
Example #12
0
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 #13
0
def run(ctx: protocol_api.ProtocolContext):
    ctx.comment('Actual used columns: ' + str(num_cols))
    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times

        1: {'Execute': True, 'description': 'Add 300 ul Wash Buffer 1 - Round 1'},
        2: {'Execute': True, 'description': 'Add 300 ul Wash Buffer 1 - Round 2'},
        3: {'Execute': True, 'description': 'Add 450 ul Wash Buffer 2 - Round 1'},
        4: {'Execute': True, 'description': 'Add 450 ul Wash Buffer 2 - Round 2'},
        5: {'Execute': True, 'description': 'Add 50 ul Elution Buffer'},
    }

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0
    folder_path = '/var/lib/jupyter/notebooks/'+run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/KB_PlateFilling_pathogen_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse,
                     reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo,
                      tip_recycling = 'none'):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay #Delay of reagent in dispense
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused=[]
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells


    # Reagents and their characteristics
    WashBuffer1 = Reagent(name='Wash Buffer 1',
                          flow_rate_aspirate=0.75,
                          flow_rate_dispense=1,
                          rinse=True,
                          delay=2,
                          reagent_reservoir_volume=100000,
                          num_wells=1,
                          h_cono=0,
                          v_fondo=0)  # Flat surface

    WashBuffer2 = Reagent(name='Wash Buffer 1',
                          flow_rate_aspirate=0.75,
                          flow_rate_dispense=1,
                          rinse=True,
                          delay=2,
                          reagent_reservoir_volume=100000,
                          num_wells=1,
                          h_cono=0,
                          v_fondo=0)  # Flat surface

    ElutionBuffer = Reagent(name='Elution Buffer',
                            flow_rate_aspirate=1,
                            flow_rate_dispense=1,
                            rinse=False,
                            delay=0,
                            reagent_reservoir_volume=50*NUM_SAMPLES,
                            num_wells=1,
                            h_cono=1.95,
                            v_fondo=695)  # Prismatic

    WashBuffer1.vol_well = WashBuffer1.vol_well_original
    WashBuffer2.vol_well = WashBuffer2.vol_well_original
    ElutionBuffer.vol_well = ElutionBuffer.vol_well_original

    ##################
    # Custom functions
    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset,
                       pickup_height, rinse, disp_height, blow_out, touch_tip):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol,
                       rounds = 2, blow_out = True, mix_height = 0,
                       x_offset = x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x = x_offset[0]))
        pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol, source.top(z = -2),
                           rate = reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z = disp_height).move(Point(x = x_offset[1]))
        pipet.dispense(vol + air_gap_vol, drop,
                       rate = reagent.flow_rate_dispense)  # dispense all
        ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z = -2))
        if touch_tip == True:
            pipet.touch_tip(radius=0.9, speed = 20, v_offset = -5) #radius here is 0.9


    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height,
    x_offset, source_height = 3):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height == 0:
            mix_height = 3
        pipet.aspirate(1, location=location.bottom(
            z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol, location=location.bottom(
                z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol, location=location.bottom(
                z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        pipet.dispense(1, location=location.bottom(
            z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out

    def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.5, extra_volume = 50):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume + extra_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
                    #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
        pip.pick_up_tip()
    ##########

    def find_side(col):
        '''
        Detects if the current column has the magnet at its left or right side
        '''
        if col % 2 == 0:
            side = -1  # left
        else:
            side = 1
        return side


####################################
    # load labware and modules

    # 12 well rack
    ####################################
    reagent_res = ctx.load_labware(
        'nest_12_reservoir_15ml', '3', 'Reservoir 12 channel, column 1')

    # WashBuffer1 reservoir
    ####################################
    WashBuffer1_reservoir = ctx.load_labware(
        'nalgene_1_reservoir_300000ul', '2', 'Ethanol 80% reservoir')

    # WashBuffer2 reservoir
    ####################################
    WashBuffer2_reservoir = ctx.load_labware(
        'nalgene_1_reservoir_300000ul', '11', 'WashBuffer reservoir')

    # Wash Buffer 1 300ul Deepwell plate
    ############################################
    WashBuffer1_300ul_plate1 = ctx.load_labware(
        'kf_96_wellplate_2400ul', '1', 'Wash Buffer 1 Deepwell plate 1')

    # Wash Buffer 1 300ul Deepwell plate
    ############################################
    WashBuffer1_300ul_plate2 = ctx.load_labware(
        'kf_96_wellplate_2400ul', '4', 'Wash Buffer 1 Deepwell plate 2')

    # Wash Buffer 2 450ul Deepwell plate
    ############################################
    WashBuffer2_450ul_plate1 = ctx.load_labware(
        'kf_96_wellplate_2400ul', '7', 'Wash Buffer 2 Deepwell plate 1')

    # Wash Buffer 2 450ul Deepwell plate
    ############################################
    WashBuffer2_450ul_plate2 = ctx.load_labware(
        'kf_96_wellplate_2400ul', '10', 'Wash Buffer 2 Deepwell plate 2')

    # Elution Deepwell plate
    ############################################
    ElutionBuffer_50ul_plate = ctx.load_labware(
        'kingfisher_std_96_wellplate_550ul', '6', 'Elution Buffer 50 ul STD plate')


####################################
    # Load tip_racks
    tips300 = [ctx.load_labware('opentrons_96_tiprack_300ul', slot, '200µl filter tiprack')
               for slot in ['8']]

################################################################################
    # Declare which reagents are in each reservoir as well as deepwell and elution plate
    WashBuffer1.reagent_reservoir = WashBuffer1_reservoir.wells()[0]
    WashBuffer2.reagent_reservoir = WashBuffer2_reservoir.wells()[0]
    ElutionBuffer.reagent_reservoir = reagent_res.rows()[0][0]

    # columns in destination plates to be filled depending the number of samples
    wb1plate1_destination = WashBuffer1_300ul_plate1.rows()[0][:num_cols]
    wb1plate2_destination = WashBuffer1_300ul_plate2.rows()[0][:num_cols]
    wb2plate1_destination = WashBuffer2_450ul_plate1.rows()[0][:num_cols]
    wb2plate2_destination = WashBuffer2_450ul_plate2.rows()[0][:num_cols]
    elutionbuffer_destination = ElutionBuffer_50ul_plate.rows()[0][:num_cols]

    # pipette
    m300 = ctx.load_instrument(
        'p300_multi_gen2', 'right', tip_racks=tips300)  # Load multi pipette

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {m300: 0},
        'maxes': {m300: len(tips300)*96}
    }

    ############################################################################
    # STEP 1 Filling with WashBuffer1 plate 1
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()

        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        wash_buffer_vol = [150, 150]
        rinse = False  # Only first time

        ########
        # Wash buffer dispense
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(wash_buffer_vol):
                if (i == 0 and j == 0):
                    rinse = True #Rinse only first transfer
                else:
                    rinse = False
                move_vol_multichannel(m300, reagent = WashBuffer1, source = WashBuffer1.reagent_reservoir,
                               dest = wb1plate1_destination[i], vol = transfer_vol,
                               air_gap_vol = air_gap_vol, x_offset = x_offset,
                               pickup_height = 1, rinse = rinse, disp_height = -2,
                               blow_out = True, touch_tip = True)
        m300.drop_tip(home_after=True)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 2 Filling with WashBuffer1 plate 2
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()

        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        wash_buffer_vol = [150, 150]
        rinse = False  # Only first time

        ########
        # Wash buffer dispense
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(wash_buffer_vol):
                if (i == 0 and j == 0):
                    rinse = True
                else:
                    rinse = False
                move_vol_multichannel(m300, reagent = WashBuffer1, source = WashBuffer1.reagent_reservoir,
                               dest = wb1plate2_destination[i], vol = transfer_vol,
                               air_gap_vol = air_gap_vol, x_offset = x_offset,
                               pickup_height = 1, rinse = rinse, disp_height = -2,
                               blow_out = True, touch_tip = True)
        m300.drop_tip(home_after=True)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 3 Filling with WashBuffer2 plate 1
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()

        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        wash_buffer_vol = [150, 150, 150]
        rinse = False  # Only first time

        ########
        # Wash buffer dispense
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(wash_buffer_vol):
                if (i == 0 and j == 0):
                    rinse = True
                else:
                    rinse = False
                move_vol_multichannel(m300, reagent = WashBuffer2, source = WashBuffer2.reagent_reservoir,
                               dest = wb2plate1_destination[i], vol = transfer_vol,
                               air_gap_vol = air_gap_vol, x_offset = x_offset,
                               pickup_height = 1, rinse = rinse, disp_height = -2,
                               blow_out = True, touch_tip = True)
        m300.drop_tip(home_after=True)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 4 Filling with WashBuffer2 plate 2
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()

        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        ethanol_vol = [150, 150, 150]
        rinse = False  # Only first time

        ########
        # Ethanol dispense
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for j, transfer_vol in enumerate(ethanol_vol):
                if (i == 0 and j == 0):
                    rinse = True
                else:
                    rinse = False
                move_vol_multichannel(m300, reagent = WashBuffer2, source = WashBuffer2.reagent_reservoir,
                              dest = wb2plate2_destination[i], vol = transfer_vol,
                              air_gap_vol = air_gap_vol, x_offset = x_offset,
                              pickup_height = 1, rinse = rinse, disp_height = -2,
                              blow_out = True, touch_tip = True)
        m300.drop_tip(home_after=True)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 5 Transfer Elution buffer
    ############################################################################

    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        # Elution buffer
        ElutionBuffer_vol = [50]

        ########
        # Water or elution buffer
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                pick_up(m300)
            for transfer_vol in ElutionBuffer_vol:
                # Calculate pickup_height based on remaining volume and shape of container
                [pickup_height, change_col] = calc_height(
                    ElutionBuffer, multi_well_rack_area, transfer_vol * 8)
                ctx.comment(
                    'Aspirate from Reservoir column: ' + str(ElutionBuffer.col))
                ctx.comment('Pickup height is ' + str(pickup_height))
                move_vol_multichannel(m300, reagent = ElutionBuffer, source = ElutionBuffer.reagent_reservoir,
                              dest = elutionbuffer_destination[i], vol = transfer_vol,
                              air_gap_vol = air_gap_vol_elutionbuffer, x_offset = x_offset,
                              pickup_height = pickup_height, rinse = False, disp_height = -2,
                              blow_out = True, touch_tip = False)
        m300.drop_tip(home_after=True)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program
    from opentrons.drivers.rpi_drivers import gpio
    for i in range(3):
        gpio.set_rail_lights(False)
        gpio.set_button_light(1, 0, 0)
        time.sleep(0.3)
        gpio.set_rail_lights(True)
        gpio.set_button_light(0, 0, 1)
        time.sleep(0.3)
    gpio.set_button_light(0, 1, 0)
    ctx.comment(
        'Finished! \nMove deepwell plates to KingFisher extractor.')
    ctx.comment('Used tips in total: ' + str(tip_track['counts'][m300]))
    ctx.comment('Used racks in total: ' + str(tip_track['counts'][m300] / 96))
Example #14
0
def run(ctx: protocol_api.ProtocolContext):
    # Init protocol run
    run = ProtocolRun(ctx)
    run.comment("You are about to run %s samples\n STEPS:%s" %
                (NUM_SAMPLES, steps),
                add_hash=True)
    run.pause(
        "Are you sure the set up is correct? Check the desk before continue")

    # Define stesp
    run.add_step(
        description="Transfer PK A6 - To AW_PLATE Single Slot1 -> Slot2")  # 1
    run.add_step(description="Transfer MS2 B6 - To AW_PLATE Single 1->2")  # 4
    run.add_step(description="Transfer Beats 3 - 2 Multi and mix")  # 3

    # execute avaliaible steps
    run.init_steps(steps)

    ##################################

    # Tube rack
    tube_rack = ctx.load_labware('opentrons_24_tuberack_nest_1.5ml_screwcap',
                                 4)

    # Destination plate SLOT 2
    if (ctx.is_simulating()):
        aw_slot = ctx.load_labware(
            'opentrons_96_aluminumblock_generic_pcr_strip_200ul', 5)
    else:
        aw_slot = ctx.load_labware('axygen_96_wellplate_2000ul', 5)

    aw_wells = aw_slot.wells()[:NUM_SAMPLES]
    aw_wells_multi = aw_slot.rows()[0][:num_cols]

    # Magnetic Beads Pool
    beads_slot = ctx.load_labware('nest_12_reservoir_15ml', 6)
    beads_wells_multi = beads_slot.rows()[0][:num_cols]

    # Mount pippets and set racks
    # Tipracks20_multi
    tips20 = ctx.load_labware('opentrons_96_tiprack_20ul', 7)
    tips300_1 = ctx.load_labware('opentrons_96_filtertiprack_200ul', 8)
    tips300_2 = ctx.load_labware('opentrons_96_filtertiprack_200ul', 9)

    run.mount_right_pip('p20_single_gen2', tip_racks=[tips20], capacity=20)
    run.mount_left_pip('p300_multi_gen2',
                       tip_racks=[tips300_1, tips300_2],
                       capacity=200,
                       multi=True)

    ############################################################################
    # STEP 1: Transfer A6 - To AW_PLATE
    ############################################################################
    if (run.next_step()):
        run.set_pip("right")  # single 20
        volumen_move = 5
        source = tube_rack.wells("A6")[0]
        liquid = Reagent(
            name='Proteinasa K',
            num_wells=1,  # change with num samples
            flow_rate_aspirate=0.75,  # Original 0.5
            flow_rate_dispense=3,  # Original 1
            reagent_reservoir_volume=528,
            h_cono=4,
            v_fondo=4 * math.pi * 4**3 / 3)

        run.pick_up()
        for dest in aw_wells:
            [pickup_height,
             col_change] = run.calc_height(liquid, 4.12 * 4.12 * math.pi,
                                           volumen_move)
            run.move_volume(reagent=liquid,
                            source=source,
                            dest=dest,
                            vol=volumen_move,
                            air_gap_vol=1,
                            pickup_height=pickup_height,
                            disp_height=-10,
                            blow_out=True,
                            post_dispense=True,
                            post_dispense_vol=5)

        run.drop_tip()
        run.finish_step()

    ############################################################################
    # STEP 2: Transfer B6 MS2 - To AW_PLATE
    ############################################################################
    if (run.next_step()):
        run.set_pip("right")  # single 20
        volumen_move = 5
        source = tube_rack.wells("B6")[0]
        liquid = Reagent(
            name='MS2',
            num_wells=1,  # change with num samples
            delay=0,
            flow_rate_aspirate=3,  # Original 0.5
            flow_rate_dispense=3,  # Original 1
            flow_rate_aspirate_mix=15,
            flow_rate_dispense_mix=25,
            reagent_reservoir_volume=528,
            h_cono=4,
            v_fondo=4 * math.pi * 4**3 / 3)
        run.pick_up()
        for dest in aw_wells:

            [pickup_height,
             col_change] = run.calc_height(liquid, 4.12 * 4.12 * math.pi,
                                           volumen_move)
            run.move_volume(reagent=liquid,
                            source=source,
                            dest=dest,
                            vol=volumen_move,
                            air_gap_vol=1,
                            pickup_height=pickup_height,
                            disp_height=-10,
                            blow_out=True,
                            post_dispense=True,
                            post_dispense_vol=5)

        run.drop_tip()

        run.finish_step()

    ############################################################################
    # STEP 3: Slot 3 -2 beats_PK AW
    ############################################################################
    if (run.next_step()):
        ############################################################################
        # Light flash end of program
        run.set_pip("left")  # p300 multi
        volume = 275
        beads = Reagent(name='Magnetic beads',
                        flow_rate_aspirate=0.5,
                        flow_rate_dispense=0.5,
                        flow_rate_dispense_mix=4,
                        flow_rate_aspirate_mix=4,
                        rinse=True,
                        delay=2,
                        reagent_reservoir_volume=30000,
                        num_wells=3,
                        h_cono=1.95,
                        v_fondo=695,
                        rinse_loops=3)

        air_gap_vol = 5
        disposal_height = -5
        pickup_height = 1
        beads.reagent_reservoir = beads_slot.rows()[0][0:3]
        pool_area = 8.3 * 71.1

        for destination in aw_wells_multi:
            run.pick_up()
            vol = 150
            vol_min = 1000
            [pickup_height, col_change] = run.calc_height(beads,
                                                          pool_area,
                                                          vol * 8,
                                                          extra_volume=vol_min)
            run.move_volume(reagent=beads,
                            source=beads.reagent_reservoir[beads.col],
                            dest=destination,
                            vol=vol,
                            air_gap_vol=air_gap_vol,
                            pickup_height=pickup_height,
                            disp_height=disposal_height,
                            rinse=True,
                            blow_out=True)
            run.change_tip()
            vol = 125
            [pickup_height, col_change] = run.calc_height(beads,
                                                          pool_area,
                                                          vol * 8,
                                                          extra_volume=vol_min)
            run.move_volume(reagent=beads,
                            source=beads.reagent_reservoir[beads.col],
                            dest=destination,
                            vol=vol,
                            air_gap_vol=air_gap_vol,
                            pickup_height=pickup_height,
                            disp_height=disposal_height,
                            rinse=True,
                            blow_out=True)

            run.custom_mix(beads,
                           location=destination,
                           vol=150,
                           rounds=3,
                           blow_out=True,
                           mix_height=0)

            run.drop_tip()

        run.finish_step()

    run.log_steps_time()
    run.blink()
    for c in robot.commands():
        ctx.comment(c)
    ctx.comment('Finished! \nMove plate to PCR')
Example #15
0
def run(ctx: protocol_api.ProtocolContext):
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description and times
        1: {
            'Execute': True,
            'description': 'Add Lysis buffer (' + str(volume_control) + 'ul)'
        },
        2: {
            'Execute': True,
            'description': 'Add samples (' + str(volume_sample) + 'ul)'
        }
    }
    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    if not ctx.is_simulating():
        folder_path = '/var/lib/jupyter/notebooks/' + run_id
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/StationA_time_log.txt'
        file_path2 = folder_path + '/StationA_tips_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self,
                     name,
                     flow_rate_aspirate,
                     flow_rate_dispense,
                     rinse,
                     reagent_reservoir_volume,
                     delay,
                     num_wells,
                     h_cono,
                     v_fondo,
                     tip_recycling='none'):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused = []
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells

    # Reagents and their characteristics
    BUFFER = Reagent(name='TNA+Beads+Isopropanol',
                     flow_rate_aspirate=1,
                     flow_rate_dispense=1,
                     rinse=False,
                     delay=0,
                     reagent_reservoir_volume=50000,
                     num_wells=1,
                     h_cono=(v_cone_falcon * 3 / falcon_cross_section_area),
                     v_fondo=v_cone_falcon)

    Samples = Reagent(
        name='Samples',
        flow_rate_aspirate=1,
        flow_rate_dispense=1,
        rinse=False,
        delay=0,
        reagent_reservoir_volume=700 * 24,
        num_wells=24,  # num_cols comes from available columns
        h_cono=4,
        v_fondo=4 * area_section_sample * diameter_sample * 0.5 / 3)  # Sphere

    BUFFER.vol_well = BUFFER.vol_well_original
    Samples.vol_well = 700

    ##################
    # Custom functions

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol,
                              x_offset, pickup_height, rinse, disp_height,
                              blow_out, touch_tip):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet,
                       reagent,
                       location=source,
                       vol=vol,
                       rounds=2,
                       blow_out=True,
                       mix_height=0,
                       x_offset=x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x=x_offset[0]))
        pipet.aspirate(vol, s)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol,
                           source.top(z=-2),
                           rate=reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z=disp_height).move(Point(x=x_offset[1]))
        pipet.dispense(vol + air_gap_vol,
                       drop,
                       rate=reagent.flow_rate_dispense)  # dispense all
        ctx.delay(
            seconds=reagent.delay)  # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z=-2))
        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=-5)

    def custom_mix(pipet,
                   reagent,
                   location,
                   vol,
                   rounds,
                   blow_out,
                   mix_height,
                   x_offset,
                   source_height=3):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height == 0:
            mix_height = 3
        pipet.aspirate(1,
                       location=location.bottom(z=source_height).move(
                           Point(x=x_offset[0])),
                       rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol,
                           location=location.bottom(z=source_height).move(
                               Point(x=x_offset[0])),
                           rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol,
                           location=location.bottom(z=mix_height).move(
                               Point(x=x_offset[1])),
                           rate=reagent.flow_rate_dispense)
        pipet.dispense(1,
                       location=location.bottom(z=mix_height).move(
                           Point(x=x_offset[1])),
                       rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out

    def calc_height(reagent,
                    cross_section_area,
                    aspirate_volume,
                    min_height=0.5):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area  #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    def generate_source_table(source):
        '''
        Concatenate the wells frome the different origin racks
        '''
        for rack_number in range(len(source)):
            if rack_number == 0:
                s = source[rack_number].wells()
            else:
                s = s + source[rack_number].wells()
        return s

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) +
                          'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
        pip.pick_up_tip()

    ####################################
    # load labware and modules
    ####################################

    # Load Sample racks
    if NUM_SAMPLES < 96:
        rack_num = math.ceil(NUM_SAMPLES / 24)
        ctx.comment('Used source racks are ' + str(rack_num))
        samples_last_rack = NUM_SAMPLES - rack_num * 24
    else:
        rack_num = 4
    source_racks = [
        ctx.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', slot,
                         'source tuberack with screwcap' + str(i + 1))
        for i, slot in enumerate(['4', '1', '6', '3'][:rack_num])
    ]

    ##################################
    # Destination plate
    dest_plate = ctx.load_labware('abgene_96_wellplate_800ul', '5',
                                  'ABGENE 96 Well Plate 800 µL')

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

    ##################################
    # Cooled reagents in tempdeck
    #reagents = tempdeck.load_labware(
    #'opentrons_24_aluminumblock_generic_2ml_screwcap',
    #'cooled reagent tubes')

    reagents = ctx.load_labware('opentrons_6_tuberack_falcon_50ml_conical',
                                '7', 'Lysis buffer tuberack in Falcon tube')

    ####################################
    # Load tip_racks
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot,
                         '20µl filter tiprack') for slot in ['11']
    ]
    tips1000 = [
        ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot,
                         '1000µl filter tiprack') for slot in ['10']
    ]

    ################################################################################
    # Declare which reagents are in each reservoir as well as deepwell and elution plate
    BUFFER.reagent_reservoir = reagents.wells()[0]

    # setup samples and destinations
    sample_sources_full = generate_source_table(source_racks)
    sample_sources = sample_sources_full[:NUM_SAMPLES]
    destinations = dest_plate.wells()[:NUM_SAMPLES]

    p20 = ctx.load_instrument('p20_single_gen2',
                              mount='right',
                              tip_racks=tips20)
    p1000 = ctx.load_instrument('p1000_single_gen2',
                                'left',
                                tip_racks=tips1000)  # load P1000 pipette

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {
            p20: 0,
            p1000: 0
        },
        'maxes': {
            p20: len(tips20) * 96,
            p1000: len(tips1000) * 96
        }
    }

    ############################################################################
    # STEP 1: Add TNA
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        # Transfer parameters
        start = datetime.now()
        if not p1000.hw_pipette['has_tip']:
            pick_up(p1000)
        for d in destinations:
            # Calculate pickup_height based on remaining volume and shape of container
            [pickup_height,
             change_col] = calc_height(BUFFER, falcon_cross_section_area,
                                       volume_control)
            move_vol_multichannel(p1000,
                                  reagent=BUFFER,
                                  source=BUFFER.reagent_reservoir,
                                  dest=d,
                                  vol=volume_control,
                                  air_gap_vol=air_gap_vol_ci,
                                  x_offset=x_offset,
                                  pickup_height=pickup_height,
                                  rinse=BUFFER.rinse,
                                  disp_height=height_control,
                                  blow_out=True,
                                  touch_tip=True)

            # Mix the sample AFTER dispensing using 15µl of volume
            #custom_mix(p20, reagent = Control_I, location = d, vol = 15, rounds = 4, blow_out = True, mix_height = 15)

            #Do not drop tip as it is not contaminated
            #p1000.drop_tip()
            #tip_track['counts'][p20]+=1

        #Time statistics
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 2: Add Samples
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        # Transfer parameters
        start = datetime.now()
        for s, d in zip(sample_sources, destinations):
            if not p1000.hw_pipette['has_tip']:
                pick_up(p1000)

            # Mix the sample BEFORE dispensing
            #custom_mix(p1000, reagent = Samples, location = s, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15)
            move_vol_multichannel(p1000,
                                  reagent=Samples,
                                  source=s,
                                  dest=d,
                                  vol=volume_sample,
                                  air_gap_vol=air_gap_vol_sample,
                                  x_offset=x_offset,
                                  pickup_height=1,
                                  rinse=Samples.rinse,
                                  disp_height=-10,
                                  blow_out=True,
                                  touch_tip=True)
            # Mix the sample AFTER dispensing using 15µl of volume
            custom_mix(p1000,
                       reagent=Samples,
                       location=d,
                       vol=800,
                       rounds=2,
                       blow_out=False,
                       mix_height=10)

            p1000.drop_tip()
            tip_track['counts'][p1000] += 1

        # Time statistics
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write(
                'STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()
        with open(file_path2, 'w') as f2:
            f2.write('pipette\ttip_count\n')
            for key in tip_track['counts'].keys():
                row = str(key)
                f.write(str(key) + '\t' + format(tip_track['counts'][key]))
        f2.close()

    ############################################################################
    # Light flash end of program
    from opentrons.drivers.rpi_drivers import gpio
    #if not ctx.is_simulating():
    #os.system('mpg123 -f -14000 /var/lib/jupyter/notebooks/lionking.mp3')
    for i in range(3):
        gpio.set_rail_lights(False)
        gpio.set_button_light(1, 0, 0)
        time.sleep(0.3)
        gpio.set_rail_lights(True)
        gpio.set_button_light(0, 0, 1)
        time.sleep(0.3)
    gpio.set_button_light(0, 1, 0)
    ctx.comment(
        'Finished! \nMove deepwell plate (slot 5) to Station B for extraction protocol'
    )
    ctx.comment('Used p1000 tips in total: ' + str(tip_track['counts'][p1000]))
    ctx.comment('Used p1000 racks in total: ' +
                str(tip_track['counts'][p1000] / 96))
    ctx.comment('Used p20 tips in total: ' + str(tip_track['counts'][p20]))
    ctx.comment('Used p20 racks in total: ' +
                str(tip_track['counts'][p20] / 96))
Example #16
0
def run(ctx: protocol_api.ProtocolContext):
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description and times
        1: {
            'Execute':
            True,
            'description':
            'Mezclar y dispensar muestras (' + str(VOLUME_SAMPLE) + 'ul)'
        }
    }
    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    if not ctx.is_simulating():
        folder_path = '/var/lib/jupyter/notebooks/' + run_id
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/StationA_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse,
                     delay):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.delay = delay

    # Reagents and their characteristics
    Samples = Reagent(name='Samples',
                      flow_rate_aspirate=25,
                      flow_rate_dispense=100,
                      rinse=False,
                      delay=0)

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('CONTROLES: ' + str(NUM_CONTROL_SPACES))
    ctx.comment('MUESTRAS: ' + str(NUM_REAL_SAMPLES))
    ctx.comment('###############################################')
    ctx.comment(' ')

    ##################
    # Custom functions
    def move_vol_multichannel(pipet,
                              reagent,
                              source,
                              dest,
                              vol,
                              air_gap_vol,
                              x_offset,
                              pickup_height,
                              rinse,
                              disp_height,
                              blow_out,
                              touch_tip,
                              shakes=0,
                              shake_v_offset=-45):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet,
                       reagent,
                       location=source,
                       vol=vol,
                       rounds=2,
                       blow_out=True,
                       mix_height=0,
                       x_offset=x_offset)

        # SOURCE
        s = source.bottom(pickup_height).move(Point(x=x_offset[0]))
        pipet.aspirate(vol, s,
                       rate=reagent.flow_rate_aspirate)  # aspirate liquid

        if shakes > 0:
            shake_pipet(pipet, v_offset=shake_v_offset)

        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol,
                           source.top(z=-2),
                           rate=reagent.flow_rate_aspirate)  # air gap

        # GO TO DESTINATION
        drop = dest.top(z=disp_height).move(Point(x=x_offset[1]))
        pipet.dispense(vol + air_gap_vol,
                       drop,
                       rate=reagent.flow_rate_dispense)  # dispense all

        ctx.delay(
            seconds=reagent.delay)  # pause for x seconds depending on reagent

        if blow_out == True:
            pipet.blow_out(dest.top(z=disp_height))

        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=-10)

        if air_gap_vol != 0:
            pipet.air_gap(air_gap_vol, height=disp_height)  #air gap

    def custom_mix(pipet,
                   reagent,
                   location,
                   vol,
                   rounds,
                   blow_out,
                   mix_height,
                   x_offset,
                   source_height=5):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height <= 0:
            mix_height = 3

        pipet.aspirate(1,
                       location=location.bottom(z=source_height).move(
                           Point(x=x_offset[0])),
                       rate=reagent.flow_rate_aspirate)

        for _ in range(rounds):
            pipet.aspirate(vol,
                           location=location.bottom(z=source_height).move(
                               Point(x=x_offset[0])),
                           rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol,
                           location=location.bottom(z=mix_height).move(
                               Point(x=x_offset[1])),
                           rate=reagent.flow_rate_dispense)

        pipet.dispense(1,
                       location=location.bottom(z=mix_height).move(
                           Point(x=x_offset[1])),
                       rate=reagent.flow_rate_dispense)

        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out

    def generate_source_table(source):
        '''
        Concatenate the wells frome the different origin racks
        '''
        num_cols = math.ceil(num_samples / 8)
        s = []
        for i in range(num_cols):
            if i < 6:
                s += source[0].columns()[i] + source[1].columns()[i]
            else:
                s += source[2].columns()[i - 6] + source[3].columns()[i - 6]
        return s

    def shake_pipet(pipet, rounds=2, speed=100, v_offset=0):
        ctx.comment("Shaking " + str(rounds) + " rounds.")
        for i in range(rounds):
            pipet.touch_tip(speed=speed, radius=0.1, v_offset=v_offset)

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) +
                          'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
        pip.pick_up_tip()

    def run_quiet_process(command):
        subprocess.check_output('{} &> /dev/null'.format(command), shell=True)

    def play_sound(filename):
        print('Speaker')
        print('Next\t--> CTRL-C')
        try:
            run_quiet_process('mpg123 {}'.format(path_sounds + filename +
                                                 '.mp3'))
            run_quiet_process('mpg123 {}'.format(path_sounds + sonido_defecto))
            run_quiet_process('mpg123 {}'.format(path_sounds + filename +
                                                 '.mp3'))

        except KeyboardInterrupt:
            pass
            print()

    def start_run():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Empezando protocolo')
        if PHOTOSENSITIVE == False:
            ctx._hw_manager.hardware.set_lights(button=True, rails=True)
        else:
            ctx._hw_manager.hardware.set_lights(button=True, rails=False)
        now = datetime.now()

        # dd/mm/YY H:M:S
        start_time = now.strftime("%Y/%m/%d %H:%M:%S")
        return start_time

    def finish_run(switch_off_lights=False):
        ctx.comment('###############################################')
        ctx.comment('Protocolo finalizado')
        ctx.comment(' ')
        #Set light color to blue
        ctx._hw_manager.hardware.set_lights(button=True, rails=False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        finish_time = now.strftime("%Y/%m/%d %H:%M:%S")
        if PHOTOSENSITIVE == False:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button=False, rails=False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=True, rails=True)
                time.sleep(0.3)
        else:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button=False, rails=False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=True, rails=False)
                time.sleep(0.3)
        if switch_off_lights:
            ctx._hw_manager.hardware.set_lights(button=True, rails=False)

        used_tips = tip_track['num_refills'][p1000] * 96 * len(
            p1000.tip_racks) + tip_track['counts'][p1000]
        ctx.comment('Puntas de 1000 ul utilizadas: ' + str(used_tips) + ' (' +
                    str(round(used_tips / 96, 2)) + ' caja(s))')
        ctx.comment('###############################################')

        if not ctx.is_simulating():
            for i in range(SOUND_NUM_PLAYS):
                if i > 0:
                    time.sleep(60)
                play_sound('finished_process_esp')

            return finish_time

    ####################################
    # load labware and modules

    ####################################
    # Load Sample racks
    if num_samples <= 48:
        rack_num = 2
        ctx.comment('Used source racks are ' + str(rack_num))
    else:
        rack_num = 4

    #source_racks = [ctx.load_labware(
    #    'pcr_24_wellplate_13200ul', slot,
    #    'source tuberack with snapcap' + str(i + 1)) for i, slot in enumerate(['4', '1', '5', '2'][:rack_num])
    #]

    source_racks = [
        ctx.load_labware('vitrobiocomma_24_tuberack_15000ul_1', '4',
                         'samples grid 1'),
        ctx.load_labware('vitrobiocomma_24_tuberack_15000ul_2', '1',
                         'samples grid 2'),
        ctx.load_labware('vitrobiocomma_24_tuberack_15000ul_3', '5',
                         'samples grid 3'),
        ctx.load_labware('vitrobiocomma_24_tuberack_15000ul_4', '2',
                         'samples grid 4')
    ]

    ##################################
    # Destination plate
    dest_plate = ctx.load_labware('nest_96_wellplate_2ml_deep', '6',
                                  'NEST 96 Deepwell Plate 2mL')

    ####################################
    # Load tip_racks
    tips1000 = [
        ctx.load_labware(
            'opentrons_96_filtertiprack_1000ul' if OPENTRONS_TIPS else
            'geb_96_tiprack_1000ul', slot, '1000µl filter tiprack')
        for slot in ['8']
    ]

    ################################################################################
    # setup samples and destinations
    sample_sources_full = generate_source_table(source_racks)
    sample_sources = sample_sources_full[NUM_CONTROL_SPACES:num_samples]
    destinations = dest_plate.wells()[NUM_CONTROL_SPACES:num_samples]

    p1000 = ctx.load_instrument('p1000_single_gen2',
                                'left',
                                tip_racks=tips1000)  # load P1000 pipette

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

    start_run()

    ############################################################################
    # STEP 1: MIX AND MOVE SAMPLES
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        start = datetime.now()
        for s, d in zip(sample_sources, destinations):
            if not p1000.hw_pipette['has_tip']:
                pick_up(p1000)

            # Mix the sample BEFORE dispensing
            if NUM_MIXES > 0:
                custom_mix(p1000,
                           reagent=Samples,
                           location=s,
                           vol=volume_mix,
                           rounds=NUM_MIXES,
                           blow_out=True,
                           mix_height=5,
                           x_offset=x_offset)

            move_vol_multichannel(p1000,
                                  reagent=Samples,
                                  source=s,
                                  dest=d,
                                  vol=VOLUME_SAMPLE,
                                  air_gap_vol=air_gap_vol_sample,
                                  x_offset=x_offset,
                                  pickup_height=8,
                                  rinse=Samples.rinse,
                                  disp_height=-10,
                                  blow_out=True,
                                  touch_tip=False,
                                  shakes=SAMPLE_SAKES)

            p1000.drop_tip(home_after=False)
            tip_track['counts'][p1000] += 1

        # Time statistics
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write(
                'STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program
    # from opentrons.drivers.rpi_drivers import gpio

    finish_run(switch_off_lights)
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': 'Transfer beads'
        }
    }
    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    folder_path = '/var/lib/jupyter/notebooks'
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/Station_KB_sample_prep_viral_path2_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self,
                     name,
                     flow_rate_aspirate,
                     flow_rate_dispense,
                     rinse,
                     reagent_reservoir_volume,
                     delay,
                     num_wells,
                     h_cono,
                     v_fondo,
                     tip_recycling='none'):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused = []
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells

    # Reagents and their characteristics
    Sample = Reagent(name='Sample',
                     flow_rate_aspirate=1,
                     flow_rate_dispense=1,
                     rinse=True,
                     delay=0,
                     reagent_reservoir_volume=460 * 96,
                     num_wells=96,
                     h_cono=1.95,
                     v_fondo=35)

    Beads = Reagent(name='Magnetic beads and binding solution',
                    flow_rate_aspirate=0.75,
                    flow_rate_dispense=0.75,
                    rinse=True,
                    num_wells=math.ceil(NUM_SAMPLES / 32),
                    delay=2,
                    reagent_reservoir_volume=550 * 8 * num_cols * 1.1,
                    h_cono=1.95,
                    v_fondo=695)  # Prismatic

    MS = Reagent(
        name='MS2',
        flow_rate_aspirate=1,
        flow_rate_dispense=1,
        rinse=False,
        reagent_reservoir_volume=total_MS_volume,
        num_wells=8,
        delay=0,
        h_cono=h_cone,
        v_fondo=volume_cone  # V cono
    )  # Prismatic)

    Sample.vol_well = Sample.reagent_reservoir_volume
    Beads.vol_well = Beads.vol_well_original
    MS.vol_well = MS.reagent_reservoir_volume

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol,
                              x_offset, pickup_height, rinse, disp_height,
                              blow_out, touch_tip):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet,
                       reagent,
                       location=source,
                       vol=vol,
                       rounds=2,
                       blow_out=True,
                       mix_height=0,
                       x_offset=x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x=x_offset[0]))
        pipet.aspirate(vol, s,
                       rate=reagent.flow_rate_aspirate)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol,
                           source.top(z=-2),
                           rate=reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z=disp_height).move(Point(x=x_offset[1]))
        pipet.dispense(vol + air_gap_vol,
                       drop,
                       rate=reagent.flow_rate_dispense)  # dispense all
        ctx.delay(
            seconds=reagent.delay)  # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z=-2))
        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=-5)

    def custom_mix(pipet,
                   reagent,
                   location,
                   vol,
                   rounds,
                   blow_out,
                   mix_height,
                   x_offset,
                   source_height=3):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height == 0:
            mix_height = 3
        pipet.aspirate(1,
                       location=location.bottom(z=source_height).move(
                           Point(x=x_offset[0])),
                       rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol,
                           location=location.bottom(z=source_height).move(
                               Point(x=x_offset[0])),
                           rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol,
                           location=location.bottom(z=mix_height).move(
                               Point(x=x_offset[1])),
                           rate=reagent.flow_rate_dispense)
        pipet.dispense(1,
                       location=location.bottom(z=mix_height).move(
                           Point(x=x_offset[1])),
                       rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out

    def calc_height(reagent,
                    cross_section_area,
                    aspirate_volume,
                    min_height=0.5,
                    extra_volume=50):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume + extra_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area  #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    def divide_destinations(l, n):
        # Divide the list of destinations in size n lists.
        for i in range(0, len(l), n):
            yield l[i:i + n]

    ####################################
    # load labware and modules
    # 12 well rack
    reagent_res = ctx.load_labware('perkinelmer_12_reservoir_21000ul', '2',
                                   'Reagent deepwell plate')

    ##################################
    # Sample prep plate - final plate, goes to Kingfisher
    sample_plate = ctx.load_labware('kf_96_wellplate_2400ul', '1',
                                    'KF 96 Well 2400ul elution plate')

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

    ##################################
    # MS plate -  plate with a column containing the internal control MS
    ms_plate = tempdeck.load_labware('vwr_96_wellplate_200ul_alum_opentrons',
                                     'pcr plate with MS control')

    ####################################
    # load labware and modules
    # 24 well rack aluminium opentrons
    #tuberack = ctx.load_labware(
    #    'opentrons_24_aluminumblock_generic_2ml_screwcap', '3',
    #    'Bloque Aluminio opentrons 24 Screwcaps')

    ##################################
    # Load Tipracks
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot)
        for slot in ['6']
    ]

    tips200 = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', slot)
        for slot in ['3']
    ]

    # pipettes. P1000 currently deactivated
    m300 = ctx.load_instrument('p300_multi_gen2', 'right',
                               tip_racks=tips200)  # Load multi pipette
    m20 = ctx.load_instrument('p20_multi_gen2', 'left', tip_racks=tips20)

    #p20 = ctx.load_instrument(
    #    'p20_single_gen2', 'left', tip_racks=tips20)  # load P1000 pipette

    tip_track = {
        'counts': {
            m300: 0,
            m20: 0
        },
        'maxes': {
            m300: len(tips200) * 96,
            m20: len(tips20) * 96
        }
    }

    # Divide destination wells in small groups for P300 pipette
    # Declare which reagents are in each reservoir as well as deepwell and elution plate
    #destinations = list(divide_destinations(sample_plate.wells()[:NUM_SAMPLES], size_transfer))
    Beads.reagent_reservoir = reagent_res.rows(
    )[0][:Beads.num_wells]  # 1 row, 4 columns (first ones)
    work_destinations = sample_plate.wells()[:NUM_SAMPLES]
    work_destinations_cols = sample_plate.rows()[0][:num_cols]
    ms_origins = ms_plate.rows()[0][0]  # 1 row, 1 columns

    ############################################################################
    # STEP 1: Transfer MS
    ############################################################################

    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment('ms_wells')
        #Loop over defined wells
        for d in work_destinations_cols:
            m20.pick_up_tip()
            #Source samples
            move_vol_multichannel(m20,
                                  reagent=MS,
                                  source=ms_origins,
                                  dest=d,
                                  vol=MS_vol,
                                  air_gap_vol=air_gap_vol_MS,
                                  x_offset=x_offset,
                                  pickup_height=0.2,
                                  disp_height=-35,
                                  rinse=False,
                                  blow_out=True,
                                  touch_tip=True)
            m20.drop_tip()
            tip_track['counts'][m20] += 8

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write(
                'STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # STEP 2: TRANSFER BEADS
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        # Transfer parameters
        start = datetime.now()
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        beads_transfer_vol = [150, 150, 150,
                              100]  # 4 rounds of different volumes
        rinse = True
        for i in range(num_cols):
            if not m300.hw_pipette['has_tip']:
                m300.pick_up_tip()
            for j, transfer_vol in enumerate(beads_transfer_vol):
                # Calculate pickup_height based on remaining volume and shape of container
                [pickup_height, change_col] = calc_height(Beads,
                                                          multi_well_rack_area,
                                                          transfer_vol * 8,
                                                          min_height=1)
                if change_col == True:  # If we switch column because there is not enough volume left in current reservoir column we mix new column
                    ctx.comment('Mixing new reservoir column: ' +
                                str(Beads.col))
                    custom_mix(m300,
                               Beads,
                               Beads.reagent_reservoir[Beads.col],
                               vol=170,
                               rounds=10,
                               blow_out=False,
                               mix_height=0,
                               x_offset=x_offset)
                ctx.comment('Aspirate from reservoir column: ' +
                            str(Beads.col))
                ctx.comment('Pickup height is ' + str(pickup_height))
                if j != 0:
                    rinse = False
                move_vol_multichannel(
                    m300,
                    reagent=Beads,
                    source=Beads.reagent_reservoir[Beads.col],
                    dest=work_destinations_cols[i],
                    vol=transfer_vol,
                    air_gap_vol=air_gap_vol,
                    x_offset=x_offset,
                    pickup_height=pickup_height,
                    disp_height=-2,
                    rinse=rinse,
                    blow_out=False,
                    touch_tip=False)

        m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write(
                'STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program
    gpio.set_rail_lights(False)
    time.sleep(2)
    #os.system('mpg123 -f -8000 /var/lib/jupyter/notebooks/toreador.mp3 &')
    for i in range(3):
        gpio.set_rail_lights(False)
        gpio.set_button_light(1, 0, 0)
        time.sleep(0.3)
        gpio.set_rail_lights(True)
        gpio.set_button_light(0, 0, 1)
        time.sleep(0.3)
    gpio.set_button_light(0, 1, 0)
    ctx.comment('Finished! \nMove plate to KingFisher')
Example #18
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')
Example #19
0
def run(ctx: protocol_api.ProtocolContext):
    ctx.comment('Actual used columns: ' + str(num_cols))

    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times
        1: {
            'Execute': True,
            'description': 'Transfer Mmix'
        },
        2: {
            'Execute': True,
            'description': 'Transfer samples'
        },
        3: {
            'Execute': True,
            'description': 'Transfer negative control'
        },
        4: {
            'Execute': True,
            'description': 'Transfer positive control'
        }
    }

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    folder_path = '/var/lib/jupyter/notebooks' + run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/Station_C_Vitro_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self,
                     name,
                     flow_rate_aspirate,
                     flow_rate_dispense,
                     rinse,
                     reagent_reservoir_volume,
                     delay,
                     num_wells,
                     h_cono,
                     v_fondo,
                     tip_recycling='none'):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused = []
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells

    # Reagents and their characteristics
    Mmix = Reagent(
        name='Mmix',
        rinse=False,
        flow_rate_aspirate=3,
        flow_rate_dispense=3,
        reagent_reservoir_volume=1800,
        num_wells=1,  #change with num samples
        delay=0,
        h_cono=h_cone,
        v_fondo=volume_cone  # V cono
    )

    Samples = Reagent(
        name='Samples',
        rinse=False,
        flow_rate_aspirate=1,
        flow_rate_dispense=1,
        reagent_reservoir_volume=50,
        delay=0,
        num_wells=num_cols,  # num_cols comes from available columns
        h_cono=0,
        v_fondo=0)

    Mmix.vol_well = Mmix.vol_well_original
    Samples.vol_well = Samples.vol_well_original

    ##################
    # Custom functions
    def divide_volume(volume, max_vol):
        num_transfers = math.ceil(volume / max_vol)
        vol_roundup = math.ceil(volume / num_transfers)
        last_vol = volume - vol_roundup * (num_transfers - 1)
        vol_list = [vol_roundup for v in range(1, num_transfers)]
        vol_list.append(last_vol)
        return vol_list

    def divide_destinations(l, n):
        # Divide the list of destinations in size n lists.
        for i in range(0, len(l), n):
            yield l[i:i + n]

    def distribute_custom(pipette,
                          volume,
                          src,
                          dest,
                          waste_pool,
                          pickup_height,
                          extra_dispensal,
                          dest_x_offset,
                          disp_height=0):
        # Custom distribute function that allows for blow_out in different location and adjustement of touch_tip
        pipette.aspirate((len(dest) * volume) + extra_dispensal,
                         src.bottom(pickup_height))
        pipette.touch_tip(speed=20, v_offset=-5)
        pipette.move_to(src.top(z=5))
        pipette.aspirate(5)  # air gap
        for d in dest:
            pipette.dispense(5, d.top())
            drop = d.top(z=disp_height).move(Point(x=dest_x_offset))
            pipette.dispense(volume, drop)
            pipette.move_to(d.top(z=5))
            pipette.aspirate(5)  # air gap
        try:
            pipette.blow_out(waste_pool.wells()[0].bottom(pickup_height + 3))
        except:
            pipette.blow_out(waste_pool.bottom(pickup_height + 3))
        return (len(dest) * volume)

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol,
                              x_offset, pickup_height, rinse, disp_height,
                              blow_out, touch_tip):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet,
                       reagent,
                       location=source,
                       vol=vol,
                       rounds=2,
                       blow_out=True,
                       mix_height=0,
                       x_offset=x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x=x_offset[0]))
        pipet.aspirate(vol, s)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol,
                           source.top(z=-2),
                           rate=reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z=disp_height).move(Point(x=x_offset[1]))
        pipet.dispense(vol + air_gap_vol,
                       drop,
                       rate=reagent.flow_rate_dispense)  # dispense all
        ctx.delay(
            seconds=reagent.delay)  # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z=-2))
        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=-5, radius=0.5)

    def custom_mix(pipet,
                   reagent,
                   location,
                   vol,
                   rounds,
                   blow_out,
                   mix_height,
                   x_offset,
                   source_height=3):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height <= 0:
            mix_height = 3
        pipet.aspirate(1,
                       location=location.bottom(z=source_height).move(
                           Point(x=x_offset[0])),
                       rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol,
                           location=location.bottom(z=source_height).move(
                               Point(x=x_offset[0])),
                           rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol,
                           location=location.bottom(z=mix_height).move(
                               Point(x=x_offset[1])),
                           rate=reagent.flow_rate_dispense)
        pipet.dispense(1,
                       location=location.bottom(z=mix_height).move(
                           Point(x=x_offset[1])),
                       rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out

    def calc_height(reagent,
                    cross_section_area,
                    aspirate_volume,
                    min_height=0.5):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area  #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    ####################################
    # load labware and modules
    # 24 well rack
    tuberack = ctx.load_labware(
        'opentrons_24_aluminumblock_generic_2ml_screwcap', '2',
        'Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap')

    ############################################
    # tempdecks
    tempdeck_orig = ctx.load_module('Temperature Module Gen2', '4')
    tempdeck_dest = ctx.load_module('Temperature Module Gen2', '1')

    if SET_TEMP_ON_SLOT_4:
        tempdeck_orig.set_temperature(TEMPERATURE_SLOT_4)
    if SET_TEMP_ON_SLOT_1:
        tempdeck_dest.set_temperature(TEMPERATURE_SLOT_1)

    ##################################
    # Sample plate - comes from B
    source_plate = tempdeck_orig.load_labware(
        'kingfisher_96_aluminumblock_200ul',
        'Kingfisher 96 Aluminum Block 200 uL')
    samples = source_plate.wells()[:NUM_SAMPLES]

    ##################################
    # qPCR plate - final plate, goes to PCR
    qpcr_plate = tempdeck_dest.load_labware(
        'opentrons_96_aluminumblock_nest_wellplate_100ul',
        'Opentrons 96 Well Aluminum Block with NEST Well Plate 100 uL')

    ##################################
    # Load Tipracks
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot)
        for slot in ['5']
    ]

    tips200 = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', slot)
        for slot in ['3']
    ]

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

    # setup up sample sources and destinations
    samples = source_plate.wells()[2:NUM_SAMPLES]
    pcr_wells = qpcr_plate.wells()[:NUM_SAMPLES]
    pcr_wells_samples = qpcr_plate.wells()[2:NUM_SAMPLES]

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

    # pipettes
    p20 = ctx.load_instrument('p20_single_gen2',
                              mount='right',
                              tip_racks=tips20)
    p300 = ctx.load_instrument('p300_single_gen2',
                               mount='left',
                               tip_racks=tips200)

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {
            p300: 0,
            p20: 0
        },
        'maxes': {
            p300: 96 * len(p300.tip_racks),
            p20: 96 * len(p20.tip_racks)
        }
    }

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) +
                          'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0

        if not pip.hw_pipette['has_tip']:
            pip.pick_up_tip()

    ##########

    ############################################################################
    # STEP 1: TRANSFER MMIX
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        pick_up(p300)
        used_vol = []

        for dest in dests:
            aspirate_volume = MMIX_VOL_PER_SAMPLE * len(dest) + extra_dispensal
            used_vol_temp = distribute_custom(
                p300,
                volume=MMIX_VOL_PER_SAMPLE,
                src=Mmix.reagent_reservoir,
                dest=dest,
                waste_pool=Mmix.reagent_reservoir,
                pickup_height=0.2,
                extra_dispensal=extra_dispensal,
                dest_x_offset=2,
                disp_height=-1)
            used_vol.append(used_vol_temp)
        p300.drop_tip(home_after=False)
        tip_track['counts'][p300] += 1

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 2: TRANSFER SAMPLES
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        for s, d in zip(samples, pcr_wells_samples):
            pick_up(p20)
            move_vol_multichannel(p20,
                                  reagent=Samples,
                                  source=s,
                                  dest=d,
                                  vol=VOLUME_SAMPLE,
                                  air_gap_vol=air_gap_sample,
                                  x_offset=x_offset,
                                  pickup_height=0.2,
                                  disp_height=0,
                                  rinse=False,
                                  blow_out=True,
                                  touch_tip=True)
            p20.drop_tip(home_after=False)
            tip_track['counts'][p20] += 1

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 3: TRANSFER NEGATIVE CONTROL
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        pick_up(p20)
        s = tuberack.rows()[0][1]  # A2
        d = qpcr_plate.wells()[1]  # B1
        move_vol_multichannel(p20,
                              reagent=Samples,
                              source=s,
                              dest=d,
                              vol=VOLUME_SAMPLE,
                              air_gap_vol=air_gap_sample,
                              x_offset=x_offset,
                              pickup_height=0.2,
                              disp_height=0,
                              rinse=False,
                              blow_out=True,
                              touch_tip=True)
        p20.drop_tip(home_after=False)
        tip_track['counts'][p20] += 1

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 4: TRANSFER POSITIVE CONTROL
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        pick_up(p20)
        s = tuberack.rows()[0][2]  # A3
        d = qpcr_plate.wells()[0]  # A1
        move_vol_multichannel(p20,
                              reagent=Samples,
                              source=s,
                              dest=d,
                              vol=VOLUME_SAMPLE,
                              air_gap_vol=air_gap_sample,
                              x_offset=x_offset,
                              pickup_height=0.2,
                              disp_height=0,
                              rinse=False,
                              blow_out=True,
                              touch_tip=True)
        p20.drop_tip(home_after=False)
        tip_track['counts'][p20] += 1

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write(
                'STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program
    for i in range(3):
        #ctx._hw_manager.hardware.set_lights(rails=False)
        ctx._hw_manager.hardware.set_lights(button=(1, 0, 0))
        time.sleep(0.3)
        #ctx._hw_manager.hardware.set_lights(rails=True)
        ctx._hw_manager.hardware.set_lights(button=(0, 0, 1))
        time.sleep(0.3)
    ctx._hw_manager.hardware.set_lights(button=(0, 1, 0))
    ctx.comment('Finished! \nMove plate to PCR')

    total_used_vol = np.sum(used_vol)
    total_needed_volume = total_used_vol
    ctx.comment('Total Mmix used volume is: ' + str(total_used_vol) +
                '\u03BCl.')
    ctx.comment('Needed Mmix volume is ' +
                str(total_needed_volume + extra_dispensal * len(dests)) +
                '\u03BCl')
    ctx.comment('Mmix remaining in tubes is: ' + format(
        np.sum(Mmix.unused) + extra_dispensal * len(dests) + Mmix.vol_well) +
                '\u03BCl.')
    ctx.comment('200 ul Used tips in total: ' + str(tip_track['counts'][p300]))
    ctx.comment('200 ul Used racks in total: ' +
                str(tip_track['counts'][p300] / 96))
    ctx.comment('20 ul Used tips in total: ' + str(tip_track['counts'][p20]))
    ctx.comment('20 ul Used racks in total: ' +
                str(tip_track['counts'][p20] / 96))
def run(ctx: protocol_api.ProtocolContext):
    ctx.comment('Columnas a utilizar: ' + str(num_cols))

    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times
        1: {
            'Execute': True,
            'description': 'Transferir muestras a la placa PCR'
        }
    }

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    folder_path = '/var/lib/jupyter/notebooks' + run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/Station_C_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self,
                     name,
                     flow_rate_aspirate,
                     flow_rate_dispense,
                     rinse,
                     reagent_reservoir_volume,
                     delay,
                     num_wells,
                     h_cono,
                     v_fondo,
                     tip_recycling='none'):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused = []
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells

    # Reagents and their characteristics
    Samples = Reagent(
        name='Samples',
        rinse=False,
        flow_rate_aspirate=sample_aspirate_rate,
        flow_rate_dispense=sample_dispense_rate,
        reagent_reservoir_volume=50,
        delay=0,
        num_wells=num_cols,  # num_cols comes from available columns
        h_cono=0,
        v_fondo=0)

    Samples.vol_well = Samples.vol_well_original

    ##################
    # Custom functions

    def log_parameters():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('VALORES DE VARIABLES')
        ctx.comment(' ')
        ctx.comment('Número de muestras: ' + str(NUM_SAMPLES))
        ctx.comment('Volumen a transferir a la placa PCR: ' +
                    str(VOLUME_PCR_SAMPLE) + ' ul')
        ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE))
        ctx.comment('###############################################')
        ctx.comment(' ')

    def start_run():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Empezando protocolo')
        if PHOTOSENSITIVE == False:
            ctx._hw_manager.hardware.set_lights(button=True, rails=True)
        else:
            ctx._hw_manager.hardware.set_lights(button=True, rails=False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        start_time = now.strftime("%Y/%m/%d %H:%M:%S")
        return start_time

    def finish_run():
        ctx.comment('###############################################')
        ctx.comment('Protocolo finalizado')
        ctx.comment(' ')
        #Set light color to blue
        ctx._hw_manager.hardware.set_lights(button=True, rails=False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        finish_time = now.strftime("%Y/%m/%d %H:%M:%S")
        if PHOTOSENSITIVE == False:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button=False, rails=False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=True, rails=True)
                time.sleep(0.3)
        else:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button=False, rails=False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=True, rails=False)
                time.sleep(0.3)
        ctx._hw_manager.hardware.set_lights(button=True, rails=False)

        ctx.comment('Puntas de 20 ul utilizadas: ' +
                    str(tip_track['counts'][p20]) + ' (' +
                    str(round(tip_track['counts'][p20] / 96, 2)) + ' caja(s))')
        ctx.comment('###############################################')

        return finish_time

    def log_step_start():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('PASO ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')
        return datetime.now()

    def log_step_end(start):
        end = datetime.now()
        time_taken = (end - start)
        STEPS[STEP]['Time:'] = str(time_taken)

        ctx.comment(' ')
        ctx.comment('Paso ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' hizo un tiempo de ' + str(time_taken))
        ctx.comment(' ')

    def move_vol_multichannel(pipet,
                              reagent,
                              source,
                              dest,
                              vol,
                              air_gap_vol,
                              x_offset,
                              pickup_height,
                              rinse,
                              disp_height,
                              blow_out,
                              touch_tip,
                              v_offset=-5,
                              radius=0.5):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet,
                       reagent,
                       location=source,
                       vol=vol,
                       rounds=2,
                       blow_out=True,
                       mix_height=0,
                       x_offset=x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x=x_offset[0]))
        pipet.aspirate(vol, s)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol,
                           source.top(z=-2),
                           rate=reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z=disp_height).move(Point(x=x_offset[1]))
        pipet.dispense(vol + air_gap_vol,
                       drop,
                       rate=reagent.flow_rate_dispense)  # dispense all
        ctx.delay(
            seconds=reagent.delay)  # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z=-2))
        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=v_offset, radius=radius)

    def custom_mix(pipet,
                   reagent,
                   location,
                   vol,
                   rounds,
                   blow_out,
                   mix_height,
                   x_offset,
                   source_height=3):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height <= 0:
            mix_height = 3
        pipet.aspirate(1,
                       location=location.bottom(z=source_height).move(
                           Point(x=x_offset[0])),
                       rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol,
                           location=location.bottom(z=source_height).move(
                               Point(x=x_offset[0])),
                           rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol,
                           location=location.bottom(z=mix_height).move(
                               Point(x=x_offset[1])),
                           rate=reagent.flow_rate_dispense)
        pipet.dispense(1,
                       location=location.bottom(z=mix_height).move(
                           Point(x=x_offset[1])),
                       rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out

    ####################################
    # load labware and modules

    ##################################
    # Sample plate - comes from B
    #source_plate1 = ctx.load_labware(
    #    'nest_96_wellplate_100ul_pcr_full_skirt', '1',
    #    'NEST 96 Well Plate 100 uL PCR Full Skirt')

    source_plate1 = ctx.load_labware(
        'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '1',
        'Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 µL')
    source_plate2 = ctx.load_labware(
        'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '2',
        'Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 µL')

    samples_source1 = source_plate1.columns()[
        0::2]  # Select odd columns from source plate
    samples_source2 = source_plate2.columns()[
        0::2]  # Select odd columns from source plate

    samples_source1 = [
        well for columns in samples_source1 for well in columns
    ]  # list of lists to list
    samples_source2 = [well for columns in samples_source2 for well in columns]

    samples = samples_source1 + samples_source2
    samples = samples[:NUM_SAMPLES]

    ##################################
    # qPCR plate - final plate, goes to PCR
    qpcr_plate = ctx.load_labware(
        'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '4',
        'Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 µL')

    ##################################
    # Load Tipracks
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot)
        for slot in ['7']
    ]

    ################################################################################

    # setup up sample sources and destinations
    pcr_wells = qpcr_plate.wells()[pcr_plate_well_offset:NUM_SAMPLES +
                                   pcr_plate_well_offset]

    # pipettes
    p20 = ctx.load_instrument('p20_single_gen2',
                              mount='right',
                              tip_racks=tips20)

    # used tip counter and set maximum tips available
    tip_track = {'counts': {p20: 0}, 'maxes': {p20: 96 * len(p20.tip_racks)}}

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Reemplaza las cajas de puntas de ' +
                          str(pip.max_volume) + 'µl antes \
                de continuar.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0

        if not pip.hw_pipette['has_tip']:
            pip.pick_up_tip()

    ##########

    log_parameters()
    start_run()
    ############################################################################
    # STEP 1: TRANSFER SAMPLES
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = log_step_start()

        for source, dest in zip(samples, pcr_wells):
            pick_up(p20)
            move_vol_multichannel(p20,
                                  reagent=Samples,
                                  source=source,
                                  dest=dest,
                                  vol=VOLUME_PCR_SAMPLE + 5,
                                  air_gap_vol=air_gap_pcr_sample,
                                  x_offset=x_offset,
                                  pickup_height=0.1,
                                  disp_height=pcr_disp_height,
                                  v_offset=pcr_disp_height,
                                  rinse=False,
                                  blow_out=True,
                                  touch_tip=dispense_touch_tip,
                                  radius=1)

            if dispense_touch_tip == False:
                p20.aspirate(air_gap_vol)

            if recycle_tip:
                p20.return_tip()
            else:
                p20.drop_tip(home_after=False)
                tip_track['counts'][p20] += 1

        log_step_end(start)

    ############################################################################

    finish_run()
Example #21
0
def run(ctx: protocol_api.ProtocolContext):
    import os
    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times
        1: {'Execute': True, 'description': 'Add samples ('+str(volume_sample)+'ul)'},
        2: {'Execute': False, 'description': 'Add internal control one by one (10ul)'}
        # We won't use it as we will do it in KB with the multichannel pipette
    }

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    if not ctx.is_simulating():
        # Folder and file_path for log time
        folder_path = '/var/lib/jupyter/notebooks/'+run_id
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/KA_SampleSetup_viral_time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse,
                     reagent_reservoir_volume, delay, num_wells, h_cono, v_fondo,
                      tip_recycling = 'none',rinse_loops = 2):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.rinse = bool(rinse)
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.delay = delay
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.unused=[]
            self.tip_recycling = tip_recycling
            self.vol_well_original = reagent_reservoir_volume / num_wells
            self.rinse_loops = rinse_loops

    Samples = Reagent(name = 'Samples',
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 1,
                      rinse = False,
                      delay = 0,
                      reagent_reservoir_volume = 100 * 24,
                      num_wells = 24,  # num_cols comes from available columns
                      h_cono = h_cone,
                      v_fondo = volume_cone
                      )  # cone

    IC = Reagent(name = 'Internal control',
                      flow_rate_aspirate = 1,
                      flow_rate_dispense = 3,
                      rinse = False,
                      delay = 0,
                      reagent_reservoir_volume = $IC_total_volume,
                      num_wells = $IC_wells,  # num_cols comes from available columns
                      h_cono = h_cone,
                      v_fondo = volume_cone
                      )  # cone

    Samples.vol_well = Samples.vol_well_original
    IC.vol_well = IC.vol_well_original

    ##################
    # Custom functions
    def generate_source_table(source):
        '''
        Concatenate the wells from the different origin racks
        '''
        for rack_number in range(len(source)):
            if rack_number == 0:
                s = source[rack_number].wells()
            else:
                s = s + source[rack_number].wells()
        return s

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset,
                       pickup_height, rinse, disp_height, blow_out, touch_tip,
                       post_dispense=False, post_dispense_vol=20,
                       post_airgap=False, post_airgap_vol=10):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol,
                       rounds = 2, blow_out = True, mix_height = 0,
                       x_offset = x_offset)
        # SOURCE
        s = source.bottom(pickup_height).move(Point(x = x_offset[0]))
        pipet.aspirate(vol, s, rate = reagent.flow_rate_aspirate)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol, source.top(z = -2),
                           rate = reagent.flow_rate_aspirate)  # air gap
        # GO TO DESTINATION
        drop = dest.top(z = disp_height).move(Point(x = x_offset[1]))
        pipet.dispense(vol + air_gap_vol, drop,
                       rate = reagent.flow_rate_dispense)  # dispense all
        ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent
        if blow_out == True:
            pipet.blow_out(dest.top(z = -2))
        if post_dispense == True:
            pipet.dispense(post_dispense_vol, dest.top(z = -2))
        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = -5, radius = 0.9)
        if post_airgap == True:
            pipet.aspirate(post_airgap_vol, dest.top(z = 5))



    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height,
    x_offset, source_height = 3, post_airgap=False, post_airgap_vol=10,
    post_dispense=False, post_dispense_vol=20,):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height == 0:
            mix_height = 3
        pipet.aspirate(1, location=location.bottom(
            z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
        for _ in range(rounds):
            pipet.aspirate(vol, location=location.bottom(
                z=source_height).move(Point(x=x_offset[0])), rate=reagent.flow_rate_aspirate)
            pipet.dispense(vol, location=location.bottom(
                z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        pipet.dispense(1, location=location.bottom(
            z=mix_height).move(Point(x=x_offset[1])), rate=reagent.flow_rate_dispense)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out
        if post_dispense == True:
            pipet.dispense(post_dispense_vol, location.top(z = -2))
        if post_airgap == True:
            pipet.dispense(post_airgap_vol, location.top(z = 5))

    def calc_height(reagent, cross_section_area, aspirate_volume, min_height = 0.4, extra_volume = 50):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if reagent.vol_well < aspirate_volume + extra_volume:
            reagent.unused.append(reagent.vol_well)
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area
                    #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume - reagent.v_cono) / cross_section_area #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
        pip.pick_up_tip()

    ####################################
    # load labware and modules

    ####################################
    # Load Sample racks
    if NUM_SAMPLES < 96:
        rack_num = math.ceil(NUM_SAMPLES / 24)
        ctx.comment('Used source racks are ' + str(rack_num))
        samples_last_rack = NUM_SAMPLES - rack_num * 24
    else:
        rack_num = 4

    source_tube_types={'Screwcap 2ml': ['opentrons_24_tuberack_generic_2ml_screwcap','source tuberack with screwcap'],
                        'Eppendorf 1.5ml': ['opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap','source tuberack with eppendorf'],
                        }

    source_racks = [ctx.load_labware(
        source_tube_types[source_type][0], slot,
        source_tube_types[source_type][1] + str(i + 1)) for i, slot in enumerate(['4', '1', '6', '3'][:rack_num])
    ]

    ic_source_rack = ctx.load_labware('opentrons_24_tuberack_generic_2ml_screwcap', '9',
    'Internal control source')

    ic_source = ic_source_rack.wells()[0] # internal control comes from 1 bottle

    ##################################
    # Destination plate
    dest_plate = ctx.load_labware(
        'kf_96_wellplate_2400ul', '5', 'KF 96well destination plate')

    ####################################
    # Load tip_racks
    # tips20 = [ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack')
    # for slot in ['2', '8']]
    tips300 = [ctx.load_labware('opentrons_96_filtertiprack_200ul', slot, '200µl filter tiprack')
                for slot in ['10', '11']]
    tips20 = [ctx.load_labware('opentrons_96_filtertiprack_20ul', slot, '20µl filter tiprack')
                for slot in ['8']]

    ################################################################################
    # Declare which reagents are in each reservoir as well as deepwell and elution plate

    # setup samples and destinations
    sample_sources_full = generate_source_table(source_racks)
    sample_sources = sample_sources_full[:NUM_SAMPLES]
    destinations = dest_plate.wells()[:NUM_SAMPLES]

    p20 = ctx.load_instrument(
        'p20_single_gen2', mount='left', tip_racks=tips20)

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

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {p300: 0, p20: 0},  # p1000: 0},
        'maxes': {p300: len(tips300) * 96, p20: len(tips20)*96}  # ,p20: len(tips20)*96,
    }

    ############################################################################
    # STEP 1: Add Samples
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        # Transfer parameters
        start = datetime.now()
        for s, d in zip(sample_sources, destinations):
            if not p300.hw_pipette['has_tip']:
                pick_up(p300)
            # Mix the sample BEFORE dispensing
            #custom_mix(p1000, reagent = Samples, location = s, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15)
            move_vol_multichannel(p300, reagent = Samples, source = s, dest = d,
            vol=volume_sample, air_gap_vol = air_gap_vol, x_offset = x_offset,
                               pickup_height = 0.3, rinse = Samples.rinse, disp_height = -10,
                               blow_out = True, touch_tip = True)
            # Mix the sample AFTER dispensing
            #custom_mix(p300, reagent = Samples, location = d, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15)
            # Drop tip and update counter
            p300.drop_tip(home_after=False)
            tip_track['counts'][p300] += 1

        # Time statistics
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 2: Add internal control
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'])
        ctx.comment('###############################################')

        #BEWARE, everything with the same tip, dispensed from top!

        # Transfer parameters
        start = datetime.now()
        for d in destinations:
            if not p20.hw_pipette['has_tip']:
                pick_up(p20)
            # Mix the sample BEFORE dispensing
            #custom_mix(p1000, reagent = Samples, location = s, vol = volume_sample, rounds = 2, blow_out = True, mix_height = 15)
            move_vol_multichannel(p20, reagent = IC, source = ic_source, dest = d,
                                  vol = ic_volume, air_gap_vol = air_gap_vol, x_offset = x_offset,
                                  pickup_height = 0.4, rinse = IC.rinse, disp_height = -8,
                                  blow_out = True, touch_tip = False)
            # Mix the sample AFTER dispensing
            #custom_mix(p20, reagent = Samples, location = d, vol = 10, rounds = 2,
            #blow_out = True, mix_height = 2, x_offset = x_offset)
            # Drop tip and update counter
        p20.drop_tip()
        tip_track['counts'][p20] += 1

        # Time statistics
        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' + STEPS[STEP]['description'] +
                    ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    # Light flash end of program

    '''if not ctx.is_simulating():
        os.system('mpg123 -f -8000 /etc/audio/speaker-test.mp3 &')'''

    for i in range(3):
        ctx._hw_manager.hardware.set_lights(rails=False)

        time.sleep(0.3)
        ctx._hw_manager.hardware.set_lights(rails=True)

        time.sleep(0.3)
        ctx._hw_manager.hardware.set_lights(rails=False)
    ctx.home()

    ctx.comment(
        'Finished! \nMove deepwell plate to Station B.')

    ctx.comment('Used 200µl tips in total: ' + str(tip_track['counts'][p300]))
    ctx.comment('Used 200ul racks in total: '+str(tip_track['counts'][p300] / 96))

    ctx.comment('Used p20 tips in total: ' + str(tip_track['counts'][p20]))
    ctx.comment('Used p20 racks in total: ' + str(tip_track['counts'][p20] / 96))
Example #22
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):

    # load labware
    dest_plate = ctx.load_labware('nest_96_wellplate_2ml_deep', '2',
                                  '96-deepwell sample plate')
    tipracks1000 = [
        ctx.load_labware('opentrons_96_filtertiprack_1000ul', '1',
                         '1000µl filter tiprack')
    ]

    # load pipette
    p1000 = ctx.load_instrument('p1000_single_gen2',
                                'right',
                                tip_racks=tipracks1000)

    tip_log = {'count': {}}
    folder_path = '/data/A'
    tip_file_path = folder_path + '/tip_log.json'
    if TIP_TRACK and not ctx.is_simulating():
        if os.path.isfile(tip_file_path):
            with open(tip_file_path) as json_file:
                data = json.load(json_file)
                if 'tips1000' in data:
                    tip_log['count'][p1000] = data['tips1000']
                else:
                    tip_log['count'][p1000] = 0
    else:
        tip_log['count'] = {p1000: 0}

    tip_log['tips'] = {
        p1000: [tip for rack in tipracks1000 for tip in rack.wells()]
    }
    tip_log['max'] = {pip: len(tip_log['tips'][pip]) for pip in [p1000]}

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

    # pool samples
    for i in range(math.ceil(NUM_SAMPLES / 2)):
        if NUM_SAMPLES % 2 != 0 and i == math.ceil(NUM_SAMPLES / 2) - 1:
            pool_source_set = [dest_plate.wells()[NUM_SAMPLES]]
            vol = SAMPLE_VOLUME * 2
        else:
            pool_source_set = dest_plate.wells()[i * 2:i * 2 + 2]
            vol = SAMPLE_VOLUME
        for s in pool_source_set:
            pick_up(p1000)
            p1000.transfer(vol,
                           s,
                           dest_plate.wells()[i + 64],
                           air_gap=20,
                           new_tip='never')
            p1000.air_gap(100)
            p1000.drop_tip()

    ctx.comment('Move deepwell plate (slot 2) to Station B for RNA \
extraction.')

    # track final used tip
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        data = {'tips1000': tip_log['count'][p1000]}
        with open(tip_file_path, 'w') as outfile:
            json.dump(data, outfile)
Example #24
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]))
def run(ctx: protocol_api.ProtocolContext):
    # Init protocol run
    run = ProtocolRun(ctx)
    
    # Define stesp
    run.add_step(
        description="Transfer PK+MS2 A6 - To AW_PLATE Single Slot1 -> Slot2")  # 1
    run.add_step(description="Transfer Beats 3 - 2 Multi and mix")  # 2

    # execute avaliaible steps
    run.init_steps(steps)

    ##################################

    # Tube rack
    tube_rack = ctx.load_labware(
        'opentrons_24_tuberack_nest_1.5ml_screwcap', 4)

    # Destination plate SLOT 2
    if(ctx.is_simulating()):
        aw_slot = ctx.load_labware(
            'opentrons_96_aluminumblock_generic_pcr_strip_200ul', 5)
    else:
        aw_slot = ctx.load_labware(
            'axygen_96_wellplate_2000ul', 5)

    aw_wells = aw_slot.wells()[:NUM_SAMPLES]

    # Mount pippets and set racks
    # Tipracks20_multi
    tips20_1 = ctx.load_labware('opentrons_96_tiprack_20ul', 7)
    tips20_2 = ctx.load_labware('opentrons_96_tiprack_20ul', 8)
    
    run.mount_right_pip('p20_single_gen2', tip_racks=[tips20_1,tips20_2], capacity=20)
    
    tips300 = ctx.load_labware('opentrons_96_filtertiprack_200ul', 7)
    
    run.mount_right_pip('p20_single_gen2', tip_racks=[tips20_1,tips20_2], capacity=20)

    ############################################################################
    # STEP 1: Transfer A6 - To AW_PLATE
    ############################################################################
    if (run.next_step()):
        run.set_pip("right")  # single 20
        volumen_move = 5
        source = tube_rack.wells("A6")[0]
        pkms2 = Reagent(
                         name='PK + MS2',
                         num_wells=1,  # change with num samples
                         flow_rate_aspirate=0.75,  # Original 0.5
                         flow_rate_dispense=3,  # Original 1
                         reagent_reservoir_volume=vol_pkms2*(NUM_SAMPLES+1),
                         h_cono=4,
                         v_fondo=4 * math.pi * 4 ** 3 / 3
                         )
        pkms2.set_positions([tube_rack.wells("A5"),tube_rack.wells("B5"),tube_rack.wells("B6")])
        run.comment(pkms2.get_volumes_fill_print(),add_hash=True)

        run.pick_up()
        for dest in aw_wells:
            pickup_height = run.calc_height(
                liquid, 4.12*4.12*math.pi, vol_pkms2)
            run.move_volume(reagent=liquid, source=source,
                            dest=pkms2.get_current_position(), vol=vol_pkms2, air_gap_vol=1,
                            pickup_height=pickup_height, disp_height=-10,
                            blow_out=True, post_dispense=True, post_dispense_vol=5)

        run.drop_tip()
        run.finish_step()

    ############################################################################
    # STEP 2: Slot 3 -2 beats_PK AW
    ############################################################################
    if (run.next_step()):
        ############################################################################
        # Light flash end of program
        run.set_pip("left")  # p300 multi
        volume = 275
        beads = Reagent(name='Magnetic beads',
                        flow_rate_aspirate=0.5,
                        flow_rate_dispense=0.5,
                        flow_rate_dispense_mix=4,
                        flow_rate_aspirate_mix=4,
                        rinse=True,
                        delay=2,
                        reagent_reservoir_volume=vol_beads*(NUM_SAMPLES+1),
                        h_cono=1.95,
                        v_fondo=695,
                        rinse_loops=3)

        beads.set_positions([tube_rack.wells("A6"),tube_rack.wells("B6")])
        air_gap_vol = 5
        disposal_height = -5
        
        for destination in aw_wells:
            
            volumes = beads.divide_volume(vol_beads,180)
            for vol in volumes:
                run.pick_up()
                pickup_height = beads.calc_height(
                    beads, pool_area, vol, extra_volume=vol_min)
                run.move_volume(reagent=beads, source=beads.get_current_position(),
                                dest=destination, vol=vol, air_gap_vol=air_gap_vol,
                                pickup_height=pickup_height, disp_height=disposal_height,
                                rinse=True, blow_out=True)
                
                run.custom_mix(beads, location=destination, vol=vol/2,
                            rounds=3, blow_out=True, mix_height=0)
                run.drop_tip()

        run.finish_step()

    run.log_steps_time()
    run.blink()
    for c in robot.commands():
        ctx.comment(c)
    ctx.comment('Finished! \nMove plate to PCR')
def run(ctx: protocol_api.ProtocolContext):
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description and times
        1: {
            'Execute': True,
            'description': 'Dispensar Antibioticos'
        },
        2: {
            'Execute': True,
            'description': 'Dispensar caldo y mezclar '
        }
    }
    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    if not ctx.is_simulating():
        folder_path = '/var/lib/jupyter/notebooks/' + run_id
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/time_log.txt'

    # Define Reagents as objects with their properties
    #Define Reagents as objects with their properties
    class Reagent:
        def __init__(self,
                     name,
                     flow_rate_aspirate,
                     flow_rate_dispense,
                     flow_rate_aspirate_mix,
                     flow_rate_dispense_mix,
                     air_gap_vol_bottom,
                     air_gap_vol_top,
                     disposal_volume,
                     rinse,
                     max_volume_allowed,
                     reagent_volume,
                     reagent_reservoir_volume,
                     num_wells,
                     h_cono,
                     v_fondo,
                     tip_recycling='none',
                     dead_vol=DEFAULT_DEAD_VOL):
            self.name = name
            self.flow_rate_aspirate = flow_rate_aspirate
            self.flow_rate_dispense = flow_rate_dispense
            self.flow_rate_aspirate_mix = flow_rate_aspirate_mix
            self.flow_rate_dispense_mix = flow_rate_dispense_mix
            self.air_gap_vol_bottom = air_gap_vol_bottom
            self.air_gap_vol_top = air_gap_vol_top
            self.disposal_volume = disposal_volume
            self.rinse = bool(rinse)
            self.max_volume_allowed = max_volume_allowed
            self.reagent_volume = reagent_volume
            self.reagent_reservoir_volume = reagent_reservoir_volume
            self.num_wells = num_wells
            self.col = 0
            self.vol_well = 0
            self.h_cono = h_cono
            self.v_cono = v_fondo
            self.tip_recycling = tip_recycling
            self.dead_vol = dead_vol
            self.vol_well_original = (reagent_reservoir_volume / num_wells
                                      ) + dead_vol if num_wells > 0 else 0

    # Reagents and their characteristics
    Samples = Reagent(
        name='Antibioticos',
        flow_rate_aspirate=3,
        flow_rate_dispense=3,
        flow_rate_aspirate_mix=1,
        flow_rate_dispense_mix=1,
        air_gap_vol_bottom=5,
        air_gap_vol_top=0,
        disposal_volume=0,
        rinse=True,
        max_volume_allowed=180,
        reagent_volume=VOLUME_SAMPLE,
        reagent_reservoir_volume=NUM_REAL_SAMPLES * VOLUME_SAMPLE * 1.1,
        num_wells=math.ceil(NUM_REAL_SAMPLES * VOLUME_SAMPLE * 1.1 / 11500),
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    Caldo = Reagent(
        name='Caldo ',
        flow_rate_aspirate=2,
        flow_rate_dispense=2,
        flow_rate_aspirate_mix=1,
        flow_rate_dispense_mix=1,
        air_gap_vol_bottom=6,
        air_gap_vol_top=0,
        disposal_volume=0,
        rinse=True,
        max_volume_allowed=180,
        reagent_volume=VOLUME_SAMPLE,
        reagent_reservoir_volume=NUM_REAL_SAMPLES * VOLUME_SAMPLE * 1.1,
        num_wells=math.ceil(NUM_REAL_SAMPLES * VOLUME_SAMPLE * 1.1 / 11500),
        h_cono=1.95,
        v_fondo=695)  #1.95 * multi_well_rack_area / 2, #Prismatic

    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VALORES DE VARIABLES')
    ctx.comment(' ')
    ctx.comment('Número Antibióticos: ' + str(NUM_SAMPLE_ANTIBIOTIC) + ' (' +
                str(NUM_FINAL_PLATES) + ' placas)')
    ctx.comment('Número de muestras: ' + str(NUM_REAL_SAMPLES) + ' (' +
                str(math.ceil(NUM_REAL_SAMPLES / 8)) + ' columnas)')
    ctx.comment('Número de mezclas: ' + str(NUM_DILUTION_MIXES))
    ctx.comment(' ')
    ctx.comment('Volumen de muestra a mover al deepwell: ' +
                str(VOLUME_SAMPLE) + ' ul')
    ctx.comment(' ')
    ctx.comment('Número de mezclas en la muestra: ' + str(TUBE_NUM_MIXES))
    ctx.comment(' ')
    ctx.comment('Repeticiones del sonido final: ' + str(SOUND_NUM_PLAYS))
    ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE))
    ctx.comment(' ')

    ##################
    # Custom functions
    def move_multichanel_caldo(dest):

        if not m300.hw_pipette['has_tip']:
            pick_up_multi(m300)

        caldo_trips = math.floor(Caldo.max_volume_allowed /
                                 Caldo.reagent_volume)
        caldo_volume = Caldo.reagent_volume * caldo_trips  #136.66
        Caldo.max_volume_allowed = caldo_volume
        caldo_transfer_vol = []

        x_offset_source = 0
        x_offset_dest = 0
        rinse = False  # Original: True
        first_mix_done = False
        actual_pipette_vol = 0
        for i in range(num_cols_caldo):
            ctx.comment("Column: " + str(i))
            #Calculate pickup_height based on remaining volume and shape of container
            ctx.comment('Aspirate from reservoir column: ' + str(Caldo.col))
            if actual_pipette_vol >= VOLUME_SAMPLE:
                recharge = False
            else:
                actual_pipette_vol = Caldo.max_volume_allowed
                recharge = True
            move_vol_multi(m300,
                           reagent=Caldo,
                           source=Caldo.reagent_reservoir,
                           dest=dest[i],
                           vol=Caldo.reagent_volume,
                           x_offset_source=x_offset_source,
                           x_offset_dest=x_offset_dest,
                           pickup_height=0,
                           rinse=rinse,
                           wait_time=0.5,
                           blow_out=False,
                           touch_tip=True,
                           drop_height=-3,
                           recharge=recharge)
            actual_pipette_vol = actual_pipette_vol - VOLUME_SAMPLE

        m300.blow_out(Caldo.reagent_reservoir.top(z=0))
        ctx.delay(seconds=2, msg='Waiting for ' + str(1) + ' seconds.')
        m300.move_to(Caldo.reagent_reservoir.bottom(2))
        m300.blow_out(Caldo.reagent_reservoir.top(z=0))

    def move_multichanel_mezcla(origin, dest, pickup_height=2, air_gap_vol=5):
        x_offset_source = 0
        x_offset_dest = 0

        first_mix_done = False
        if not m300.hw_pipette['has_tip']:
            pick_up(m300)
        for i in range(num_cols_caldo):
            ctx.comment("Column: " + str(i))
            ctx.comment('Aspirate from reservoir column: ' + str(Caldo.col))

            move_vol_multi(m300,
                           reagent=Caldo,
                           source=origin[i],
                           dest=dest[i],
                           vol=Caldo.reagent_volume,
                           multitripCount=1,
                           x_offset_source=x_offset_source,
                           x_offset_dest=x_offset_dest,
                           pickup_height=pickup_height,
                           rinse=False,
                           rinseEnd=True,
                           wait_time=0.5,
                           blow_out=False,
                           touch_tip=False,
                           drop_height=-1,
                           rate_multiplier=0.1)
        # Aspirar 50 ul de la última columna para igualar volúmenes
        m300.aspirate(Caldo.reagent_volume,
                      location=dest[num_cols_caldo -
                                    1].bottom(z=pickup_height),
                      rate=Caldo.flow_rate_aspirate)
        if air_gap_vol > 0:
            m300.aspirate(air_gap_vol,
                          dest[num_cols_caldo - 1].top(z=0),
                          rate=Caldo.flow_rate_aspirate)  # air gap

        m300.drop_tip(home_after=False)
        tip_track['counts'][m300] += 8

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol,
                              x_offset, pickup_height, rinse, disp_height,
                              blow_out, touch_tip):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''

        # SOURCE
        s = source.bottom(pickup_height).move(Point(x=x_offset[0]))
        pipet.aspirate(vol, s,
                       rate=reagent.flow_rate_aspirate)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol,
                           source.top(z=-2),
                           rate=reagent.flow_rate_aspirate)  # air gap

        # GO TO DESTINATION
        drop = dest.top(z=disp_height).move(Point(x=x_offset[1]))
        pipet.dispense(vol + air_gap_vol,
                       drop,
                       rate=reagent.flow_rate_dispense)  # dispense all

        #ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent

        if blow_out == True:
            pipet.blow_out(dest.top(z=disp_height))

        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=-10)

        if air_gap_vol != 0:
            pipet.air_gap(air_gap_vol, height=disp_height)  #air gap

    def move_vol_multi(pipet,
                       reagent,
                       source,
                       dest,
                       vol,
                       x_offset_source,
                       x_offset_dest,
                       pickup_height,
                       rinse,
                       wait_time,
                       blow_out,
                       touch_tip=False,
                       drop_height=-5,
                       dispense_bottom_air_gap_before=False,
                       rinseEnd=False,
                       recharge=True,
                       multitripCount=3,
                       rate_multiplier=1):
        # Rinse before aspirating
        if rinse == True:
            #pipet.aspirate(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap
            custom_mix(pipet,
                       reagent,
                       location=source,
                       vol=vol * .6,
                       rounds=NUM_DILUTION_MIXES,
                       blow_out=False,
                       mix_height=pickup_height,
                       offset=0)
            #pipet.dispense(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_dispense)

        # SOURCE
        if recharge == True:

            #if dispense_bottom_air_gap_before and reagent.air_gap_vol_bottom:
            #    pipet.dispense(reagent.air_gap_vol_bottom, source.top(z = -2), rate = reagent.flow_rate_dispense)

            if reagent.air_gap_vol_top != 0:  #If there is air_gap_vol, switch pipette to slow speed
                pipet.move_to(source.top(z=0))
                pipet.air_gap(reagent.air_gap_vol_top)  #air gap
                #pipet.aspirate(reagent.air_gap_vol_top, source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap

            pipet.blow_out(source.top(z=drop_height))
            s = source.bottom(pickup_height).move(Point(x=x_offset_source))
            pipet.aspirate(vol * multitripCount,
                           s,
                           rate=reagent.flow_rate_aspirate *
                           rate_multiplier)  # aspirate liquid

            if reagent.air_gap_vol_bottom != 0:  #If there is air_gap_vol, switch pipette to slow speed
                pipet.air_gap(reagent.air_gap_vol_bottom, height=0)  #air gap

            if wait_time != 0:
                ctx.delay(seconds=wait_time,
                          msg='Waiting for ' + str(wait_time) + ' seconds.')

        # GO TO DESTINATION
        d = dest.top(z=drop_height).move(Point(x=x_offset_dest))
        pipet.dispense(vol + reagent.air_gap_vol_bottom,
                       d,
                       rate=reagent.flow_rate_dispense * rate_multiplier)

        if wait_time != 0:
            ctx.delay(seconds=wait_time,
                      msg='Waiting for ' + str(wait_time) + ' seconds.')

        if reagent.air_gap_vol_top != 0:
            pipet.dispense(reagent.air_gap_vol_top,
                           dest.top(z=0),
                           rate=reagent.flow_rate_dispense)

        if blow_out == True:
            pipet.blow_out(dest.top(z=drop_height))

        if touch_tip == True:
            pipet.touch_tip(speed=20, v_offset=-3, radius=0.7)

        # Rinse after aspirating
        if rinseEnd == True:
            #pipet.aspirate(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_aspirate) #air gap
            custom_mix(pipet,
                       reagent,
                       location=dest,
                       vol=vol * .6,
                       rounds=2,
                       blow_out=False,
                       mix_height=pickup_height,
                       offset=0)
            pipet.blow_out(dest.top(z=drop_height))
            #pipet.dispense(air_gap_vol_top, location = source.top(z = -5), rate = reagent.flow_rate_dispense)

        if reagent.air_gap_vol_bottom != 0 and rinseEnd == False:
            pipet.move_to(dest.top(z=0))
            pipet.air_gap(reagent.air_gap_vol_bottom)  #air gap
            #pipet.aspirate(air_gap_vol_bottom, dest.top(z = 0),rate = reagent.flow_rate_aspirate) #air gap

    def custom_mix(pipet,
                   reagent,
                   location,
                   vol,
                   rounds,
                   blow_out,
                   mix_height,
                   offset,
                   wait_time=0,
                   drop_height=-1,
                   two_thirds_mix_bottom=False):
        '''
        Function for mix in the same location a certain number of rounds. Blow out optional. Offset
        can set to 0 or a higher/lower value which indicates the lateral movement
        '''
        if mix_height <= 0:
            mix_height = 1
        pipet.aspirate(1,
                       location=location.bottom(z=mix_height),
                       rate=reagent.flow_rate_aspirate_mix)
        for i in range(rounds):
            pipet.aspirate(vol,
                           location=location.bottom(z=mix_height),
                           rate=reagent.flow_rate_aspirate_mix)
            if two_thirds_mix_bottom and i < ((rounds / 3) * 2):
                pipet.dispense(vol,
                               location=location.bottom(z=5).move(
                                   Point(x=offset)),
                               rate=reagent.flow_rate_dispense_mix)
            else:
                pipet.dispense(vol,
                               location=location.top(z=drop_height).move(
                                   Point(x=offset)),
                               rate=reagent.flow_rate_dispense_mix)
        pipet.dispense(1,
                       location=location.bottom(z=mix_height),
                       rate=reagent.flow_rate_dispense_mix)
        if blow_out == True:
            pipet.blow_out(location.top(z=-2))  # Blow out
        if wait_time != 0:
            ctx.delay(seconds=wait_time,
                      msg='Waiting for ' + str(wait_time) + ' seconds.')

    def generate_atibiotic_source(source_plate):
        result = []
        '''
        for i in range (NUM_FINAL_PLATES):
            result += source_plate.rows()[i][0]
        '''
        result = source_plate.rows()[:NUM_FINAL_PLATES][0]
        return result

    def generate_antibiotic_dest(dest_plates):
        '''
        Concatenate cols from all destination plates
        '''
        result = []
        #result = dest_plates[0].columns()[0][0] + dest_plates[1].columns()[0] + dest_plates[2].columns()[0]
        for i in range(NUM_FINAL_PLATES):
            result += [dest_plates[i].columns()[0][0]]
        return result

    def calc_height(reagent,
                    cross_section_area,
                    aspirate_volume,
                    min_height=0.4):
        nonlocal ctx
        ctx.comment('Remaining volume ' + str(reagent.vol_well) +
                    '< needed volume ' + str(aspirate_volume) + '?')
        if (reagent.vol_well - reagent.dead_vol) < aspirate_volume:
            ctx.comment('Next column should be picked')
            ctx.comment('Previous to change: ' + str(reagent.col))
            # column selector position; intialize to required number
            reagent.col = reagent.col + 1
            ctx.comment(str('After change: ' + str(reagent.col)))
            reagent.vol_well = reagent.vol_well_original
            ctx.comment('New volume:' + str(reagent.vol_well))
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            #- reagent.h_cono
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Remaining volume:' + str(reagent.vol_well))
            if height < min_height:
                height = min_height
            col_change = True
        else:
            height = (reagent.vol_well - aspirate_volume -
                      reagent.v_cono) / cross_section_area
            reagent.vol_well = reagent.vol_well - aspirate_volume
            ctx.comment('Calculated height is ' + str(height))
            if height < min_height:
                height = min_height
            ctx.comment('Used height is ' + str(height))
            col_change = False
        return height, col_change

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) +
                          'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0
        pip.pick_up_tip()

    def pick_up_multi(pip):
        nonlocal tip_track
        #if not ctx.is_simulating():
        if tip_track['counts'][pip] >= tip_track['maxes'][pip]:
            for i in range(3):
                ctx._hw_manager.hardware.set_lights(rails=False)
                ctx._hw_manager.hardware.set_lights(button=(1, 0, 0))
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(rails=True)
                ctx._hw_manager.hardware.set_lights(button=(0, 0, 1))
                time.sleep(0.3)
            ctx._hw_manager.hardware.set_lights(button=(0, 1, 0))
            ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
            resuming.')
            pip.reset_tipracks()
            tip_track['counts'][pip] = 0
            tip_track['num_refills'][pip] += 1
        pip.pick_up_tip()

    def run_quiet_process(command):
        subprocess.check_output('{} &> /dev/null'.format(command), shell=True)

    def play_sound(filename):
        print('Speaker')
        print('Next\t--> CTRL-C')
        try:
            run_quiet_process('mpg123 {}'.format(path_sounds + filename +
                                                 '.mp3'))
            run_quiet_process('mpg123 {}'.format(path_sounds + sonido_defecto))
            run_quiet_process('mpg123 {}'.format(path_sounds + filename +
                                                 '.mp3'))

        except KeyboardInterrupt:
            pass
            print()

    def start_run():
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Empezando protocolo')
        if PHOTOSENSITIVE == False:
            ctx._hw_manager.hardware.set_lights(button=True, rails=True)
        else:
            ctx._hw_manager.hardware.set_lights(button=True, rails=False)
        now = datetime.now()

        # dd/mm/YY H:M:S
        start_time = now.strftime("%Y/%m/%d %H:%M:%S")
        return start_time

    def finish_run(switch_off_lights=False):
        ctx.comment('###############################################')
        ctx.comment('Protocolo finalizado')
        ctx.comment(' ')
        #Set light color to blue
        ctx._hw_manager.hardware.set_lights(button=True, rails=False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        finish_time = now.strftime("%Y/%m/%d %H:%M:%S")
        if PHOTOSENSITIVE == False:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button=False, rails=False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=True, rails=True)
                time.sleep(0.3)
        else:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button=False, rails=False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button=True, rails=False)
                time.sleep(0.3)
        if switch_off_lights:
            ctx._hw_manager.hardware.set_lights(button=True, rails=False)

        used_tips_300 = tip_track['num_refills'][m300] * 96 * len(
            m300.tip_racks) + tip_track['counts'][m300]
        ctx.comment('Puntas de 300 ul utilizadas: ' + str(used_tips_300) +
                    ' (' + str(round(used_tips_300 / 96, 2)) + ' caja(s))')
        ctx.comment('###############################################')

        if not ctx.is_simulating():
            for i in range(SOUND_NUM_PLAYS):
                if i > 0:
                    time.sleep(60)
                play_sound('finished_process_esp')

            return finish_time

    def validate_parameters():
        result = True

        return result

    ####################################
    # load labware and modules

    ####################################
    # Load Sample racks

    source_antibiotic = ctx.load_labware('nest_96_wellplate_2ml_deep', '8',
                                         'NEST 96 Deepwell Plate 2mL')
    '''
     ctx.load_labware(
        'opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap', '8',
        'Opentrons 24 Tuberack Eppendorf 2ml Safelock Snapcap')
    '''

    source_caldo = ctx.load_labware('nest_1_reservoir_195ml', '1',
                                    'NEST 1 Reservoir 195ul')

    ##################################
    # Destination plate
    # Destination
    dest_plate1 = ctx.load_labware('nest_96_wellplate_200ul_flat', '3',
                                   'NEST 96 Well Plate 200ul 1')

    dest_plate2 = ctx.load_labware('nest_96_wellplate_200ul_flat', '5',
                                   'NEST 96 Well Plate 200ul 2')

    dest_plate3 = ctx.load_labware('nest_96_wellplate_200ul_flat', '7',
                                   'NEST 96 Well Plate 200ul 3')

    ####################################
    # Load tip_racks

    tips200 = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', slot)
        for slot in ['10']
    ]

    ################################################################################
    # setup samples and destinations
    num_destination_plates = NUM_FINAL_PLATES
    #sample_antibiotic      = source_antibiotic.wells()[:NUM_SAMPLE_ANTIBIOTIC]
    sample_antibiotic = generate_atibiotic_source(source_antibiotic)
    Caldo.reagent_reservoir = source_caldo.wells()[0]
    destinations_antibiotic = generate_antibiotic_dest(
        [dest_plate1, dest_plate2, dest_plate3])  #[:NUM_SAMPLE_ANTIBIOTIC]
    destinations1 = dest_plate1.rows()[0][1:]
    destinations2 = dest_plate2.rows()[0][1:]
    destinations3 = dest_plate3.rows()[0][1:]

    sourceMix1 = dest_plate1.rows()[0][:11]
    sourceMix2 = dest_plate2.rows()[0][:11]
    sourceMix3 = dest_plate3.rows()[0][:11]
    destinationsMix1 = dest_plate1.rows()[0][1:12]
    destinationsMix2 = dest_plate2.rows()[0][1:12]
    destinationsMix3 = dest_plate3.rows()[0][1:12]

    m300 = ctx.load_instrument('p300_multi_gen2', 'right',
                               tip_racks=tips200)  # load P300 pipette

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {
            m300: 0
        },
        'maxes': {
            m300: 96 * len(m300.tip_racks)
        },
        'num_refills': {
            m300: 0
        },
        'tips': {
            m300: [tip for rack in tips200 for tip in rack.rows()[0]]
        }
    }

    if validate_parameters():

        start_run()

        ############################################################################
        # STEP 1: Dispensación de Atnibiótico
        ############################################################################
        STEP += 1
        if STEPS[STEP]['Execute'] == True:
            ctx.comment('Step ' + str(STEP) + ': ' +
                        STEPS[STEP]['description'])
            ctx.comment('###############################################')

            start = datetime.now()
            for s, d in zip(sample_antibiotic, destinations_antibiotic):
                # Mix the sample BEFORE dispensing
                if not m300.hw_pipette['has_tip']:
                    pick_up(m300)
                move_vol_multichannel(m300,
                                      reagent=Samples,
                                      source=s,
                                      dest=d,
                                      vol=VOLUME_ANTBIOTIC,
                                      air_gap_vol=air_gap_vol_sample,
                                      x_offset=x_offset,
                                      pickup_height=3,
                                      rinse=Samples.rinse,
                                      disp_height=0,
                                      blow_out=True,
                                      touch_tip=False)
                m300.drop_tip(home_after=False)
                tip_track['counts'][m300] += 1

            # Time statistics
            end = datetime.now()
            time_taken = (end - start)
            ctx.comment('Step ' + str(STEP) + ': ' +
                        STEPS[STEP]['description'] + ' took ' +
                        str(time_taken))
            STEPS[STEP]['Time:'] = str(time_taken)

        ############################################################################
        # STEP 2: Dilución de antibiótico sobre el caldo
        ############################################################################
        STEP += 1
        if STEPS[STEP]['Execute'] == True:
            ctx.comment('Step ' + str(STEP) + ': ' +
                        STEPS[STEP]['description'])
            ctx.comment('###############################################')

            start = datetime.now()

            if num_destination_plates >= 1:
                move_multichanel_caldo(destinations1)
                move_multichanel_mezcla(sourceMix1, destinationsMix1)
            if num_destination_plates >= 2:
                move_multichanel_caldo(destinations2)
                move_multichanel_mezcla(sourceMix2, destinationsMix2)
            if num_destination_plates >= 3:
                move_multichanel_caldo(destinations3)
                move_multichanel_mezcla(sourceMix3, destinationsMix3)

            # Time statistics
            end = datetime.now()
            time_taken = (end - start)
            ctx.comment('Step ' + str(STEP) + ': ' +
                        STEPS[STEP]['description'] + ' took ' +
                        str(time_taken))
            STEPS[STEP]['Time:'] = str(time_taken)

        # Export the time log to a tsv file
        if not ctx.is_simulating():
            with open(file_path, 'w') as f:
                f.write(
                    'STEP\texecution\tdescription\twait_time\texecution_time\n'
                )
                for key in STEPS.keys():
                    row = str(key)
                    for key2 in STEPS[key].keys():
                        row += '\t' + format(STEPS[key][key2])
                    f.write(row + '\n')
            f.close()

        ############################################################################

        finish_run(switch_off_lights)
Example #27
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(
        'opentrons_6_tuberack_falcon_50ml_conical', '11',
        '50ml tuberack for binding buffer (tube B1)').wells('B1')
    # binding_buffer = ctx.load_labware(
    #     'biorad_96_wellplate_200ul_pcr', '7',
    #     '50ml tuberack for lysis buffer + PK (tube A1)').wells()[:1]
    tipracks1000 = [
        ctx.load_labware('opentrons_96_filtertiprack_1000ul', slot,
                         '1000µl filter tiprack') for slot in ['10', '7']
    ]
    tipracks20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', '6',
                         '20µl filter tiprack')
    ]

    # load pipette
    s20 = ctx.load_instrument('p20_single_gen2', 'left', tip_racks=tipracks20)
    p1000 = ctx.load_instrument('p1000_single_gen2',
                                'right',
                                tip_racks=tipracks1000)

    # setup samples
    sources = [well for rack in source_racks
               for well in rack.wells()][:NUM_SAMPLES]
    dests_single = dest_plate.wells()[:NUM_SAMPLES]
    num_cols = math.ceil(NUM_SAMPLES / 8)
    dests_multi = dest_plate.rows()[0][:num_cols]

    tip_log = {'count': {}}
    folder_path = '/data/A'
    tip_file_path = folder_path + '/tip_log.json'
    if TIP_TRACK and not ctx.is_simulating():
        if os.path.isfile(tip_file_path):
            with open(tip_file_path) as json_file:
                data = json.load(json_file)
                if 'tips1000' in data:
                    tip_log['count'][p1000] = data['tips1000']
                else:
                    tip_log['count'][p1000] = 0
                if 'tips20' in data:
                    tip_log['count'][s20] = data['tips20']
                else:
                    tip_log['count'][s20] = 0
    else:
        tip_log['count'] = {p1000: 0, s20: 0}

    tip_log['tips'] = {
        p1000: [tip for rack in tipracks1000 for tip in rack.wells()],
        #s20: [tip for rack in tipracks20 for tip in rack.rows()[0]]
        s20: [tip for rack in tipracks20 for tip in rack.wells()]
    }
    tip_log['max'] = {pip: len(tip_log['tips'][pip]) for pip in [p1000, s20]}

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

    heights = {tube: TUBE50_VOlUME * 1 for tube in binding_buffer}
    radius = (binding_buffer[0].diameter) / 2
    min_h = 5

    def h_track(vol, tube):
        nonlocal heights
        dh = vol / (math.pi * (radius**2))
        if heights[tube] - dh > min_h:
            heights[tube] = heights[tube] - dh
        else:
            heights[tube] = min_h  # stop 5mm short of the bottom
        return heights[tube]

    p1000.flow_rate.aspirate = 50
    p1000.flow_rate.dispense = 60
    p1000.flow_rate.blow_out = 100

    # transfer internal control + proteinase K
    pick_up(s20)
    for d in dests_single:
        s20.dispense(10, ic_pk.bottom(2))
        s20.transfer(ICPK_VOlUME,
                     ic_pk.bottom(2),
                     d.bottom(2),
                     air_gap=5,
                     new_tip='never')
        s20.air_gap(5)
    s20.drop_tip()

    # transfer binding buffer and mix
    pick_up(p1000)
    for i, (s, d) in enumerate(zip(sources, dests_single)):

        source = binding_buffer[
            i //
            96]  # 1 tube of binding buffer can accommodate all samples here
        h = h_track(275, source)
        # custom mix
        p1000.flow_rate.aspirate = 100
        p1000.flow_rate.dispense = 100
        p1000.dispense(500, source.bottom(h + 20))
        for _ in range(4):
            # p1000.air_gap(500)
            p1000.aspirate(500, source.bottom(h))
            p1000.dispense(500, source.bottom(h + 20))

    # p1000.transfer(BB_VOLUME, source.bottom(h), d.bottom(5), air_gap=100,
    #              new_tip='never')

        p1000.flow_rate.aspirate = 50
        p1000.flow_rate.dispense = 100
        p1000.aspirate(BB_VOLUME, source.bottom(h))
        p1000.air_gap(10)
        p1000.dispense(BB_VOLUME + 100, d.bottom(10))
        p1000.air_gap(10)
    p1000.drop_tip()

    ctx.comment('Move deepwell plate (slot 4) to Station B for RNA \
extraction.')

    # track final used tip
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        data = {
            'tips1000': tip_log['count'][p1000],
            'tips20': tip_log['count'][s20]
        }
        with open(tip_file_path, 'w') as outfile:
            json.dump(data, outfile)
def run(ctx: protocol_api.ProtocolContext):

    # Define the STEPS of the protocol
    STEP = 0
    STEPS = {  # Dictionary with STEP activation, description, and times
        1: {'Execute': HYDRATATE, 'description': 'Hidratate'},
        2: {'Execute': True, 'description': 'Transfer samples'}
    }

    for s in STEPS:  # Create an empty wait_time
        if 'wait_time' not in STEPS[s]:
            STEPS[s]['wait_time'] = 0

    #Folder and file_path for log time
    folder_path = '/var/lib/jupyter/notebooks' + run_id
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        file_path = folder_path + '/time_log.txt'

    # Define Reagents as objects with their properties
    class Reagent:
        def __init__(self, name, flow_rate_aspirate, flow_rate_dispense, rinse,
                     reagent_reservoir_volume, delay, num_wells):
            self.name                       = name
            self.flow_rate_aspirate         = flow_rate_aspirate
            self.flow_rate_dispense         = flow_rate_dispense
            self.rinse                      = bool(rinse)
            self.reagent_reservoir_volume   = reagent_reservoir_volume
            self.delay                      = delay
            self.num_wells                  = num_wells
            self.vol_well                   = 0
            self.unused                     = []
            self.vol_well_original          = reagent_reservoir_volume / num_wells

    # Reagents and their characteristics
    Hydr    = Reagent(name                      = 'Hydr',
                      rinse                     = False,
                      flow_rate_aspirate        = 3,
                      flow_rate_dispense        = 3,
                      reagent_reservoir_volume  = 1800,
                      num_wells                 = 1,
                      delay                     = 0
                      )

    Samples = Reagent(name                      = 'Samples',
                      rinse                     = False,
                      flow_rate_aspirate        = 1,
                      flow_rate_dispense        = 1,
                      reagent_reservoir_volume  = 50,
                      delay                     = 0,
                      num_wells                 = NUM_SAMPLES 
                      )
    
    Hydr.vol_well       = Hydr.vol_well_original
    Samples.vol_well    = Samples.vol_well_original
    
    ctx.comment(' ')
    ctx.comment('###############################################')
    ctx.comment('VALORES DE VARIABLES')
    ctx.comment(' ')
    ctx.comment('Número de muestras: ' + str(NUM_SAMPLES) + ' las dos primeras son controles.')
    ctx.comment(' ')
    ctx.comment('Hidratar muestras: ' + str(HYDRATATE))
    if HYDRATATE:
        ctx.comment('Volumen de Hidratante por muestra: ' + str(HYDR_VOL_PER_SAMPLE) + ' uL')
    
    ctx.comment(' ')
    ctx.comment('Volumen de muestra: ' + str(VOLUME_SAMPLE) + ' uL')
    ctx.comment(' ')
    ctx.comment('Foto-sensible: ' + str(PHOTOSENSITIVE))
    ctx.comment('Repeticiones del sonido final: ' + str(SOUND_NUM_PLAYS))
    ctx.comment(' ')

    ##################
    # Custom functions
    def divide_destinations(l, n):
        # Divide the list of destinations in size n lists.
        for i in range(0, len(l), n):
            yield l[i:i + n]

    def distribute_custom(pipette, volume, src, dest, waste_pool, pickup_height, extra_dispensal, dest_x_offset, disp_height = 0, touch_tip = False, num_shakes = 0):
        pipette.aspirate((len(dest) * volume) + extra_dispensal, src.bottom(pickup_height))
        if touch_tip :
            pipette.touch_tip(speed = 20, v_offset = -5)

        for d in dest:
            drop = d.top(z = disp_height)
            pipette.dispense(volume, drop)

            shake_pipet(pipette, rounds = num_shakes, v_offset = disp_height)
        try:
            pipette.blow_out(waste_pool.wells()[0].bottom(pickup_height + 3))
        except:
            pipette.blow_out(waste_pool.bottom(pickup_height + 3))

        return (len(dest) * volume)
        
    def shake_pipet (pipet, rounds = 2, speed = 100, v_offset = 0):
        ctx.comment("Shaking " + str(rounds) + " rounds.")
        for i in range(rounds):
                pipet.touch_tip(speed = speed, radius = 0.1, v_offset = v_offset)

    def move_vol_multichannel(pipet, reagent, source, dest, vol, air_gap_vol, x_offset,
                       pickup_height, rinse, disp_height, blow_out, touch_tip, num_shakes = 0):
        '''
        x_offset: list with two values. x_offset in source and x_offset in destination i.e. [-1,1]
        pickup_height: height from bottom where volume
        rinse: if True it will do 2 rounds of aspirate and dispense before the tranfer
        disp_height: dispense height; by default it's close to the top (z=-2), but in case it is needed it can be lowered
        blow_out, touch_tip: if True they will be done after dispensing
        '''
        # Rinse before aspirating
        if rinse == True:
            custom_mix(pipet, reagent, location = source, vol = vol,
                       rounds = 2, blow_out = True, mix_height = 0,
                       x_offset = x_offset)

        # SOURCE
        s = source.bottom(pickup_height).move(Point(x = x_offset[0]))
        pipet.aspirate(vol, s)  # aspirate liquid
        if air_gap_vol != 0:  # If there is air_gap_vol, switch pipette to slow speed
            pipet.aspirate(air_gap_vol, source.top(z = -2),
                           rate = reagent.flow_rate_aspirate)  # air gap

        # GO TO DESTINATION
        drop = dest.top(z = disp_height).move(Point(x = x_offset[1]))
        pipet.dispense(vol + air_gap_vol, drop,
                       rate = reagent.flow_rate_dispense)  # dispense all
        

        ctx.delay(seconds = reagent.delay) # pause for x seconds depending on reagent

        shake_pipet(pipet, rounds = num_shakes, v_offset = disp_height)
        
        if blow_out == True:
            pipet.blow_out(dest.top(z = -10))
        if touch_tip == True:
            pipet.touch_tip(speed = 20, v_offset = -10, radius = 0.5)


    def custom_mix(pipet, reagent, location, vol, rounds, blow_out, mix_height,
                    x_offset, source_height = 3):
        '''
        Function for mixing a given [vol] in the same [location] a x number of [rounds].
        blow_out: Blow out optional [True,False]
        x_offset = [source, destination]
        source_height: height from bottom to aspirate
        mix_height: height from bottom to dispense
        '''
        if mix_height <= 0:
            mix_height = 3

        pipet.aspirate(1, location = location.bottom(
            z = source_height).move(Point(x = x_offset[0])), rate = reagent.flow_rate_aspirate)

        for _ in range(rounds):
            pipet.aspirate(vol, location = location.bottom(
                z = source_height).move(Point(x = x_offset[0])), rate = reagent.flow_rate_aspirate)
            pipet.dispense(vol, location = location.bottom(
                z = mix_height).move(Point(x = x_offset[1])), rate = reagent.flow_rate_dispense)

        pipet.dispense(1, location = location.bottom(
            z = mix_height).move(Point(x = x_offset[1])), rate = reagent.flow_rate_dispense)

        if blow_out == True:
            pipet.blow_out(location.top(z = -2))  # Blow out

    def run_quiet_process(command):
        subprocess.check_output('{} &> /dev/null'.format(command), shell=True)
    
    def play_sound(filename):
        print('Speaker')
        print('Next\t--> CTRL-C')
        try:
            run_quiet_process('mpg123 {}'.format(path_sounds + filename + '.mp3'))
        except KeyboardInterrupt:
            pass
            print()

    def finish_run(switch_off_lights = False):
        ctx.comment('###############################################')
        ctx.comment('Protocolo finalizado')
        ctx.comment(' ')
        #Set light color to blue
        ctx._hw_manager.hardware.set_lights(button = True, rails =  False)
        now = datetime.now()
        # dd/mm/YY H:M:S
        finish_time = now.strftime("%Y/%m/%d %H:%M:%S")
        if PHOTOSENSITIVE==False:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button = False, rails =  False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button = True, rails =  True)
                time.sleep(0.3)
        else:
            for i in range(10):
                ctx._hw_manager.hardware.set_lights(button = False, rails =  False)
                time.sleep(0.3)
                ctx._hw_manager.hardware.set_lights(button = True, rails =  False)
                time.sleep(0.3)
        if switch_off_lights:
            ctx._hw_manager.hardware.set_lights(button = True, rails =  False)

        ctx.comment('Puntas de 20 uL utilizadas: ' + str(tip_track['counts'][m20]) + ' (' + str(round(tip_track['counts'][m20] / 96, 2)) + ' caja(s))')
        ctx.comment('###############################################')

        if not ctx.is_simulating():
            for i in range(SOUND_NUM_PLAYS):
                if i > 0:
                    time.sleep(60)
                play_sound('finalizado')

        return finish_time

    ####################################
    # load labware and modules
    ####################################
    
    ####################################
    # 24 well rack
    tuberack = ctx.load_labware(
        'opentrons_24_aluminumblock_generic_2ml_screwcap', '8',
        'Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap')


    ##################################
    # Sample plate - comes from B
    source_plate = ctx.load_labware(
        'biorad_96_wellplate_200ul_pcr', '3', 
        'Bio-Rad 96 Well Plate 200 µL PCR')
    ##################################
    # qPCR plate - final plate, goes to PCR
    qpcr_plate = ctx.load_labware(
        'opentrons_96_aluminumblock_generic_pcr_strip_200ul', '6',
        'Opentrons 96 Well Aluminum Block with Generic PCR Strip 200 µL')

    ##################################
    # Load Tipracks
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', slot)
        for slot in ['2']
    ]

    tips200 = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', slot)
        for slot in ['5']
    ]

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

    # setup up sample sources and destinations
    samples             = source_plate.rows()[0][:num_cols]
    pcr_wells           = qpcr_plate.wells()[:NUM_SAMPLES]
    pcr_wells_samples   = qpcr_plate.rows()[0][:num_cols]
    tipCols             = tips20[0].rows()[0][:num_cols]

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

    # pipettes
    m20 = ctx.load_instrument(
        'p20_multi_gen2', mount = 'right', 
        tip_racks = tips20) # load m20 pipette
    p300 = ctx.load_instrument(
        'p300_single_gen2', mount = 'left', tip_racks = tips200)

    # used tip counter and set maximum tips available
    tip_track = {
        'counts': {p300: 0,
                    m20: 0},
        'maxes': {p300: 96 * len(p300.tip_racks),
                   m20: 96 * len(m20.tip_racks)}
    }

    ##########
    # pick up tip and if there is none left, prompt user for a new rack
    def pick_up(pip):
        nonlocal tip_track
        if not ctx.is_simulating():
            if tip_track['counts'][pip] == tip_track['maxes'][pip]:
                ctx.pause('Replace ' + str(pip.max_volume) + 'µl tipracks before \
                resuming.')
                pip.reset_tipracks()
                tip_track['counts'][pip] = 0

        if not pip.hw_pipette['has_tip']:
            pip.pick_up_tip()
    ##########

    
    ############################################################################
    # STEP 1: HIDRATATE
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step '+str(STEP)+': '+STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')

        pick_up(p300)
        used_vol = []

        for dest in dests:
            aspirate_volume = HYDR_VOL_PER_SAMPLE * len(dest) + extra_dispensal
            used_vol_temp = distribute_custom(p300, volume = HYDR_VOL_PER_SAMPLE,
                src = Hydr.reagent_reservoir, dest = dest, touch_tip = False,
                waste_pool = Hydr.reagent_reservoir, pickup_height = 0.2,
                extra_dispensal = extra_dispensal, dest_x_offset = 0, 
                disp_height = -15, num_shakes = 1)
            used_vol.append(used_vol_temp)

        p300.drop_tip(home_after = False)
        tip_track['counts'][p300] += 1

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    ############################################################################
    # STEP 2: TRANSFER SAMPLES
    ############################################################################
    STEP += 1
    if STEPS[STEP]['Execute'] == True:
        start = datetime.now()
        ctx.comment(' ')
        ctx.comment('###############################################')
        ctx.comment('Step '+str(STEP)+': '+STEPS[STEP]['description'])
        ctx.comment('###############################################')
        ctx.comment(' ')
        
        i = 0
        for s, d in zip(samples, pcr_wells_samples):
            pick_up(m20)

            move_vol_multichannel(m20, reagent = Samples, source = s, dest = d,
                    vol = VOLUME_SAMPLE, air_gap_vol = air_gap_sample, x_offset = x_offset,
                    pickup_height = 0.2, disp_height = -10, rinse = False,
                    blow_out=True, touch_tip=False)
            
            m20.drop_tip(home_after = False)

            tip_track['counts'][m20] += 8
            i = i + 1

        end = datetime.now()
        time_taken = (end - start)
        ctx.comment('Step ' + str(STEP) + ': ' +
                    STEPS[STEP]['description'] + ' took ' + str(time_taken))
        STEPS[STEP]['Time:'] = str(time_taken)

    # Export the time log to a tsv file
    if not ctx.is_simulating():
        with open(file_path, 'w') as f:
            f.write('STEP\texecution\tdescription\twait_time\texecution_time\n')
            for key in STEPS.keys():
                row = str(key)
                for key2 in STEPS[key].keys():
                    row += '\t' + format(STEPS[key][key2])
                f.write(row + '\n')
        f.close()

    ############################################################################
    finish_run()
Example #29
0
def run(ctx: protocol_api.ProtocolContext):

    # load labware
    ic_pk = ctx.load_labware(
        'opentrons_96_aluminumblock_nest_wellplate_100ul', '9',
        'chilled tubeblock for internal control and proteinase K (strip 1)'
    ).wells()[0]

    bb = ctx.load_labware('nest_12_reservoir_15ml', '8',
                          'reagent reservoir Binding Buffer')
    binding_buffer = bb.wells()[:2]
    dest_plate = ctx.load_labware('nest_96_wellplate_2ml_deep', '11',
                                  '96-deepwell sample plate')

    # load tips

    tips300 = [
        ctx.load_labware('opentrons_96_filtertiprack_200ul', '5',
                         '200µl filter tiprack')
    ]
    tips20 = [
        ctx.load_labware('opentrons_96_filtertiprack_20ul', '6',
                         '20µl filter tiprack')
    ]

    # load pipette

    m300 = ctx.load_instrument('p300_multi_gen2', 'right', tip_racks=tips300)
    s20 = ctx.load_instrument('p20_single_gen2', 'left', tip_racks=tips20)

    m300.flow_rate.aspirate = 50
    m300.flow_rate.dispense = 150
    m300.flow_rate.blow_out = 300

    s20.flow_rate.aspirate = 50
    s20.flow_rate.dispense = 100
    s20.flow_rate.blow_out = 300

    # setup samples
    num_cols = math.ceil(NUM_SAMPLES / 8)
    bbs = bb.wells()[:2]
    dests_single = dest_plate.wells()[:NUM_SAMPLES]
    dests_multi = dest_plate.rows()[0][:num_cols]

    tip_log = {'count': {}}
    folder_path = '/data/A'
    tip_file_path = folder_path + '/tip_log.json'
    if TIP_TRACK and not ctx.is_simulating():
        if os.path.isfile(tip_file_path):
            with open(tip_file_path) as json_file:
                data = json.load(json_file)
                if 'tips1000' in data:
                    tip_log['count'][m300] = data['tips1000']
                else:
                    tip_log['count'][m300] = 0
                if 'tips20' in data:
                    tip_log['count'][s20] = data['tips20']
                else:
                    tip_log['count'][s20] = 0
    else:
        tip_log['count'] = {m300: 0, s20: 0}

    tip_log['tips'] = {
        m300: [tip for rack in tips300 for tip in rack.wells()],
        s20: [tip for rack in tips20 for tip in rack.rows()[0]]
    }
    tip_log['max'] = {pip: len(tip_log['tips'][pip]) for pip in [m300, s20]}

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

    heights = {tube: 20 for tube in binding_buffer}
    # radius = (binding_buffer[0].diameter)/2
    min_h = 5

    def h_track(vol, tube):
        nonlocal heights
        dh = vol / (math.pi * (radius**2))
        if heights[tube] - dh > min_h:
            heights[tube] = heights[tube] - dh
        else:
            heights[tube] = min_h  # stop 5mm short of the bottom
        return heights[tube]

        # transfer internal control + proteinase K

    for d in dests_single:
        pick_up(s20)
        s20.transfer(ICPK_VOlUME,
                     ic_pk.bottom(2),
                     d.bottom(2),
                     air_gap=5,
                     new_tip='never')
        s20.air_gap(5)
        s20.drop_tip()

        # transfer binding buffer
    for i, e in enumerate(dests_multi):
        pick_up(m300)
        m300.transfer(BB_VOLUME,
                      bbs[i // 6].bottom(2),
                      e.bottom(10),
                      air_gap=5,
                      mix_after=(5, 100),
                      new_tip='never')
        m300.air_gap(100)
        m300.drop_tip()

    ctx.comment('Terminado.')

    # track final used tip
    if not ctx.is_simulating():
        if not os.path.isdir(folder_path):
            os.mkdir(folder_path)
        data = {'tips300': tip_log['count'][m300]}
        with open(tip_file_path, 'w') as outfile:
            json.dump(data, outfile)
Example #30
0
def run(ctx: protocol_api.ProtocolContext):

    [csv, m300_mount, p300_mount, temp_mod_temp, asp_rate_step1,
     pbs_dispense_rate,
     incubation_time, first_media_x, second_media_y, track_tips
     ] = get_values(  # noqa: F821
        "csv", "m300_mount", "p300_mount", "temp_mod_temp",
        "asp_rate_step1", "pbs_dispense_rate",
        "incubation_time", "first_media_x", "second_media_y", "track_tips")

    # LABWARE
    temp_mod = ctx.load_module('temperature module gen2', '10')
    reagents = ctx.load_labware('nest_12_reservoir_15ml', '11')
    waste_res = ctx.load_labware('nest_12_reservoir_15ml', '7')
    plate = temp_mod.load_labware(
                'corning_96_wellplate_360ul_flat', '10')

    # TIPRACKS
    tipracks = [ctx.load_labware('opentrons_96_filtertiprack_200ul', slot)
                for slot in ['4', '5', '6']]

    # INSTRUMENTS
    p300 = ctx.load_instrument('p300_single_gen2',
                               p300_mount,
                               tip_racks=tipracks)
    m300 = ctx.load_instrument('p300_multi_gen2',
                               m300_mount,
                               tip_racks=tipracks)

    tips_by_col = [tip for rack in tipracks
                   for col in rack.columns() for tip in col[::-1]]
    tip_cols = [tips_by_col[i:i+8] for i in range(0, len(tips_by_col), 8)]

    """ TIP-TRACKING BETWEEN RUNS. """
    total_tip_cols = 36

    file_path = '/data/csv/tiptracking.json'
    file_dir = os.path.dirname(file_path)

    tips_by_col = [tip for rack in tipracks
                   for col in rack.columns() for tip in col[::-1]]
    tip_cols = [tips_by_col[i:i+8] for i in range(0, len(tips_by_col), 8)]

    if track_tips and not ctx.is_simulating():
        # check for file directory
        if not os.path.exists(file_dir):
            os.makedirs(file_dir)
        # if no file, then use standard tip_chunks definition, and the
        # end of the code will write to updated tip_chunks list to
        # the file created. This if statement handles the case in which
        # tip tracking is selected for the first time.
        if not os.path.isfile(file_path):
            tip_chunks = [tips_by_col[i:i+8] for i in range(0,
                          len(tips_by_col), 8)]
        else:
            # grab nested list tip_chunks from file.
            source = open(file_path, 'rb').read()
            # see below for conversion of tip_chunks nested list to bools.
            # in order to dump the well objects in tip_chunks to a json file,
            # they had to be serializable (int, string, bool). The end of the
            # protocol does this conversion.
            tip_bool_chunks = json.loads(source)

            # convert bools back to well objects to use.
            tip_chunks = [[] for _ in range(total_tip_cols)]
            for i, (bool_chunk, tip_chunk) in enumerate(zip(tip_bool_chunks,
                                                            tip_cols)):
                if len(bool_chunk) == 0:
                    continue
                for true_tip, tip_loc in zip(bool_chunk, tip_chunk):
                    if true_tip:
                        tip_chunks[i].append(tip_loc)
                    else:
                        continue

    else:
        # standard definition of tip_chunks if not tracking tips.
        tip_chunks = [tips_by_col[i:i+8] for i in range(0,
                      len(tips_by_col), 8)]
    """PROTOCOL BEGINS """
    csv_rows = [[val.strip() for val in line.split(',')]
                for line in csv.splitlines()
                if line.split(',')[0].strip()][1:]

    """FIND INVOLVED WELLS"""
    values_from_csv = []
    wells_from_csv = []
    for row in csv_rows:
        well, value = row[:2]
        value = int(value)
        values_from_csv.append(value)
        wells_from_csv.append(well)

    # create nested list of all values in csv (by column).
    value_chunk_cols = [values_from_csv[i:i+8]
                        for i in range(0, len(values_from_csv), 8)]
    list_well_tips = []

    """CREATE A LIST OF # TIPS FOR EACH WELL"""
    start_point = 0
    tip_count = 0
    for i, chunk in enumerate(value_chunk_cols):
        start_point = 0

        # check for the values in each column.
        # if we find a well with a value of 85 or greater,
        # use that index (j) as the starting point and see how many values
        # after that are also greater than 85. Once we don't find one,
        # break.
        for j, value in enumerate(chunk[start_point:]):
            if value >= 85:
                for check_values in chunk[j:]:
                    if check_values >= 85:
                        tip_count += 1
                    else:
                        break

                list_well_tips.append(tip_count)
                tip_count = 0
                continue

            else:
                list_well_tips.append(0)

    # create a dictionary which says how many tips go to each well.
    # For example, if the entire first column has values higher than 85,
    # the first value in the dictionary will be "A1: 8". If first four wells
    # in column 2 are greater than 85, "B1:4".
    dict_tips_per_well = {}
    tip_ctr = 0
    for j, (well, num_tips) in enumerate(zip(wells_from_csv,
                                             list_well_tips)):
        if tip_ctr > 0:
            tip_ctr -= 1
            continue

        if num_tips > 0:
            tip_ctr = num_tips - 1
            dict_tips_per_well[well] = num_tips

    # print('\n\n', dict_tips_per_well, '\n\n')

    """PICKUP FUNCTION"""
    def pick_up(num_channels_per_pickup):
        nonlocal tip_chunks
        if num_channels_per_pickup > 1:
            pip = m300
        else:
            pip = p300
        try:
            col = 0
            # based on the demand of the next well (1-8 tips), this for loop
            # will find the first available column having adequate number
            # of tips starting from the first. If the tip pick up order is:
            # 6, 8, 2 for the first 3 pick ups, then 6 tips will be taken from
            # column 1, 8 tips from column 2, and 2 tips back from column 1.
            # efficient tip pick up instead of "throwing away" a whole column
            # after pick up.
            for _ in range(36):
                if num_channels_per_pickup <= len(tip_chunks[col]):
                    break
                else:
                    col += 1
            pip.pick_up_tip(tip_chunks[col][num_channels_per_pickup-1])

            # remove as many tips as we picked up in that column
            # from the 0 index.
            for _ in range(num_channels_per_pickup):
                tip_chunks[col].pop(0)

        # replace tip exception
        except IndexError:
            ctx.pause("Replace empty tip racks on slots 4, 5, and 6")
            pip.reset_tipracks()
            tip_chunks = [tips_by_col[i:i+8] for i in range(0,
                          len(tips_by_col), 8)]
            col = 0
            for _ in range(36):
                if num_channels_per_pickup <= len(tip_chunks[col]):
                    break
                else:
                    col += 1

            pip.pick_up_tip(tip_chunks[col][num_channels_per_pickup-1])

            for _ in range(num_channels_per_pickup):
                tip_chunks[col].pop(0)

                if len(tip_chunks[col]) == 0:
                    tip_chunks.remove(tip_chunks[col])

    # DUMP WASTE
    vol_ctr = 0
    waste_well = 0

    # move to next well in reservoir once we fill one.
    def check_waste_vol(vol):
        nonlocal vol_ctr
        nonlocal waste_well
        vol_ctr += vol
        if vol_ctr > 12000:
            waste_well += 1
            vol_ctr = 0
    waste = waste_res.wells()[waste_well]
    temp_mod.set_temperature(temp_mod_temp)

    ctx.pause("""
    Ensure temperature module is at correct temperature, then,
    select "Resume" on the Opentrons app.
    """)

    # REAGENTS
    pbs = reagents.wells()[0]
    trypsin = reagents.wells()[1]
    media = reagents.wells()[-1]

    airgap = 20

    ctx.comment("MOVING INCLUDED WELLS TO WASTE")
    for i, well in enumerate(dict_tips_per_well):
        num_tips = dict_tips_per_well[well]
        plate_well = plate.wells_by_name()[well]
        if num_tips > 1:
            pip = m300
        else:
            pip = p300

        pick_up(num_tips)
        # aspirate from side so as to not disturb cell culture.
        pip.aspirate(200, plate_well.bottom(z=1).move(
                Point(x=(plate_well.diameter/2-2))), rate=asp_rate_step1)
        pip.dispense(200, waste)
        check_waste_vol(200)
        pip.air_gap(airgap)
        pip.drop_tip()
        ctx.comment('\n')
    ctx.comment("\n\n\nMOVING PBS TO PLATE")
    for i, well in enumerate(dict_tips_per_well):
        num_tips = dict_tips_per_well[well]
        plate_well = plate.wells_by_name()[well]
        if num_tips > 1:
            pip = m300
        else:
            pip = p300

        pick_up(num_tips)
        pip.aspirate(150, pbs, rate=pbs_dispense_rate)
        pip.dispense(150, plate_well.bottom(z=1).move(
                Point(x=(plate_well.diameter/2-2))))
        pip.air_gap(airgap)
        pip.drop_tip()
        ctx.comment('\n')

    ctx.comment("\n\n\nREMOVING PBS FROM PLATE")
    for i, well in enumerate(dict_tips_per_well):
        num_tips = dict_tips_per_well[well]
        plate_well = plate.wells_by_name()[well]
        if num_tips > 1:
            pip = m300
        else:
            pip = p300

        pick_up(num_tips)
        pip.aspirate(175, plate_well.bottom(z=1).move(
                Point(x=(plate_well.diameter/2-2))))
        pip.dispense(175, waste)
        pip.air_gap(airgap)
        pip.drop_tip()
        ctx.comment('\n')

    ctx.comment("\n\n\nMOVING TRYPSIN TO PLATE")
    for i, well in enumerate(dict_tips_per_well):

        num_tips = dict_tips_per_well[well]
        plate_well = plate.wells_by_name()[well]
        if num_tips > 1:
            pip = m300
        else:
            pip = p300

        pick_up(num_tips)
        pip.aspirate(25, trypsin)
        pip.dispense(25, plate_well)
        pip.blow_out()
        pip.touch_tip()
        pip.air_gap(airgap)
        pip.drop_tip()
        ctx.comment('\n')

    ctx.delay(minutes=incubation_time)

    ctx.comment("\n\n\nMOVING MEDIA TO PLATE")
    for i, well in enumerate(dict_tips_per_well):

        num_tips = dict_tips_per_well[well]
        plate_well = plate.wells_by_name()[well]
        if num_tips > 1:
            pip = m300
        else:
            pip = p300

        pick_up(num_tips)
        pip.aspirate(140, media)
        pip.dispense(140, plate_well)
        pip.blow_out()
        pip.touch_tip()
        pip.air_gap(airgap)
        pip.drop_tip()
        ctx.comment('\n')

    ctx.comment("\n\n\nASPIRATE FIRST MEDIA FROM PLATE")
    for i, well in enumerate(dict_tips_per_well):

        num_tips = dict_tips_per_well[well]
        plate_well = plate.wells_by_name()[well]
        if num_tips > 1:
            pip = m300
        else:
            pip = p300

        pick_up(num_tips)
        pip.aspirate(first_media_x, plate_well.bottom(z=1).move(
                Point(x=(plate_well.diameter/2-2))))
        pip.dispense(first_media_x, waste)
        pip.air_gap(airgap)
        pip.drop_tip()
        ctx.comment('\n')

    ctx.comment("\n\n\nDISPENSE SECOND MEDIA TO PLATE")
    for i, well in enumerate(dict_tips_per_well):

        num_tips = dict_tips_per_well[well]
        plate_well = plate.wells_by_name()[well]
        if num_tips > 1:
            pip = m300
        else:
            pip = p300

        pick_up(num_tips)
        pip.aspirate(second_media_y, media)
        pip.dispense(second_media_y, plate_well)
        pip.air_gap(airgap)
        pip.drop_tip()
        ctx.comment('\n')

    tip_data = []
    for i, chunk in enumerate(tip_chunks):
        tip_data.append([])
        if len(chunk) > 0:
            for value in chunk:
                tip_data[i].append(True)
        else:
            continue

    # write to the ot-2 no matter what in case the user would like to start
    # tracking tips for the next run
    if not ctx.is_simulating():
        with open(file_path, 'w') as outfile:
            outfile.write(json.dumps(tip_data))