|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +''' |
| 4 | +Picture Viewer Window |
| 5 | +
|
| 6 | +Displays a window for users to review a collection of images quickly |
| 7 | +
|
| 8 | +AP_FLAKE8_CLEAN |
| 9 | +''' |
| 10 | + |
| 11 | +from threading import Thread |
| 12 | +from math import ceil |
| 13 | +import cv2 |
| 14 | +import time |
| 15 | +import os |
| 16 | +import numpy as np |
| 17 | +from MAVProxy.modules.lib import mp_util |
| 18 | +if mp_util.has_wxpython: |
| 19 | + from MAVProxy.modules.lib.mp_menu import MPMenuTop |
| 20 | + from MAVProxy.modules.lib.mp_menu import MPMenuItem |
| 21 | + from MAVProxy.modules.lib.mp_menu import MPMenuSubMenu |
| 22 | + from MAVProxy.modules.lib.mp_image import MPImage |
| 23 | + from MAVProxy.modules.lib.mp_menu import MPMenuCallDirDialog |
| 24 | + |
| 25 | + |
| 26 | +class mosaic_window: |
| 27 | + """displays a mosaic of images""" |
| 28 | + |
| 29 | + def __init__(self, filelist): |
| 30 | + |
| 31 | + # determine if filelist is a string or a list of strings |
| 32 | + self.filenumber = 0 |
| 33 | + if type(filelist) is str: |
| 34 | + self.filelist = [] |
| 35 | + self.filelist.append(filelist) |
| 36 | + else: |
| 37 | + # use the first item in the list |
| 38 | + self.filelist = filelist |
| 39 | + |
| 40 | + # hardcoded thumbnail image size and number of columns |
| 41 | + self.thumb_size = 100 |
| 42 | + self.thumb_columns = 5 |
| 43 | + self.thumb_rows = ceil(len(filelist) / self.thumb_columns) |
| 44 | + |
| 45 | + # create image viewer |
| 46 | + self.im = None |
| 47 | + self.update_image() |
| 48 | + |
| 49 | + # create menu |
| 50 | + self.menu = None |
| 51 | + if mp_util.has_wxpython: |
| 52 | + self.menu = MPMenuTop([MPMenuSubMenu('&File', items=[MPMenuItem(name='&Open\tCtrl+O', returnkey='openfolder', |
| 53 | + handler=MPMenuCallDirDialog(title='Open Folder')), |
| 54 | + MPMenuItem('&Save\tCtrl+S'), |
| 55 | + MPMenuItem('Close', 'Close'), |
| 56 | + MPMenuItem('&Quit\tCtrl+Q', 'Quit')])]) |
| 57 | + self.im.set_menu(self.menu) |
| 58 | + |
| 59 | + popup = self.im.get_popup_menu() |
| 60 | + popup.add_to_submenu(["Mode"], MPMenuItem("ClickTrack", returnkey="Mode:ClickTrack")) |
| 61 | + popup.add_to_submenu(["Mode"], MPMenuItem("Flag", returnkey="Mode:Flag")) |
| 62 | + |
| 63 | + self.thread = Thread(target=self.mosaic_window_loop, name='mosaic_window_loop') |
| 64 | + self.thread.daemon = False |
| 65 | + self.thread.start() |
| 66 | + |
| 67 | + # main loop |
| 68 | + def mosaic_window_loop(self): |
| 69 | + """main thread""" |
| 70 | + while True: |
| 71 | + if self.im is None: |
| 72 | + break |
| 73 | + time.sleep(0.25) |
| 74 | + self.check_events() |
| 75 | + |
| 76 | + # set window title |
| 77 | + def set_title(self, title): |
| 78 | + """set image title""" |
| 79 | + if self.im is None: |
| 80 | + return |
| 81 | + self.im.set_title(title) |
| 82 | + |
| 83 | + # process window events |
| 84 | + def check_events(self): |
| 85 | + """check for image events""" |
| 86 | + if self.im is None: |
| 87 | + return |
| 88 | + if not self.im.is_alive(): |
| 89 | + self.im = None |
| 90 | + return |
| 91 | + for event in self.im.events(): |
| 92 | + if isinstance(event, MPMenuItem): |
| 93 | + if event.returnkey == "openfolder": |
| 94 | + self.cmd_openfolder() |
| 95 | + elif event.returnkey == "fitWindow": |
| 96 | + print("fitting to window") |
| 97 | + self.im.fit_to_window() |
| 98 | + elif event.returnkey == "fullSize": |
| 99 | + print("full size") |
| 100 | + self.im.full_size() |
| 101 | + elif event.returnkey == "nextimage": |
| 102 | + self.cmd_nextimage() |
| 103 | + elif event.returnkey == "previmage": |
| 104 | + self.cmd_previmage() |
| 105 | + else: |
| 106 | + debug_str = "event: %s" % event |
| 107 | + self.set_title(debug_str) |
| 108 | + continue |
| 109 | + if event.ClassName == "wxMouseEvent": |
| 110 | + if event.X is not None and event.Y is not None: |
| 111 | + print("mosaic pixel x:%f y:%f" % (event.X, event.Y)) |
| 112 | + |
| 113 | + # display dialog to open a folder |
| 114 | + def cmd_openfolder(self): |
| 115 | + print("I will open a folder") |
| 116 | + |
| 117 | + # display dialog to open a file |
| 118 | + def cmd_openfile(self): |
| 119 | + print("I will open a file") |
| 120 | + |
| 121 | + # update current image to next image |
| 122 | + def cmd_nextimage(self): |
| 123 | + if self.filenumber >= len(self.filelist)-1: |
| 124 | + print("picviewer: already at last image %d" % self.filenumber) |
| 125 | + return |
| 126 | + self.filenumber = self.filenumber+1 |
| 127 | + self.update_image() |
| 128 | + |
| 129 | + # update current image to previous image |
| 130 | + def cmd_previmage(self): |
| 131 | + if self.filenumber <= 0: |
| 132 | + print("picviewer: already at first image") |
| 133 | + return |
| 134 | + self.filenumber = self.filenumber - 1 |
| 135 | + self.update_image() |
| 136 | + |
| 137 | + # update the mosaic of images |
| 138 | + # should be called if filenumber is changed |
| 139 | + def update_image(self): |
| 140 | + # update filename |
| 141 | + self.filename = self.filelist[self.filenumber] |
| 142 | + base_filename = os.path.basename(self.filename) |
| 143 | + |
| 144 | + # create image viewer if required |
| 145 | + if self.im is None: |
| 146 | + self.im = MPImage(title=base_filename, |
| 147 | + mouse_events=True, |
| 148 | + mouse_movement_events=True, |
| 149 | + key_events=True, |
| 150 | + can_drag=True, |
| 151 | + can_zoom=False, |
| 152 | + auto_size=False, |
| 153 | + auto_fit=False) |
| 154 | + |
| 155 | + # check if image viewer was created |
| 156 | + if self.im is None: |
| 157 | + print("picviewer: failed to create image viewer") |
| 158 | + return |
| 159 | + |
| 160 | + # set title to filename |
| 161 | + self.set_title("Mosaic " + base_filename) |
| 162 | + |
| 163 | + # create blank image |
| 164 | + temp_image = cv2.imread(self.filename) |
| 165 | + h, w, c = temp_image.shape |
| 166 | + mosaic_image = 255 * np.ones(shape=(self.thumb_rows * self.thumb_size, |
| 167 | + self.thumb_columns * self.thumb_size, c), |
| 168 | + dtype=np.uint8) |
| 169 | + |
| 170 | + # iterate through images and add thumbnails to mosaic |
| 171 | + row = 0 |
| 172 | + col = 0 |
| 173 | + for i in range(len(self.filelist)): |
| 174 | + image_filename = self.filelist[i] |
| 175 | + image = cv2.imread(image_filename) |
| 176 | + image_small = cv2.resize(image, (self.thumb_size, self.thumb_size), interpolation=cv2.INTER_AREA) |
| 177 | + self.overlay_image(mosaic_image, image_small, col * self.thumb_size, row * self.thumb_size) |
| 178 | + col = col + 1 |
| 179 | + if col >= self.thumb_columns: |
| 180 | + col = 0 |
| 181 | + row = row + 1 |
| 182 | + |
| 183 | + # update image and colormap |
| 184 | + self.im.set_image(mosaic_image) |
| 185 | + self.im.set_colormap("None") |
| 186 | + |
| 187 | + def overlay_image(self, img, img2, x, y): |
| 188 | + '''overlay a 2nd image on a first image, at position x,y on the first image''' |
| 189 | + (img_width, img_height) = self.image_shape(img2) |
| 190 | + img[y:y+img_height, x:x+img_width] = img2 |
| 191 | + |
| 192 | + def image_shape(self, img): |
| 193 | + '''return (w,h) of an image, coping with different image formats''' |
| 194 | + height, width = img.shape[:2] |
| 195 | + return (width, height) |
0 commit comments