Displaying images like a presentation without showing the desktop during update - python

I'm trying to display images that are stored under ./pics directory in a row like a presentation on my secondary monitor using the following Python code under Xubuntu 14.04.
from PyQt4 import QtCore, QtGui
import sys
import os
import multiprocessing as mp
import time
def displayImage( impath ):
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
# Select monitor
screenres = QtGui.QApplication.desktop().screenGeometry(1);
window.move(QtCore.QPoint(screenres.x(), screenres.y()))
window.resize(screenres.width(), screenres.height())
# Set the image to be shown
pic = QtGui.QLabel(window)
pic.setGeometry(0, 0, 1960, 1080)
pic.setPixmap(QtGui.QPixmap( impath ))
window.showFullScreen()
app.exec_()
return
def controller( imdir ):
# List the contents of the directory
im_paths = os.listdir( imdir )
# Keep only image files
pc = list(im_paths)
for p in pc:
print p
if not p.split('.')[-1] in [ 'jpg', 'JPG', 'png', 'PNG' ]:
im_paths.remove( p )
if not im_paths:
return 1
# Start showing the images by calling project function as a new process (multiprocessing module)
for f in im_paths:
print 'Showing image:', p, '...'
# Run project process
p = mp.Process( target=displayImage, args=( imdir+f, ) )
p.start()
# Keep dispaying image for 3 seconds
time.sleep(2)
p.terminate()
return 0
if __name__ == '__main__':
controller( './pics/' )
exit(0)
The issue
There is a time interval when the process that displays the image A is terminated and until the process
that displays the image B to come up where the application displays nothing and for a moment the desktop is presented destroying the user experience.
Any ideas how to display images using Qt4 continuously?
PS. Answers that involve matplotlib and/or opencv may be accepted but I think that the straight forward path is through Qt.

Finally, I figured out the solution. The alexblae's idea that the qt application should not be restarted on each image update was the key point. But first... I had to understand in some detail the architecture of the Qt framework, how the one class inherits from another and how events are generated and handled.
Let me quote the updated code in order other users to review and get advantage when facing similar problems.
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import sys
import os
import time
class Pic( QtGui.QLabel ):
def __init__( self, w, im_paths ):
QtGui.QLabel.__init__(self,w)
self.ims = im_paths
# Set the first image that will be displayed
self.setPixmap(QtGui.QPixmap( self.ims[0] ) )
self.i = 1
# Catch timer events. On each event display the next image
def timerEvent(self):
# Update the displayed image
self.setPixmap(QtGui.QPixmap( self.ims[self.i] ) )
self.i = ( self.i + 1 ) % len( self.ims )
def displayImages( im_paths ):
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
screenres = QtGui.QApplication.desktop().screenGeometry(1);
window.move(QtCore.QPoint(screenres.x(), screenres.y()))
window.resize(screenres.width(), screenres.height())
pic = Pic(window, im_paths)
pic.setGeometry(0, 0, 1960, 1080)
window.showFullScreen()
# Generate a timerEvent every 1 second. On each second the handler of
# the event updates the displayed image
timer = QtCore.QTimer()
timer.timeout.connect(pic.timerEvent)
timer.start(1000) #1 second
sys.exit(app.exec_())
def controller( imdir ):
# List the contents of the directory
im_paths = sorted( os.listdir( imdir ) )
for i, path in enumerate( im_paths ):
im_paths[i] = imdir + '/' + path
# Keep only image files
pc = list(im_paths)
for p in pc:
if not p.split('.')[-1] in [ 'jpg', 'JPG', 'png', 'PNG' ]:
im_paths.remove( p )
if not im_paths:
return 1
# Run project process
p = mp.Process( target=displayImages, args=( im_paths, ) )
p.start()
# Keep displaying images for 1 minute
time.sleep(5)
p.terminate()
return 0
if __name__ == '__main__':
controller( './pics/' )
exit(0)
Pic class inherits from QLabel in order the timerEvent method to be defined. Using timer events we are able to inform the qt app when to update the displayed image.
Also, let me note that the usage of the multiprocessing module seems to be a bit overkill. Someone may call the displayImages function directly.

I have a solution for this using OpenCV with python.
Here is the code along with the explanation:
import cv2 # for read/display images
import glob # for linking to image file (.jpg)
#--- following is the directory containing my images of jpg format ---
jpg_files=glob.glob("C:/Users/Desktop/Stack/*.jpg")
#--- Here I run through every image file, read it and then display it with a lapse time of 1 second ---
for img_no in range(len(jpg_files)):
x = jpg_files[img_no]
img = cv2.imread(x,1)
cv2.imshow('BINGO.jpg',img)
cv2.waitKey(1000)
cv2.destroyAllWindows()
Hope this helps
:D

Related

Qt: getting a thumbnail for a video

Is there a better way to get a thumbnail for a video? OS is primarily Linux, but hopefully there's a cross platform way to do it.
This is what I have right now:
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
app = qtw.QApplication()
thumbnail_file = "video.mp4"
loop = qtc.QEventLoop()
widget = qtmw.QVideoWidget()
widget.setVisible(False)
media_player = qtm.QMediaPlayer()
media_player.setVideoOutput(widget)
media_player.mediaStatusChanged.connect(loop.exit)
media_player.positionChanged.connect(loop.exit)
media_player.setSource(thumbnail_file)
loop.exec()
media_player.mediaStatusChanged.disconnect()
media_player.play()
if media_player.isSeekable():
media_player.setPosition(media_player.duration() // 2)
loop.exec()
media_player.positionChanged.disconnect()
media_player.stop()
image = media_player.videoSink().videoFrame().toImage()
image.save('thumbnail.jpg')
app.exec()
This will be ran in a separate thread so the time is not really an issue, but it's still pretty convoluted.
There are many different approaches to this, since QMediaPlayer can undergo multiple state changes which can be monitored in a variety of ways. So the question of what is "best" probably comes down to what is most reliable/predictable on the target platform(s). I have only tested on Arch Linux using Qt-6.3.1 with a GStreamer-1.20.3 backend, but I think the solution presented below should work correctly with most other setups.
As it stands, the example code in the question doesn't work on Linux. As pointed out in the comments, this can be fixed by using the videoFrameChanged signal of the mediad-player's video-sink. (Unfortunately, at time of writing, these APIs are rather poorly documented). However, the example can be simplified and improved in various ways, so I have provided an alternative solution below.
During testing, I found that the durationChanged and postionChanged signals cannot be relied upon to give the relevant notifications at the appropriate time, so I have avoided using them. It's best to wait for the player to reach the buffered-state before setting the position, and then wait until a frame is received that can be verified as matching the requested position before saving the image. (I would also advise adding a suitable timeout in case the media-player gets stuck in an indeterminate state).
To illustrate the timing issues alluded to above, here is some sample output:
frame changed: 0
duration change: 13504
status change: b'LoadedMedia'
frame changed: 0
status change: b'BufferedMedia'
set position: 6752
frame changed: 6752
save: exit
frame changed: 0
frame changed: 0
duration change: 13504
status changed: b'LoadedMedia'
status changed: b'BufferedMedia'
set position: 6752
frame changed: 6752
save: exit
As you can see, the exact sequence of events isn't totally predictable. If the position is set before the buffered-state is reached, the output looks like this:
frame changed: 0
duration changed: 13504
status changed: LoadedMedia
set position: 0
frame changed: 0
status change: BufferedMedia
frame changed: 40
frame changed: 80
... # long list of changes
frame changed: 6720
frame changed: 6760
save: exit
Here is the demo script (which works with both PySide6 and PyQt6):
import os
from PySide6.QtCore import QCoreApplication, QTimer, QEventLoop, QUrl
from PySide6.QtMultimedia import QMediaPlayer, QVideoSink
# from PyQt6.QtCore import QCoreApplication, QTimer, QEventLoop, QUrl
# from PyQt6.QtMultimedia import QMediaPlayer, QVideoSink
def thumbnail(url):
position = 0
image = None
loop = QEventLoop()
QTimer.singleShot(15000, lambda: loop.exit(1))
player = QMediaPlayer()
player.setVideoSink(sink := QVideoSink())
player.setSource(url)
def handle_status(status):
nonlocal position
print('status changed:', status.name)
# if status == QMediaPlayer.MediaStatus.LoadedMedia:
if status == QMediaPlayer.MediaStatus.BufferedMedia:
player.setPosition(position := player.duration() // 2)
print('set position:', player.position())
def handle_frame(frame):
nonlocal image
print('frame changed:', frame.startTime() // 1000)
if (start := frame.startTime() // 1000) and start >= position:
sink.videoFrameChanged.disconnect()
image = frame.toImage()
print('save: exit')
loop.exit()
player.mediaStatusChanged.connect(handle_status)
sink.videoFrameChanged.connect(handle_frame)
player.durationChanged.connect(
lambda value: print('duration changed:', value))
player.play()
if loop.exec() == 1:
print('ERROR: process timed out')
return image
video_file = 'video.mp4'
thumbnail_file = 'thumbnail.jpg'
try:
os.remove(thumbnail_file)
except OSError:
pass
app = QCoreApplication(['Test'])
image = thumbnail(QUrl.fromLocalFile(video_file))
if image is not None:
image.save(thumbnail_file)
Answer reached thanks to the discussion in the comments of OP:
from PySide6 import QtWidgets as qtw
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
app = qtw.QApplication()
thumbnail_file = "video.mp4"
loop = qtc.QEventLoop()
video_sink = qtm.QVideoSink()
media_player = qtm.QMediaPlayer()
media_player.setVideoSink(video_sink)
media_player.mediaStatusChanged.connect(loop.exit)
media_player.setSource(thumbnail_file)
loop.exec()
media_player.mediaStatusChanged.disconnect()
if media_player.isSeekable() and media_player.duration():
media_player.positionChanged.connect(loop.exit)
media_player.setPosition(media_player.duration() // 2)
media_player.play()
loop.exec()
media_player.positionChanged.disconnect()
else:
media_player.play()
video_sink.videoFrameChanged.connect(loop.exit)
loop.exec()
video_sink.videoFrameChanged.disconnect()
media_player.stop()
image = media_player.videoSink().videoFrame().toImage()
image.save('thumbnail.jpg')

File dialog always returns alpha order

I'm creating an application which plots data in files. The first file plotted defines the origin to which all other data is transformed relative to. AFAICT, the QFileDialog always returns files in alphabetical order, regardless of selection order.
Is there a way to return the data ordered by selection?
To illustrate, create a folder with files named something A, B, C or 1, 2, 3. Regardless of the manner they're selected or appear in File Name line edit, the list of paths returned is in alphabetical order.
import os
import sys
from PyQt5 import QtCore, QtWidgets
MYDIR = (os.environ['USERPROFILE'] + '/Desktop/numbered').replace("\\", "/")
def on_button_pressed():
paths, _ = QtWidgets.QFileDialog.getOpenFileNames(
directory = MYDIR,
caption='Open',
filter=(
'All (*.*)'
))
for i, path in enumerate(paths):
print(i, path, flush=True)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
button = QtWidgets.QPushButton("Open")
button.pressed.connect(on_button_pressed)
button.show()
sys.exit(app.exec_())
EDIT An implementation of #musicamate's response which may hang:
import os
import sys
from PyQt5 import QtCore, QtWidgets
MYDIR = (os.environ['USERPROFILE'] + '/Desktop/numbered').replace("\\", "/")
class SelectionOrderFileDialog(QtWidgets.QFileDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setOption(QtWidgets.QFileDialog.DontUseNativeDialog)
self.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
list_view = self.findChild(QtWidgets.QListView, 'listView')
self.selection_model = list_view.selectionModel()
self.selection_model.selectionChanged.connect(self.check_selection)
self.current_selection = []
def check_selection(self):
active_selection = []
for index in self.selection_model.selectedRows():
path = index.data(QtWidgets.QFileSystemModel.FilePathRole)
active_selection.append(path)
updated_current_selection = []
for path in self.current_selection:
if path in active_selection:
updated_current_selection.append(path)
active_selection.remove(path)
updated_current_selection.extend(active_selection)
self.current_selection[:] = updated_current_selection
print(self.current_selection, flush=True)
def on_button_pressed():
# Works fine when called as...
# dialog = SelectionOrderFileDialog()
# Causes hangs on Open
dialog = SelectionOrderFileDialog(
directory = MYDIR,
caption='Open',
filter=(
'text (*.txt)'
';;python (*.py)'
';;All (*.*)'
))
dialog.exec_()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
button = QtWidgets.QPushButton("Open")
button.resize(300, 25)
button.pressed.connect(on_button_pressed)
button.show()
sys.exit(app.exec_())
Such level of control cannot be achieved by using static methods, but it can be done by using an instance of QFileDialog.
The only requirement is to avoid the native dialog, so that we can access one of the file views (a non-native QFileDialog has a list view and a tree view), and then connect to the selectionChanged signal of its selection model.
Then, everytime the signal is emitted, we check with the current ordered selection, remove paths that already exist and build a new list by extending with the new elements.
def showDialog(self):
def checkSelection():
selection = []
for index in selectionModel.selectedRows():
path = index.data(QtWidgets.QFileSystemModel.FilePathRole)
selection.append(path)
newOrderedPaths = []
for path in orderedPaths:
if path in selection:
newOrderedPaths.append(path)
selection.remove(path)
newOrderedPaths.extend(selection)
orderedPaths[:] = newOrderedPaths
dialog = QtWidgets.QFileDialog(self)
dialog.setOption(dialog.DontUseNativeDialog)
dialog.setFileMode(dialog.ExistingFiles)
listView = dialog.findChild(QtWidgets.QListView, 'listView')
selectionModel = listView.selectionModel()
selectionModel.selectionChanged.connect(checkSelection)
orderedPaths = []
dialog.exec_()
print(orderedPaths)
Obviously, the same can be done using a subclass.

QML gauges not updating from Python

I'm still getting to grips with QT... I've made a python file and a QML file. the Python file updates the gauge's value from data it gets over UDP.
This only works once though... the first UDP packet comes in and updates the gauge, but when it gets the next packet,despite the value updating, the gauge itself does not.
QML
CircularGauge {
id: circularGauge
x: 30
y: 30
value: itt1value
minimumValue: 0
maximumValue: 1200
tickmarksVisible: false
style: CircularGaugeStyle {
maximumValueAngle: 400
minimumValueAngle: 90
}
}
Python:
def configureApplication():
# Set up the application window
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
view.setTitle("my title")
# Load the QML file
qml_file = os.path.join(os.path.dirname(__file__), "maingui.qml")
view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file)))
# load the slots into the QML file
view.rootContext().setContextProperty("itt1value", 0)
t = threading.Thread(target=receivedata, args=(view,))
t.start()
# Show the window
if view.status() == QQuickView.Error:
sys.exit(-1)
view.show()
# execute and cleanup
app.exec_()
del view
In the threaded method receivedata() I get the data from UDP, process it, then send it to the gauge like so:
view.rootContext().setContextProperty("itt1value", itt)
receivedata() has a while loop in it with the above details, but the gauge only actually updates once. If I put a statement in the QML file to display itt1value, it always has the correct value, so do I need to put in a method to detect the change to this value and re-paint the gauge?
Edit: I was asked for the details of receivedata(), so I have attached it here:
def receivedata(view):
print("Starting UDP server...")
UDP_IP = "192.168.0.14"
UDP_PORT = 49000
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))
olditt = 0
loopruns = 0 # for debugging
while True:
rawstring = sock.recv(1024)
hexarray = []
#lots of irrelevent formatting here, result is int(value)
itt = float(hextoint(value, olditt))
olditt = itt
itt = format(itt, '.3f')
current = str(loopruns) # for debugging
view.setTitle(current) # for debugging
view.rootContext().setContextProperty("itt1value", itt)
loopruns = loopruns + 1
print(itt)
You have the following errors:
You cannot directly modify the GUI from another thread.
A value can be exported again with setContextProperty(), this will not change the previous value unless the QML is reloaded.
If you want "itt" to modify any value in QML it must be of compatible types, in this case the value of CircularGauge is "real" and therefore the type of data supported in python is float.
Considering the above, I have created a QObject since it can notify changes through signals since it is thread-safe, and export the QObject making connections using Connections.
main.py
import os
import random
import sys
import threading
import time
from PySide2.QtCore import QObject, QUrl, Signal
from PySide2.QtGui import QGuiApplication
from PySide2.QtQuick import QQuickView
class Connections(QObject):
titleChanged = Signal(str, arguments=["title"])
valueChanged = Signal(float, arguments=["value"])
def receivedata(connector):
# configurations
loopruns = 0
while True:
# other stuff
time.sleep(0.1)
itt = random.uniform(0.0, 1200.0)
connector.valueChanged.emit(itt)
connector.titleChanged.emit(str(loopruns))
loopruns += 1
def main(args):
app = QGuiApplication(args)
view = QQuickView(title="my title", resizeMode=QQuickView.SizeRootObjectToView)
connector = Connections()
connector.titleChanged.connect(view.setTitle)
view.rootContext().setContextProperty("connector", connector)
# Load the QML file
qml_file = os.path.join(os.path.dirname(__file__), "maingui.qml")
view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file)))
# start thread
threading.Thread(target=receivedata, args=(connector,)).start()
# Show the window
if view.status() == QQuickView.Error:
return -1
view.show()
# execute and cleanup
ret = app.exec_()
del view
return ret
if __name__ == "__main__":
sys.exit(main(sys.argv))
maingui.qml
import QtQml 2.13
import QtQuick.Extras 1.4
import QtQuick.Controls.Styles 1.4
CircularGauge {
id: circularGauge
value: 100
minimumValue: 0
maximumValue: 1200
tickmarksVisible: false
style: CircularGaugeStyle {
maximumValueAngle: 400
minimumValueAngle: 90
}
Connections{
target: connector
onValueChanged: circularGauge.value = value
}
}

Find the last window created in Maya?

I was wondering if there is any way to find the name of the last window created in Maya, knowing that I can't add any information to the window itself before that... I checked in both the cmds and API but couldn't find anything. Maybe in PyQt but I don't know much about it.
I'm looking for any solution. Thanks
you can work with something like a close callback, save the needed information and restore it again
def restoreLayout(self):
"""
Restore the layout of each widget
"""
settings=self.settings
try:
self.restoreGeometry(settings.value("geometry").toByteArray())
self.restoreState(settings.value("windowState").toByteArray())
size=settings.value('fontSize').toFloat()[0]
self.setFontSize(size)
except:
pass
def saveLayout(self):
"""
Save the layout of each widget
Save the main window id to your data base
"""
settings=self.settings
settings.setValue("geometry", self.saveGeometry())
settings.setValue("windowState", self.saveState())
settings.setValue("fontSize", app.font().pointSize())
def closeEvent(self, event):
QtGui.QMainWindow.closeEvent(self, event)
self.saveLayout()
a simple case/idea to save tha main win_id and a child button_id:
from functools import partial
import json
def close_ui(*args):
win_id = args[0]
if cmds.window(win_id, exists=True):
cmds.deleteUI(win_id, window=True)
with open('dataBase/ui/uidata.json', 'w') as outfile:
json.dump(args, outfile)
win = {}
win["main_win"] = cmds.window()
cmds.columnLayout()
cmds.text( label='closing it' )
win["btn"] = cmds.button( label='Close')
cmds.button(win["btn"],e=True, command=partial(close_ui, win["main_win"], win["btn"]))
cmds.showWindow(win["main_win"])
Here is what I came up with, it's surely not the "cleanest" solution but it works!
# List all the currently opened windows
uisBefore = cmds.lsUI (wnd = True)
# Execute the function which may or may not create a window
func(*args, **kwargs)
# List all the opened windows again
uisAfter = cmds.lsUI (wnd = True)
# Find all the windows that were opened after executing func()
newUIs = [ui for ui in uisAfter if ui not in uisBefore]
If you create a window with the window command, you'll get back the name of the window you just created:
import maya.cmds as cmds
w = cmds.window()
c= cmds.columnLayout()
def who_am_i(*_):
print "window is", w
b = cmds.button('push', c=who_am_i)
cmds.showWindow(w)
If for some reason you don't own the code that creates the window:
existing_windows = set(cmds.lsUI(type = 'window'))
// make your window here
new_windows = list(set(cmds.lsUI(type = 'window') - existing_windows))

Pyforms - Form appears always maximized

When I run my script, the main form pops-up maximized (i.e. taking all the space on my screen). I tried to set the height and width of the form using the CSS file but it did not work. I haven't seen anything about it elsewhere.
Here's my code:
import sys
import pyforms
from pyforms import BaseWidget
from pyforms.Controls import ControlText
from pyforms.Controls import ControlButton
from pyforms.Controls import ControlFile
class ImportIntoFile(BaseWidget):
def __init__(self):
super(ImportIntoFile,self).__init__('HTCondor & EnergyPlus')
self._Input = ControlFile('Input')
self._Output = ControlFile('Output')
self._Import = ControlButton('Import')
self._Close = ControlButton('Close')
self._formset = ['',(' ','_Input',' '),(' ','_Output',' '),('','_Close','','_Import',''),'']
self._Import.value = self.__ImportAction
self._Close.value = self.__CloseAction
def __ImportAction(self):
OutputFile = open(self._Output.value,'a')
InputFile = open(self._Input.value,'r')
OutputFile.close
InputFile.close
def __CloseAction(self):
sys.exit()
if __name__ == "__main__": pyforms.startApp( ImportIntoFile )`
You can pass a window geometry to the pyforms.start_app() call. So something like the code below should work.
if __name__ == "__main__":
pyforms.start_app( ImportIntoFile, geometry=(200, 200, 400, 400) )
I had the same problem. Pyforms uses Qt, so some of these modules will be familiar, and this css will modify them.
I used:
QMainWindow{
max-width:500px;
max-height:500px;
}
to successfully set the size of the main window, but you can't increase the size later, so if all you want is a fixed size window, it works.
I can't find a good solution too. For temporary workaround when I want to avoid maximized by default, what I did is modifying C:\Python27\Lib\site-packages\pyforms\gui\standaloneManager.py.
From
if geometry is not None:
w.show()
w.setGeometry(*geometry)
else:
w.showMaximized()
To
if geometry is not None:
w.show()
w.setGeometry(*geometry)
else:
w.showNormal()

Categories