"""
PlotBarcodeWindow Class
ColorHistogramWindow Class
RGBColorCubeWindow Class
OutputCSVWindow Class
"""
import tkinter
import tkinter.filedialog
from tkinter.messagebox import showinfo, showerror
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
import numpy as np
import os
from kalmus.utils.visualization_utils import show_colors_in_cube, show_colors_in_hue_light_scatter_plot, \
show_colors_in_hue_light_3d_bar_plot
from kalmus.tkinter_windows.gui_utils import resource_path, update_hist
from skimage.color import rgb2hsv
import pandas as pd
[docs]class PlotBarcodeWindow():
"""
PlotBarcodeWindow Class
GUI window of plotting the barcode for user to inspect in details
"""
def __init__(self, barcode, figsize=(6, 4), dpi=100):
"""
Initialize
:param barcode: The barcode that will be inspected
:param figsize: The size of the plotted figure
:param dpi: The dpi of the figure
"""
self.barcode = barcode
# Initialize the window
self.plot_window = tkinter.Tk()
self.plot_window.wm_title("Inspect Barcode")
self.plot_window.iconbitmap(resource_path("kalmus_icon.ico"))
# Set up the plotted figure
self.fig = plt.figure(figsize=figsize, dpi=dpi)
# Use the correct color map based on the input barcode's type
if barcode.barcode_type == "Brightness":
plt.imshow(barcode.get_barcode().astype("uint8"), cmap="gray")
else:
plt.imshow(barcode.get_barcode().astype("uint8"))
plt.axis("off")
plt.tight_layout()
# Set up the canvas for the figure
self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_window) # A tk.DrawingArea.
self.canvas.draw()
# Dynamic layout based on the type of the inspected barcode
if barcode.barcode_type == "Color":
column_span = 5
else:
column_span = 2
self.canvas.get_tk_widget().grid(row=0, column=0, columnspan=column_span, pady=3)
# Use tkinter Frame to organize the figure widget
toolbarFrame = tkinter.Frame(master=self.plot_window)
toolbarFrame.grid(row=2, column=0, columnspan=column_span, sticky=tkinter.W, pady=6)
# Initialize the plotting tool bar
self.toolbar = NavigationToolbar2Tk(self.canvas, toolbarFrame)
self.toolbar.update()
# Button to output the data in the barcode to a csv file
self.button_output_csv = tkinter.Button(master=self.plot_window, text="Output CSV",
command=self.output_csv)
self.button_output_csv.grid(row=1, column=0, padx=6)
# Button to check the histogram distribution of the barcode's hue/brightness value
self.button_hist = tkinter.Button(master=self.plot_window, text="Hue Histogram",
command=self.show_color_histogram)
self.button_hist.grid(row=1, column=1, padx=6)
# If the barcode is a color barcode, allow user to inspect the RGB color distribution in a RGB cube
if barcode.barcode_type == "Color":
self.button_cube = tkinter.Button(master=self.plot_window, text="Colors in RGB Cube",
command=self.show_RGB_color_in_cube)
self.button_cube.grid(row=1, column=2)
self.button_scatter = tkinter.Button(master=self.plot_window, text="Hue vs. Light Scatter",
command=self.show_color_in_scatter)
self.button_scatter.grid(row=1, column=3)
self.button_bar = tkinter.Button(master=self.plot_window, text="Hue vs. Light Bar Plot",
command=self.show_color_in_bar)
self.button_bar.grid(row=1, column=4)
[docs] def show_RGB_color_in_cube(self):
"""
Instantiate the RGBColorCubeWindow if user press the show color in RGB cube button
"""
RGBColorCubeWindow(self.barcode)
[docs] def show_color_in_scatter(self):
"""
Instantiate the LightHueScatterPlotWindow if user press the show color in hue light scatter button
"""
HueLightScatterPlotWindow(self.barcode)
[docs] def show_color_in_bar(self):
"""
Instantiate the HueBrightness3DBarPlotWindow if user press the show color in hue light scatter button
"""
HueLight3DBarPlotWindow(self.barcode)
[docs] def show_color_histogram(self):
"""
Instantiate the ColorHistogramWindow if user press the show histogram button
"""
ColorHistogramWindow(self.barcode)
[docs] def output_csv(self):
OutputCSVWindow(self.barcode)
[docs]class ColorHistogramWindow():
"""
ColorHistogramWindow Class
GUI window that show the distribution of the barcode's hue[0, 360]/brightness[0, 255] value
"""
def __init__(self, barcode):
"""
Initialize
:param barcode: The input barcode
"""
# Set up the window
self.window = tkinter.Tk()
self.window.wm_title("Histogram Distribution")
self.window.iconbitmap(resource_path("kalmus_icon.ico"))
# Set up the plotted figure
fig, ax = plt.subplots(figsize=(9, 5))
update_hist(barcode, ax=ax, bin_step=5)
# Plot the histogram based on the barcode's type
if barcode.barcode_type == "Color":
ax.set_xticks(np.arange(0, 361, 30))
ax.set_xlabel("Color Hue (0 - 360)")
ax.set_ylabel("Number of frames")
else:
ax.set_xticks(np.arange(0, 255, 15))
ax.set_xlabel("Brightness (0 - 255)")
ax.set_ylabel("Number of frames")
# Set up the canvas of the figure
canvas = FigureCanvasTkAgg(fig, master=self.window) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
# Set up the tool bar of the figure
toolbar = NavigationToolbar2Tk(canvas, self.window)
toolbar.update()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
[docs]class RGBColorCubeWindow():
"""
RGBColorCubeWindow Class
GUI window that shows the distribution of the barcode's RGB color in a RGB cube
range in [0, 255] for all three channels
"""
def __init__(self, barcode):
"""
Initialize
:param barcode: The input barcode
"""
self.barcode = barcode
# Set up the window
self.window = tkinter.Tk()
self.window.wm_title("Colors in RGB cube")
self.window.iconbitmap(resource_path("kalmus_icon.ico"))
# Set up the plotted figure
sampling = 6000
if sampling > self.barcode.colors.shape[0]:
sampling = self.barcode.colors.shape[0]
fig, ax = show_colors_in_cube(self.barcode.colors, return_figure=True,
figure_size=(6.5, 6.5), sampling=sampling)
# Set up the canvas
canvas = FigureCanvasTkAgg(fig, master=self.window) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
# Allow mouse events on 3D figure
ax.mouse_init()
# Set up the tool bar of the figure
toolbar = NavigationToolbar2Tk(canvas, self.window)
toolbar.update()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
[docs]class HueLight3DBarPlotWindow():
"""
HueLight3DBarPlotWindow Class
GUI window that shows the distribution of the barcode's color in a Hue (x-axis) vs. Light (y-axis)
vs. Counts/Frequency (z-axis) 3D bar plot. The color of the barcode will be converted from RGB to
HSV/HSL color space. Hue ranges from 0 to 360 degree and Light range from 0 to 1 (darkest to the brightest)
"""
def __init__(self, barcode, figure_size=(6.5, 6.5)):
"""
Initialize
:param barcode: The input barcode
:param figure_size: The size of the plotted figure
"""
self.barcode = barcode
# Set up the window
self.window = tkinter.Tk()
self.window.wm_title("Colors in Hue Light 3D Bar Plot")
self.window.iconbitmap(resource_path("kalmus_icon.ico"))
self.figure_size = figure_size
# Set up the plotted figure
self.fig, self.ax = show_colors_in_hue_light_3d_bar_plot(self.barcode.colors,
figure_size=self.figure_size,
return_figure=True,
shaded=False)
# Set up the canvas
self.canvas = FigureCanvasTkAgg(self.fig, master=self.window) # A tk.DrawingArea.
self.canvas.draw()
self.canvas.get_tk_widget().grid(row=0, column=0, columnspan=1, pady=3, rowspan=13)
# Use tkinter Frame to organize the figure widget
toolbarFrame = tkinter.Frame(master=self.window)
toolbarFrame.grid(row=13, column=0, columnspan=1, sticky=tkinter.W, pady=6, rowspan=1)
# Initialize the plotting tool bar
toolbar = NavigationToolbar2Tk(self.canvas, toolbarFrame)
toolbar.update()
# Allow mouse events on 3D figure
self.ax.mouse_init()
# Shaded/Unshaded option
self.config_shade = tkinter.IntVar(self.window)
# self.config_shade.set("Unshaded") # initialize
self.config_shade.set(0)
label_config = tkinter.Label(self.window, text="Plot Configurations:")
label_config.grid(row=0, column=1, columnspan=2)
self.checkbox_shade_object = tkinter.Checkbutton(self.window, text="Shade 3D Bar Objects",
variable=self.config_shade,
onvalue=1, offvalue=0, command=self.shade_bar_plot)
self.checkbox_shade_object.grid(row=1, column=1, sticky="nw", padx=(15, 0), columnspan=2)
# Grid on/off option
self.config_grid = tkinter.IntVar(self.window)
# self.config_shade.set("Unshaded") # initialize
self.config_grid.set(0)
self.checkbox_grid = tkinter.Checkbutton(self.window, text="Turn on Grid",
variable=self.config_grid,
onvalue=1, offvalue=0, command=self.turn_on_plot_grid)
self.checkbox_grid.grid(row=2, column=1, sticky="nw", padx=(15, 0), columnspan=2)
# Axis on/off option
self.config_axis = tkinter.IntVar(self.window)
# self.config_shade.set("Unshaded") # initialize
self.config_axis.set(1)
self.checkbox_axis = tkinter.Checkbutton(self.window, text="Turn on Axis",
variable=self.config_axis,
onvalue=1, offvalue=0, command=self.turn_on_plot_axis)
self.checkbox_axis.grid(row=3, column=1, sticky="nw", padx=(15, 0), columnspan=2)
label_resolution = tkinter.Label(self.window, text="Plot Resolutions:\n"
"Changes to resolution will be effective\n"
" after clicking the Submit Changes Button")
label_resolution.grid(row=4, column=1, sticky="w", padx=5, columnspan=2)
self.label_hue_resolution = tkinter.Label(self.window, text="Bar width on Hue axis:")
self.label_hue_resolution.grid(row=5, column=1, sticky="nw", padx=5)
# Variable that stores the user's choice of Hue resolution
self.var_hue_resolution = tkinter.StringVar(self.window)
self.var_hue_resolution.set("10")
self.cur_hue_res = 10
# Dropdown menu for the Hue resolution selection
dropdown_hue_type = tkinter.OptionMenu(self.window, self.var_hue_resolution, "5", "10", "15", "30")
dropdown_hue_type.grid(row=5, column=2, sticky="nw", padx=5)
self.label_hue_resolution = tkinter.Label(self.window, text="Bar width on Light axis:")
self.label_hue_resolution.grid(row=6, column=1, sticky="nw", padx=5)
# Variable that stores the user's choice of Brightness resolution
self.var_bri_resolution = tkinter.StringVar(self.window)
self.var_bri_resolution.set("0.02")
self.cur_bri_res = 0.02
# Dropdown menu for the Light resolution selection
dropdown_bri_type = tkinter.OptionMenu(self.window, self.var_bri_resolution, "0.01", "0.02", "0.05", "0.10")
dropdown_bri_type.grid(row=6, column=2, sticky="nw", padx=5)
# Button to submit the changes
self.button_submit = tkinter.Button(master=self.window, text="Submit Changes",
command=self.generate_new_plot_with_changes)
self.button_submit.grid(row=7, column=1, columnspan=2, sticky="n")
label_camera = tkinter.Label(self.window, text="Camera Views:")
label_camera.grid(row=8, column=1, columnspan=2)
# Variable that stores the Camera view selected
self.var_view_option = tkinter.StringVar(self.window)
self.var_view_option.set("Diag View 1") # initialize
# Stores the original x, y, z limits
self.original_xlim = self.ax.get_xlim3d()
self.original_ylim = self.ax.get_ylim3d()
self.original_zlim = self.ax.get_zlim3d()
# Radio button for the Camera view selection
self.radio_hue_light_view_1 = tkinter.Radiobutton(self.window, text="Diag View 1",
variable=self.var_view_option,
value="Diag View 1",
command=self.change_view)
self.radio_hue_light_view_1.grid(row=9, column=1, sticky="nw")
self.radio_hue_light_view_1.select()
self.radio_hue_light_view_2 = tkinter.Radiobutton(self.window, text="Diag View 2",
variable=self.var_view_option,
value="Diag View 2",
command=self.change_view)
self.radio_hue_light_view_2.grid(row=9, column=2, sticky="nw")
self.radio_hue_view_1 = tkinter.Radiobutton(self.window, text="Hue View 1",
variable=self.var_view_option,
value="Hue View 1",
command=self.change_view)
self.radio_hue_view_1.grid(row=10, column=1, sticky="nw")
self.radio_hue_view_2 = tkinter.Radiobutton(self.window, text="Hue View 2",
variable=self.var_view_option,
value="Hue View 2",
command=self.change_view)
self.radio_hue_view_2.grid(row=10, column=2, sticky="nw")
self.radio_light_view_1 = tkinter.Radiobutton(self.window, text="Light View 1",
variable=self.var_view_option,
value="Light View 1",
command=self.change_view)
self.radio_light_view_1.grid(row=11, column=1, sticky="nw")
self.radio_light_view_2 = tkinter.Radiobutton(self.window, text="Light View 2",
variable=self.var_view_option,
value="Light View 2",
command=self.change_view)
self.radio_light_view_2.grid(row=11, column=2, sticky="nw")
self.radio_top_view = tkinter.Radiobutton(self.window, text="Top View",
variable=self.var_view_option,
value="Top View",
command=self.change_view)
self.radio_top_view.grid(row=12, column=1, columnspan=2, sticky="n")
[docs] def change_view(self):
view_selected = self.var_view_option.get()
if view_selected == "Diag View 1":
self.ax.view_init(elev=30, azim=-60)
elif view_selected == "Diag View 2":
self.ax.view_init(elev=30, azim=120)
elif view_selected == "Hue View 1":
self.ax.view_init(elev=0, azim=-90)
elif view_selected == "Hue View 2":
self.ax.view_init(elev=0, azim=90)
elif view_selected == "Light View 1":
self.ax.view_init(elev=0, azim=0)
elif view_selected == "Light View 2":
self.ax.view_init(elev=0, azim=180)
elif view_selected == "Top View":
self.ax.view_init(elev=93, azim=-90)
self.ax.set_xlim3d(self.original_xlim[0], self.original_xlim[1])
self.ax.set_ylim3d(self.original_ylim[0], self.original_ylim[1])
self.ax.set_zlim3d(self.original_zlim[0], self.original_zlim[1])
self.canvas.draw()
[docs] def generate_new_plot_with_changes(self):
if self.config_grid.get() == 0:
grid_on = False
else:
grid_on = True
if self.config_shade.get() == 0:
shade_on = False
else:
shade_on = True
self.cur_hue_res = int(self.var_hue_resolution.get())
self.cur_bri_res = float(self.var_bri_resolution.get())
self.ax.cla()
show_colors_in_hue_light_3d_bar_plot(self.barcode.colors,
hue_resolution=self.cur_hue_res,
bri_resolution=self.cur_bri_res,
figure_size=self.figure_size,
return_figure=True,
shaded=shade_on,
grid_off=not grid_on,
axes=self.ax)
if self.config_axis.get() == 0:
self.ax.axis("off")
# Stores the original x, y, z limits
self.original_xlim = self.ax.get_xlim3d()
self.original_ylim = self.ax.get_ylim3d()
self.original_zlim = self.ax.get_zlim3d()
self.canvas.draw()
[docs] def turn_on_plot_axis(self):
if self.config_axis.get() == 0:
self.checkbox_grid.config(state="disabled")
self.ax.axis("off")
self.canvas.draw()
elif self.config_axis.get() == 1:
self.checkbox_grid.config(state="normal")
self.ax.axis("on")
self.canvas.draw()
[docs] def turn_on_plot_grid(self):
if self.config_grid.get() == 0:
self.ax.grid(False)
self.canvas.draw()
elif self.config_grid.get() == 1:
self.ax.grid(True)
self.canvas.draw()
[docs] def shade_bar_plot(self):
if self.config_grid.get() == 0:
grid_on = False
else:
grid_on = True
if self.config_shade.get() == 0:
shade_on = False
else:
shade_on = True
if self.config_shade.get() == 0:
self.ax.cla()
show_colors_in_hue_light_3d_bar_plot(self.barcode.colors,
figure_size=self.figure_size,
hue_resolution=self.cur_hue_res,
bri_resolution=self.cur_bri_res,
return_figure=True,
shaded=shade_on,
grid_off=not grid_on,
axes=self.ax)
elif self.config_shade.get() == 1:
self.ax.cla()
show_colors_in_hue_light_3d_bar_plot(self.barcode.colors,
figure_size=self.figure_size,
hue_resolution=self.cur_hue_res,
bri_resolution=self.cur_bri_res,
return_figure=True,
shaded=shade_on,
grid_off=not grid_on,
axes=self.ax)
if self.config_axis.get() == 0:
self.ax.axis("off")
self.canvas.draw()
[docs]class HueLightScatterPlotWindow():
"""
HueLightScatterPlotWindow Class
GUI window that shows the distribution of the barcode's color in a Hue (x-axis) vs. Light (y-axis)
scatter plot. The color of the barcode will be converted from RGB to HSV/HSL color space.
Hue ranges from 0 to 360 degree and light range from 0 to 1 (darkest to the brightest)
"""
def __init__(self, barcode):
"""
Initialize
:param barcode: The input barcode
"""
self.barcode = barcode
# Set up the window
self.window = tkinter.Tk()
self.window.wm_title("Colors in Hue Light Scatter Plot")
self.window.iconbitmap(resource_path("kalmus_icon.ico"))
saturation_threshold = 0.15
# Set up the plotted figure
fig, ax = show_colors_in_hue_light_scatter_plot(self.barcode.colors, figure_size=(9, 4.5),
return_figure=True, remove_border=True,
saturation_threshold=saturation_threshold)
frame_type = barcode.frame_type
frame_type = frame_type.replace("_", " ")
frame_type = frame_type.title()
ax.set_title("{:s} Color of {:s} (Only colors with saturation > {:.2f} are included)"
.format(barcode.color_metric, frame_type, saturation_threshold))
plt.tight_layout()
# Set up the canvas
canvas = FigureCanvasTkAgg(fig, master=self.window) # A tk.DrawingArea.
canvas.draw()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
# Set up the tool bar of the figure
toolbar = NavigationToolbar2Tk(canvas, self.window)
toolbar.update()
canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
[docs]class OutputCSVWindow():
"""
OutputCSVWindow class
GUI window that outputs the per frame level color/brightness data of the inspected barcode
The data output are stored in the csv file, and the data frame depends on the type of the barcode
"""
def __init__(self, barcode):
"""
Initialize
:param barcode: The barcode to output the per frame level data
"""
self.barcode = barcode
# Set up the window
self.window = tkinter.Tk()
self.window.wm_title("Output the barcode to CSV")
self.window.iconbitmap(resource_path("kalmus_icon.ico"))
# Label prompt for the file name/path to the csv file
filename_label = tkinter.Label(self.window, text="CSV file path: ")
filename_label.grid(row=0, column=0, sticky=tkinter.W)
# Text entry for user to type the file name/path to the csv file
self.filename_entry = tkinter.Entry(self.window, textvariable="", width=40)
self.filename_entry.grid(row=0, column=1, columnspan=1, sticky=tkinter.W)
# Button to browse the folder
self.button_browse_folder = tkinter.Button(self.window, text="Browse", command=self.browse_folder)
self.button_browse_folder.grid(row=0, column=2)
# Button to build/load the barcode using the given json file
self.button_build_barcode = tkinter.Button(self.window, text="Output", command=self.output_csv_file)
self.button_build_barcode.grid(row=1, column=1, columnspan=1)
[docs] def output_csv_file(self):
"""
Output the per frame level data to a csv file
"""
# Get the file name of the output csv file
csv_filename = self.filename_entry.get()
if len(csv_filename) == 0:
showerror("Invalid Path or Filename", "Please specify the path/filename of the generated csv file.\n")
return
# Get the sampled frame rate of the barcode
sample_rate = self.barcode.sampled_frame_rate
# Get the starting/skipped over frame of the barcode
starting_frame = self.barcode.skip_over
# Generate the corresponding csv file for the type of the barcode
if self.barcode.barcode_type == 'Color':
# Data frame of the csv file for the color barcode
colors = self.barcode.colors
hsvs = rgb2hsv(colors.reshape(-1, 1, 3).astype("float64") / 255)
hsvs[..., 0] = 360 * hsvs[..., 0]
colors = colors.astype("float64")
brightness = 0.299 * colors[..., 0] + 0.587 * colors[..., 1] + 0.114 * colors[..., 1]
colors = colors.astype("uint8")
hsvs = hsvs.reshape(-1, 3)
brightness = brightness.astype("int64")
frame_indexes = np.arange(starting_frame, len(colors) * sample_rate + starting_frame, sample_rate)
dataframe = pd.DataFrame(data={'Frame index': frame_indexes,
'Red (0-255)': colors[..., 0],
'Green (0-255)': colors[..., 1],
'Blue (0-255)': colors[..., 2],
'Hue (0 -360)': (hsvs[..., 0]).astype("int64"),
'Saturation (0 - 1)': hsvs[..., 1],
'Value (lightness) (0 - 1)': hsvs[..., 2],
'Brightness': brightness})
elif self.barcode.barcode_type == 'Brightness':
# Data frame of the csv file for the brightness barcode
brightness = self.barcode.brightness
frame_indexes = np.arange(starting_frame, len(brightness) * sample_rate + starting_frame, sample_rate)
# Get the per frame level brightness data
dataframe = pd.DataFrame(data={'Frame index': frame_indexes,
'Brightness': brightness.astype("uint8").reshape(-1)})
dataframe = dataframe.set_index('Frame index')
if not csv_filename.endswith(".csv"):
csv_filename += ".csv"
dataframe.to_csv(csv_filename)
# Quit the window after outputting csv file
self.window.destroy()
showinfo("CSV File Generated Successfully", "CSV file has been generated successfully.\n"
"Path to the File: {:20s}".format(os.path.abspath(csv_filename)))
[docs] def browse_folder(self):
"""
Browse the folder to locate the json file
"""
# Get the file name from the user selection
filename = tkinter.filedialog.asksaveasfilename(initialdir=".", title="Select CSV file",
filetypes=(("csv files", "*.csv"), ("txt files", "*.txt"),
("All files", "*.*")))
# Update the file name to the file name text entry
self.filename_entry.delete(0, tkinter.END)
self.filename_entry.insert(0, filename)