clemensspielvogel.png

# Grundlagen der Programmierung SS 2015, Task - Clemens Spielvogel
 
from tkinter import *
from tkinter import ttk
import tkinter.filedialog
import math
from datetime import datetime
 
class GPTask(tkinter.Tk):
    def __init__(self):
        tkinter.Tk.__init__(self)
        self.wm_title("GPTask")
        self.set_fasta = False
        self.oneletter = True
        self.version = 1.0
        self.showless = True  # Zum Aktualisieren des show-more/less Button
        self.v = IntVar()     # Für Radiobuttons (One-letter / Three letter code)
        self.v.set(1)         # Voreingestellter Radiobutton
        self.letter_code = [("One-letter code", 1),("Three-letter code", 2)]
        self.code = {
                "AAA" : ["K" ,"Lys"], "AAG" : ["K" ,"Lys"], "AAU" : ["N" ,"Asn"], "AAC" : ["N" ,"Asn"], "AGA" : ["R" ,"Arg"],
                "AGG" : ["R" ,"Arg"], "AGU" : ["S" ,"Ser"], "AGC" : ["S" ,"Ser"], "AUA" : ["I" ,"Ile"], "AUG" : ["M" ,"Met"],
                "AUU" : ["I" ,"Ile"], "AUC" : ["I" ,"Ile"], "ACA" : ["T" ,"Thr"], "ACG" : ["T" ,"Thr"], "ACU" : ["T" ,"Thr"],
                "ACC" : ["T" ,"Thr"], "GAA" : ["E" ,"Glu"], "GAG" : ["E" ,"Glu"], "GAU" : ["D" ,"Asp"], "GAC" : ["D" ,"Asp"],
                "GGA" : ["G" ,"Gly"], "GGG" : ["G" ,"Gly"], "GGU" : ["G" ,"Gly"], "GGC" : ["G" ,"Gly"], "GUA" : ["V" ,"Val"],
                "GUG" : ["V" ,"Val"], "GUU" : ["V" ,"Val"], "GUC" : ["V" ,"Val"], "GCA" : ["A" ,"Ala"], "GCG" : ["A" ,"Ala"],
                "GCU" : ["A" ,"Ala"], "GCC" : ["A" ,"Ala"], "UAA" : ["*" ,"ochre"], "UAG" : ["*" ,"amber"], "UAU" : ["Y" ,"Tyr"],
                "UAC" : ["Y" ,"Tyr"], "UGA" : ["*" ,"opal"], "UGG" : ["W" ,"Trp"], "UGU" : ["C" ,"Cys"], "UGC" : ["C" ,"Cys"],
                "UUA" : ["L" ,"Leu"], "UUG" : ["L" ,"Leu"], "UUU" : ["F" ,"Phe"], "UUC" : ["F" ,"Phe"], "UCA" : ["S" ,"Ser"],
                "UCG" : ["S" ,"Ser"], "UCU" : ["S" ,"Ser"], "UCC" : ["S" ,"Ser"], "CAA" : ["Q" ,"Gln"], "CAG" : ["Q" ,"Gln"],
                "CAU" : ["H" ,"His"], "CAC" : ["H" ,"His"], "CGA" : ["R" ,"Arg"], "CGG" : ["R" ,"Arg"], "CGU" : ["R" ,"Arg"],
                "CGC" : ["R" ,"Arg"], "CUA" : ["L" ,"Leu"], "CUG" : ["L" ,"Leu"], "CUU" : ["L" ,"Leu"], "CUC" : ["L" ,"Leu"],
                "CCA" : ["P" ,"Pro"], "CCG" : ["P" ,"Pro"], "CCU" : ["P" ,"Pro"], "CCC" : ["P" ,"Pro"], "&&&" : ["\n\n", "\n\n"]
            }
 
        # Menus + Submenus
        self.menu_file = Menu()
        Tk.config(self, menu = self.menu_file)
        self.submenu = Menu(self.menu_file)
        self.menu_file.add_cascade(label = "File", menu = self.submenu)
        self.submenu.add_command(label = "Open...", command = self.file_open)
        self.submenu.add_separator()
        self.submenu.add_command(label = "Save as...", command = self.file_saveas)
        self.submenu.add_separator()
        self.submenu.add_command(label = "Exit", command = self.exitscript)
 
        self.menu_tools = Menu(self.menu_file)
        self.submenu_tools = Menu(self.menu_tools)
        self.menu_file.add_cascade(label = "Tools", menu = self.submenu_tools)
        self.submenu_tools.add_command(label = "Base converter", command = self.open_baseconverter)
        self.submenu_tools.add_command(label = "Reverse complement", command = self.open_reversecomplement)
 
        self.menu_help = Menu(self.menu_file)
        self.submenu_help = Menu(self.menu_help)
        self.menu_file.add_cascade(label = "Help", menu = self.submenu_help)
        self.submenu_help.add_command(label = "About GPTask", command = self.about)
        self.submenu.add_separator()
        self.submenu_help.add_command(label = "Help", command = self.help_info)
 
        #Frames
        self.frame_input = LabelFrame(text = "Input")
        self.frame_input.grid(sticky = E + W)
        self.frame_output = LabelFrame(text = "Output")
        self.frame_output.grid(sticky = E + W)
 
        #Text
        self.inputtext = Text(height = 1, width = 45, bg = "grey")
        self.inputtext.grid(in_ = self.frame_input, row = 1, column = 1)
        self.outputtext = Text(height = 10, width = 45, bg = "grey")
        self.outputtext.grid(in_ = self.frame_output, row = 5, column = 1)
        self.infotext = Text(height = 6, width = 45, bg = "grey")
        self.infotext.grid(in_ = self.frame_output, row = 7, column = 1)
        self.infotext.tag_configure("warning", foreground = "red")
 
        #Labels
        self.blanklabel = Label(text = "")
        self.blanklabel.grid(row = 9, column = 0)
        self.blanklabel2 = Label(text = "")
        self.blanklabel2.grid(in_ = self.frame_output, row = 6, column = 0)
        self.inputlabel = Label(text = "DNA or RNA sequence")
        self.inputlabel.grid(in_ = self.frame_input, row = 1, column = 0)
        self.outputlabel = Label(text = "Aminoacid sequence")
        self.outputlabel.grid(in_ = self.frame_output, row = 5, column = 0)
        self.infolabel = Label(text = "Additional information")
        self.infolabel.grid(in_ = self.frame_output, row = 7, column = 0)
 
        #Combobox
        self.inputformat = ttk.Combobox(text = "Raw Sequence", width = 15, state = "readonly")
        self.inputformat.grid(in_ = self.frame_input, row = 2)
        self.inputformat.config(values = ("Raw Sequence", "FASTA Format", "FASTQ Format"))
        self.inputformat.set("Raw Sequence")
 
        #Buttons
        self.transformbutton = Button(text = "Transform!", command = self.replace)
        self.transformbutton.grid(in_ = self.frame_input, row = 2, column = 1)
        self.clearoutputbutton = Button(text = "Clear output", command = self.clear_output)
        self.clearoutputbutton.grid(in_ = self.frame_output, row = 8, column = 1, sticky = N)
        self.showmorebutton = Button(text = ">>", command = self.showmoreless)
        self.showmorebutton.grid(in_ = self.frame_input, row = 2, column = 1, sticky = E)
 
        #Radiobuttons
        for txt, val in self.letter_code:
            Radiobutton(text = txt, variable = self.v, value = val, command = self.set_replace).grid(in_ = self.frame_input, row = 4, column = -1 + val, sticky = W)
 
    def showmoreless(self):
        if self.showless == True:
            self.showless = False
            self.showmorebutton.destroy()
            self.showlessbutton = Button(text = "<<", command = self.showmoreless)
            self.showlessbutton.grid(in_ = self.frame_input, row = 2, column = 1, sticky = E)
            intermediateinput = self.inputtext.get(1.0,"end-1c")
            self.inputtext.destroy()
            self.inputtext = Text(height = 10, width = 45, bg = "grey")
            self.inputtext.grid(in_ = self.frame_input, row = 1, column = 1)
            self.inputtext.insert(END, intermediateinput)
        else:
            self.showless = True
            self.showlessbutton.destroy()
            self.showmorebutton = Button(text = ">>", command = self.showmoreless)
            self.showmorebutton.grid(in_ = self.frame_input, row = 2, column = 1, sticky = E)
            intermediateinput = self.inputtext.get(1.0,"end-1c")
            self.inputtext.destroy()
            self.inputtext = Text(height = 1, width = 45, bg = "grey")
            self.inputtext.grid(in_ = self.frame_input, row = 1, column = 1)
            self.inputtext.insert(END, intermediateinput)
 
    def additional_info(self, tool):
        def get_str_invalidchars(invalidchars):
            str_invalidchars = ""
            for char in invalidchars:
                str_invalidchars += str(char + ", ")
            str_invalidchars = str_invalidchars.rstrip(", ")
            return str_invalidchars
 
        #Date and time
        now = datetime.now()
        day = now.day
        month = now.month
        year = now.year
        second = now.second
        minute = now.minute
        hour = now.hour
        minute = str(minute)
        hour = str(hour)
        second = str(second)
        if(len(minute) < 2):
            minute = str("0" + str(minute))
        if(len(hour) < 2):
            hour = str("0" + str(hour))
        if(len(second) < 2):
            second = str("0" + str(second))
        self.infotext.insert(END, "{}.{}.{}  {}:{}:{}\n".format(day, month, year, hour, minute, second))
 
        #GC content
        nC = seq.count("C")
        nG = seq.count("G")
        nGC = nG + nC
        nges = len(seq)
        proGC = round((nGC / nges) * 100, 4)
        GC = ("GC content: {}%\n".format(str(proGC)))
        self.infotext.insert(2.0, GC)
 
        # Warnings
        if tool == "baseconverter":
            if len(seq) % 3 != 0:
                self.infotext.insert(3.0, "Sequence containing non codon at end: '{}'\n".format(seq[len(seq) - len(seq) % 3:].upper()))
                self.infotext.tag_add("warning", 3.0, 99.0)
        valid_chars = ["A","T","U","G","C","&"] #& wegen neuer Zeile (siehe self.code)
        warning_invalidchar = False
        invalidchars = []
        for char in seq:
            if char not in valid_chars:
                warning_invalidchar = True
                invalidchars.append(char)
        if warning_invalidchar == True:
            self.infotext.insert(4.0, "Sequence containing {} invalid character(s): ".format(len(invalidchars)))
            self.infotext.insert(END, get_str_invalidchars(invalidchars))
            self.infotext.insert(END, "\n")
            self.infotext.tag_add("warning", 3.0, 99.0)
        if "&&&" in seq and "&&&" not in self.inputtext.get(1.0,"end-1c"): # Ausdruck nach "and": für den unwahrscheinlichen Fall, dass Benutzer versehentlich "&&&" im Input hat damit es beim
            self.infotext.insert(5.0, "Your input is containing more than one sequence. The output sequences are separated by a blank line.\n") # Konvertieren nicht als neue Zeile gelesen wird
            self.infotext.tag_add("warning", 3.0, 99.0)
        if self.inputtext.get(1.0, "end-1c").count(">") > 1 and self.inputformat.get() == "FASTA Format" and tool == "reversecomplement":
            self.infotext.insert(6.0, "Reversecomplement can only be done with one sequence.\n")
            self.infotext.tag_add("warning", 3.0, 99.0)
 
    def set_replace(self): # Zum Aktualisieren des Radiobutton Status (One- / Three-letter code)
        global oneletter
        if self.oneletter == True:
            self.oneletter = False
        else:
            self.oneletter = True
 
    def reversecomplement(self):
        global seq
        self.outputtext.delete("1.0", END)
        self.infotext.delete("1.0", END)
        seq = self.inputtext.get(1.0,"end-1c")
        seq = seq.upper()
        seq = seq.splitlines()
        if self.inputformat.get() == "FASTA Format":
            seq.remove(seq[0])
            done = False
            while done == False:
                done = True
                for line in seq:
                    if line[0] == ";":
                        seq.remove(line)
                        done = False
        elif self.inputformat.get() == "FASTQ Format":
            seq.remove(seq[3])
            seq.remove(seq[2])
            seq.remove(seq[0])
        seq = "".join(seq)
        seq = seq.replace("A","z")
        seq = seq.replace("T","A")
        seq = seq.replace("U","A")
        seq = seq.replace("z","T")
        seq = seq.replace("G","y")
        seq = seq.replace("C","G")
        seq = seq.replace("y","C")
        seq = seq[::-1]
        self.outputtext.insert(END, seq)
        self.additional_info("reversecomplement")
 
    def replace(self):
        global seq
        self.outputtext.delete("1.0", END)
        self.infotext.delete("1.0", END)
        seq = self.inputtext.get(1.0,"end-1c")
        seq = seq.upper()
        seq = seq.splitlines()
        if self.inputformat.get() == "FASTA Format":
            newsequence = 0 # Wenn FASTA Input mehr als eine Sequenz beinhaltet
            seq2 = ""
            for line in seq:
                if line[0] == ">":
                    newsequence += 1
                    if newsequence == 2:
                        newsequence -= 1
                        seq2 += "&&&" # &&& = neue Zeile (wird quasi wie ein Basentriplett konvertiert, siehe self.code)
                elif line [0] == ";":
                    pass
                else:
                    seq2 += line
            seq = seq2
        elif self.inputformat.get() == "FASTQ Format":
            seq.remove(seq[3])
            seq.remove(seq[2])
            seq.remove(seq[0])
        seq = "".join(seq)
        self.additional_info("baseconverter")
        seq = seq.replace("T", "U")
        if self.oneletter == True:
            self.replace_oneletter()
        else:
            self.replace_threeletter()
 
    def replace_oneletter(self):
        start = 0
        end = 3
        cycle = len(seq) / 3
        seqnew = ""
        while cycle > 0:
            seqappend = ""
            if seq[start:end] in self.code:
                seqappend = seq[start:end].replace(seq[start:end], self.code[seq[start:end]][0])
            seqnew += seqappend
            start += 3
            end += 3
            cycle -= 1
        self.outputtext.insert(END, seqnew)
 
    def replace_threeletter(self):
        start = 0
        end = 3
        cycle = len(seq) / 3
        seqnew = ""
        while cycle > 0:
            seqappend = ""
            if seq[start:end] in self.code:
                seqappend = seq[start:end].replace(seq[start:end], self.code[seq[start:end]][1])
            seqnew += seqappend + "-"
            start += 3
            end += 3
            cycle -= 1
        seqnew = seqnew.rstrip("-")                      # Falls mehrere Sequenzen eingegeben, damit
        seqnew = seqnew.replace("-\n\n-", "\n\n")        # die nicht mit "-" anfangen bzw enden
        self.outputtext.insert(END, seqnew)
 
    def clear_output(self):
        self.outputtext.delete("1.0", END)
        self.infotext.delete("1.0", END)
 
    def file_saveas(self):
        file = tkinter.filedialog.asksaveasfile(mode = "w", defaultextension = ".txt")
        savetxt = self.inputtext.get(1.0,"end-1c")
        file.write(savetxt)
        savetxt = self.outputtext.get(0.0,END)
        file.write("\n" + savetxt)
        savetxt = self.infotext.get(0.0,END)
        file.write(savetxt)
        file.close()
 
    def file_open(self):
        file = tkinter.filedialog.askopenfile(mode = "r")
        opentxt = file.read()
        self.inputtext.insert(END,opentxt)
        file.close()
 
    def exitscript(self):
        exit()
 
    def about(self):
        window_about = Toplevel()
        label_about = Label(window_about, text = "GPTask version {}\n\n Project for course "
                            "'Grundlagen der Programmierung'\n SS2015 by Clemens Spielvogel".format(self.version))
        label_about.pack()
 
    def help_info(self):
        window_help = Toplevel()
        label_help = Label(window_help, text = "GPTask\n\nWarnings will be displayed "
                           "red in the 'Additional information' Box\n\n[Warning] "
                           "'Sequence containing non codon at end: <non codon>'\n"
                           "The tool you are working with requires an input "
                           "sequence which can be converted into codons consisting"
                           " of 3 bases. <non codon> is representing the non codon "
                           "sequence consisting of one or two bases.\n\n[Warning] 'Your"
                           " input is containing more than one sequence. The output "
                           "sequences are separated by a blank line.\nWhen you are adding "
                           "multiple sequences as input for the baseconverter your sequences "
                           "will all be converted and separated by a blank line.\n\n"
                           "[Warning] 'Sequence containing invalid characters: <x>'\n The input"
                           " sequence is containing one or more letters which do not"
                           " represent bases contained in DNA or RNA (only A, T, U, G"
                           " and C possible). <x> represents the invalid characters "
                           "in the input sequence.\n\n[Warning] 'Reversecomplement can "
                           "only be done with one sequence.'\nAt the moment it is not"
                           " possible to reversecomplement more than one sequence at "
                           "once. Please enter only one sequence after another when"
                           " using the reversetranscription tool.")
        label_help.pack()
 
    def open_reversecomplement(self):
        global val
        global tool
        tool = "reversecomplement"
        self.revcompbutton = Button(text = "Transform!", command = self.reversecomplement)
        self.revcompbutton.grid(in_ = self.frame_input, row = 2, column = 1)
        self.outputlabel = Label(text = "Reversecomplement")
        self.outputlabel.grid(in_ = self.frame_output, row = 5, column = 0)
        self.blanklabel3 = Label(text = " " * 150)
        self.blanklabel3.grid(in_ = self.frame_input, row = 4, columnspan = 3, sticky = "w") # Um Radiobuttons zu verstecken..
 
    def open_baseconverter(self):
        global tool
        tool = "baseconverter"
        if self.showless == False: # Damit Text Widgets sich nicht übereinanderlagern und nicht mehr funktionieren
            self.showmoreless()
        intermediateinput = self.inputtext.get(1.0,"end-1c")
 
        #Text
        self.inputtext = Text(height = 1, width = 45, bg = "grey")
        self.inputtext.grid(in_ = self.frame_input, row = 1, column = 1)
        self.outputtext = Text(height = 10, width = 45, bg = "grey")
        self.outputtext.grid(in_ = self.frame_output, row = 5, column = 1)
        self.Text(height = 6, width = 45)
        self.infotext.grid(in_ = self.frame_output, row = 7, column = 1)
        self.infotext.tag_configure("warning", foreground = "red")
 
        #Labels
        self.blanklabel = Label(text = "")
        self.blanklabel.grid(row = 9, column = 0)
        self.blanklabel.grid(in_ = self.frame_output, row = 6, column = 0)
        self.inputlabel = Label(text = "DNA or RNA sequence")
        self.inputlabel.grid(in_ = self.frame_input, row = 1, column = 0)
        self.outputlabel = Label(text = "Aminoacid sequence")
        self.outputlabel.grid(in_ = self.frame_output, row = 5, column = 0)
        self.infolabel = Label(text = "Additional information")
        self.infolabel.grid(in_ = self.frame_output, row = 7, column = 0)
 
        #Combobox
        self.inputformat = ttk.Combobox(text = "Raw Sequence", width = 15, state = "readonly")
        self.inputformat.grid(in_ = self.frame_input, row = 2)
        self.inputformat.config(values = ("Raw Sequence", "FASTA Format", "FASTQ Format"))
        self.inputformat.set("Raw Sequence")
 
        #Buttons
        self.transformbutton = Button(text = "Transform!", command = self.replace)
        self.transformbutton.grid(in_ = self.frame_input, row = 2, column = 1)
        self.clearoutputbutton = Button(text = "Clear output", command = self.clear_output)
        self.clearoutputbutton.grid(in_ = self.frame_output, row = 8, column = 1, sticky = N)
        self.showmorebutton = Button(text = ">>", command = self.showmoreless)
        self.showmorebutton.grid(in_ = self.frame_input, row = 2, column = 1, sticky = E)
 
        #Radiobuttons
        for txt, val in self.letter_code:
            Radiobutton(text = txt, variable = self.v, value = val, command = self.set_replace).grid(in_ = self.frame_input, row = 4, column = -1 + val, sticky = W)
 
        self.inputtext.insert(END, intermediateinput) # Damit Inputtext nicht gelöscht wird beim Öffnen des baseconverters
GPTask().grid()