Creating a Drag and Drop Interface for Images in wxPython - python

Firstly, this is what I'm trying to make with wxPython. I would like the region labeled "Picture 1" to be able to accept a dragged image, and then when an image is dragged there, be replaced with a thumbnail of that image. I have researched "Drag and Drop" in wxPython, but I can't seem to find the tools needed for me to do this.
Any help getting me started on the right track would be greatly appreciated.

You will want the Pillow package for creating a thumbnail. The other piece you will want is most likely the wx.FileDropTarget class. I ended up doing the following:
import os
import wx
from PIL import Image
from wx.lib.pubsub import pub
PhotoMaxSize = 240
class DropTarget(wx.FileDropTarget):
def __init__(self, widget):
wx.FileDropTarget.__init__(self)
self.widget = widget
def OnDropFiles(self, x, y, filenames):
print(filenames)
image = Image.open(filenames[0])
image.thumbnail((PhotoMaxSize, PhotoMaxSize))
image.save('thumbnail.png')
pub.sendMessage('dnd', filepath='thumbnail.png')
return True
class PhotoCtrl(wx.App):
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
self.frame = wx.Frame(None, title='Photo Control')
self.panel = wx.Panel(self.frame)
pub.subscribe(self.update_image_on_dnd, 'dnd')
self.PhotoMaxSize = 240
self.createWidgets()
self.frame.Show()
def createWidgets(self):
instructions = 'Browse for an image'
img = wx.Image(240,240)
self.imageCtrl = wx.StaticBitmap(self.panel, wx.ID_ANY,
wx.Bitmap(img))
filedroptarget = DropTarget(self)
self.imageCtrl.SetDropTarget(filedroptarget)
instructLbl = wx.StaticText(self.panel, label=instructions)
self.photoTxt = wx.TextCtrl(self.panel, size=(200,-1))
browseBtn = wx.Button(self.panel, label='Browse')
browseBtn.Bind(wx.EVT_BUTTON, self.onBrowse)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY),
0, wx.ALL|wx.EXPAND, 5)
self.mainSizer.Add(instructLbl, 0, wx.ALL, 5)
self.mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
self.sizer.Add(self.photoTxt, 0, wx.ALL, 5)
self.sizer.Add(browseBtn, 0, wx.ALL, 5)
self.mainSizer.Add(self.sizer, 0, wx.ALL, 5)
self.panel.SetSizer(self.mainSizer)
self.mainSizer.Fit(self.frame)
self.panel.Layout()
def onBrowse(self, event):
"""
Browse for file
"""
wildcard = "JPEG files (*.jpg)|*.jpg"
dialog = wx.FileDialog(None, "Choose a file",
wildcard=wildcard,
style=wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
self.photoTxt.SetValue(dialog.GetPath())
dialog.Destroy()
self.onView()
def update_image_on_dnd(self, filepath):
self.onView(filepath=filepath)
def onView(self, filepath=None):
if not filepath:
filepath = self.photoTxt.GetValue()
img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.PhotoMaxSize
NewH = self.PhotoMaxSize * H / W
else:
NewH = self.PhotoMaxSize
NewW = self.PhotoMaxSize * W / H
img = img.Scale(NewW,NewH)
self.imageCtrl.SetBitmap(wx.Bitmap(img))
self.panel.Refresh()
if __name__ == '__main__':
app = PhotoCtrl()
app.MainLoop()
This worked for me on Windows 7 with wxPython 4. Note that I am dragging a file from Windows Explorer onto my wxPython application's image widget.

Related

I am trying to make a window/frame containing a picture that allows you to click through it

Just to clarify my goal. I am trying to get a resizable (via mouse click and drag) window that is see through, and allows you to change the transparency of it by up and down arrows.
I want it to ask for an image, and then rescale it as you drag the window.
The key feature which I have working is to allow users to click through it.
The issue that I am running into is I can't place an image inside the frame, nor move or resize it.
from win32api import GetSystemMetrics
import win32con
import win32gui
import sys
from PIL import Image
import numpy as np
import wx
def scale_bitmap(bitmap, width, height):
image = wx.ImageFromBitmap(bitmap) #was wx.imageFromBitmap(bitmap)
image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH)
result = wx.BitmapFromImage(image)
return result
imageName = input("Enter name of image file")
im1 = Image.open("C:\\Users\\Daniel\\Desktop\\Tracing Images" + "\\" + imageName )
#r, g, b, = im1.split()
#im1 = Image.merge("RGB", (r, g, b))
im1.save("C:\\Users\\Daniel\\Desktop\\Tracing Images\\converted\\" +str("1") + ".bmp")
Imgbmp = Image.open("C:\\Users\\Daniel\\Desktop\\Tracing Images\\converted\\" +str("1") + ".bmp")
#convert image into bitmap?
app = wx.App()
trans = 50
# create a window/frame, no parent, -1 is default ID
# change the size of the frame to fit the backgound images
frame1 = wx.Frame(None, -1, "KExA", style=wx.RESIZE_BORDER | wx.STAY_ON_TOP)
# create the class instance
frame1.Show() #was frame1.ShowFullScreen(True)
image_file = win32gui.SystemParametersInfo(win32con.SPI_GETDESKWALLPAPER, 0, 0)
bmp1 = image_file
bmp1 = wx.Image(image_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap() #trying to remove this see if it fixes anything
bmp1 = scale_bitmap(bmp1, GetSystemMetrics(1) * 1.5, GetSystemMetrics(1))
bitmap1 = wx.StaticBitmap(frame1, -1, bmp1, (-100, 0))
hwnd = frame1.GetHandle()
extendedStyleSettings = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
extendedStyleSettings | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT)
win32gui.SetLayeredWindowAttributes(hwnd, 0, 255, win32con.LWA_ALPHA)
frame1.SetTransparent(trans)
def onKeyDown(e):
global trans
key = e.GetKeyCode()
if key == wx.WXK_UP:
print()
trans
trans += 10
if trans > 255:
trans = 255
elif key == wx.WXK_DOWN:
print()
trans
trans -= 10
if trans < 0:
trans = 0
try:
win32gui.SetLayeredWindowAttributes(hwnd, 0, trans, win32con.LWA_ALPHA)
except:
pass
frame1.Bind(wx.EVT_KEY_DOWN, onKeyDown)
app.MainLoop()
If you use wx.Image to manage your image, you can access the Scale function, which will allow you to tie the size of the image to size of the window, or anything else for that matter.
For example:
import wx
class TestFrame(wx.Frame):
def __init__(self, *args):
wx.Frame.__init__(self, *args)
self.Img = wx.Image("wxPython.jpg", wx.BITMAP_TYPE_ANY)
Imgsize = self.Img.GetWidth(), self.Img.GetHeight()
self.SetSize(Imgsize)
self.Image = wx.StaticBitmap(self, bitmap=wx.Bitmap(self.Img))
self.Bind(wx.EVT_SIZE, self.onResize)
self.Show()
def onResize(self, event):
frame_h, frame_w = self.GetSize()
img = self.Img.Scale(frame_h, frame_w)
self.Image = wx.StaticBitmap(self, bitmap=wx.Bitmap(img))
if __name__ == "__main__":
app = wx.App()
myframe = TestFrame(None, -1, "Resize Me")
app.MainLoop()

WxPython's ScrolledWindow element collapses to minimum size

I am using a Panel within a Frame to display images (the GUI need to switch between multiple panels and hence the hierarchy). As images should be displayed in native size I used ScrolledWindow as the panel parent. The scrolls do appear and work, but it causes the Panel to collapse to minimum size and it needs to be resized using drag&drop every time.
Is there a way around this?
Below is a reduced version of the code, which shows the problem:
import os
import wx
from wx.lib.pubsub import pub
class Edit_Panel(wx.PyScrolledWindow):
def __init__(self, parent):
super(Edit_Panel, self).__init__(parent)
# Display size
width, height = wx.DisplaySize()
self.photoMaxSize = height - 500
# Loaded image
self.loaded_image = None
# Icons
self.open_icon_id = 500
# Generate panel
self.layout()
def layout(self):
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
divider = wx.StaticLine(self, -1, style = wx.LI_HORIZONTAL)
self.main_sizer.Add(divider, 0, wx.ALL | wx.EXPAND)
self.toolbar = self.init_toolbar()
self.main_sizer.Add(self.toolbar, 0, wx.ALL)
img = wx.EmptyImage(self.photoMaxSize, self.photoMaxSize)
self.image_control = wx.StaticBitmap(self, wx.ID_ANY,
wx.BitmapFromImage(img))
self.main_sizer.Add(self.image_control, 0, wx.ALL | wx.CENTER, 5)
self.image_label = wx.StaticText(self, -1, style = wx.ALIGN_CENTRE)
self.main_sizer.Add(self.image_label, 0, wx.ALL | wx.ALIGN_CENTRE, 5)
self.SetSizer(self.main_sizer)
fontsz = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT).GetPixelSize()
self.SetScrollRate(fontsz.x, fontsz.y)
self.EnableScrolling(True, True)
def init_toolbar(self):
toolbar = wx.ToolBar(self)
toolbar.SetToolBitmapSize((16, 16))
open_ico = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16, 16))
open_tool = toolbar.AddSimpleTool(self.open_icon_id, open_ico, "Open", "Open an Image Directory")
handler = self.on_open_reference
self.Bind(event = wx.EVT_MENU, handler = handler, source = open_tool)
toolbar.Realize()
return toolbar
def on_open_reference(self, event, wildcard = None):
if wildcard is None:
wildcard = self.get_wildcard()
defaultDir = '~/'
dbox = wx.FileDialog(self, "Choose an image to display", defaultDir = defaultDir, wildcard = wildcard, style = wx.OPEN)
if dbox.ShowModal() == wx.ID_OK:
file_name = dbox.GetPath()
# load image
self.load_image(image = file_name)
dbox.Destroy()
def get_wildcard(self):
wildcard = 'Image files (*.jpg;*.png;*.bmp)|*.png;*.bmp;*.jpg;*.jpeg'
return wildcard
def load_image(self, image):
self.loaded_image = image
# Load image
img = wx.Image(image, wx.BITMAP_TYPE_ANY)
# Label image name
image_name = os.path.basename(image)
self.image_label.SetLabel(image_name)
# scale the image, preserving the aspect ratio
scale_image = True
if scale_image:
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.photoMaxSize
NewH = self.photoMaxSize * H / W
else:
NewH = self.photoMaxSize
NewW = self.photoMaxSize * W / H
img = img.Scale(NewW, NewH)
self.image_control.SetBitmap(wx.BitmapFromImage(img))
# Render
self.main_sizer.Layout()
self.main_sizer.Fit(self)
self.Refresh()
pub.sendMessage("resize", msg = "")
class Viewer_Frame(wx.Frame):
def __init__(self, parent, id, title):
super(Viewer_Frame, self).__init__(parent = parent, id = id, title = title)
# Edit panel
self.edit_panel = Edit_Panel(self)
# Default panel
self.main_panel = self.edit_panel
# Render frame
self.render_frame()
# Subscription to re-render
pub.subscribe(self.resize_frame, ("resize"))
def render_frame(self):
# Main Sizer
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
# Add default sizer
self.main_sizer.Add(self.main_panel, 1, wx.EXPAND)
# Render
self.SetSizer(self.main_sizer)
self.Show()
self.main_sizer.Fit(self)
self.Center()
def resize_frame(self, msg):
self.main_sizer.Fit(self)
if __name__ == "__main__":
app = wx.App(False)
frame = Viewer_Frame(parent = None, id = -1, title = 'Toolkit')
app.MainLoop()
You're calling Fit(), so you're explicitly asking the panel to fit its contents, but you don't specify the min/best size of this contents anywhere (AFAICS, there is a lot of code here, so I could be missing something).
If you want to use some minimal size for the panel, just set it using SetMinSize().

wxPython : Soundboard panel [NOW PLAYING] just like in winamp

I am almost done my wxPython soundboard and want to implement one quick feature.
How does one add another panel to a wxPython window, and how do you implement the text, [ NOW PLAYING ] within that panel.
Here is my code so far:
import wx
import os
import pygame
pygame.init()
##SOUNDS##
##SOUNDS##
class windowClass(wx.Frame):
__goliathwav = pygame.mixer.Sound("goliath.wav")
__channelopen = pygame.mixer.Sound("channelopen.wav")
def __init__(self, *args, **kwargs):
super(windowClass,self).__init__(*args,**kwargs)
self.__basicGUI()
def __basicGUI(self):
panel = wx.Panel(self)
menuBar = wx.MenuBar()
fileButton = wx.Menu()
aboutButton = wx.Menu()
exitItem = fileButton.Append(wx.ID_EXIT, 'Exit','status msg...')
aboutItem = aboutButton.Append(wx.ID_ABOUT, "About")
menuBar.Append(fileButton, 'File')
menuBar.Append(aboutButton, 'About this program')
self.SetMenuBar(menuBar)
self.Bind(wx.EVT_MENU, self.__quit, exitItem)
self.Bind(wx.EVT_MENU, self.__onmenuhelpabout, aboutItem)
self.__sound_dict = { "Goliath" : self.__goliathwav,
"Goliath2" : self.__channelopen
}
self.__sound_list = sorted(self.__sound_dict.keys())
self.__list = wx.ListBox(panel,pos=(20,20), size=(250,150))
for i in self.__sound_list:
self.__list.Append(i)
self.__list.Bind(wx.EVT_LISTBOX,self.__on_click)
textarea = wx.TextCtrl(self, -1,
style=wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|
wx.TE_RICH2, size=(250,150))
self.usertext = textarea
#self.__list2 = wx.ListBox(panel,pos=(19.5,180), size=(251,21)) #second panel
#for j in self.__sound_list:
# self.__list2.Append(i)
#self.__list2.Bind(wx.EVT_LISTBOX,self.__on_click)
#wx.TextCtrl(panel,pos=(10,10), size=(250,150))
self.SetTitle("Soundboard")
self.Show(True)
def __onmenuhelpabout(self,event):
dialog = wx.Dialog(self, -1, "[Soundboard]") # ,
#style=wx.DIALOG_MODAL | wx.STAY_ON_TOP)
dialog.SetBackgroundColour(wx.WHITE)
panel = wx.Panel(dialog, -1)
panel.SetBackgroundColour(wx.WHITE)
panelSizer = wx.BoxSizer(wx.VERTICAL)
boldFont = wx.Font(panel.GetFont().GetPointSize(),
panel.GetFont().GetFamily(),
wx.NORMAL,wx.BOLD)
lab1 = wx.StaticText(panel, -1, " SOUNDBOARD ")
lab1.SetFont(wx.Font(36,boldFont.GetFamily(), wx.ITALIC, wx.BOLD))
lab1.SetSize(lab1.GetBestSize())
imageSizer = wx.BoxSizer(wx.HORIZONTAL)
imageSizer.Add(lab1, 0, wx.ALL | wx.ALIGN_CENTRE_VERTICAL, 5)
lab2 = wx.StaticText(panel, -1, "Created by youonlylegoonce(cyrex)(Kommander000) for the glory " + \
"of the republic.")
panelSizer.Add(imageSizer, 0, wx.ALIGN_CENTRE)
panelSizer.Add((10, 10)) # Spacer.
panelSizer.Add(lab2, 0, wx.ALIGN_CENTRE)
panel.SetAutoLayout(True)
panel.SetSizer(panelSizer)
panelSizer.Fit(panel)
topSizer = wx.BoxSizer(wx.HORIZONTAL)
topSizer.Add(panel, 0, wx.ALL, 10)
dialog.SetAutoLayout(True)
dialog.SetSizer(topSizer)
topSizer.Fit(dialog)
dialog.Centre()
btn = dialog.ShowModal()
dialog.Destroy()
def __on_click(self,event):
event.Skip()
name = self.__sound_list[self.__list.GetSelection()]
sound = self.__sound_dict[name]
print("[ NOW PLAYING ] ... %s" % name)
pygame.mixer.Sound.play(sound)
def __quit(self, e):
self.Close()
def main():
app = wx.App()
windowClass(None, -1, style=wx.MAXIMIZE_BOX | wx.CAPTION | wx.CENTRE)
app.MainLoop()
main()
Before:
print("[ NOW PLAYING ] ... %s" % name)
input:
self.usertext.SetValue("[ NOW PLAYING ] ... %s" % name)
P.S. Your indentation is a mess

Modifying minimal width of left tab style of wx Python's agw LabelBook?

I'm trying to use wx Python's AGW LabelBook (using wxPython 2.8.11.0, Python 2.7.1+, Ubuntu 11.04), such that the tabs (list) are left-aligned; here I have some short texts, and I expected the tablist area would have its width shortened accordingly; but instead I get this:
At that mouse position, I get a sizer pointer - and I can drag it to the right to increase the width of the tablist area as much as I want; but I cannot drag it any further to the left, to make the width shorter. I also tried to use INB_FIT_LABELTEXT, but it doesn't seem to change anything...
Is it possible to somehow instruct LabelBook to set the minimal width of the left tablist area to the approx width of text (say, indicated at the drawn red line)?
This is the code I used to generate the screenshot:
import wx
import wx.lib.agw
import wx.lib.agw.labelbook as LB
from wx.lib.agw.fmresources import *
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Labelbook test")
self.tlbook = LB.LabelBook(self, -1, size=(400, 200), style = wx.NB_LEFT, agwStyle = INB_LEFT | INB_FIT_LABELTEXT | INB_FIT_BUTTON | INB_SHOW_ONLY_TEXT | INB_USE_PIN_BUTTON)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
self.tlbook_panel_1 = wx.Panel(self.tlbook)
self.tlbook_panel_2 = wx.Panel(self.tlbook)
self.tlbook.AddPage(self.tlbook_panel_1, "Test 1")
self.tlbook.AddPage(self.tlbook_panel_2, "Test 2")
sizer_1.Add(self.tlbook, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.SetSize((450, 250))
self.Layout()
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
Ok, I think I got it:
... and the thing is, that the width of the tab area is hardcoded in the source for LabelBook as 100 pixels, but not all in the same class - so some monkeypatching is required, if one wants to leave the source in tact. Here is the code:
import wx
import wx.lib.agw
import wx.lib.agw.labelbook as LB
#~ from wx.lib.agw.labelbook import * # for INB_BOLD_TAB_SELECTION = 16384? nope
INB_BOLD_TAB_SELECTION = 16384
from wx.lib.agw.fmresources import *
WIDTHLIMITPIX=20
class OLabelContainer(LB.LabelContainer): # overloaded version
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, agwStyle=0, name="LabelContainer"):
super(OLabelContainer, self).__init__(parent, id, pos, size, style, agwStyle, name)
def Resize(self, event): # copy from wx/lib/agw/labelbook.py
# Resize our size
self._tabAreaSize = self.GetSize()
newWidth = self._tabAreaSize.x
x = event.GetX()
if self.HasAGWFlag(INB_BOTTOM) or self.HasAGWFlag(INB_RIGHT):
newWidth -= event.GetX()
else:
newWidth = x
# hack: was 100 here
if newWidth < WIDTHLIMITPIX: #100: # Dont allow width to be lower than that
newWidth = WIDTHLIMITPIX #100
self.SetSizeHints(newWidth, self._tabAreaSize.y)
# Update the tab new area width
self._nTabAreaWidth = newWidth
self.GetParent().Freeze()
self.GetParent().GetSizer().Layout()
self.GetParent().Thaw()
LB.LabelContainer = OLabelContainer # do monkeypatch old class
class MyLabelBook(LB.LabelBook):
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, agwStyle=0, name="LabelBook"):
super(MyLabelBook, self).__init__(parent, id, pos, size, style, agwStyle, name)
self._fontSizeMultiple = 1.0
self._fontBold = False
print(self._pages) # is OLabelContainer, OK
def GetFontBold(self): # copy from wx/lib/agw/labelbook.py
return self._fontBold
def ResizeTabArea(self): # copy from wx/lib/agw/labelbook.py
agwStyle = self.GetAGWWindowStyleFlag()
if agwStyle & INB_FIT_LABELTEXT == 0:
return
if agwStyle & INB_LEFT or agwStyle & INB_RIGHT:
dc = wx.MemoryDC()
dc.SelectObject(wx.EmptyBitmap(1, 1))
font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
font.SetPointSize(font.GetPointSize()*self._fontSizeMultiple)
if self.GetFontBold() or agwStyle & INB_BOLD_TAB_SELECTION:
font.SetWeight(wx.FONTWEIGHT_BOLD)
dc.SetFont(font)
maxW = 0
for page in xrange(self.GetPageCount()):
caption = self._pages.GetPageText(page)
w, h = dc.GetTextExtent(caption)
maxW = max(maxW, w)
maxW += 24 #TODO this is 6*4 6 is nPadding from drawlabel
if not agwStyle & INB_SHOW_ONLY_TEXT:
maxW += self._pages._nImgSize * 2
maxW = max(maxW, WIDTHLIMITPIX) # hack: was 100 here
self._pages.SetSizeHints(maxW, -1)
self._pages._nTabAreaWidth = maxW
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Labelbook test")
self.tlbook = MyLabelBook(self, -1, size=(400, 200), style = wx.NB_LEFT, agwStyle = INB_LEFT | INB_FIT_LABELTEXT | INB_FIT_BUTTON | INB_SHOW_ONLY_TEXT | INB_USE_PIN_BUTTON)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
self.tlbook_panel_1 = wx.Panel(self.tlbook)
self.tlbook_panel_2 = wx.Panel(self.tlbook)
self.tlbook.AddPage(self.tlbook_panel_1, "Test 1")
self.tlbook.AddPage(self.tlbook_panel_2, "Test 2")
sizer_1.Add(self.tlbook, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.SetSize((450, 250))
self.Layout()
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()

Displaying VideoCapture using OpenCV with python and wxPython in a wx.panel: Show only small region of the video

I'm trying to display OpenCV with Python interface in wxPython.
I can display it but only a small region in the top left corner of the video.
What is the problem with my code?
is it the size of parent panel or something else?
note that I use code from this https://stackoverflow.com/a/14818080/2389238
and It can run perfectly but I need to display it in a panel (or other parent frame).
Thank in Advance.
import wx
import cv, cv2
class MainWindow(wx.Panel):
def __init__(self, parent,capture):
wx.Panel.__init__(self, parent)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.inputBox = wx.TextCtrl(self)
mainSizer.Add(self.inputBox, 0, wx.ALL, 5)
# video
videoWarper = wx.StaticBox(self, label="Video",size=(640,480))
videoBoxSizer = wx.StaticBoxSizer(videoWarper, wx.VERTICAL)
videoFrame = wx.Panel(self, -1,size=(640,480))
cap = ShowCapture(videoFrame, capture)
videoBoxSizer.Add(videoFrame,0)
mainSizer.Add(videoBoxSizer,0)
parent.Centre()
self.Show()
self.SetSizerAndFit(mainSizer)
class ShowCapture(wx.Panel):
def __init__(self, parent, capture, fps=24):
wx.Panel.__init__(self, parent)
self.capture = capture
ret, frame = self.capture.read()
height, width = frame.shape[:2]
parent.SetSize((width, height))
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.bmp = wx.BitmapFromBuffer(width, height, frame)
self.timer = wx.Timer(self)
self.timer.Start(1000./fps)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_TIMER, self.NextFrame)
def OnPaint(self, evt):
dc = wx.BufferedPaintDC(self)
dc.DrawBitmap(self.bmp, 0, 0)
def NextFrame(self, event):
ret, frame = self.capture.read()
if ret:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.bmp.CopyFromBuffer(frame)
self.Refresh()
capture = cv2.VideoCapture(0)
app = wx.App(False)
frame = wx.Frame(None,-1,'HGA Count',size=(400, 400))
panel = MainWindow(frame,capture)
frame.Show()
app.MainLoop()
This is the image: http://i58.tinypic.com/2u5t7kh.png
Sorry for the link I can't put image here due to low reputation.
Thank to the answer posted by VZ
Now I understand my problem.
The small region is caused by the default size of wx.Panel that ShowCapture was inherited.
It made the dc in dc = wx.BufferedPaintDC(self) had only 20px*20px in dimension,
so the self.bmp, which is 640px * 480px, was drawn on only the small region and everything lied beyond 20px*20px is ignored.
the solution is to change the panel __init__ argument to
class ShowCapture(wx.Panel):
def __init__(self, parent, capture, fps=24):
wx.Panel.__init__(self, parent, wx.ID_ANY, (0,0), (640,480))
Note that:
I don't know whether it could be configured for auto re-size panel
videoFrame is still needed because ShowCapture need a parent frame (for layout in sizer). Don't know whether there is a better approach.
Basically, nothing sets your ShowCapture window size to the desired size, so it remains stuck at some default size. The simplest solution would probably be to do without videoFrame at all as it seems to be not needed and just create cap itself with the desired initial size, which will also become the minimal window size.
Follow up after the question update:
You can resize it automatically using the sizers and ensuring that:
The item itself grows, i.e. has positive proportion if you want it to grow in the major direction of the sizer (vertical for wxVERTICAL box sizer, horizontal for... you guessed it) or wxEXPAND flag if you want it to grow in the other direction. Or both, of course, if you want it to grow in both direction.
Very important as often forgotten: the containing sizer itself grows as well. I.e. nothing will happen if the window itself grows inside its sizer if its sizer doesn't grow inside its own containing sizer.
videoFrame is not necessary and, in fact, you ought to make your video window child of videoWarper if you're using 3.0 for the best results: since 3.0, the contents of a wxStaticBoxSizer should be created with its wxStaticBox as parent.
Here's what I came up with, it uses GenStaticBitmap, which is a subclass of wx.Panel and does the dc.DrawBitmap in its OnPaint method. I added in ROI selection with SpinCtrl and also by hovering on the right-side ROI GenStaticBitmap control. I also added in a button that when right-clicked saves both images, but when left-clicked freezes on the current images and allows the user to save either image or both. Drawing the hover and ROI selection cross-hairs was fun, hope it helps someone else!
import wx
from wx.lib import statbmp
import cv, cv2
import numpy as np
import os
import traceback
class ChooseSaveDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent)
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
btn1 = wx.Button(panel, -1, 'Save Original Image')
btn2 = wx.Button(panel, -1, 'Save ROI Image')
btn3 = wx.Button(panel, -1, 'Save Both')
sizer.Add(btn1, 0)
sizer.Add(btn2, 0)
sizer.Add(btn3, 0)
panel.SetSizer(sizer)
btn1.Bind(wx.EVT_BUTTON, self.save_orig)
btn2.Bind(wx.EVT_BUTTON, self.save_roi)
btn3.Bind(wx.EVT_BUTTON, self.save_both)
def save_orig(self, evt):
save_dialog = wx.FileDialog(self, "Save Original to JPEG", "", "", "JPG files(*.jpg)", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)#|wx.FD_CHANGE_DIR)
if save_dialog.ShowModal() == wx.ID_OK:
save_path = save_dialog.GetPath()
if save_path[:-4].lower() != '.jpg':
save_path += '.jpg'
print save_path
cv2.imwrite(save_path, self.GetParent().orig_frame)
self.EndModal(wx.OK)
def save_roi(self, evt):
save_dialog = wx.FileDialog(self, "Save ROI to JPEG", "", "", "JPG files(*.jpg)", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)#|wx.FD_CHANGE_DIR)
if save_dialog.ShowModal() == wx.ID_OK:
save_path = save_dialog.GetPath()
if save_path[:-4].lower() != '.jpg':
save_path += '.jpg'
print save_path
cv2.imwrite(save_path, self.GetParent().frameRoi)
try:
self.EndModal(wx.OK)
except wx.PyAssertionError:
pass
def save_both(self, evt):
self.save_orig(evt)
self.save_roi(evt)
class ShowCapture(wx.Frame):
def __init__(self, capture, fps=15):
wx.Frame.__init__(self, None)
panel = wx.Panel(self, -1)
#create a grid sizer with 5 pix between each cell
sizer = wx.GridBagSizer(5, 5)
self.capture = capture
ret, frame = self.capture.read()
height, width = frame.shape[:2]
self.orig_height = height
self.orig_width = width
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
self.bmp = wx.BitmapFromBuffer(width, height, frame)
self.dummy_element = wx.TextCtrl(panel, -1,'')
self.dummy_element.Hide()
#create SpinCtrl widgets, these have vertical up/down buttons and a TextCtrl that increments with up/down press
self.roi_x = wx.SpinCtrl(panel, -1, "ROI X", style=wx.TE_PROCESS_ENTER|wx.SP_ARROW_KEYS, min=0, max=width, initial=0, size=(60,-1))
self.roi_y = wx.SpinCtrl(panel, -1, "ROI Y", style=wx.TE_PROCESS_ENTER|wx.SP_ARROW_KEYS, min=0, max=height, initial=0, size=(60,-1))
self.roi_width = wx.SpinCtrl(panel, -1, "ROI W", style=wx.TE_PROCESS_ENTER|wx.SP_ARROW_KEYS, min=0, max=width, initial=width, size=(60,-1))
self.roi_height = wx.SpinCtrl(panel, -1, "ROI H", style=wx.TE_PROCESS_ENTER|wx.SP_ARROW_KEYS, min=0, max=height, initial=height, size=(60,-1))
save_bmp_path = os.path.join(os.path.dirname(__file__), 'icons', 'ic_action_save.png')
if os.path.isfile(save_bmp_path):
save_bmp = wx.Image(save_bmp_path).ConvertToBitmap()
save_button = wx.BitmapButton(panel,wx.ID_ANY, bitmap=save_bmp, style = wx.NO_BORDER, size=(32,32)) # )
else:
save_button = wx.Button(panel, -1, 'Save')
settings_bmp_path = os.path.join(os.path.dirname(__file__), 'icons', 'ic_action_settings.png')
if os.path.isfile(settings_bmp_path):
settings_bmp = wx.Image(settings_bmp_path).ConvertToBitmap()
settings_button = wx.BitmapButton(panel,wx.ID_ANY, bitmap=settings_bmp, style = wx.NO_BORDER, size=(32,32)) # )
else:
settings_button = wx.Button(panel, -1, 'Settings')
#create image display widgets
self.ImgControl = statbmp.GenStaticBitmap(panel, wx.ID_ANY, self.bmp)
self.ImgControl2 = statbmp.GenStaticBitmap(panel, wx.ID_ANY, self.bmp)
#add text to the sizer grid
sizer.Add(wx.StaticText(panel, -1, 'ROI'), (0, 0), (1,2), wx.ALL, 5)
sizer.Add(wx.StaticText(panel, -1, 'X'), (1, 0), wx.DefaultSpan, wx.ALL, 5)
sizer.Add(wx.StaticText(panel, -1, 'Y'), (2, 0), wx.DefaultSpan, wx.ALL, 5)
sizer.Add(wx.StaticText(panel, -1, 'width,'), (1, 2), wx.DefaultSpan, wx.ALL, 5)
sizer.Add(wx.StaticText(panel, -1, 'height'), (2, 2), wx.DefaultSpan, wx.ALL, 5)
sizer.Add(wx.StaticText(panel, -1, 'Right-click image to reset ROI'), (2, 4), wx.DefaultSpan, wx.ALL, 5)
tool_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
tool_button_sizer.Add(save_button, 0)
tool_button_sizer.Add(settings_button, 0)
#sizer.Add(, (0, 6), wx.DefaultSpan, wx.ALIGN_RIGHT)#, wx.ALL, 5)
sizer.Add(tool_button_sizer, (0, 7), wx.DefaultSpan, wx.ALIGN_RIGHT)#, wx.ALL, 5)
#add SpinCtrl widgets to the sizer grid
sizer.Add(self.roi_x, (1, 1), wx.DefaultSpan, wx.ALL, 5)
sizer.Add(self.roi_y, (2, 1), wx.DefaultSpan, wx.ALL, 5)
sizer.Add(self.roi_width, (1, 3), wx.DefaultSpan, wx.ALL, 5)
sizer.Add(self.roi_height, (2, 3), wx.DefaultSpan, wx.ALL, 5)
#add image widgets to the sizer grid
sizer.Add(self.ImgControl, (3, 0), (1,4), wx.EXPAND|wx.CENTER|wx.LEFT|wx.BOTTOM, 5)
sizer.Add(self.ImgControl2, (3, 4), (1,4), wx.EXPAND|wx.CENTER|wx.RIGHT|wx.BOTTOM, 5)
#set the sizer and tell the Frame about the best size
panel.SetSizer(sizer)
sizer.SetSizeHints(self)
panel.Layout()
panel.SetFocus()
#start a timer that's handler grabs a new frame and updates the image widgets
self.timer = wx.Timer(self)
self.fps = fps
self.timer.Start(1000./self.fps)
#bind timer events to the handler
self.Bind(wx.EVT_TIMER, self.NextFrame)
#bind events to ROI image widget
self.ImgControl2.Bind(wx.EVT_LEFT_DOWN, self.On_ROI_Click)
self.ImgControl2.Bind(wx.EVT_LEFT_UP, self.On_ROI_ClickRelease)
self.ImgControl2.Bind(wx.EVT_RIGHT_DOWN, self.On_ROI_RightClick)
self.ImgControl2.Bind(wx.EVT_MOTION, self.On_ROI_Hover)
self.ImgControl2.Bind(wx.EVT_ENTER_WINDOW, self.On_ROI_mouse_enter)
self.ImgControl2.Bind(wx.EVT_LEAVE_WINDOW, self.On_ROI_mouse_leave)
#bind save button
save_button.Bind(wx.EVT_BUTTON, self.on_save_click)
save_button.Bind(wx.EVT_RIGHT_DOWN, self.on_quick_save)
#bind settings button
settings_button.Bind(wx.EVT_BUTTON, self.on_settings_click)
settings_button.Bind(wx.EVT_LEFT_UP, self.on_settings_click_release)
def on_settings_click(self, evt):
pass
def on_settings_click_release(self, evt):
self.dummy_element.SetFocus()
def on_save_click(self, evt):
modal = ChooseSaveDialog(self)
self.timer.Stop()
modal.ShowModal()
modal.Destroy()
self.timer.Start(1000./self.fps)
def on_quick_save(self, evt):
cv2.imwrite('orig_frame.jpg', self.orig_frame)
cv2.imwrite('frameRoi.jpg', self.frameRoi)
def On_ROI_RightClick(self, evt):
self.roi_x.SetValue(0)
self.roi_y.SetValue(0)
self.roi_width.SetValue(self.orig_width)
self.roi_height.SetValue(self.orig_height)
def On_ROI_Hover(self, evt):
self.ROI_crosshair_pos = evt.GetPosition()
def On_ROI_mouse_enter(self, evt):
self.enable_crosshairs = True
def On_ROI_mouse_leave(self, evt):
try:
self.enable_crosshairs = False
if hasattr(self, 'roi_click_down_pos'):
self.update_spinners(evt.GetPosition())
del self.roi_click_down_pos
except AttributeError:
pass
def On_ROI_Click(self, evt):
self.roi_click_down_pos = evt.GetPosition()
def On_ROI_ClickRelease(self, evt):
roi_click_up_pos = evt.GetPosition()
#if roi_click_up_pos[0] >= 0 and roi_click_up_pos[1] >= 0:
if hasattr(self, 'roi_click_down_pos'):
self.update_spinners(roi_click_up_pos)
try:
del self.roi_click_down_pos
except AttributeError:
pass
def update_spinners(self, new_pos):
self.roi_width.SetValue(abs(new_pos[0] - self.roi_click_down_pos[0]))
self.roi_height.SetValue(abs(new_pos[1] - self.roi_click_down_pos[1]))
self.roi_x.SetValue(min(self.roi_click_down_pos[0], new_pos[0]))
self.roi_y.SetValue(min(self.roi_click_down_pos[1], new_pos[1]))
def NextFrame(self, event):
ret, self.orig_frame = self.capture.read()
if ret:
frame = cv2.cvtColor(self.orig_frame, cv2.COLOR_BGR2RGB)
self.bmp.CopyFromBuffer(frame)
self.ImgControl.SetBitmap(self.bmp)
try:
orig_height, orig_width = frame.shape[:2]
y1 = self.roi_y.GetValue()
y2 = y1 + self.roi_height.GetValue()
y2 = min(y2, orig_height)
x1 = self.roi_x.GetValue()
x2 = x1 + self.roi_width.GetValue()
x2 = min(x2, orig_width)
frameRoi = self.orig_frame[y1:y2, x1:x2]
roi_width = x2-x1
roi_height = y2-y1
#frameRoi = cv2.cvtColor(frameRoi, cv2.COLOR_BGR2GRAY )
#print len(frameRoi)
#frameRoi -= 110#frameRoi #* 1.25
#print len(frameRoi)
#frameRoi = frameRoi.clip(255.)
#frameRoi = (255./1)*((frameRoi/(255./1))**0.5)# 0.5
if hasattr(self, 'ROI_crosshair_pos') and self.enable_crosshairs:
try:
cross_x = self.ROI_crosshair_pos[0]
cross_y = self.ROI_crosshair_pos[1]
frameRoi[0:roi_height, cross_x:cross_x+1] = [42,0,255]
frameRoi[cross_y:cross_y+1, 0:roi_width] = [42,0,255]
if hasattr(self, 'roi_click_down_pos'):
roi_x1 = self.roi_click_down_pos[0]
roi_y1 = self.roi_click_down_pos[1]
if cross_y>roi_y1:
frameRoi[0:roi_y1, 0:roi_width] = frameRoi[0:roi_y1, 0:roi_width]*.50
frameRoi[cross_y:roi_height, 0:roi_width] = frameRoi[cross_y:roi_height, 0:roi_width]*.50
else:
frameRoi[roi_y1:roi_height, 0:roi_width] = frameRoi[roi_y1:roi_height, 0:roi_width]*.50
frameRoi[0:cross_y, 0:roi_width] = frameRoi[0:cross_y, 0:roi_width]*.50
if cross_x>roi_x1:
frameRoi[0:roi_height, 0:roi_x1] = frameRoi[0:roi_height, 0:roi_x1]*.50
frameRoi[0:roi_height, cross_x:roi_width] = frameRoi[0:roi_height, cross_x:roi_width]*.50
else:
frameRoi[0:roi_height, roi_x1:roi_width] = frameRoi[0:roi_height, roi_x1:roi_width]*.50
frameRoi[0:roi_height, 0:cross_x] = frameRoi[0:roi_height, 0:cross_x]*.50
except:
print 'couldn\'t draw crosshairs'
traceback.print_exc()
self.frameRoi = np.array(frameRoi , dtype = np.uint8)
#frameRoi = np.repeat(frameRoi, 3)
#frameRoi = array(newImage0,dtype=uint8)
frameRoi = cv2.cvtColor(self.frameRoi, cv2.COLOR_BGR2RGB)#GRAY2RGB)
self.bmp2 = wx.BitmapFromBuffer(roi_width, roi_height, frameRoi)
self.ImgControl2.SetBitmap(self.bmp2)
except:
traceback.print_exc()
capture = cv2.VideoCapture(0)
#capture.set(cv.CV_CAP_PROP_FRAME_WIDTH, 320)
#capture.set(cv.CV_CAP_PROP_FRAME_HEIGHT, 240)
app = wx.App()
frame = ShowCapture( capture)
frame.Show()
app.MainLoop()

Categories

Resources