#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2013 by Björn Johansson. All rights reserved.
# This code is part of the Python-dna distribution and governed by its
# license. Please see the LICENSE.txt file that should have been included
# as part of this package.
'''
This module contain experimental functionality for smulation of agarose gel electrophoresis of DNA.
Note
----
This code is in a very early stage of development and documentation.
Read the source code to get an idea for how it works.
'''
import pygame
import math
import webbrowser
[docs]def gamma(x, t, k=1):
b = 1.0 / t
x = x * k
c = 0.0
for i in range(0, k):
c += (math.exp(-b * x) * (b * x) ** i) / math.factorial(i)
return c
[docs]def testColour(c):
""" Convert integer to colour, which saturates if > 255 """
if c <= 255:
return (c, c, c)
else:
return (255,255,255)
#def display(width, height, image):
# """ Create a Pygame image of dimensions width x height and show image """
#
# screen = pygame.display.set_mode((width, height))
# screen.blit(image, (60, 30))
# pygame.display.flip()
#running = True
#while running:
# for event in pygame.event.get():
# if event.type == pygame.QUIT:
# running = False
[docs]class Gel:
""" A Gel contains lanes into which DNA can be loaded and run.
The Gel is exposured to see the location and intensity of DNA. """
LANE_WIDTH = 30.
LANE_HEIGHT = 6
LANE_MARGIN = 6
BORDER = 40
def __init__(self, size, agarose=1):
self.y = size[0]
self.x = 2 * Gel.BORDER + size[1] * (Gel.LANE_WIDTH + Gel.LANE_MARGIN) - Gel.LANE_MARGIN
self.agarose = agarose # Should test whether value is reasonable (say, 0.5% - 3%)
self.optimum_DNA_length = 2000 / agarose ** 3 # Best separate based on agarose hole size
self.samples = []
self.lanes = []
for n in range(size[1]):
self.samples.append([])
self.lanes.append([])
for lane in self.lanes:
for n in range(Gel.BORDER, self.y + 3):
lane.append(0.0)
[docs] def loadSample(self, lane, sample):
"""Add list containing tuple of DNA length and concentrations to lane in gel. """
for dna in sample:
strand = {'length': float(dna[0]),
'conc': dna[1] * 1./Gel.LANE_HEIGHT,
'position': Gel.BORDER+1}
self.samples[lane-1].append(strand)
[docs] def run(self, time=30.0, voltage=20.0):
""" Move loaded DNA down the gel at a rate dependent on voltage, DNA length and agarose concentration. """
max_dist = 0.25 * time * voltage
for sample in self.samples:
for dna in sample:
g = gamma( dna['length']/20, int(self.optimum_DNA_length/20) )
dna['position'] += max_dist * g
[docs] def expose(self, exposure=0.1, aperture=2):
"""Returns an image of the gel with the DNA highlighted"""
c1, c2 = exposure * 100, exposure * 200
fill_colour = (c1, c1, c2)
image = pygame.Surface((self.x+2, self.y+2))
pygame.draw.rect(image, fill_colour, (1,1, self.x, self.y), 0)
edge_colour = (140, 140, 150)
edges = pygame.Surface((self.x+2, self.y+2))
edges.set_colorkey((0,0,0))
edges.set_alpha(120)
pygame.draw.rect(image, edge_colour, (0,0, self.x+2, self.y+2), 1)
self._findDNAConcentrations(c1)
x = Gel.BORDER + 1
for lane in self.lanes:
for position in range(len(lane)):
if lane[position] > 0:
brightness = lane[position] * exposure / aperture + c1
colour1 = testColour(brightness)
colour2 = testColour(brightness*0.6)
colour3 = testColour(brightness*0.3)
# draw bands
pygame.draw.line(image, colour3, (x-1, position+Gel.BORDER),(x+Gel.LANE_WIDTH+1, position+Gel.BORDER))
pygame.draw.line(image, colour2, (x, position+Gel.BORDER),(x+Gel.LANE_WIDTH, position+Gel.BORDER))
pygame.draw.line(image, colour1, (x+1, position+Gel.BORDER),(x+Gel.LANE_WIDTH-1, position+Gel.BORDER))
# draw well
pygame.draw.rect(edges, edge_colour, (x, Gel.BORDER, Gel.LANE_WIDTH, Gel.LANE_HEIGHT), 1)
x += Gel.LANE_WIDTH + Gel.LANE_MARGIN
image.blit(edges, (0,0))
return image
def _findDNAConcentrations(self, background):
"""Determines where in the concentration of DNA in every part of the gel"""
length = len(self.lanes[0])
for x in range(len(self.samples)):
for dna in self.samples[x]:
for y in range(Gel.LANE_HEIGHT-2):
pos = int(dna['position']) + y
if pos < length-4:
# Very crude way to create blurred line
self.lanes[x][pos-2] += 0.06 * dna['conc'] * dna['length']
self.lanes[x][pos-1] += 0.12 * dna['conc'] * dna['length']
self.lanes[x][pos] += 0.2 * dna['conc'] * dna['length']
self.lanes[x][pos+1] += 0.12 * dna['conc'] * dna['length']
self.lanes[x][pos+2] += 0.06 * dna['conc'] * dna['length']
[docs]def AgaroseGel(samples, *args, **kwargs):
myGel = Gel(size=(360, len(samples)), agarose=1.0)
for i, sample in enumerate(samples):
try:
sample = [(len(x), 100) for x in sample]
except:
pass
myGel.loadSample(1+i, sample)
myGel.run(time=120)
my_image = myGel.expose(exposure=0.01)
pygame.image.save(my_image, 'test_run.jpg')
webbrowser.open('test_run.jpg')
#display(360, 360, my_image)
#from PIL import Image
#img = Image.open('test_run.png')
#img.show()
#display(360, 360, my_image)
#import sys;sys.exit()
# Or save image as a PNG
#import os
#os.startfile('test_run.png')
if __name__ == '__main__':
from pydna import *
lambda_ = read("../tests/lambda.gb")
from Bio.Restriction import PstI
samples = sorted(lambda_.cut(PstI), key=len)
AgaroseGel([samples,])
LADDER_1KB =[
(300, 250),
(500, 150),
(700, 100),
(1000, 75),
(1500, 50),
(2000, 40),
(2500, 35),
(3000, 30),
(4000, 25),
(5000, 30),
(6000, 15),
(8000, 12),
(10000, 15),
(14000, 4),
(24000, 2)]
# Samples are list of tuples in the form (length, concentration)
sample1 = [(400, 200), (500, 200), (900, 200)]
sample2 = [(400, 200), (900, 200), (1500, 200)]
sample3 = [(4000, 20), (4100, 20), (4900, 10)]
# Set up gel, giving its size and % agarose