Skip to content

Commit ea16945

Browse files
committed
mavpicviewer: WIP improved mosaic
1 parent 3cdb8af commit ea16945

File tree

2 files changed

+266
-2
lines changed

2 files changed

+266
-2
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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+
import wx.lib.scrolledpanel as scrolled
18+
from MAVProxy.modules.lib import mp_util
19+
if mp_util.has_wxpython:
20+
from MAVProxy.modules.lib.wx_loader import wx
21+
from MAVProxy.modules.lib.mp_menu import MPMenuTop
22+
from MAVProxy.modules.lib.mp_menu import MPMenuItem
23+
from MAVProxy.modules.lib.mp_menu import MPMenuSubMenu
24+
from MAVProxy.modules.lib.mp_image import MPImage, MPImagePanel
25+
from MAVProxy.modules.lib.mp_menu import MPMenuCallDirDialog
26+
27+
28+
class mosaic_window2:
29+
"""displays a mosaic of images"""
30+
31+
def __init__(self, filelist):
32+
33+
# determine if filelist is a string or a list of strings
34+
self.filenumber = 0
35+
if type(filelist) is str:
36+
self.filelist = []
37+
self.filelist.append(filelist)
38+
else:
39+
# use the first item in the list
40+
self.filelist = filelist
41+
42+
# hardcoded thumbnail image size and number of columns
43+
self.thumb_size = 100
44+
self.thumb_columns = 5
45+
self.thumb_rows = ceil(len(filelist) / self.thumb_columns)
46+
47+
# create image viewer
48+
self.im = None
49+
#self.update_image()
50+
51+
# create window
52+
self.app = wx.App()
53+
self.frame = wx.Frame(None, title="Mosaic", size=(650, 200))
54+
self.frame.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
55+
56+
# add menu
57+
self.menu = wx.Menu()
58+
self.menu.Append(1, "Open Folder", "Open a Folder of images")
59+
self.menu_bar = wx.MenuBar()
60+
self.menu_bar.Append(self.menu, "Menu")
61+
self.frame.SetMenuBar(self.menu_bar)
62+
#self.frame.Bind(wx.EVT_MENU, self.menu_set_api_key_show, id=1)
63+
64+
# add settings input window
65+
self.settings_frame = wx.Frame(None, title="Input Settings", size=(560, 50))
66+
self.settings_text_input = wx.TextCtrl(self.settings_frame, id=-1, pos=(10, 10), size=(450, -1),
67+
style=wx.TE_PROCESS_ENTER, value="hello")
68+
self.settings_set_button = wx.Button(self.settings_frame, id=-1, label="Set", pos=(470, 10), size=(75, 25))
69+
#self.settings_frame.Bind(wx.EVT_BUTTON, self.settings_set_button_click, self.settings_set_button)
70+
#self.settings_frame.Bind(wx.EVT_TEXT_ENTER, self.settings_set_button_click, self.settings_text_input)
71+
#self.settings_frame.Bind(wx.EVT_CLOSE, self.apikey_close_button_click)
72+
73+
# add a scrolled panel
74+
self.scrolled_panel = scrolled.ScrolledPanel(self.frame, -1, size=(600, 600), style=wx.TAB_TRAVERSAL)
75+
self.scrolled_panel_sizer = wx.GridSizer(cols=5, hgap=5, vgap=5)
76+
77+
# add an image
78+
#img1_path = self.filelist[1]
79+
#self.wx_image1 = wx.Image(img1_path, wx.BITMAP_TYPE_ANY).Scale(100, 100)
80+
#self.image = wx.StaticBitmap(self.scrolled_panel, wx.ID_ANY, wx.Bitmap(self.wx_image1))
81+
82+
#img2_path = self.filelist[2]
83+
#self.wx_image2 = wx.Image(img2_path, wx.BITMAP_TYPE_ANY).Scale(100, 100)
84+
#self.image2 = wx.StaticBitmap(self.scrolled_panel, wx.ID_ANY, wx.Bitmap(self.wx_image2))
85+
86+
#self.scrolled_panel_sizer.Add(self.image, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
87+
#self.scrolled_panel_sizer.Add(self.image2, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
88+
89+
# add images
90+
for i in range(len(self.filelist)):
91+
img_path = self.filelist[i]
92+
wx_image = wx.Image(img_path, wx.BITMAP_TYPE_ANY).Scale(100, 100)
93+
image = wx.StaticBitmap(self.scrolled_panel, wx.ID_ANY, wx.Bitmap(wx_image))
94+
self.scrolled_panel_sizer.Add(image, proportion=0, flag=wx.EXPAND | wx.ALL, border=2)
95+
96+
self.scrolled_panel.SetSizer(self.scrolled_panel_sizer)
97+
self.scrolled_panel.SetupScrolling(scroll_x=True, scroll_y=True)
98+
99+
# add a read-only reply text box
100+
self.text_reply = wx.TextCtrl(self.frame, id=-1, size=(600, 80), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH)
101+
102+
# add a cancel button
103+
self.cancel_button = wx.Button(self.frame, id=-1, label="cancel", size=(75, 25))
104+
#self.frame.Bind(wx.EVT_BUTTON, self.cancel_button_click , self.cancel_button)
105+
106+
# add a vertical and horizontal sizers
107+
self.vert_sizer = wx.BoxSizer(wx.VERTICAL)
108+
self.horiz_sizer = wx.BoxSizer(wx.HORIZONTAL)
109+
110+
self.horiz_sizer.Add(self.cancel_button, proportion=0, flag=wx.ALIGN_TOP | wx.ALL, border=5)
111+
wx.CallAfter(self.cancel_button.Disable)
112+
113+
# set size hints and add sizer to frame
114+
self.vert_sizer.Add(self.scrolled_panel, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
115+
self.vert_sizer.Add(self.text_reply, proportion=0, flag=wx.EXPAND, border=5)
116+
#self.vert_sizer.Add(self.image, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
117+
#self.vert_sizer.Add(self.image2, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
118+
self.vert_sizer.Add(self.horiz_sizer, proportion=0, flag=wx.EXPAND)
119+
#self.frame.Bind(wx.EVT_SIZE, self.on_resize)
120+
self.frame.SetSizer(self.vert_sizer)
121+
self.frame.Layout()
122+
123+
# set focus on the input text box
124+
self.text_reply.SetFocus()
125+
126+
# show frame
127+
self.frame.Show()
128+
129+
# window loop (this does not return until the window is closed)
130+
self.app.MainLoop()
131+
132+
self.thread = Thread(target=self.mosaic_window_loop, name='mosaic_window_loop')
133+
self.thread.daemon = False
134+
self.thread.start()
135+
136+
# main loop
137+
#def mosaic_window_loop(self):
138+
# """main thread"""
139+
# while True:
140+
# if self.im is None:
141+
# break
142+
# time.sleep(0.25)
143+
# self.check_events()
144+
145+
# set window title
146+
def set_title(self, title):
147+
"""set image title"""
148+
if self.im is None:
149+
return
150+
self.im.set_title(title)
151+
152+
# process window events
153+
def check_events(self):
154+
"""check for image events"""
155+
if self.im is None:
156+
return
157+
if not self.im.is_alive():
158+
self.im = None
159+
return
160+
for event in self.im.events():
161+
if isinstance(event, MPMenuItem):
162+
if event.returnkey == "openfolder":
163+
self.cmd_openfolder()
164+
elif event.returnkey == "fitWindow":
165+
print("fitting to window")
166+
self.im.fit_to_window()
167+
elif event.returnkey == "fullSize":
168+
print("full size")
169+
self.im.full_size()
170+
elif event.returnkey == "nextimage":
171+
self.cmd_nextimage()
172+
elif event.returnkey == "previmage":
173+
self.cmd_previmage()
174+
else:
175+
debug_str = "event: %s" % event
176+
self.set_title(debug_str)
177+
continue
178+
if event.ClassName == "wxMouseEvent":
179+
if event.X is not None and event.Y is not None:
180+
print("mosaic pixel x:%f y:%f" % (event.X, event.Y))
181+
182+
# display dialog to open a folder
183+
def cmd_openfolder(self):
184+
print("I will open a folder")
185+
186+
# display dialog to open a file
187+
def cmd_openfile(self):
188+
print("I will open a file")
189+
190+
# update current image to next image
191+
def cmd_nextimage(self):
192+
if self.filenumber >= len(self.filelist)-1:
193+
print("picviewer: already at last image %d" % self.filenumber)
194+
return
195+
self.filenumber = self.filenumber+1
196+
self.update_image()
197+
198+
# update current image to previous image
199+
def cmd_previmage(self):
200+
if self.filenumber <= 0:
201+
print("picviewer: already at first image")
202+
return
203+
self.filenumber = self.filenumber - 1
204+
self.update_image()
205+
206+
# update the mosaic of images
207+
# should be called if filenumber is changed
208+
def update_image(self):
209+
# update filename
210+
self.filename = self.filelist[self.filenumber]
211+
base_filename = os.path.basename(self.filename)
212+
213+
# create image viewer if required
214+
if self.im is None:
215+
self.im = MPImage(title=base_filename,
216+
mouse_events=True,
217+
mouse_movement_events=True,
218+
key_events=True,
219+
can_drag=True,
220+
can_zoom=False,
221+
auto_size=False,
222+
auto_fit=False)
223+
224+
# check if image viewer was created
225+
if self.im is None:
226+
print("picviewer: failed to create image viewer")
227+
return
228+
229+
# set title to filename
230+
self.set_title("Mosaic " + base_filename)
231+
232+
# create blank image
233+
temp_image = cv2.imread(self.filename)
234+
h, w, c = temp_image.shape
235+
mosaic_image = 255 * np.ones(shape=(self.thumb_rows * self.thumb_size,
236+
self.thumb_columns * self.thumb_size, c),
237+
dtype=np.uint8)
238+
239+
# iterate through images and add thumbnails to mosaic
240+
row = 0
241+
col = 0
242+
for i in range(len(self.filelist)):
243+
image_filename = self.filelist[i]
244+
image = cv2.imread(image_filename)
245+
image_small = cv2.resize(image, (self.thumb_size, self.thumb_size), interpolation=cv2.INTER_AREA)
246+
self.overlay_image(mosaic_image, image_small, col * self.thumb_size, row * self.thumb_size)
247+
col = col + 1
248+
if col >= self.thumb_columns:
249+
col = 0
250+
row = row + 1
251+
252+
# update image and colormap
253+
self.im.set_image(mosaic_image)
254+
self.im.set_colormap("None")
255+
256+
def overlay_image(self, img, img2, x, y):
257+
'''overlay a 2nd image on a first image, at position x,y on the first image'''
258+
(img_width, img_height) = self.image_shape(img2)
259+
img[y:y+img_height, x:x+img_width] = img2
260+
261+
def image_shape(self, img):
262+
'''return (w,h) of an image, coping with different image formats'''
263+
height, width = img.shape[:2]
264+
return (width, height)

MAVProxy/tools/mavpicviewer/picviewer_window.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import time
1414
import os
1515
import piexif
16-
import mosaic_window
16+
import mosaic_window2
1717

1818
from MAVProxy.modules.lib import mp_util
1919
from MAVProxy.modules.lib import mp_elevation
@@ -90,7 +90,7 @@ def __init__(self, filelist):
9090
self.update_map()
9191

9292
# create mosaic of images
93-
self.mosaic = mosaic_window.mosaic_window(self.filelist)
93+
self.mosaic = mosaic_window2.mosaic_window2(self.filelist)
9494

9595
# create menu
9696
self.menu = None

0 commit comments

Comments
 (0)