Визуализатор генетической последовательности в Python


Я новичок в Python, и это мой первый крупный проект. Вдохновленный этой реддите пост, я написал программу, которая принимает последовательность ДНК (в формате fasta формат файла) и генерирует графическое представление enter image description here

Вирус Эбола

Я оптимизировал его столько, сколько я могу, но я попал в узкое место с построением фактического изображения и отображения путь на него. Я нарваться на ошибки памяти при больших исходных файлов. Вот некоторые из исходного файла я проверяю. Я могу вынести все, кроме фруктов геноме мухи, которая вылетает до завершения.

У кого-нибудь есть советы для рефакторинга и оптимизации этого? Особенно мне было интересно, если я мог бы разбить изображение на блоки, сделать их по-одному, сохранять их на диск (как с array класса), а затем склеить их обратно вместе, но я не могу выяснить способ.

#!/usr/bin/env python3
# Python 3.6

from PIL import Image, ImageDraw
from re import sub
from copy import deepcopy, copy
from os import listdir, path, makedirs
from shutil import rmtree
import pickle

filepath = input("""What is the input file? FASTA format recommended (path/to/file)
?> """)
file = open(filepath,'r')
print("Serializing %s ..."%filepath)
raw = ''.join([n for n in file.readlines() if not n.startswith('>')]).replace('\n',"").lower()
file.close()
del file
raw = sub(r'[rykmswbdhv-]', "n", raw) # Handles miscellaneous FASTA characters
raw = sub(r'[^atgcn]', "", raw) # Handles 4 bases and not-lnown
sequence = deepcopy(list(raw)) # Completed filtered list containing all the bases
del sub, raw
print("The input file has been serialized. (%s items) Calculating path..." %str(len(sequence)))

action = { # The different bases and their respective colours and movements
    "a": ((0,255,0),0,-1), #green - Moves up
    "t": ((255,0,0),0,1), #red - Moves Down
    "g": ((255,0,255),-1,0), #hot pink - Moves Left
    "c": ((0,0,255),1,0), #blue - Moves Right
    "n": ((0,0,0),0,0), #black - Stays on spot
}

class array(): # Array class that dynamically saves temp files to disk to conserve memory
    def __init__(self):
        self.a = []
        self.maxmem = int(5e6) # How much data should be let accumulated in memory before being dumped to disk?
        # 1e6 ~ 20,000mb and 5e6 ~ 100,000mb
        self.fc = 0 # File Count
        if path.exists("temp"):
            rmtree('temp')
        makedirs("temp")
        self.path = "temp/temp%d.dat" # Name of temp files
    def append(self,n):
        self.a.append(n)
        if len(self.a) >= self.maxmem:
            self.fc += 1
            with open(self.path%self.fc,'wb') as pfile:
                pickle.dump(self.a,pfile) # Dump the data
            del self.a[:]
    def setupiterate(self):
        if len(self.a) > 0:
            self.fc += 1
            with open(self.path%self.fc,'wb') as pfile:
                pickle.dump(self.a,pfile)
        self.maxfc = copy(self.fc)
        self.fc = 0
    def iterate(self): # This is called in a loop
        self.fc += 1
        with open(self.path%self.fc,'rb') as pfile:
            return pickle.load(pfile) # Get the data

count = [[0,0],[0,0]] # Top left and bottom right corners of completed path
curr = [0,0]

pendingactions = array()
for i in sequence:
    #get the actions associated from dict
    curr[0] += action[i][1]
    curr[1] += action[i][2]
    if curr[0] > count[0][0]:
        count[0][0] = curr[0]
    elif curr[0] < count[1][0]:
        count[1][0] = curr[0]
    if curr[1] > count[0][1]:
        count[0][1] = curr[1]
    elif curr[1] < count[1][1]:
        count[1][1] = curr[1]
    pendingactions.append((copy(curr),action[i][0]))
pendingactions.setupiterate()
del sequence, deepcopy, copy

# Final dimensions of image + 10px border
dim = (abs(count[0][0]-count[1][0])+20,abs(count[0][1]-count[1][1])+20)
print("The path has been calculated. Rendering image... %s"%("("+str(dim[0])+"x"+str(dim[1])+")"))
img = Image.new("RGBA", dim, None) # Memory intensive with larger source files
draw = ImageDraw.Draw(img)

for i in range(0,pendingactions.maxfc):
        for j in pendingactions.iterate(): # This method returns a single file's worth of data
            draw.point((j[0][0]+abs(count[1][0])+10,j[0][1]+abs(count[1][1])+10), fill=j[1]) # Plots a point for every coordinate on the path

def mean(n): # I couldn't find an average function in base python
    s = float(n[0] + n[1])/2
    return s

# Start and End points are dynamically sized to the dimensions of the final image
draw.ellipse([((abs(count[1][0])+10)-round(mean(dim)/500),(abs(count[1][1])+10)-round(mean(dim)/500)),((abs(count[1][0])+10)+round(mean(dim)/500),(abs(count[1][1])+10)+round(mean(dim)/500))], fill = (255,255,0), outline = (255,255,0)) #yellow
draw.ellipse([((curr[0]+abs(count[1][0])+10)-round(mean(dim)/500),(curr[1]+abs(count[1][1])+10)-round(mean(dim)/500)),((curr[0]+abs(count[1][0])+10)+round(mean(dim)/500),(curr[1]+abs(count[1][1])+10)+round(mean(dim)/500))], fill = (51,255,255), outline = (51,255,255)) #neon blue

del count, curr, mean, dim, draw, ImageDraw

print("The image has been rendered. Saving...")
loc = '%s.png'%filepath.split(".", 1)[0]
img.save(loc)
img.close()
del img
print("Done! Image is saved as: %s"%loc)
rmtree('temp')
print("Temp files have been deleted.")
input("Press [enter] to exit")


Комментарии
1 ответ

Вы должны сделать вашу array класса ведут себя немного больше, как питон списка. Было бы неплохо, если бы было хорошее представление о ней, если бы вы могли extend список, если бы вы могли просто перебрать его, не беспокоясь о настройке итерации первого или даже инициализировать его с некоторыми значениями уже в массиве. В настоящее время вы можете иметь не более одного экземпляра array, потому что она удаляет временный каталог при инициализации

Все это может быть достигнуто с помощью так называемой магии или Дандер-методов. Они специально назвали методы, которые автоматически вызываются Python в определенных обстоятельствах. Для приятного представления это __repr__, Если вы называете print(x) или str(x)это __str__ и перебрать его __iter__. С этими (и еще несколько методов) ваш класс становится:

from itertools import islice
from os import path, makedirs
from shutil import rmtree
import pickle

class array():
"""1D Array class
Dynamically saves temp files to disk to conserve memory"""

def __init__(self, a=None, maxmem=int(5e6)):
# How much data to keep in memory before dumping to disk
self.maxmem = maxmem
# 1e6 ~ 20,000mb and 5e6 ~ 100,000mb
self.fc = 0 # file counter
# make a unique subfolder (unique as long as the array exists)
self.uuid = id(self)
self.dir = ".array_cache/%d" % self.uuid
if path.exists(self.dir):
rmtree(self.dir)
makedirs(self.dir)
self.path = self.dir + "/temp%d.dat" # Name of temp files

self.a = []
if a is not None:
self.extend(a)

def append(self, n):
"""Append n to the array.
If size exceeds self.maxmem, dump to disk
"""
self.a.append(n)
if len(self.a) >= self.maxmem:
with open(self.path % self.fc, 'wb') as pfile:
pickle.dump(self.a, pfile) # Dump the data
self.a = []
self.fc += 1

def extend(self, values):
"""Convenience method to append multiple values"""
for n in values:
self.append(n)

def __iter__(self):
"""Allows iterating over the values in the array.
Loads the values from disk as necessary."""
for fc in range(self.fc):
with open(self.path % fc, 'rb') as pfile:
yield from pickle.load(pfile)
yield from self.a

def __repr__(self):
"""The values currently in memory"""
s = "[..., " if self.fc else "["
return s + ", ".join(map(str, self.a)) + "]"

def __getitem__(self, index):
"""Get the item at index or the items in slice.
Loads all dumps from disk until start of slice for the latter."""
if isinstance(index, slice):
return list(islice(self, index.start, index.stop, index.step))
else:
fc, i = divmod(index, self.maxmem)
with open(self.path % fc, 'rb') as pfile:
return pickle.load(pfile)[i]

def __len__(self):
"""Length of the array (including values on disk)"""
return self.fc * self.maxmem + len(self.a)

Я также добавил docstrings чтобы описать то, что методов сделать и сделали размер необязательный параметр (для тестирования, в основном)

Вы можете использовать этот класс как это:

x = array(maxmem=5)
x.extend(range(21))
print(x)
# [..., 20]
list(x)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
x[4]
# 4
x[:4]
# [0, 1, 2, 3]
len(x)
# 21
for i in x:
print(i)
# 0
# 1
# 2
# ...
x2 = array(range(10), maxmem=9)
list(x2)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Эта реализация еще далека от совершенства. Действительно extend вероятно, может быть реализовано лучше, расширяясь в партии self.maxmem (забота о первый фрагмент). Добавление элемента в произвольное место не реализовано (__setitem__), и не удаляет (__delitem__). Было бы лучше, если бы путь был также настраивается, так что если у вас есть два экземпляра arrayони не заменяют друг друга, и так далее. Но сейчас это осталось в качестве упражнения для читателя...

4
ответ дан 11 апреля 2018 в 02:04 Источник Поделиться