Source code for kalmus.tkinter_windows.gui_utils

""" KALMUS tkinter GUI utility """

import cv2
import kalmus.utils.measure_utils as measure_utils
import os
import sys
import numpy as np
from skimage.color import hsv2rgb, rgb2hsv

from matplotlib.ticker import FuncFormatter


[docs]def compare_two_barcodes(barcode_1, barcode_2): """ Compare the similarity between two barcodes using NRMSE, SSIM, cross correlation, local cross correlation, Needleman Wunsch, and Smith Waterman alignment matching :param barcode_1: The input barcode 1 :param barcode_2: The input barcode 2 :return: The simiarity scores computed using all six metrics """ # Resize two barcodes so they have the same shape # Alway resize the small barcode to the shape of the larger barcode target_barcode_1 = barcode_1.get_barcode().astype("float64") target_barcode_2 = barcode_2.get_barcode().astype("float64") if target_barcode_1.size <= target_barcode_2.size: target_barcode_1 = cv2.resize(target_barcode_1, dsize=target_barcode_2.shape[:2][::-1], interpolation=cv2.INTER_LINEAR) elif target_barcode_1.size > target_barcode_2.size: target_barcode_2 = cv2.resize(target_barcode_2, dsize=target_barcode_1.shape[:2][::-1], interpolation=cv2.INTER_LINEAR) # Get the cross correlation and local cross correlation cross_corre = measure_utils.cross_correlation(target_barcode_1.astype("float64"), target_barcode_2.astype("float64")) loc_cross_corre = measure_utils.local_cross_correlation(target_barcode_1.astype("float64"), target_barcode_2.astype("float64")) # Get the Normalized root mean squared error and and Structual similarity index measurement nrmse = measure_utils.nrmse_similarity(target_barcode_1.astype("float64"), target_barcode_2.astype("float64")) ssim = measure_utils.ssim_similarity(target_barcode_1.astype("float64"), target_barcode_2.astype("float64")) # Get the color/brightness of two barcodes if barcode_1.barcode_type == "Color": colors_barcode_1 = barcode_1.colors colors_barcode_2 = barcode_2.colors else: colors_barcode_1 = barcode_1.brightness colors_barcode_2 = barcode_2.brightness # Resample the barcodes if the barcodes have different size/length if colors_barcode_1.shape[0] > colors_barcode_2.shape[0]: colors_barcode_1 = colors_barcode_1[measure_utils.get_resample_index(len(colors_barcode_1), len(colors_barcode_2))] elif colors_barcode_1.shape[0] < colors_barcode_2.shape[0]: colors_barcode_2 = colors_barcode_2[measure_utils.get_resample_index(len(colors_barcode_2), len(colors_barcode_1))] # Convert the barcode to character string for alignment matching comparison # Convert the barcode to correct character based on its barcode type if barcode_1.barcode_type == "Color": string_1 = measure_utils.generate_hue_strings_from_color_barcode(colors_barcode_1) string_2 = measure_utils.generate_hue_strings_from_color_barcode(colors_barcode_2) else: string_1 = measure_utils.generate_brightness_string_from_brightness_barcode(colors_barcode_1) string_2 = measure_utils.generate_brightness_string_from_brightness_barcode(colors_barcode_2) # Get the Needleman Wunsch and Smith Waterman alignment matching score needleman = measure_utils.compare_needleman_wunsch(string_1, string_2, normalized=True) smithw = measure_utils.compare_smith_waterman(string_1, string_2, normalized=True) return cross_corre, loc_cross_corre, nrmse, ssim, needleman, smithw
[docs]def get_comparison_result_text(barcode_1, barcode_2): """ Get the text comparison result using the compare_two_barcodes function :param barcode_1: The compared barcode 1 :param barcode_2: The compared barcode 2 :return: The text comparison result """ # Return error text if two barcodes are not in the same type if barcode_1.barcode_type != barcode_2.barcode_type: result_text = "ERROR:\nComparison between different types of\n" \ "barcode type {:s} and {:s}\nare not allowed.".format(barcode_1.barcode_type, barcode_2.barcode_type) return result_text # Get the similarity score from the compare_two_barcodes function cross_corre, loc_cross_corre, nrmse, ssim, needleman, smith = compare_two_barcodes(barcode_1, barcode_2) # Get the text result result_text = "Comparison metrics:\n" \ "NRMSE Similarity: {:10.4f}\n" \ "SSIM Similarity: {:10.4f}\n" \ "Cross Correlation: {:10.4f}\n" \ "Local Cross Correlation: {:10.4f}\n" \ "Needleman Wunsch Similarity:{:10.4f}\n" \ "Smith Waterman Similarity: {:10.4f}".format(nrmse, ssim, cross_corre, loc_cross_corre, needleman, smith) return result_text
[docs]def get_time(barcode, x_pos, y_pos): """ Get the time (hr, min, sec) at a point of barcode :param barcode: The barcode object :param x_pos: The x position of the query :param y_pos: The y position of the query :return: time frame, hr, min, sec at the given point (x_pos, y_pos) """ # Compute the frame label frame = (barcode.skip_over + barcode.sampled_frame_rate * ((x_pos * barcode.get_barcode().shape[0]) + y_pos)) * barcode.scale_factor frame = int(frame) # If frame rate is not given, use 30 as default if barcode.fps is None: barcode.fps = 30 # Compute the time label time_tot_sec = frame / barcode.fps time_sec = int(time_tot_sec % 60) time_min = int((time_tot_sec / 60) % 60) time_hr = int(time_tot_sec / 3600) return frame, time_hr, time_min, time_sec
[docs]def update_graph(barcode_1, barcode_2, axes, bin_step=5): """ Update the plotted graph (in place) :param barcode_1: The barcode 1 :param barcode_2: The barcode 2 :param axes: The axes of the plotted figure :param bin_step: The step of histogram bin """ # Plot the barcode with the correct color map based on their barcode types if barcode_1.barcode_type == "Brightness": axes[0][0].imshow(barcode_1.get_barcode().astype("uint8"), cmap='gray', vmin=0, vmax=255) else: axes[0][0].imshow(barcode_1.get_barcode().astype("uint8")) if barcode_2.barcode_type == "Brightness": axes[1][0].imshow(barcode_2.get_barcode().astype("uint8"), cmap='gray', vmin=0, vmax=255) else: axes[1][0].imshow(barcode_2.get_barcode().astype("uint8")) # Rescale the axis range # And update the plot for axis in axes.ravel(): axis.relim() axis.autoscale_view() # Update the axis ticks of the plotted axes update_axes_ticks(barcode_1, barcode_2, axes) # Update the title of the plotted axes update_axes_title(axes, barcode_1, barcode_2) # Update the histogram of the barcode 1 update_hist(barcode_1, ax=axes[0][1], bin_step=bin_step) # Update the histogram of the barcode 2 update_hist(barcode_2, ax=axes[1][1], bin_step=bin_step)
[docs]def update_axes_ticks(barcode1, barcode2, axes): """ Update the axes ticks If two barcodes have the same temporal dimensions, the ticks will show the temporal position of a pixel in the barcode image. Otherwise, the axes ticks will show the spatial position of a pixel. :param barcode1: The barcode 1 :param barcode2: The barcode 2 :param axes: The axes of the plotted figure """ frames_per_column_1 = barcode1.sampled_frame_rate * barcode1.get_barcode().shape[0] \ * barcode1.scale_factor frames_per_column_2 = barcode2.sampled_frame_rate * barcode2.get_barcode().shape[0] \ * barcode2.scale_factor seconds_per_column1 = frames_per_column_1 / barcode1.fps seconds_per_column2 = frames_per_column_2 / barcode2.fps axes[0][0].set_ylabel("{:.2f}s per column".format(seconds_per_column1)) axes[1][0].set_ylabel("{:.2f}s per column".format(seconds_per_column2)) if np.round(seconds_per_column1, decimals=0) == np.round(seconds_per_column2, decimals=0): def get_seconds_str_yticks(tick_value, pos): seconds = np.round(tick_value * seconds_per_column2, decimals=0).astype("int") return "{:02d}:{:02d}".format(seconds // 60, seconds % 60) axes[1][0].xaxis.set_major_formatter(FuncFormatter(get_seconds_str_yticks)) axes[1][0].set_xlabel("Elapsed Time (mins:secs)") def get_seconds_str_xticks(tick_value, pos): seconds = tick_value * barcode1.sampled_frame_rate * barcode1.scale_factor / barcode1.fps return "{:.1f}s".format(seconds) axes[0][0].yaxis.set_major_formatter(FuncFormatter(get_seconds_str_xticks)) axes[1][0].yaxis.set_major_formatter(FuncFormatter(get_seconds_str_xticks))
[docs]def update_axes_title(axes, barcode_1, barcode_2): """ Update the title of the plotted axes (in place) :param axes: The plotted axes of the figure :param barcode_1: The barcode 1 :param barcode_2: The barcode 2 """ title_1 = "Barcode 1" title_2 = "Barcode 2" keys = ["Film Title", "Produced Year", "Genre"] # Update the meta data into the title of the plotted figure if barcode_1.meta_data is not None: for key in keys: if key in barcode_1.meta_data.keys(): title_1 += " {:s}: {:s}".format(key, barcode_1.meta_data[key]) # Update the Frame sampling type and color/brightness metric into the title title_1 += " Frame Type:{:s} {:s} Metric:{:s}".format(barcode_1.frame_type, barcode_1.barcode_type, barcode_1.color_metric) # Update the meta data into the title of the plotted figure if barcode_2.meta_data is not None: for key in keys: if key in barcode_2.meta_data.keys(): title_2 += " {:s}: {:s}".format(key, barcode_2.meta_data[key]) # Update the Frame sampling type and color/brightness metric into the title title_2 += " Frame Type:{:s} {:s} Metric:{:s}".format(barcode_2.frame_type, barcode_2.barcode_type, barcode_2.color_metric) # Set up the title axes[0][0].set_title(title_1, fontsize=8) axes[1][0].set_title(title_2, fontsize=8) # Change the histogram's label and xticks accordingly if barcode_1.barcode_type == "Color": axes[0][1].set_xticks(np.arange(0, 361, 30)) axes[0][1].set_xlabel("Color Hue (0 - 360)") axes[0][1].set_ylabel("Number of frames") elif barcode_1.barcode_type == "Brightness": axes[0][1].set_xticks(np.arange(0, 256, 16)) axes[0][1].set_xlabel("Brightness (0 - 255)") axes[0][1].set_ylabel("Number of frames") if barcode_2.barcode_type == "Color": axes[1][1].set_xticks(np.arange(0, 361, 30)) axes[1][1].set_xlabel("Color Hue (0 - 360)") axes[1][1].set_ylabel("Number of frames") elif barcode_2.barcode_type == "Brightness": axes[1][1].set_xticks(np.arange(0, 256, 32)) axes[1][1].set_xlabel("Brightness (0 - 255)") axes[1][1].set_ylabel("Number of frames")
[docs]def update_hist(barcode, ax, bin_step=5): """ Update the histogram of the plotted figure (in place) :param barcode: The barcode of the corresponding histogram :param ax: The axis that contain the histogram :param bin_step: The step of the bin in the plotted histogram """ bin_step = bin_step # Plot the histogram based on the barcode's type if barcode.barcode_type == "Color": # If the barcode type is color # Then plot the barcode's hue value normalized_barcode = barcode.get_barcode().astype("float") / 255 hsv_colors = rgb2hsv(normalized_barcode.reshape(-1, 1, 3)) hue = hsv_colors[..., 0] * 360 N, bins, patches = ax.hist(hue[:, 0], bins=(np.arange(0, 361, bin_step))) # Paint each bin with its corresponding color in hue paint_hue_hist(bin_step, patches) else: # If the barcode type is brightness N, bins, patches = ax.hist(barcode.brightness[:, 0], bins=(np.arange(0, 256, bin_step))) # Then paint each bin with its brightness intensity paint_gray_hist(bin_step, patches, opacity=0.9)
[docs]def paint_hue_hist(bin_step, patches): """ Paint each bin of the hue histogram with its corresponding color in hue (in place) :param bin_step: The step of the bin in the histogram :param patches: The patches of the histogram that will be painted later """ # Paint each bin of the histogram for i in range(len(patches)): # Get the hue first hue = np.array([i * bin_step / 360.0, 1.0, 1.0]).astype("float64") hue = np.expand_dims(np.expand_dims(hue, 0), 0) # Then get the corresponding color rgb = hsv2rgb(hue).tolist() rgb_tuple = (rgb[0][0][0], rgb[0][0][1], rgb[0][0][2]) # Paint each patch of the histogram patches[i].set_facecolor(rgb_tuple)
[docs]def paint_gray_hist(bin_step, patches, opacity=0.8): """ Paint each bin of the brightness histogram with its corresponding brightness intensity (in place) :param bin_step: The step of the bin in the histogram :param patches: The patches of the histogram :param opacity: Opacity of the brightness intensity to avoid totally white in the high intensity bin \ To make the histogram bin in high intensity area more observable """ for i in range(len(patches)): # Get the RGB gray color for each bin bri_rgb_tuple = (bin_step * i * opacity / 255, bin_step * i * opacity / 255, bin_step * i * opacity / 255) patches[i].set_facecolor(bri_rgb_tuple)
[docs]def resource_path(relative_path): """ Internal utility function Use to convert the input relative path to the absolute path This is used for the Pyinstaller wrapper :param relative_path: The relative path to the file :return: The absolute path to the file """ try: # If the MEIPASS base path exist base_path = sys._MEIPASS except Exception: # Otherwise use the absolute path if relative_path.endswith(".ico"): # If the path points to .ico image if os.name != "nt": # If the running os is linux relative_path = relative_path[:-3] # Replace the .ico image with image in .xbm format relative_path += "xbm" # Join the path base_path = os.path.abspath(os.path.dirname(__file__)) base_path = os.path.join(base_path, '../data') if relative_path.endswith(".xbm"): # If the image is .xbm image if os.name != "nt": # and the running os is linux # Add @ identifier at the front of path base_path = "@" + base_path # Return the path return os.path.join(base_path, relative_path)