diff --git a/MAVProxy/tools/mavpicviewer/mosaic_window2.py b/MAVProxy/tools/mavpicviewer/mosaic_window2.py new file mode 100644 index 0000000000..471a90d71a --- /dev/null +++ b/MAVProxy/tools/mavpicviewer/mosaic_window2.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 + +''' +Picture Viewer Window + +Displays a window for users to review a collection of images quickly + +AP_FLAKE8_CLEAN +''' + +from threading import Thread +from math import ceil +import cv2 +import time +import os +import numpy as np +import wx.lib.scrolledpanel as scrolled +from MAVProxy.modules.lib import mp_util +if mp_util.has_wxpython: + from MAVProxy.modules.lib.wx_loader import wx + from MAVProxy.modules.lib.mp_menu import MPMenuTop + from MAVProxy.modules.lib.mp_menu import MPMenuItem + from MAVProxy.modules.lib.mp_menu import MPMenuSubMenu + from MAVProxy.modules.lib.mp_image import MPImage, MPImagePanel + from MAVProxy.modules.lib.mp_menu import MPMenuCallDirDialog + + +class mosaic_window2: + """displays a mosaic of images""" + + def __init__(self, filelist): + + # determine if filelist is a string or a list of strings + self.filenumber = 0 + if type(filelist) is str: + self.filelist = [] + self.filelist.append(filelist) + else: + # use the first item in the list + self.filelist = filelist + + # hardcoded thumbnail image size and number of columns + self.thumb_size = 100 + self.thumb_columns = 5 + self.thumb_rows = ceil(len(filelist) / self.thumb_columns) + + # create image viewer + self.im = None + #self.update_image() + + # create window + self.app = wx.App() + self.frame = wx.Frame(None, title="Mosaic", size=(650, 200)) + self.frame.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) + + # add menu + self.menu = wx.Menu() + self.menu.Append(1, "Open Folder", "Open a Folder of images") + self.menu_bar = wx.MenuBar() + self.menu_bar.Append(self.menu, "Menu") + self.frame.SetMenuBar(self.menu_bar) + #self.frame.Bind(wx.EVT_MENU, self.menu_set_api_key_show, id=1) + + # add settings input window + self.settings_frame = wx.Frame(None, title="Input Settings", size=(560, 50)) + self.settings_text_input = wx.TextCtrl(self.settings_frame, id=-1, pos=(10, 10), size=(450, -1), + style=wx.TE_PROCESS_ENTER, value="hello") + self.settings_set_button = wx.Button(self.settings_frame, id=-1, label="Set", pos=(470, 10), size=(75, 25)) + #self.settings_frame.Bind(wx.EVT_BUTTON, self.settings_set_button_click, self.settings_set_button) + #self.settings_frame.Bind(wx.EVT_TEXT_ENTER, self.settings_set_button_click, self.settings_text_input) + #self.settings_frame.Bind(wx.EVT_CLOSE, self.apikey_close_button_click) + + # add a scrolled panel + self.scrolled_panel = scrolled.ScrolledPanel(self.frame, -1, size=(600, 600), style=wx.TAB_TRAVERSAL) + self.scrolled_panel_sizer = wx.GridSizer(cols=5, hgap=5, vgap=5) + + # add an image + #img1_path = self.filelist[1] + #self.wx_image1 = wx.Image(img1_path, wx.BITMAP_TYPE_ANY).Scale(100, 100) + #self.image = wx.StaticBitmap(self.scrolled_panel, wx.ID_ANY, wx.Bitmap(self.wx_image1)) + + #img2_path = self.filelist[2] + #self.wx_image2 = wx.Image(img2_path, wx.BITMAP_TYPE_ANY).Scale(100, 100) + #self.image2 = wx.StaticBitmap(self.scrolled_panel, wx.ID_ANY, wx.Bitmap(self.wx_image2)) + + #self.scrolled_panel_sizer.Add(self.image, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) + #self.scrolled_panel_sizer.Add(self.image2, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) + + # add images + for i in range(len(self.filelist)): + img_path = self.filelist[i] + wx_image = wx.Image(img_path, wx.BITMAP_TYPE_ANY).Scale(100, 100) + image = wx.StaticBitmap(self.scrolled_panel, wx.ID_ANY, wx.Bitmap(wx_image)) + self.scrolled_panel_sizer.Add(image, proportion=0, flag=wx.EXPAND | wx.ALL, border=2) + + self.scrolled_panel.SetSizer(self.scrolled_panel_sizer) + self.scrolled_panel.SetupScrolling(scroll_x=True, scroll_y=True) + + # add a read-only reply text box + self.text_reply = wx.TextCtrl(self.frame, id=-1, size=(600, 80), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH) + + # add a cancel button + self.cancel_button = wx.Button(self.frame, id=-1, label="cancel", size=(75, 25)) + #self.frame.Bind(wx.EVT_BUTTON, self.cancel_button_click , self.cancel_button) + + # add a vertical and horizontal sizers + self.vert_sizer = wx.BoxSizer(wx.VERTICAL) + self.horiz_sizer = wx.BoxSizer(wx.HORIZONTAL) + + self.horiz_sizer.Add(self.cancel_button, proportion=0, flag=wx.ALIGN_TOP | wx.ALL, border=5) + wx.CallAfter(self.cancel_button.Disable) + + # set size hints and add sizer to frame + self.vert_sizer.Add(self.scrolled_panel, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) + self.vert_sizer.Add(self.text_reply, proportion=0, flag=wx.EXPAND, border=5) + #self.vert_sizer.Add(self.image, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) + #self.vert_sizer.Add(self.image2, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) + self.vert_sizer.Add(self.horiz_sizer, proportion=0, flag=wx.EXPAND) + #self.frame.Bind(wx.EVT_SIZE, self.on_resize) + self.frame.SetSizer(self.vert_sizer) + self.frame.Layout() + + # set focus on the input text box + self.text_reply.SetFocus() + + # show frame + self.frame.Show() + + # window loop (this does not return until the window is closed) + self.app.MainLoop() + + self.thread = Thread(target=self.mosaic_window_loop, name='mosaic_window_loop') + self.thread.daemon = False + self.thread.start() + + # main loop + #def mosaic_window_loop(self): + # """main thread""" + # while True: + # if self.im is None: + # break + # time.sleep(0.25) + # self.check_events() + + # set window title + def set_title(self, title): + """set image title""" + if self.im is None: + return + self.im.set_title(title) + + # process window events + def check_events(self): + """check for image events""" + if self.im is None: + return + if not self.im.is_alive(): + self.im = None + return + for event in self.im.events(): + if isinstance(event, MPMenuItem): + if event.returnkey == "openfolder": + self.cmd_openfolder() + elif event.returnkey == "fitWindow": + print("fitting to window") + self.im.fit_to_window() + elif event.returnkey == "fullSize": + print("full size") + self.im.full_size() + elif event.returnkey == "nextimage": + self.cmd_nextimage() + elif event.returnkey == "previmage": + self.cmd_previmage() + else: + debug_str = "event: %s" % event + self.set_title(debug_str) + continue + if event.ClassName == "wxMouseEvent": + if event.X is not None and event.Y is not None: + print("mosaic pixel x:%f y:%f" % (event.X, event.Y)) + + # display dialog to open a folder + def cmd_openfolder(self): + print("I will open a folder") + + # display dialog to open a file + def cmd_openfile(self): + print("I will open a file") + + # update current image to next image + def cmd_nextimage(self): + if self.filenumber >= len(self.filelist)-1: + print("picviewer: already at last image %d" % self.filenumber) + return + self.filenumber = self.filenumber+1 + self.update_image() + + # update current image to previous image + def cmd_previmage(self): + if self.filenumber <= 0: + print("picviewer: already at first image") + return + self.filenumber = self.filenumber - 1 + self.update_image() + + # update the mosaic of images + # should be called if filenumber is changed + def update_image(self): + # update filename + self.filename = self.filelist[self.filenumber] + base_filename = os.path.basename(self.filename) + + # create image viewer if required + if self.im is None: + self.im = MPImage(title=base_filename, + mouse_events=True, + mouse_movement_events=True, + key_events=True, + can_drag=True, + can_zoom=False, + auto_size=False, + auto_fit=False) + + # check if image viewer was created + if self.im is None: + print("picviewer: failed to create image viewer") + return + + # set title to filename + self.set_title("Mosaic " + base_filename) + + # create blank image + temp_image = cv2.imread(self.filename) + h, w, c = temp_image.shape + mosaic_image = 255 * np.ones(shape=(self.thumb_rows * self.thumb_size, + self.thumb_columns * self.thumb_size, c), + dtype=np.uint8) + + # iterate through images and add thumbnails to mosaic + row = 0 + col = 0 + for i in range(len(self.filelist)): + image_filename = self.filelist[i] + image = cv2.imread(image_filename) + image_small = cv2.resize(image, (self.thumb_size, self.thumb_size), interpolation=cv2.INTER_AREA) + self.overlay_image(mosaic_image, image_small, col * self.thumb_size, row * self.thumb_size) + col = col + 1 + if col >= self.thumb_columns: + col = 0 + row = row + 1 + + # update image and colormap + self.im.set_image(mosaic_image) + self.im.set_colormap("None") + + def overlay_image(self, img, img2, x, y): + '''overlay a 2nd image on a first image, at position x,y on the first image''' + (img_width, img_height) = self.image_shape(img2) + img[y:y+img_height, x:x+img_width] = img2 + + def image_shape(self, img): + '''return (w,h) of an image, coping with different image formats''' + height, width = img.shape[:2] + return (width, height) diff --git a/MAVProxy/tools/mavpicviewer/picviewer_window.py b/MAVProxy/tools/mavpicviewer/picviewer_window.py index b0bef2d936..733c1f3732 100644 --- a/MAVProxy/tools/mavpicviewer/picviewer_window.py +++ b/MAVProxy/tools/mavpicviewer/picviewer_window.py @@ -13,7 +13,7 @@ import time import os import piexif -import mosaic_window +import mosaic_window2 from MAVProxy.modules.lib import mp_util from MAVProxy.modules.lib import mp_elevation @@ -90,7 +90,7 @@ def __init__(self, filelist): self.update_map() # create mosaic of images - self.mosaic = mosaic_window.mosaic_window(self.filelist) + self.mosaic = mosaic_window2.mosaic_window2(self.filelist) # create menu self.menu = None