00001 # -*- coding: utf-8 -*- 00002 import Tkinter 00003 import roslib 00004 roslib.load_manifest('face_contour_detector') 00005 import rospy 00006 import cv 00007 import cv_bridge 00008 import Image 00009 import ImageTk 00010 import image_settings_frame 00011 from face_contour_detector.srv import * 00012 from face_contour_detector.msg import * 00013 00014 ## @package face_contour_detector.gui.image_selection_frame 00015 # 00016 # This package contains only the class ImageSelectionFrame. See class documentation for details. 00017 # @author Fabian Wenzelmann 00018 00019 ## @brief constant defining the maximum width an displayed image is allowed to have 00020 MAX_WIDTH = 250 00021 ## @brief constant defining the maximum width an displayed image is allowed to have 00022 MAX_HEIGHT = 500 00023 00024 ## @brief This class is used to display proposals to the user. 00025 # @author Fabian Wenzelmann 00026 # 00027 # This class displays up to four proposals images. The images can be clicked which means that the users selects the filter settings of that image. 00028 # The images are scaled to a max size so that the four images fit in a single row. 00029 # Here's an example instance of this frame: 00030 # @image html image_selection_frame.png 00031 class ImageSelectionFrame(Tkinter.Frame): 00032 ## @brief Constructor method 00033 # @param self the object pointer 00034 # @param master The Tkinter master widget of this frame 00035 # @param proposals a list of type autoselect_result. Each proposal consists of the original image and the initial filter settings. 00036 # 00037 # This frame contains only for simple labels which display the result of applying the filters of that proposal to the original image. 00038 # Each label is clickable. When clicked the settings of this proposal are used to create a ImageSettingsFrame. 00039 def __init__(self, master, proposals): 00040 image_settings_frame.current_image = None 00041 Tkinter.Frame.__init__(self, master) 00042 self.master.title("Select Edge Image") 00043 bridge = cv_bridge.CvBridge() 00044 images = [] 00045 self.proposals = proposals = proposals[:4] 00046 f = rospy.ServiceProxy("apply_filters", ApplyFilters) 00047 for proposal in proposals: 00048 resp = f(proposal.image, proposal.areas) 00049 cv_img = bridge.imgmsg_to_cv(resp.result_image, "mono8") 00050 py_img = Image.fromstring("L", cv.GetSize(cv_img), cv_img.tostring()) 00051 scaled = scale_image(py_img, MAX_WIDTH, MAX_HEIGHT) 00052 images.append(scaled) 00053 information_label = Tkinter.Label(self, text="Choose one of the following proposals") 00054 information_label.grid(row=0, column=0) 00055 button_mode = lambda n: lambda *args: self.select(n) 00056 # keep references so that there is still the reference (strange tkinter behavior...) 00057 self.image_buffer = image_buffer = [] 00058 for i, image in enumerate(images): 00059 photo_image = ImageTk.PhotoImage(image) 00060 image_buffer.append(photo_image) 00061 image_label = Tkinter.Label(self, image=photo_image) 00062 image_label.bind("<1>", button_mode(i)) 00063 image_label.grid(row=1, column=i) 00064 00065 # member documentation 00066 00067 ## @var proposals 00068 # a list of type autoselect_result. Each proposal consists of the original image and the initial filter settings. 00069 00070 ## @var image_buffer 00071 # a list of the ImateTk.PhotoImages that are displayed (or python will destroy them even if they're displayed...) 00072 00073 ## @brief Callback method for image label clicks. 00074 # @param self the object pointer 00075 # @param n The number of the proposal that is choose. For example if the list contained for proposals and the second is choosen by the user n is 1 00076 # 00077 # This method creates a new ImageSettingsFrame with the selected initial settings. A new Tkinter.Toplevel widget is created as master of that frame. 00078 # The new frame gets displayed and this method waits for this window. After that the master of this object (which is usually a Tkinter.Tk widget) gets destroyed. 00079 def select(self, n): 00080 top = Tkinter.Toplevel() 00081 settings = image_settings_frame.ImageSettingsFrame(top, self.proposals[n]) 00082 settings.pack() 00083 self.master.withdraw() 00084 top.wait_window() 00085 self.master.destroy() 00086 00087 ## @brief This method is used to scale an image to a maximum size. 00088 # @param img The image to scale (PIL Image) 00089 # @param max_width The maximum width that the scaled version of this image is allowed to have 00090 # @param max_height The maximum height that the scaled version of this image is allowed to have 00091 # 00092 # This function returns a scaled version of the image. The image has a width <= max_width and a height <= max_height. The scaled version preserve the proportions of the original image. 00093 def scale_image(img, max_width, max_height): 00094 width, height = img.size 00095 w_factor, h_factor = max_width / float(width), max_height / float(height) 00096 factor = min(w_factor, h_factor) 00097 if factor >= 1: 00098 return img 00099 else: 00100 return img.resize(( int(factor * width), int(factor * height) ), Image.ANTIALIAS)