OT Protocol Datastructure Guidelines

Protocol Datastructure Guidelines

A guide to writing your own JSON protocols for the OT-One liquid handling robot.

OK, so you have your OT-One pipetting robot, you've visited our protocol library, and you've run your first 'hello world' protocol. Now, you're ready to make your own protocol.

To do that, you can work with our graphical protocol editor. It is the simplest way to edit and customize a protocol. Here is a good blogpost about using it.

But sometimes, that is not the right tool. Sometimes its easier to copy and paste a chunk of one protocol and paste it into another. And to that you have to dive into the actual code. So, lets dive!

What is JSON?

JSON stands for JavaScript Object Notation. It is a web-native, text-only, data-object format. But you dont have to know all of that.

The main thing to know about JSON is that it is 'nested.' That means that attributes exist inside of each other.

{"people":
    "person1":
    {
     "firstName":"Jane", 
     "lastName":"Doe"
    }
}

The above example shows how you could represent information about people. The larger object, 'people,' is an array that contains multiple nested objects, 'person1' enclosed by{}, which in turn contains two of its own nested objects inside them, in this case 'firstName' and 'lastName.'

{
    "people":[
        "person1":
        {
            "firstName": "Jane", 
            "lastName": "Doe"
        },
        "person2":
        {
            "firstName": "Sally", 
            "lastName": "Smith",
            "favoriteColor": "green"
        }
    ]
}

Now we've added a second person to our list of people. And this person has one more attribute, favorite color.

Its simple! The only tricky thing can be getting all your ,'s and {}'s in the right place. For that JSON Lint is a very helpful tool.

OK, now you know the basics of JSON!

Anatomy of OT JSON

OpenTrons JSON format has three major sections:

  1. Head
    "head": {
  2. Deck
    "deck": {
  3. Ingredients
    "ingredients": {
  4. Instructions
    "instructions": [

Deck

The Deck section defines which locations and labware will be used in the protocol. This is where you tell the robot you are using a standard 96 well plate, 200 μl tip rack, and whatever else you need for your protocol.

Each element follows the same rules:

"useful-name" : {
    "labware": "exact-labware-definition",
    "slot" : "Slot-on-deck"
}

When you put it all together it looks like this:

"deck": {
    "p200-rack": {
        "labware": "tiprack-200ul",
        "slot": "C2"
    },
    "trash": {
        "labware": "point",
        "slot": "B2"
    },
    "sample-plate": {
        "labware": "96-PCR-flat",
        "slot": "C1"
    }
}

Here is where you can find the Labware Library to copy-and-paste the exact definitions of the containers you need.

The Head section describes what tools (pipettes) are on the moving head of the OT-One. Here is an example of a a p200 single channel pipette mounted on the left of the head.

"p200": { Starts the tool block and names the tool. 'p200' here is just a descriptive name, might as well say 'elephant' or any other word that is helpful for you.

"tool": "pipette", Defines what type of tool. Eventually this will be things like "gripper" or "pH probe" - but for now, we only have pipettes.

"tip-racks": [ define what tip rack that pipette will draw from, the name must match the useful name you gave your tip rack container. Can define one or multiple tip racks:

"tip-racks": [
    {
        "container": "p200-rack"
    },
    {
        "container": "p200-rack-2"
    }
],

"trash-container": { - define which deck container will be the trash for that pipette:

"trash-container": {
                "container": "trash"
            },

"multi-channel": false, define if it is a single- channel or 8-channel pipette.

"axis": "b", define which position (a = center, b = left) the pipette is in on the head.

"volume": 200, define the maximum volume of that pipette in μL.

"down-plunger-speed": 300, define how fast the robot presses down the pipette plunger (mm/s).

"up-plunger-speed": 500, define how fast the robot pulls up the pipette plunger (mm/s).

"tip-plunge": 6, define how much further than the calibrated position the pipette should go on the z-axis (mm).

"extra-pull-volume": 0, define how much extra volume the robot will pick up with each transfer. Extra liquid will be dispensed with used tips in the trash container.

"extra-pull-delay": 2, define how long should the robot pause before the extra-pull if any is set (seconds).

"distribute-percentage": 0.1, similar to 'extra-pull-volume' except for a distribute command not a transfer command, and expressed as a % of the total volume to be distributed. If the command is set to pick up a total of 100 uL, it will actually pick up 110 uL.

"points": [ these are for users hacking a non-standard pipette onto their OT-One. They enable you to tweak the calibration curve that the OT uses to calculate pipette volume.

Here is what it looks like when you put it all together, as well as where you would start the section for your second pipette:

"head": {
    "p200": {
        "tool": "pipette",
          "tip-racks": [
            {
                   "container": "p200-rack"
            }
        ],
        "trash-container": {
            "container": "trash"
        },
        "multi-channel": false,
        "axis": "b",
        "volume": 200,
        "down-plunger-speed": 300,
        "up-plunger-speed": 500,
        "tip-plunge": 6,
        "extra-pull-volume": 0,
        "extra-pull-delay": 200,
        "distribute-percentage": 0.1,
        "points": [
            {
                "f1": 1,
                "f2": 1
            },
            {
                "f1": 5,
                "f2": 5
            },
            {
                "f1": 7,
                "f2": 7
            },
            {
                "f1": 10,
                "f2": 10
            }
        ]    
    },
    "p10": {
        ...
        ...
    }
}

Ingredients

Most of the time, this section is not used. However, it must be included for the protocol to be read.

"ingredients": {
},

If you want to use the liquid tracking feature for your protocol, where the robot can track the rise and fall of liquid levels in various wells, and adjust its depth accordingly - you can add your ingredients like this:

"ingredients": {
    "water": [
    {
        "container": "trough",
        "location": "A1",
        "volume": "5000"
    },
    {
        "container": "trough",
        "location": "A2",
        "volume": "6000"
    }
},

Like all other volumes in the protocol, this ingredients volume is in μL. We will discuss how to implement the liquid tracking variable later on in this post.

Instructions

The "instructions" : [ are the bulk of most protocols. Its where you tell the robot what to do.

Instructions are grouped by tool. The tools are the things you have on the head, AKA the pipettes. Below shows how you would open up a p10 tool group of instructions:

"instructions": [
    {
        "tool": "p10",
    "groups": [

Each tool has four instructions: transfer, distribute, consolidate, and mix. We will show you the basics of each one, and then discuss possible additional parameters for each one.

Transfer

"transfer": [ is the most basic command. It instructs the robot to take a certain amount of liquid from one deck location and put it into another.

{
    "transfer": [
        {
            "from": {
                "container": "trough", 
                "location": "A1"
            },             
            "to": {
                "container": "plate", 
                "location": "D4"
            }, 
            "volume": 100
        }
    ] 
}

Within each transfer you specify the from and to fields, each of which specifies a container, a location within that container, and whether or not the robot will do a touch-tip when it draws from or dispenses into that container.

You also specify the volume of the transfer in μl.

Transfer commands are unique in that you can string multiple from and to groups together in order to do multiple transfers without changing the tip. The example above shows one transfer with one tip - but with a little bit of bracket play, you can do multiple transfers with the same tip.

{
    "transfer" : [
        {
            "from" : {
            "container": "plate",
            "location": "F1"
            },
            "to": {
                "container" : "plate",
                "location" : "A12"
            },
            "volume" : 10
        },
        {
            "from" : {
                "container": "plate",
                "location": "D1"
            },
            "to": {
                "container" : "plate",
                "location" : "A2"
            },
            "volume" : 10
        }
    ]
},
Distribute

Distribute picks up liquid from one location, and deposits it in a range of output wells. Like transfer, you specify the from and to fields, but you treat the to fields like an array and include multiple locations and volumes.

{
    "distribute": {
        "from": {
            "container": "trough", 
             "location": "A2"
        }, 
        "to": [
            {
                "container": "plate", 
                "location": "A2", 
                "volume": 50
            }, 
            {
                "container": "plate", 
                "location": "B3", 
                "volume": 100
            }
        ], 
        "blowout": false
    }
},
Consolidate

Consolidate is the reverse of distribute, it collects liquid from multiple locations and deposits it all in a final location. In this command, the 'from' locations are in an array.

{
    "consolidate": { 
        "from": [
            {
                "container": "plate", 
                "location": "A1", 
                "volume": 100
            }, 
            {
                "container": "plate", 
                "location": "A2", 
                "volume": 100
            }
        ], 
        "to": {
            "container": "trough", 
            "location": "A2"
        },
        "blowout": false
    }
},
Mix

Instead of setting up a transfer command to transfer between the same locations, you can use a mix command to, well, mix your sample. Just give it the container and location, and volume, then tell it how many times to pipette up and down - repetitions.

{
    "mix": [
        {
            "container": "plate", 
            "location": "A2", 
            "volume": 100, 
            "repetitions": 5, 
            "blowout": true
        }
    ]
},
Parameters

There are many parameters you can apply to a command, with the most basic being container, location, and "volume": 100. These are the only three mandatory parameters for a command to be used, but there are many more that can help improve your experiment.

"container": "plate" the user given name of the contianer as stated in the deck section

"location": "A1" the specific location (well, tube, etc.) within the container

"volume": 100 in μL

"tip-offset": 5 (optional) the robot automatically moves to the bottom of the well, but if you wanted to pull/deposit liquid at the top, you can apply a tip-offset to raise it (in mm)

"delay": 10 (optional) tell the robot to pause after picking up or depositing liquid (in seconds)

"touch-tip": true (optional) the tip can be touched against the sides of the well after depositing or picking up liquid in order to remove any liquid on the outside of the tip

"blowout": true (optional) the pipette will move to the "blowout" position after dispensing liquid in order to push the last drops out

"extra-pull": true (optional) the pipette will pull up extra liquid (per "head" settings)

"liquid-tracking": true (optional) the tip will move to the current height of the liquid based on the ingredients section, instead of to the bottom of the well, and will continue to track liquid levels throughout the protocol

These parameters can be applied in both the from and to sections.