Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Python Code Generator
#1
I'm new to coding, but I've taken on a project to write a python code that generates plating for PCR set up.

At my lab we use a machine called OpenTrons hood and within it through python you can program protocols. This part is generally straightforward. Below is one code I set up for a PCR set up:

   from opentrons import robot, containers, instruments

robot.head_speed(x=3000,  y=3000,  z=800, a=300, b=300)

# containers
plate = containers.load('96-PCR-short', 'D1', 'plate')
tube_rack = containers.load('tube-rack-.75ml', 'C1', 'tube_rack')

# a tip rack for our pipette
p10rack = containers.load('tiprack-10ul', 'B1', 'tiprack')
trash = containers.load('point', 'A1', 'trash' )

# p10 pipette on robot axis A
p10 = instruments.Pipette(
    trash_container=trash,
    name="p10",
    axis= "a",
    min_volume=1,
    max_volume=10,
    tip_racks=[p10rack]
)

# simple, atomic commands to control fine details
p10.pick_up_tip()

p10.transfer(
    6,
    tube_rack.wells('A1'),
    plate.wells('A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'G1', 'G2', 'G3'),
    blow_out=True

)

p10.transfer(
    6,
    tube_rack.wells('A2'),
    plate.wells('D1', 'D2', 'D3', 'E1', 'E2', 'E3', 'F1', 'F2', 'F3', 'H1', 'H2', 'H3'),
    blow_out=True

)


p10.transfer(
    4,
    tube_rack.wells('B1'),
    plate.wells('A1', 'B1', 'C1', 'D1', 'E1', 'F1'),
    blow_out=True
)

p10.transfer(
    4,
    tube_rack.wells('B2'),
    plate.wells('A2', 'B2', 'C2', 'D2', 'E2', 'F2'),
    blow_out=True
)

p10.transfer(
    4,
    tube_rack.wells('B3'),
    plate.wells('A3', 'B3', 'C3', 'D3', 'E3', 'F3',),
    blow_out=True
)

p10.transfer(
    4,
    tube_rack.wells('B4'),
    plate.wells('G1', 'G2', 'G3', 'H1', 'H2', 'H3',),
    blow_out=True
)
What this code does is first it pipettes different master mixes (liquid) into a certain amount of holes (wells). Then, it transfer another liquid (cDNA samples) into certain wells to test each mastermix against each cDNA sample 3 times. Lastly, it transfers water into 3 wells for each mastermix as a negative. The code above is for 2 mastermixes and 3 samples of cDNA and preforms this trice.

What I want to do though, is set up a code that allows you to enter in a number of mastermixes, a number of cDNA samples, and generates the plating for you (as you can see it dictates exactly what plate to transport liquid into in the code).

Here is what I want the code to do:

1. Assign mastermix to the x or y axis
The plate has rows A-H (y) and columns (1-12) (x). Assign mastermix to any number of rows or columns

I.e. say we had 3 mastermixes we could arrange it as

Mastermix 1 (A)
Mastermix 2 (B)... and so on

2. Assign cDNA & water to the remaining axis
Say we were testing 3 cDNA samples and we wanted to preform this test twice it would look something like this...

cDNA 1 (1-2)
cDNA 2 (3-4)
cDNA 3 (5-6)

then water for our negatives

water (7-8)

The code would then generate the plating for you.

In the code you might also notice tubing - there is a tube rack where we aspirate liquid from and it (will have) default set up as

A1 - Ax for mastermix
B1 - Bx for cDNA
C1 - water

I'm very new to coding and I was wondering how I could first approach creating this code.

**in the code I posted water is B4 but I will move it to C1 for simplicity
Reply
#2
Cool project!

I wrote this example for you that 'transfers' mastermix to a series of wells as you wanted. Note that I used a dummy transfer function to generate some output. You'll need to adjust this of course.

The idea is: first you make a list of sources for both your mastermix and cDNA. Then, for each mastermix, you must make a list of target wells to transfer them to. Then you make the transfer.
# dummy transfer function, just to test
def transfer(p_amount, p_source, p_target):
 print("Transfering {:>5}ul from {} to {}".format(p_amount, p_source, p_target))


# list of sources of mastermix and cDNA
mastermix_sources = ['A1', 'A2']
cDNA_sources = ['B1', 'B2', 'B3']


# loop through each source of mastermixes
for i, mm in enumerate(mastermix_sources):
 
 # make a list of all targets for this mastermix
 targets =list("{}{}".format(chr(65+i), j+1) for j in range(len(cDNA_sources)))
 
 # transfer mastermixes to wells
 transfer(
           10, 
           mm,
           targets
         )
Which gives me the output:
Output:
Transfering 10ul from A1 to ['A1', 'A2', 'A3'] Transfering 10ul from A2 to ['B1', 'B2', 'B3']
Now this line is a bit complicated, so let me explain:
targets = list( "{}{}".format(chr(65+i), j+1) for j in range(len(cDNA_sources)) )
It generates a list of targets like this:
  • for j in range(len(cDNA_sources))
    loops j through numbers 0 up to the amount of cDNA sources you have
  • "{}{}".format( chr(65+i), j+1)
    format replaces {} in the string with the arguments given:
    • chr(65+i)
      chr returns an ASCII character, chr(65) is A, chr(66) is B, etc. i is the index of this mastermix which we have because we used enumerate
    • j comes from the for loop. We want the first target to be A1, and j starts at 0, so we add 1
  • list makes it into a list (obviously :p)

If anything else is unclear don't hesitate to ask.

Now try and see if you can adapt this example so you can generate the transfers from the cDNA_sources to the wells as well.
Reply
#3
Firstly, thank you SO much for your help! This has been amazing. I've been looking over the code though, and I had a few questions about adjustments I could make- here's what I wrote it as 


def transfer(p_amount, p_source, p_target):
 print("Transfering {:>10}ul from {} to {}".format(p_amount, p_source, p_target))
  
mastermix_sources = ['A1', 'A2' ]
cDNA_sources = ['B1', 'B2', 'B3', 'B4' ]
 
for i, mm in enumerate(mastermix_sources):

 #list of targets for mastermix
 targets =list("{}{}".format(chr(65+i), j+1) for j in range(len(cDNA_sources)))
  
 transfer(
           6, 
           mm,
           targets
         )
 
for i, mm in enumerate(cDNA_sources):
    
#list of targets for mastermix
 targets =list("{}{}".format(chr(63+i), j+1) for j in range(len(mastermix_sources)))

transfer(
           4, 
           mm,
           targets
         )

Sometimes we run the test twice or thrice so we need the mastermix to dispense into more wells. For example, if we had 3 mastermixes and 2 cDNA samples we would have

3 x 2 = 6 platings of mastermix, but if we wanted to preform it double it would be 12 (x2). How could I incorporate a double or triple feature into the code?

I also tried to copy what you did with the first part with mastermix and apply it to cDNA because first we dispense mastermix and then we dispense cDNA - do you know where I went wrong? The output I got was 

Transfering          6ul from A1 to ['A1', 'A2', 'A3', 'A4']
Transfering          6ul from A2 to ['B1', 'B2', 'B3', 'B4']
Transfering          4ul from B4 to ['B1', 'B2']

And so the first part with mastermixes worked great - but not the part I did with cDNA. For cDNA In this case with 2 mastermixes and 4 cDNA samples I would want it to dispense 

from B1 into (on the wells of a plate) into A1, B1
from B2 into (on the wells of a plate) into A2, B2 and so on (assuming we wanted to run the experiment just once, not twice or thrice. 

Thank you for all your help!
Reply
#4
Next time please post your code between [ python ] tags (but without the spaces), that makes it a lot easier to read ;)

In python indentation matters. Try this:
for i in range(3):
  # do some stuff
  a = i + 10
  
  print("I'm in the loop!", i)

for j in range(3):
  # do some stuff
  a = i + 10
  
print("I'm not in the loop!", j)
See the difference? Now if there's nothing in your loop, python will give an error. Also if you use indentation where you shouldn't. Try to use 4 spaces for each level of indentation (most editors will do this for you). This will keep your code readable (and it's what everybody else uses).

When you fix the indentation, you'll notice that the transfer function is executed 4 times instead of only once.

Now, about getting the right targets. Do you understand how the variable i corresponds to the column and j to the row of the target well? This piece of code does exactly the same as the first example I gave, maybe that will clear it up for you:
for i, mm in enumerate(mastermix_sources):
  #list of targets for mastermix
  targets = []
  for j in range(len(cDNA_sources)):
    column = chr(65+i)
    row = j+1
    target = "{}{}".format(column, row)
    targets.append(target)
  
  transfer(
           6, 
           mm,
           targets
         )
You hardly need to change anything to change this from A1, A2, A3 to A1, B1, C1. Also notice that the transfer function is part of the first loop, but not of the second, because of the indentation used.

If you got this figured out we can look at using multiple wells for 1 sample of cDNA, etc. Let me know if anything is unclear.
Reply
#5
Hi! Thanks for your help! I've got it figured out so the mastermix and cDNA go to the right columns and wells! Here is the code:

def transfer(p_amount, p_source, p_target):
 print("Transfering {:>10}ul from {} to {}".format(p_amount, p_source, p_target))
  
mastermix_sources = ['A1', 'A2', 'A3' ]
cDNA_sources = ['B1', 'B2', 'B3' ]

for i, mm in enumerate(mastermix_sources):
  #list of targets for mastermix
  targets = []
  for j in range(len(cDNA_sources)):
    column = chr(65+i)
    row = j+1
    target = "{}{}".format(column, row)
    targets.append(target)
   
  transfer(
           6, 
           mm,
           targets
         )

for i, mm in enumerate(cDNA_sources):
  #list of targets for mastermix
  targets = []
  for j in range(len(mastermix_sources)):
    column = chr(65+j)
    row = i+1
    target = "{}{}".format(column, row)
    targets.append(target)
   
  transfer(
           4, 
           mm,
           targets
         )
The only thing I need to do now is figure out how to duplicate the experiment.

Right now the output is:

Transfering          6ul from A1 to ['A1', 'A2', 'A3']
Transfering          6ul from A2 to ['B1', 'B2', 'B3']
Transfering          6ul from A3 to ['C1', 'C2', 'C3']
Transfering          4ul from B1 to ['A1', 'B1', 'C1']
Transfering          4ul from B2 to ['A2', 'B2', 'C2']
Transfering          4ul from B3 to ['A3', 'B3', 'C3']
and I'm looking to find a way to adjust it so it can be duplicated or tripled. For example, if we duplicate it it looks like this instead:

Transfering          6ul from A1 to ['A1', 'A2', 'A3', 'A4', 'A5', 'A6' ]
Transfering          6ul from A2 to ['B1', 'B2', 'B3', 'B4', 'B5', 'B6']
Transfering          6ul from A3 to ['C1', 'C2', 'C3', 'C4', 'C5', 'C6']
Transfering          4ul from B1 to ['A1', 'B1', 'C1', 'A4', 'B4', 'C4' ]
Transfering          4ul from B2 to ['A2', 'B2', 'C2', 'A5', 'B5', 'C5' ]
Transfering          4ul from B3 to ['A3', 'B3', 'C3', 'A6', 'B6', 'C6' ]
or something to similar affect- also I can't say thank you enough for your help!
Reply
#6
Well done! 

The easiest way to achieve what you want is to just repeat the source in the cDNA_sources list. However that will give you a result like this:
Output:
Transfering          6ul from A1 to ['A1', 'A2', 'A3', 'A4', 'A5', 'A6'] Transfering          6ul from A2 to ['B1', 'B2', 'B3', 'B4', 'B5', 'B6'] Transfering          6ul from A3 to ['C1', 'C2', 'C3', 'C4', 'C5', 'C6'] Transfering          4ul from B1 to ['A1', 'B1', 'C1'] Transfering          4ul from B2 to ['A2', 'B2', 'C2'] Transfering          4ul from B3 to ['A3', 'B3', 'C3'] Transfering          4ul from B1 to ['A4', 'B4', 'C4'] Transfering          4ul from B2 to ['A5', 'B5', 'C5'] Transfering          4ul from B3 to ['A6', 'B6', 'C6']
which may not be what you want.

Here's what I would do:
  • create variables for the amount of mastermixes and cDNA you have. Also create a variable for the nr of tests you want.
  • From these variables, create a list of sources for your mm and cDNA.
  • For each mastermix, generate the list of target wells and transfer it. Keep in mind how much wells you need.
  • For each cDNA sample
    • loop through the amount of mastermixes
    • then loop through the amount of tests
    • generate the target
    • transfer everything at once
Good luck!
Reply
#7
To make the functions replicate I just added in a variable to replicate it. It's basically the same code but:

def transfer(p_amount, p_source, p_target):
 print("Transfering {:>10}ul from {} to {}".format(p_amount, p_source, p_target))
  
mastermix_sources = ['A1', 'A2', 'A3' ]
cDNA_sources = ['B1', 'B2', 'B3' ]

replicate = 2

for i, mm in enumerate(mastermix_sources):
  #list of targets for mastermix
  targets = []
  for j in range(len(cDNA_sources * replicate)):
    column = chr(65+i
                 )
    row = j+1
    target = "{}{}".format(column, row)
    targets.append(target)
    
  transfer(
           6, 
           mm,
           targets
         )
 
for i, mm in enumerate(cDNA_sources * replicate):
  #list of targets for mastermix
  targets = []
  for j in range(len(mastermix_sources)):
    column = chr(65+j)
    row = i+1
    target = "{}{}".format(column, row)
    targets.append(target)
    
  transfer(
           4, 
           mm,
           targets
         )
I'm still working to collaborate all the results into one line, so results are all put together, i.e. there aren't two lines for cDNA where 

B= values

However, as I work through that, I was wondering, if alternatively, there was is a way to transfer plate numbers (A1, A2,) to just numbers (1,2,3,4,5,6)

so A1 = 1
A12 = 12
B1 = 13
B12 = 24 

and so on. What would be the easiest way of working towards this?

What I've tried so far is assigning A1 = 1 and so on but I get an error that says I can't assign it to literal - which makes sense becaues it is a number. I find the A1, B1, C1 format easier to understand, but for some plates we require combinations that go past H1 (which is the last letter we have for a column). Do I need to make a list of consecutive numbers somewhere?
Reply
#8
I would use a dictionary, and make use of the modulo operator. Something like this:
N_cols = 30
name2index = {"{}{}".format(chr(65 + int(a/12)), a%12 + 1):(a+1) for a in range(N_cols)}
print(name2index)

print(name2index['A1'])
print(name2index['B12'])
int(a/12) turns a number into an integer, dropping any decimals. So int(1/12) = 0, int(2/12) = 0, ..., int(12/12) = 1, int(13/12) = 1, etc. We can use this to get the letter.
The modulo operator % gives the remainder of a certain division. So 0 % 12 = 0, 1 % 12 = 1, 2 % 12 = 2, ... , 12 % 12 = 0, 13 % 12 = 1, 14 % 12 = 2, etc.

The outermost braces {} turn it into a dictionary. The keys are 'A1', 'A2', etc. and the values are 1, 2, 3, 4...

P.S. here's my version for replicating tests
Reply
#9
Thank you for the dictionary. It was able to translate wells (A1) to their number (1) but I had trouble adding it into my code to convert the A1 coordinates to numbers. So, I tried out another thing. 

I thought that maybe I could combine the rows/columns and create just one 'wells' as the target like this:

def transfer(p_amount, p_source, p_target):
 print("Transfering {:>5}ul from {} to {}".format(p_amount, p_source, p_target))
 
# number of mastermixes
N_mm = 3

# number of cDNA samples 
N_cDNA = 2
# number of tests to run on each sample
replicates = 2

mastermix_sources = list("A{}".format(j+1) for j in range(N_mm))
cDNA_sources = list("B{}".format(j+1) for j in range(N_cDNA))
 
for i, mm in enumerate(mastermix_sources):
  #list of targets for mastermix
  targets =
  for j in range(N_cDNA * replicates):
    wells = j + 1 + i*replicates 
    target = "{}".format(wells)
    targets.append(target)
   
  transfer(
           6, 
           mm,
           targets
         )
 
I did some fiddling around and it spits out the right numbers in ascending order (for the first half - haven't touched the cDNA portion yet) but it repeats numbers. Is there a way to restrict repeating numbers. I tried some if commands but I kept getting syntax errors. 

Here's the output-

Transfering     6ul from A1 to ['1', '2', '3', '4']
Transfering     6ul from A2 to ['3', '4', '5', '6']
Transfering     6ul from A3 to ['5', '6', '7', '8']
Reply
#10
I'm on mobile right now so I can't respond in as much detail.

Take a look at your calculation. If i=0, the values should be 1,2,3,... If i=1, at what value should it start next?

If you use wells = j + 1 + i * replicates * N_cDNA I think it should work.

Another solution would be to keep a counter. Initialize before the loop wells = 0. And then in the loop you just say wells += 1
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Is the following code returning a generator expression? quazirfan 8 1,629 Apr-11-2023, 11:44 PM
Last Post: quazirfan
  Python returns generator instead of None Tawnwen 4 4,704 Mar-09-2018, 07:06 AM
Last Post: DeaD_EyE
  receive from a generator, send to a generator Skaperen 9 5,505 Feb-05-2018, 06:26 AM
Last Post: Skaperen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020