#!/usr/bin/env python3
# -*- coding: utf-8 -*
"""View architecture of the main application, i.e; a GUI."""
from __future__ import absolute_import
import os
from tkinter import Tk, Toplevel, filedialog, Menu, messagebox, ttk
from tkinter import Label, Entry, Button, StringVar, Text, Scrollbar
from tkinter import RIGHT, Y, X, HORIZONTAL, BOTTOM, NONE, END
from typing import Iterator, Generator
from dnazip.sequence import Sequence
from dnazip.decoder import HuffDecoder, BWDecoder, FullDecoder
from dnazip.encoder import HuffEncoder, BWEncoder, FullEncoder
[docs]class Interface(Tk):
"""View class of the application using a Tkinter interface."""
def __init__(self: object) -> None:
"""Class constructor
Returns
-------
None
An interface instance.
"""
super().__init__()
self.title("dnazip")
self.configure(bg='#ebebeb')
self.create_main()
self.create_menu()
self.create_buttons()
self.file = None
[docs] def create_main(self: object) -> None:
"""This method is used to create the main menu interface of the GUI."""
welcome_msg = "Welcome to dnazip! \n A graphical " + \
"representation of Burros-Wheeler and Huffman " + \
"Coding algorithms"
rand = "You don't have a DNA sequence? we'll generate a small " + \
"random one for you"
Label(self, text=welcome_msg, bg='#ebebeb', font=(None, 15)).grid(row=0, columnspan=3, padx=5, pady=6)
Label(self, text="Compression", bg='#ebebeb', borderwidth=2, relief="groove", font=(None, 13, 'bold')).grid(row=1, columnspan=3, padx=5, pady=6)
Label(self, text="Decompression", bg='#ebebeb', borderwidth=2, relief="groove", font=(None, 13, 'bold')).grid(row=5, columnspan=3, padx=5, pady=6)
separator_one = ttk.Separator(self, orient='horizontal')
separator_one.grid(row=4, columnspan=3, ipadx=500, padx=5, pady=6)
Label(self, text=rand, bg='#ebebeb', font=(None, 12, 'bold')).grid(row=8, columnspan=3, padx=5, pady=6)
self.random = StringVar()
self.entry = Entry(self, textvariable=self.random, width=50)
self.entry.grid(row=9, columnspan=3, padx=5, pady=10)
separator_two = ttk.Separator(self, orient='horizontal')
separator_two.grid(row=7, columnspan=3, ipadx=500, padx=5, pady=6)
[docs] def open_file(self: object) -> None:
"""This method is used to create a file chooser for the interface."""
self.file = filedialog.askopenfilename(
initialdir= os.getcwd(),title="Select File",filetypes=(
("Text Files", "*.txt"),("all files","*.*")))
if self.file:
messagebox.showinfo("Selected file", "You have selected %s"%(
self.file))
[docs] def save_random(self: object) -> None:
"""This method is used to save a randomly generated DNA sequence
to a file.
"""
seq_to_save = self.entry.get()
if seq_to_save:
path = filedialog.asksaveasfilename(
initialdir= os.getcwd(),title="Select File", filetypes=(
("Text Files", "*.txt"), ("all files","*.*")))
if path:
Sequence(path).write(seq_to_save)
messagebox.showinfo("Sequence saved", "The sequence was saved to %s" %(
path))
else:
messagebox.showerror("No sequence entered", "The sequence box is empty")
[docs] def about(self: object) -> None:
"""This method is used to generate the about menu page information."""
msg = 'This project is developed for the "Sequence Algorithms" ' + \
'Course as a part of The M.Sc. in Bioinformatics degree at ' + \
'Aix-Marseille University, France. \n' + \
'Source code available at: https://github.com/dabane-ghassan/dnazip'
messagebox.showinfo("About dnazip", msg)
[docs] def generate_random(self: object) -> None:
"""This method is used to generate random sequences of DNA of length
50 and show them inside the random text box tkinter entry.
"""
self.random.set(Sequence.generate(length=50))
[docs] def next_btn(self: object, controller: Iterator[str]) -> str:
"""This method is used to create the output of a universal next button
for all protocols of the program, it will be passed to a given label
that changes its content.
Parameters
----------
controller : Iterator[str]
The controller of a given protocol that contains all steps to show.
Returns
-------
str
Every step to be shown, one at a time.
"""
try:
to_print = next(controller)
if isinstance(to_print, list):
return '\n'.join(to_print)
else:
return to_print
except StopIteration:
return "The protocole is finished"
[docs] def final_btn(self: object, controller: Iterator[str]) -> str:
"""This method is used to create the output of a universal final step
button for all protocols of the program, it will be passed to a given
label that changes its content.
Parameters
----------
controller : Iterator[str]
The controller of a given protocol that contains all steps to show.
Returns
-------
str
The final step to be shown.
"""
try:
to_print = list(controller)[-1] # Getting the final result of a generator
return to_print
except IndexError:
return "The protocole is finished, please refer to the main menu."
[docs] def step_by_step(self: object, window: Tk, protocol: Iterator[str], names: Generator)-> None:
"""This method creates a universal step by step advancing interface
for a given chosen protocole.
Parameters
----------
window : Tk
The given window.
protocol : Iterator[str]
The output protocol to be displayed.
names : Generator
The names of the steps to be displayed.
"""
steps = StringVar()
current_step = Label(window, textvariable=steps, bg="#ebebeb",
font=(None, 9, "bold"), borderwidth=2)
current_step.place(relx = 0.5,
rely = 0.1, anchor="center")
xscrollbar = Scrollbar(window, orient=HORIZONTAL)
xscrollbar.pack(side=BOTTOM, fill=X)
# Vertical (y) Scroll Bar
yscrollbar = Scrollbar(window)
yscrollbar.pack(side=RIGHT, fill=Y)
# Text Widget
text = Text(window, wrap=NONE, width=100, height=40,
xscrollcommand=xscrollbar.set,
yscrollcommand=yscrollbar.set)
text.place(relx = 0.5,
rely = 0.5,
anchor = 'center')
# Configure the scrollbars
xscrollbar.config(command=text.xview)
yscrollbar.config(command=text.yview)
Button(window, text="Next",
command=lambda : [self.update_text(text,
self.next_btn(protocol)),steps.set(
self.next_btn(names))]).pack(side="bottom")
Button(window, text="Final step",
command=lambda : [self.update_text(text,
self.final_btn(protocol)), steps.set(
self.final_btn(names))]).pack(side="bottom")
[docs] def update_text(self: object, widget: Text, new_text: str) -> None:
"""This method updates the Text tkinter widget with every step
in every protocol. it deletes its contents and then replaces it with
new_text parameter.
Parameters
----------
widget : Text
Tkinter's Text widget.
new_text : str
The new text to be shown in the widget.
Returns
-------
None
Replaces text in a given Text widget.
"""
widget.delete("1.0", END) #Clear the text window so we can write.
widget.insert(END,new_text)
[docs] def BW_output(self: object, controller: BWEncoder) -> Iterator[str]:
"""This method is used to collect all output for the BW encoding.
Parameters
----------
controller : BWEncoder
The given controller.
Yields
------
Iterator[str]
The content to be shown in the interface.
"""
yield controller.seq.read()
for rot in controller.rotations:
yield rot
yield controller.bwm
yield controller.bwt
[docs] def bwt_window(self: object) -> None:
"""This method creates a Tkinter Toplevel window for the step-by-step
BWT protocol, The output file of the protocol will be shown at the
beginning.
"""
if self.file:
bwt_window = Toplevel(self)
bwt_window.title("Burros-Wheeler Transform")
bwt_window.geometry("1000x1000")
bwt_window.configure(bg='#ebebeb')
controller = BWEncoder(self.file)
controller.encode()
protocol = self.BW_output(controller)
prot= list(protocol)
rots = ["Step 2: Generating all rotations of the sequence" for n in range(len(prot) - 3)]
names = (step for step in ["Step 1 : Visualizing the sequence",
*rots,
"Step 3: Creating the Burros-Wheeler matrix by sorting all rotations",
"Step 4: The Burros-Wheeler transform is the last column of the matrix",
"Please refer to the main menu to select another sequence"])
self.step_by_step(bwt_window, iter(prot), names)
self.program_output(bwt_window, controller.bwt_output)
else:
self.no_file_error()
[docs] def DeBW_output(self: object, controller: BWDecoder) -> Iterator[str]:
"""This method is used to collect all output for the BW decoding.
Parameters
----------
controller : BWDecoder
The given controller.
Yields
------
Iterator[str]
The content to be shown in the interface.
"""
yield controller.seq.read()
for mat in controller.bwm:
yield mat
yield controller.original
[docs] def debwt_window(self: object) -> None:
"""This method creates a Tkinter Toplevel window for the step-by-step
inverse BWT protocol, The output file of the protocol will be shown at
the beginning.
"""
if self.file:
debwt_window = Toplevel(self)
debwt_window.title("Reversing Burros-Wheeler Transform")
debwt_window.geometry("1000x1000")
debwt_window.configure(bg='#ebebeb')
controller = BWDecoder(self.file)
controller.decode()
protocol = self.DeBW_output(controller)
prot= list(protocol)
reconstructed = ["Step 2: Creating the Burros-Wheeler Matrix" for n in range(len(prot) - 2)]
names = (step for step in ["Step 1 : Visualizing the sequence",
*reconstructed,
"Step 3: The original sequence is the one that has a $ sign as a last column",
"Please refer to the main menu to select another sequence"])
self.step_by_step(debwt_window, iter(prot), names)
self.program_output(debwt_window, controller.debwt_output)
else:
self.no_file_error()
[docs] def Huff_output(self: object, controller: HuffEncoder) -> Iterator[str]:
"""This method is used to collect all output for Huffman encoding.
Parameters
----------
controller : HuffEncoder
The given controller.
Yields
------
Iterator[str]
The content to be shown in the interface.
"""
yield controller.seq.read()
yield controller.header
yield controller.binary
yield controller.unicode
yield controller.compressed
[docs] def huffcode_window(self: object) -> None:
"""This method creates a Tkinter Toplevel window for the step-by-step
Huffman coding protocol, The output file of the protocol will be shown
at the beginning.
"""
if self.file:
huff_code_window = Toplevel(self)
huff_code_window.title("Huffman coding")
huff_code_window.geometry("1000x1000")
huff_code_window.configure(bg='#ebebeb')
names = (step for step in ["Step 1 : Visualizing the sequence",
"Step 2: Creating Huffman tree from sequence and calculating paths",
"Step 3: Generating the binary sequence from paths and adding a padding",
"Step 4: Coding the binary in 8-bits to unicode",
"Step 5: Writing paths and unicode to an output file",
"Please refer to the main menu to select another sequence"])
controller = HuffEncoder(self.file)
controller.encode()
protocol = self.Huff_output(controller)
self.step_by_step(huff_code_window, protocol, names)
self.program_output(huff_code_window, controller.huff_output)
else:
self.no_file_error()
[docs] def deHuff_output(self: object, controller: HuffDecoder) -> Iterator[str]:
"""This method is used to collect all output for the Huff decoding.
Parameters
----------
controller : HuffDecoder
The given controller.
Yields
------
Iterator[str]
The content to be shown in the interface.
"""
yield controller.seq.read()
yield controller.header
yield controller.unicode
yield controller.binary
yield controller.decompressed
[docs] def huffdecode_window(self: object) -> None:
"""This method creates a Tkinter Toplevel window for the step-by-step
Huffman decoding, The output file of the protocol will be shown at the
beginning.
"""
if self.file:
huff_decode_window = Toplevel(self)
huff_decode_window.title("Huffman decoding")
huff_decode_window.geometry("1000x1000")
huff_decode_window.configure(bg='#ebebeb')
names = (step for step in ["Step 1 : Visualizing the compressed sequence",
"Step 2: Reading the header of the file that corresponds to Huffman codes that where created with the tree during compression",
"Step 3: Separating the unicode sequence",
"Step 4: Transforming the unicode sequence to binary using huffman codes in the header and stripping padding",
"Step 5: The decompressed sequence : ",
"Please refer to the main menu to select another sequence"])
controller = HuffDecoder(self.file)
controller.decode()
protocol = self.deHuff_output(controller)
self.step_by_step(huff_decode_window, protocol, names)
self.program_output(huff_decode_window, controller.dehuffman_output)
else:
self.no_file_error()
[docs] def fullzip_output(self: object, controller: FullEncoder) -> Iterator[str]:
"""This method is used to collect all output for a full protocol
encoding (BWT + Huffman compression).
Parameters
----------
controller : FullEncoder
The given controller.
Yields
------
Iterator[str]
The content to be shown in the interface.
"""
yield controller.bw_encoder.seq.read()
for rot in controller.bw_encoder.rotations:
yield rot
yield controller.bw_encoder.bwm
yield controller.bw_encoder.bwt
yield controller.huff_encoder.header
yield controller.huff_encoder.binary
yield controller.huff_encoder.unicode
yield controller.huff_encoder.compressed
[docs] def fullzip_window(self: object) -> None:
"""This method creates a Tkinter Toplevel window for the step-by-step
Fullzip protocol (BWT+Huffman compression), The output file of the
protocol will be shown at the beginning.
"""
if self.file:
fullzip_window = Toplevel(self)
fullzip_window.title("Burrow-Wheeler Transform + Huffman coding")
fullzip_window.geometry("1000x1000")
fullzip_window.configure(bg='#ebebeb')
controller = FullEncoder(self.file)
controller.full_zip()
protocol = self.fullzip_output(controller)
prot= list(protocol)
rots = ["Step 2: Generating all rotations of the sequence" for n in range(len(prot) - 7)]
names = (step for step in ["Step 1 : Visualizing the sequence",
*rots,
"Step 3: Creating the Burros-Wheeler matrix by sorting all rotations",
"Step 4: The Burros-Wheeler transform is the last column of the matrix",
"Step 5: Creating Huffman tree from Burros-Wheeler transform and calculating paths",
"Step 6: Generating the binary sequence from paths and adding a padding",
"Step 7: Coding the binary in 8-bits to unicode",
"Step 8: Writing paths and unicode to an output file",
"Please refer to the main menu to select another sequence"])
self.step_by_step(fullzip_window, iter(prot), names)
outputs = controller.bw_encoder.bwt_output + \
'\n' + controller.huff_encoder.huff_output
self.program_output(fullzip_window, outputs)
else:
self.no_file_error()
[docs] def fullunzip_output(self: object, controller: FullDecoder) -> Iterator[str]:
"""This method is used to collect all output for a full decompression
protocol.
Parameters
----------
controller : FullDecoder
The given controller.
Yields
------
Iterator[str]
The content to be shown in the interface.
"""
yield controller.huff_decoder.seq.read()
yield controller.huff_decoder.header
yield controller.huff_decoder.unicode
yield controller.huff_decoder.binary
yield controller.huff_decoder.decompressed
for line in controller.bw_decoder.bwm:
yield line
yield controller.bw_decoder.original
[docs] def fullunzip_window(self: object) -> None:
"""This method creates a Tkinter Toplevel window for the step-by-step
Full unzipping protocol (HUffman decoding + inverse BWT), The output
file of the protocol will be shown at the beginning.
"""
if self.file:
fullunzip_window = Toplevel(self)
fullunzip_window.title("Huffman decoding + reverse Burros-Wheeler transform")
fullunzip_window.geometry("1000x1000")
fullunzip_window.configure(bg='#ebebeb')
controller = FullDecoder(self.file)
controller.full_unzip()
protocol = self.fullunzip_output(controller)
prot= list(protocol)
reconstructed = ["Step 6: Creating the Burros-Wheeler Matrix" for n in range(len(prot) - 6)]
names = (step for step in ["Step 1 : Visualizing the compressed sequence",
"Step 2: Reading the header of the file that corresponds to Huffman codes that where created with the tree during compression",
"Step 3: Separating the unicode sequence",
"Step 4: Transforming the unicode sequence to binary using huffman codes in the header and stripping padding",
"Step 5: The decompressed sequence is the burros wheeler transform of the original sequence:",
*reconstructed,
"Step 7: The original sequence is the one that has a $ sign as a last column in the Burros-Wheeler Matrix",
"Please refer to the main menu to select another sequence"])
self.step_by_step(fullunzip_window, iter(prot), names)
outputs = controller.huff_decoder.dehuffman_output + \
'\n' + controller.bw_decoder.debwt_output
self.program_output(fullunzip_window, outputs)
else:
self.no_file_error()
[docs] def no_file_error(self: object) -> None:
"""Shows an error when no file is selected."""
messagebox.showerror("No file selected", "Please select a file")
[docs] def program_output(self: object, window: Tk, path: str) -> None:
"""Shows the output of a given protocol.
Parameters
----------
window : Tk
The given window.
path : str
The output file path to be shown in the messagebox.
"""
messagebox.showinfo(parent=window, title="Program output",
message="The output will be saved to \n %s" %(
path))
[docs] def main(self: object) -> None:
"""Launches the mainloop of the interface."""
print("[View] main")
self.mainloop()
[docs] def quit(self: object) -> None:
"""Quits the current interface."""
self.destroy()