Opentrons: create() function generate json wells out of order

Created on 28 Aug 2017  路  9Comments  路  Source: Opentrons/opentrons

This code's create function:

```from opentrons import robot, containers, instruments

only change the values within these boundaries

##################start

This opens the csv file with absolute path (will be different depending on operating system)

file paths on Windows look more like 'C:\path\to\your\csv_file.csv'

on Mac more like '/path/to/your/csv_file.csv'

path = 'C:\Users\calel_000\Libraries\Documents\Opentrons\Protocols\exampleGBF.csv'

number of tubes of sample (not counting empty pooling tubes)

sampletubes = 9 # can change this number here

number of resulting tubes you are harvesting (the empty tubes that get 3 samples in each)

harvest = 3 # can change this number

number of wells to dispense DILUENT DISTRIBUTION COMMAND on HCV ("13") plate

diluentnum = 16 # can change here

number of wells to dispense conjugate for each plate

HCVconjwells = 16 # can change here
HIVconjwells = 4 # can change here
HCGconjwells = 4 # can change here
HBsAgconjwells = 8 # can change here

number of wells to dispense substrates for each plate

HCVsubswells = 16 # can change here
HIVsubswells = 4 # can change here
HCGsubswells = 4 # can change here
HBsAgsubswells = 8 # can change here

number of wells to dispense during stop command each plate

HCVstopwells = 16 # can change here
HIVstopwells = 4 # can change here
HCGstopwells = 4 # can change here
HBsAgstopwells = 8 # can change here

##################end

number of samples combined in one tube

poolnum = 3

volumes for varieties of elisa

HBsAg_vol = 50
HCV_vol = 10
HCG_vol = 100
HIV_vol = 100

volumes for conjugates and substrate

conjsub_vol = 50

length of sample rack row

rackrow = 4

containers.create(
'4x6_tuberackGBF', # name of you container
grid=(4, 6), # specify amount of (columns, rows)
spacing=(6, 10), # distances (mm) between each (column, row)
diameter=12, # diameter (mm) of each well on the plate
depth=75) # depth (mm) of each well on the plate

containers.create(
'3x4_vialrackGBF', # name of you container
grid=(3, 4), # specify amount of (columns, rows)
spacing=(10, 15), # distances (mm) between each (column, row)
diameter=20, # diameter (mm) of each well on the plate
depth=50) # depth (mm) of each well on the plate

96 well plates

HBsAgplate = containers.load('96-PCR-flat', 'A3') # was "deck 12" in protocol
HCVplate = containers.load('96-PCR-flat', 'B3') # was "deck 13" in protocol
HIVplate = containers.load('96-PCR-flat', 'C3') # was "deck 14" in protocol
HCGplate = containers.load('96-PCR-flat', 'D3') # was "deck 15" in protocol

tube rack holding samples

samples1 = containers.load('4x6_tuberackGBF', 'A1', 'samples1') # was "deck 1"
samples2 = containers.load('4x6_tuberackGBF', 'A2', 'samples2') # was "deck 6"

reagent racks

reagents1 = containers.load('3x4_vialrackGBF', 'B1') # was "deck 2"
reagents2 = containers.load('3x4_vialrackGBF', 'C1') # was "deck 3"

tip rack for p100 pipette

tip200_rack1 = containers.load('tiprack-200ul', 'B2')
tip200_rack2 = containers.load('tiprack-200ul', 'C2')

trash to dispose of tips

trash = containers.load('point', 'D2')

p100 (10 - 100 uL) (single)

p100 = instruments.Pipette(
axis='b',
name='p100single',
max_volume=200,
min_volume=10,
channels=1,
trash_container=trash,
tip_racks=[tip200_rack1, tip200_rack2])

deck 3 diluent

diluent = reagents2.wells('B1')

conjugates and substrates deck 2

conjugate1 = reagents1.wells('A1')
conjugate2 = reagents1.wells('A2')
conjugate3 = reagents1.wells('A3')
conjugate4 = reagents1.wells('A4')
substrate1_1 = reagents1.wells('B1')
substrate1_2 = reagents1.wells('C1')
substrate2_1 = reagents1.wells('B2')
substrate2_2 = reagents1.wells('C2')
substrate3_1 = reagents1.wells('B3')
substrate3_2 = reagents1.wells('C3')
substrate4_1 = reagents1.wells('B4')
substrate4_2 = reagents1.wells('C4')

stop command reagent deck 3

finalreagent = reagents2.wells('A1')

lists of which samples go in which harvest tubes

samples = []

for tube in range(0, harvest):
if tube < 6:
rack = samples1
elif tube < 12:
rack = samples2
elif tube < 18:
rack = samples3
samples.append(rack.wells(tube*rackrow, length=poolnum))

tubes to pool samples in

pooltubes = []

for tube in range(0, harvest):
if tube < 6:
rack = samples1
elif tube < 12:
rack = samples2
tube -= 6
elif tube < 18:
rack = samples3
tube -= 6
pooltubes.append(rack.wells(3 + rackrow*tube))

transfer numsamples (3) of sample from tubes to the harvest tubes

for tube in range(0, harvest):
p100.transfer(200, samples[tube], pooltubes[tube], new_tip='always')
p100.pick_up_tip()
p100.mix(3, 100, pooltubes[tube])
p100.drop_tip()
```
generates out of order json like this:

"4x6_tuberackGBF": { "locations": { "A2": { "diameter": 12, "x": 0, "depth": 75, "total-liquid-volume": 0, "y": 10, "z": 0 }, "B2": { "diameter": 12, "x": 6, "depth": 75, "total-liquid-volume": 0, "y": 10, "z": 0 }, "C3": { "diameter": 12, "x": 12, "depth": 75, "total-liquid-volume": 0, "y": 20, "z": 0 }, "C4": { "diameter": 12, "x": 12, "depth": 75, "total-liquid-volume": 0, "y": 30, "z": 0 }, "A3": { "diameter": 12, "x": 0, "depth": 75, "total-liquid-volume": 0, "y": 20, "z": 0 }, "A5": { "diameter": 12, "x": 0, "depth": 75, "total-liquid-volume": 0, "y": 40, "z": 0 }, "D6": { "diameter": 12, "x": 18, "depth": 75, "total-liquid-volume": 0, "y": 50, "z": 0 }, "C6": { "diameter": 12, "x": 12, "depth": 75, "total-liquid-volume": 0, "y": 50, "z": 0 }, "B1": { "diameter": 12, "x": 6, "depth": 75, "total-liquid-volume": 0, "y": 0, "z": 0 }, "A4": { "diameter": 12, "x": 0, "depth": 75, "total-liquid-volume": 0, "y": 30, "z": 0 }, "C5": { "diameter": 12, "x": 12, "depth": 75, "total-liquid-volume": 0, "y": 40, "z": 0 }, "C1": { "diameter": 12, "x": 12, "depth": 75, "total-liquid-volume": 0, "y": 0, "z": 0 }, "D3": { "diameter": 12, "x": 18, "depth": 75, "total-liquid-volume": 0, "y": 20, "z": 0 }, "B6": { "diameter": 12, "x": 6, "depth": 75, "total-liquid-volume": 0, "y": 50, "z": 0 }, "B3": { "diameter": 12, "x": 6, "depth": 75, "total-liquid-volume": 0, "y": 20, "z": 0 }, "C2": { "diameter": 12, "x": 12, "depth": 75, "total-liquid-volume": 0, "y": 10, "z": 0 }, "D5": { "diameter": 12, "x": 18, "depth": 75, "total-liquid-volume": 0, "y": 40, "z": 0 }, "D1": { "diameter": 12, "x": 18, "depth": 75, "total-liquid-volume": 0, "y": 0, "z": 0 }, "D4": { "diameter": 12, "x": 18, "depth": 75, "total-liquid-volume": 0, "y": 30, "z": 0 }, "A1": { "diameter": 12, "x": 0, "depth": 75, "total-liquid-volume": 0, "y": 0, "z": 0 }, "B5": { "diameter": 12, "x": 6, "depth": 75, "total-liquid-volume": 0, "y": 40, "z": 0 }, "D2": { "diameter": 12, "x": 18, "depth": 75, "total-liquid-volume": 0, "y": 10, "z": 0 }, "A6": { "diameter": 12, "x": 0, "depth": 75, "total-liquid-volume": 0, "y": 50, "z": 0 }, "B4": { "diameter": 12, "x": 6, "depth": 75, "total-liquid-volume": 0, "y": 30, "z": 0 } } },

api bug container

All 9 comments

For some reason, works normally in jupyter

@lmtgalhardo can you confirm python versions in both environments?

running Python 3.6.1 in jupyter, not sure how I would check what version of python the app uses but guessing 3 as well?

confirmed in Python 3.5.1, opentrons version '2.4.2+195.g5b792ce'

But the JSON dict is unordered anyway, things like my_container.wells() are ordered correctly, it's just uglier to edit to JSON.

I had an issue with a customer where the wells were not functioning normally becasue of the json: if I said transfer to well 'A1', it would transfer to whichever well the unordered json had listed first. If I said transfer to well 'B1', it would transfer to whatever well the unordered json had as second, etc

Shown:
viber image2
Above my output from jupyter, wells should be accessed in order A1 to D1, B1 to D1, C1 to D1

viber image

Shown above is a screenshot from a customer (testing here produced similar results) where the wells being accessed were the corresponding wells in that position of the unordered json. So well "A1" turned into well D1, well "D1" turned into well D4, etc

The quick ugly fix for this to copy and paste this function into your protocol:

def create_container_instance(name, grid, spacing, diameter, depth,
                              volume=0, slot=None, label=None):
    from opentrons import robot
    from opentrons.containers.placeable import Container, Well

    if slot is None:
        raise RuntimeError('"slot" argument is required.')
    if label is None:
        label = name
    columns, rows = grid
    col_spacing, row_spacing = spacing
    custom_container = Container()
    well_properties = {
        'type': 'custom',
        'diameter': diameter,
        'height': depth,
        'total-liquid-volume': volume
    }

    for r in range(rows):
        for c in range(columns):
            well = Well(properties=well_properties)
            well_name = chr(c + ord('A')) + str(1 + r)
            coordinates = (c * col_spacing, r * row_spacing, 0)
            custom_container.add(well, well_name, coordinates)

    # if a container is added to Deck AFTER a Pipette, the Pipette's
    # Calibrator must update to include all children of Deck
    for _, instr in robot.get_instruments():
        if hasattr(instr, 'update_calibrator'):
            instr.update_calibrator()

    custom_container.properties['type'] = name
    custom_container.get_name = lambda: label

    # add to robot deck
    robot.deck[slot].add(custom_container, label)

    return custom_container

After copying that function into your protocol, use it like this:

plate1 = create_container_instance(
    '2x3_plate',        # name of you container
    grid=(2, 3),                   # specify amount of (columns, rows)
    spacing=(38.4, 38.4),          # distances (mm) between each (column, row)
    diameter=19,                   # diameter (mm) of each well on the plate
    depth=8,
    slot='A1'
    )                       # depth (mm) of each well on the plate

plate2 = create_container_instance(
    '2x3_plate',      # name of your container
    grid=(2, 3),                   # specify amount of (columns, rows)
    spacing=(38.4, 38.4),          # distances (mm) between each (column, row)
    diameter=19,                   # diameter (mm) of each well on the plate
    depth=8,                       # depth (mm) of each well on the plate
    slot='A2')                     # slot on robot to load container into

These container instances will function just like a the normal container instance returned from containers.load:

p1000 = instruments.Pipette(
    axis='b',
    name='p1000',
    max_volume=1000,
    min_volume=100,
    channels=1,
    )

# plate1 and plate2 are just normal containers now
p1000.transfer(5, plate1.wells(0, to=10), plate2.wells(0, to=10))

I just ran into this issue today. What the robot thought was Well 0 in a custom container was actually Well C2, well 1 was B5, etc. There really was no apparent pattern, other than that it was consistent across multiple instances of the container on the same deck.

I worked around it by getting the robot to map out which wells it was using. Then I created a dictionary to map the usual well numbers (0,1,2,3 ...) to the numbers the robot understood.

BTW, this affects calibration, too. If the robot thinks Well C2 is first, you have to calibrate the whole slot to the bottom of C2, not A1.

This bug may already be fixed in v3, but if it isn't, it will definitely be fixed by the Update Labware milestone, because the new labware definitions (unlike the old ones) do not rely on the order of keys in JSON dictionaries, which are inherently unordered structures.

This ticket is closed or superseded accordingly

Was this page helpful?
0 / 5 - 0 ratings