"""
MainWindow Class
Version2
"""
import tkinter
from tkinter.messagebox import askokcancel, showinfo, showerror
from kalmus.tkinter_windows.GenerateBarcodeWindow import GenerateBarcodeWindow
from kalmus.tkinter_windows.SaveBarcodeWindow import SaveBarcodeWindow
from kalmus.tkinter_windows.LoadStackWindow import LoadStackWindow
from kalmus.tkinter_windows.LoadJsonWindow import LoadJsonWindow
from kalmus.tkinter_windows.ReshapeBarcodeWindow import ReshapeBarcodeWindow
from kalmus.tkinter_windows.gui_utils import get_time, paint_hue_hist, update_axes_title, update_axes_ticks, resource_path
from kalmus.tkinter_windows.plot_barcodes_windows.WhichBarcodeInspectWindow import WhichBarcodeInspectWindow
from kalmus.tkinter_windows.StatsInfoWindow import StatsInfoWindow
from kalmus.tkinter_windows.SaveImageWindow import SaveImageWindow
from kalmus.tkinter_windows.meta_info_windows.WhichBarcodeCheckMeta import WhichBarcodeCheckMeta
from kalmus.tkinter_windows.time_points_windows.CheckTimePointWindow import CheckTimePointWindow
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from skimage.color import rgb2hsv
import numpy as np
import copy
import matplotlib
# General setup of the figure
font = {'family': 'DejaVu Sans',
'size': 8}
matplotlib.rc('font', **font)
[docs]class MainWindow():
"""
MainWindow Class.
The main GUI window for user to interact with the Kalmus software.
Has two displays for the barcodes and the histograms of their hue/brightness values.
Has all buttons to the subwindow of the kalmus software.
"""
def __init__(self, barcode_tmp, barcode_gn, figsize=(12, 5), dpi=100):
"""
Initialize
:param barcode_tmp: The temporary barcode object for software initialization
:param barcode_gn: The barcode generator object
:param figsize: The size of the plotted figure
:param dpi: The dpi of the plotted figure
"""
# Initialize the barcode memory stack
self.barcodes_stack = {"default": copy.deepcopy(barcode_tmp)}
# Copy over the barcodes
self.barcode_1 = copy.deepcopy(barcode_tmp)
self.barcode_2 = copy.deepcopy(barcode_tmp)
# Initialize the barcode's meta data to none
self.barcode_1.meta_data = {}
self.barcode_2.meta_data = {}
# Get the barcode generator
self.barcode_gn = barcode_gn
# Initialize the window
self.root = tkinter.Tk()
self.root.configure(bg='#85C1FA')
self.root.wm_title("KALMUS Version 1.3.13b1")
self.root.iconbitmap(resource_path("kalmus_icon.ico"))
self.dpi = dpi
# Initialize the figure
self.fig, self.ax = plt.subplots(2, 2, figsize=figsize, dpi=self.dpi,
gridspec_kw={'width_ratios': [2.8, 1]}, sharex='col', sharey='col')
update_axes_title(self.ax, self.barcode_1, self.barcode_2)
# Plot the barcodes into the figure
self.barcode_display_1 = self.ax[0][0].imshow(self.barcode_1.get_barcode().astype("uint8"))
self.barcode_display_2 = self.ax[1][0].imshow(self.barcode_2.get_barcode().astype("uint8"))
# Normalize the barcode before converting the color map
normalized_barcode_1 = self.barcode_1.get_barcode().astype("float") / 255
hsv_colors_1 = rgb2hsv(normalized_barcode_1.reshape(-1, 1, 3))
hue_1 = hsv_colors_1[..., 0] * 360
# The step of the histogram
bin_step = 5
N, bins, patches = self.ax[0][1].hist(hue_1[:, 0], bins=(np.arange(0, 361, bin_step)))
paint_hue_hist(bin_step, patches)
# Update the hue histogram
self.ax[0][1].set_xticks(np.arange(0, 361, 30))
self.ax[0][1].set_xlabel("Color Hue (0 - 360)")
self.ax[0][1].set_ylabel("Number of frames")
normalized_barcode_2 = self.barcode_2.get_barcode().astype("float") / 255
hsv_colors_2 = rgb2hsv(normalized_barcode_2.reshape(-1, 1, 3))
hue_2 = hsv_colors_2[..., 0] * 360
N, bins, patches = self.ax[1][1].hist(hue_2[:, 0], bins=(np.arange(0, 361, bin_step)))
# Paint the hue histogram where each bin of the histogram is in the color of the corresponding hue
paint_hue_hist(bin_step, patches)
self.ax[1][1].set_xticks(np.arange(0, 361, 30))
self.ax[1][1].set_xlabel("Color Hue (0 - 360)")
self.ax[1][1].set_ylabel("Number of frames")
# Use tight layout
plt.tight_layout()
update_axes_ticks(self.barcode_1, self.barcode_2, self.ax)
# Draw the canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root) # A tk.DrawingArea.
self.canvas.draw()
self.canvas.mpl_connect('button_press_event', self.time_pick)
# Use tkinter Frame to organize the figure widget
toolbarFrame = tkinter.Frame(master=self.root, width=500, height=40)
toolbarFrame.grid(row=8, column=2, sticky="nse", rowspan=2)
toolbarFrame.pack_propagate(False)
# Set up the tool bar of the plotted figure
self.toolbar = NavigationToolbar2Tk(self.canvas, toolbarFrame)
self.toolbar.update()
# Position the canvas/plotted figure into the window
self.canvas.get_tk_widget().grid(row=0, column=1, rowspan=8, columnspan=5)
r = g = b = 0
self.color_swatch = tkinter.Label(master=self.root,
text="",
bg=f'#{r:02x}{g:02x}{b:02x}',
width=4,
height=2,
borderwidth=1.5,
relief="solid")
self.color_swatch.grid(row=8, column=3, rowspan=1, padx=0, sticky=tkinter.E)
self.canvas.mpl_connect('motion_notify_event', self.display_color)
self.color_label = tkinter.Label(master=self.root,
text="Red = {:>3d} "
"Green = {:>3d} "
"Blue = {:>3d}\n".format(0, 0, 0) +
"Frame: {:>8d} ".format(0) +
"Time: {:02d}:{:02d}:{:02d} ".format(0, 0, 0),
font=("Arial", 8),
width=32,
bg='#85C1FA',
padx=0,
pady=0,
justify=tkinter.LEFT)
self.color_label.grid(row=8, column=4, rowspan=1, padx=0, sticky=tkinter.W)
self.generate_window_opened = False
# Button to generate the barcode
button_generate = tkinter.Button(master=self.root, text="Generate Barcode",
command=self.generate_barcode)
button_generate.grid(row=0, column=0, padx=3)
# Button to load the barcode from existed json files
button_load = tkinter.Button(master=self.root, text="Load JSON",
command=self.load_json_barcode)
button_load.grid(row=1, column=0)
# Button to load the barcode from the memory stack
button_load_stack = tkinter.Button(master=self.root, text="Load Memory",
command=self.load_stack_barcode)
button_load_stack.grid(row=2, column=0)
# Button to reshape the barcode displayed in the main window
button_reshape_barcode = tkinter.Button(master=self.root, text="Reshape Barcode",
command=self.reshape_barcode)
button_reshape_barcode.grid(row=3, column=0)
# Button to save the barcode into json files
button_save_json = tkinter.Button(master=self.root, text="Save JSON",
command=self.save_barcode_on_stack)
button_save_json.grid(row=4, column=0)
# Button to save the barcode displayed into the image
button_save_image = tkinter.Button(master=self.root, text="Save Image",
command=self.save_image_from_display)
button_save_image.grid(row=5, column=0)
# Button to inspect the barcode in details
button_barcode = tkinter.Button(master=self.root, text="Inspect Barcode",
command=self.show_barcode)
button_barcode.grid(row=6, column=0)
# Button to show the statistics of the barcode
button_stats_info = tkinter.Button(master=self.root, text="Stats Info",
command=self.stats_info)
button_stats_info.grid(row=7, column=0)
# Button to quit the main window
button_quit = tkinter.Button(master=self.root, text="Quit", command=self.close_window)
button_quit.grid(row=8, column=0)
# Button to check the meta data of the displayed barcodes
button_check_meta = tkinter.Button(master=self.root, text="Check Meta Info", command=self.check_meta_info)
button_check_meta.grid(row=8, column=5, sticky=tkinter.W)
# Close the window mainloop if user try to close the window
self.root.protocol("WM_DELETE_WINDOW", self.close_window)
# Start the main window
self.root.mainloop()
[docs] def close_window(self):
"""
close the Mainwindow.
Check if the Generate Barcode window is still open before quiting the Main program.
Return (cancel the quit) if the Generate Barcode window is still open.
"""
# Check if generate barcode window is opened
if self.generate_window_opened:
# If it is opened show an error
showerror("Generate Barcode Window is Opened", "Generate Barcode window is still opened!\n"
"Please close the Generate Barcode window before Quit.")
return
# Otherwise check if user want to quit the software
quit_software = askokcancel("Quit KALMUS", "Are you sure you want to close the KALMUS?\n"
"All unsaved results will be lost.")
# Quit if yes
if quit_software:
self.quit()
[docs] def quit(self):
"""
Quit the main window
"""
self.root.quit()
self.root.destroy()
[docs] def check_meta_info(self):
"""
Instantiate the WhichBarcodeCheckMeta window
"""
WhichBarcodeCheckMeta(self.barcode_1, self.barcode_2, self.barcodes_stack)
[docs] def show_barcode(self):
"""
Instantiate the WhichBarcodeInspectWindow
"""
WhichBarcodeInspectWindow(self.barcode_1, self.barcode_2, dpi=self.dpi, figsize=(7.6, 4.3))
[docs] def load_json_barcode(self):
"""
Instantiate the LoadJsonWindow
"""
LoadJsonWindow(self.barcode_gn, self.barcode_1, self.barcode_2, self.ax,
self.canvas, self.barcodes_stack)
[docs] def load_stack_barcode(self):
"""
Instantiate the LoadStackWindow
"""
LoadStackWindow(self.barcodes_stack, self.barcode_1, self.barcode_2, self.ax,
self.canvas)
[docs] def reshape_barcode(self):
"""
Instantiate the ReshapeBarcodeWindow
"""
ReshapeBarcodeWindow(self.barcode_1, self.barcode_2, self.ax, self.canvas)
[docs] def stats_info(self):
"""
Instantiate the StatsInfoWindow
"""
StatsInfoWindow(self.barcode_1, self.barcode_2)
[docs] def generate_barcode(self):
"""
Instantiate the GenerateBarcodeWindow
"""
if not self.generate_window_opened:
self.generate_window_opened = True
GenerateBarcodeWindow(self.barcode_gn, self.barcodes_stack)
self.generate_window_opened = False
else:
showinfo("Generate Barcode window is Opened", "Generate Barcode Window is already opened.")
[docs] def save_barcode_on_stack(self):
"""
Instantiate the SaveBarcodeWindow
"""
SaveBarcodeWindow(self.barcodes_stack)
[docs] def save_image_from_display(self):
"""
Instantiate the SaveImageWindow
"""
SaveImageWindow(self.barcode_1, self.barcode_2)
[docs] def display_color(self, event):
if event.xdata and event.ydata:
ix, iy = int(event.xdata + 0.5), int(event.ydata + 0.5)
else:
return
for i, axe in enumerate(self.ax[:, 0]):
if axe == event.inaxes:
# Check if it is plotted barcode 1 or plotted barcode 2
if i == 0:
barcode = self.barcode_1
else:
barcode = self.barcode_2
barcode_shape = barcode.get_barcode().shape
if 0 <= iy < barcode_shape[0] and 0 <= ix < barcode_shape[1]:
if barcode.barcode_type == "Color":
r, g, b = barcode.get_barcode().astype("uint8")[iy, ix]
self.color_swatch.config(bg=f'#{r:02x}{g:02x}{b:02x}')
color_label_text = "Red = {:>3d} " \
"Green = {:>3d} " \
"Blue = {:>3d}\n".format(r, g, b)
elif barcode.barcode_type == "Brightness":
r = g = b = barcode.get_barcode().astype("uint8")[iy, ix]
self.color_swatch.config(bg=f'#{r:02x}{g:02x}{b:02x}')
color_label_text = "Brightness = {:>3d}\n".format(r)
frame, time_hr, time_min, time_sec = get_time(barcode, ix, iy)
color_label_text = color_label_text + "Frame: {:>8d} ".format(frame) + \
"Time: {:02d}:{:02d}:{:02d} ".format(time_hr, time_min, time_sec)
self.color_label.config(text=color_label_text)
[docs] def time_pick(self, event):
"""
Pick the time at a point of the plotted barcode if user double click on that point
:param event: matplotlib event. Only catch double click event
"""
# If user is double click on the graph
if event.dblclick:
# Try get the x and y position of the clicked point
try:
ix, iy = int(event.xdata + 0.5), int(event.ydata + 0.5)
except Exception:
return
# find which axis of the graph does the clicked point belong to
for i, axe in enumerate(self.ax[:, 0]):
if axe == event.inaxes:
# Check if it is plotted barcode 1 or plotted barcode 2
if i == 0:
barcode = self.barcode_1
else:
barcode = self.barcode_2
# Make sure the point is within the plotted barcode
barcode_shape = barcode.get_barcode().shape
if 0 <= iy < barcode_shape[0] and 0 <= ix < barcode_shape[1]:
# Instantiate the CheckTimePointWindow
CheckTimePointWindow(barcode, mouse_x=ix, mouse_y=iy)