pyqt update qgraphicsscene with mousewheelevent output - python

I have a set of images that I want to be able to scroll through. The below code works for me on Mac, such that I can scroll on the visible widget. Printing "count" results in a visible rising or lowering number in the python output. However, I wish to use "count" to update the image and I am kinda stuck on how.
The main question is: How can I use the constantly varying variable "count" from my mousewheel function, and use it to update the image shown in the DCMViewer? I expect some kind of signal to work, but haven't got it working and I'm stuck. Any help is much appreciated.
class DCMViewer(QGraphicsView):
def __init__(self):
super(DCMViewer, self).__init__()
# Create a QGraphicsScene
self.scene = QGraphicsScene()
# Provide image in normalized double format
image = np.double(dcmIm)
image *= 255.0 / image.max()
image = QPixmap.fromImage(qimage2ndarray.array2qimage(image))
image = QGraphicsPixmapItem(image)
# Add the image to the QGraphicsScene window
self.scene.addItem(image)
# Initiate the scene
self.setScene(self.scene)
# Create a mousewheelevent to scroll through images
def wheelEvent(self, QWheelEvent):
super(DCMViewer, self).wheelEvent(QWheelEvent)
global count
wheelcounter = QWheelEvent.angleDelta()
if wheelcounter.y() / 120 == -1:
count += 1
if count >= 21:
count = 21
elif wheelcounter.y() / 120 == 1:
count -= 1
if count <= 0:
count = 0
class Mainwidget(QMainWindow):
def __init__(self):
super(Mainwidget, self).__init__()
# self.initUI()
image_viewer = DCMViewer()
self.setCentralWidget(image_viewer)
self.showFullScreen()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Mainwidget()
win.showFullScreen()
sys.exit(app.exec_())

To be able to make an item visible, the first task we must do is to identify it, for that we could establish an id for each item. We use the setData() and data() method, then we use the fitInView() method to scale the window to the item, this does not make the item totally visible because if there is another item on it we will get an unwanted output, for that we use setZValue(), the higher the value of Z will be on top of another item, your code will not provide an example that can be tested correctly so create my own example:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class DCMViewer(QGraphicsView):
def __init__(self):
super(DCMViewer, self).__init__()
self.currentId = 0
self.maxId = 0
self.setScene(QGraphicsScene())
directory = QStandardPaths.writableLocation(QStandardPaths.PicturesLocation)
it = QDirIterator(directory, QDir.Files)
while it.hasNext():
pixmap = QPixmap(it.next())
item = QGraphicsPixmapItem(pixmap)
item.setData(0, self.maxId)
self.scene().addItem(item)
self.maxId += 1
def scrollToItem(self, id):
for item in self.scene().items():
if item.data(0) == id:
item.setZValue(1)
self.fitInView(item)
else:
item.setZValue(0)
def wheelEvent(self, event):
wheelcounter = event.angleDelta()
if wheelcounter.y() / 120 == -1:
self.currentId += 1
if self.currentId > self.maxId:
self.currentId = self.maxId -1
elif wheelcounter.y() / 120 == 1:
self.currentId -= 1
if self.currentId <= 0:
self.currentId = 0
self.scrollToItem(self.currentId)
class Mainwidget(QMainWindow):
def __init__(self):
super(Mainwidget, self).__init__()
image_viewer = DCMViewer()
self.setCentralWidget(image_viewer)
self.showFullScreen()
image_viewer.scrollToItem(0)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = Mainwidget()
sys.exit(app.exec_())

Related

Pyside/Pyqt5 dynamically adding and sorting items

I have a simple user interface where I want to dynamically add frames and labels in a widget (as I will use these labels to transmit a video feed from my webcams).
In the following code I set a function where the user selects an integer which represents the number of labels(webcams) they want to see and then dynamically adds these labels& frames to the widget:
def loopCamFeed(self,n):
if (n % 2) == 0:
dividnd = n / 2
for i in range(2):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.horizontalLayout_14 = QHBoxLayout(self.frame_12)
self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
for i in range(int(dividnd)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.horizontalLayout_14.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
Which displays the labels as in the image below:
--By adding a value of 2:
--By adding a value of 4):
By adding a value of 8:
The challenge that I am facing is how to handle an odd number selection. For example, if a user selects 3 or 7 webcams/labels.
If a user selects 3 labels/webcams, I'd want to show one on the top frame and two at the bottom.
MAIN.PY (Where this piece of code was written):
from ui_interface import *
import sys
from Custom_Widgets.Widgets import *
import cv2
import numpy as np
from PyQt5.QtCore import pyqtSignal, QObject, QThread
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
loadJsonStyle(self, self.ui)
self.show()
#Expand Center Menu Widget
self.ui.settingsBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
self.ui.infoBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
self.ui.helpBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
#Close Center Menu Widget
self.ui.closeCenterMenuButton.clicked.connect(lambda: self.ui.centerMenuContainer.collapseMenu())
#Close Notification Menu Widget
self.ui.closeNotificationBtn.clicked.connect(lambda: self.ui.popUpNotificationContainer.collapseMenu())
self.loopCamFeed(4)
def ImageUpdateSlot(self, Image):
self.ui.label_5.setPixmap(QPixmap.fromImage(Image))
def CancelFeed(self):
self.worker1.stop()
def startVideo(self):
self.worker1 = Worker1()
self.worker1.start()
self.worker1.ImageUpdate.connect(self.ImageUpdateSlot)
def loopCamFeed(self,n):
if (n % 2) == 0:
dividnd = n / 2
for i in range(2):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.horizontalLayout_14 = QHBoxLayout(self.frame_12)
self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
for i in range(int(dividnd)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.horizontalLayout_14.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
class Worker1(QThread):
ImageUpdate = pyqtSignal(QImage)
def __init__(self):
super().__init__()
def run(self):
self.ThreadActive = True
Capture = cv2.VideoCapture(0)
while self.ThreadActive:
ret, frame = Capture.read()
if ret:
Image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
FlippedImage = cv2.flip(Image, 1)
ConvertToQtFormat = QImage(FlippedImage.data, FlippedImage.shape[1], FlippedImage.shape[0], QImage.Format_RGB888)
Pic = ConvertToQtFormat.scaled(1200, 900, Qt.KeepAspectRatio)
self.ImageUpdate.emit(Pic)
def stop(self):
self.ThreadActive = False
self.quit()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Use a grid layout instead of a horizontal layout:
def loopCamFeed(self,n):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.grid_layout = QGridLayout(self.frame_12)
self.grid_layout.setObjectName(u"grid_layout")
for i in range(int(n)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.grid_layout.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
As Medhat mentioned, applying the GridLayout was the best solution.
I applied the following code:
def loopCamFeed(self,n):
w = 0
if n > 0:
# if ( n % 2) == 0:
for i in range(int(n)):
if (i%2) == 0:
w +=1
print(int(w / 2), (i%2))
self.label = QtWidgets.QLabel()
self.label.setText("Screen " +str(i))
self.label.setStyleSheet("background-color: black5;")
self.label.setObjectName(u"label")
self.gridLayout.addWidget(self.label, (i%2) ,int(w),Qt.AlignHCenter )
This works perfectly! Thanks #Medhat

PYQT5: Window crashes trying to dynamically create image labels

Apologies if the question is confusing, I'm not the best at wording stuff. I'll try to explain further if required.
Background: I'm creating a simple image viewer that gives the user the ability to cycle through their library of images. I wanted to give the user the ability to browse through their image library (basically a file browser) so I tried creating a little browser that dynamically added QLabels with pixmap for each image in the library.
Problem: First my window crashed without loading any image labels, but since my image library is pretty huge I thought limiting the number would help. The results weren't so different; with the limitation (of only 4 images) the image labels displayed but anytime I try to do anything in the window (resize the window, resize the dock) it crashes. What am I doing wrong here?
Code:
import sys
import os
import time
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc
from PIL import ImageQt, Image
import memory_profiler
from DBManager import DBBrowser
class MainWindow(qtw.QMainWindow):
resize_constant = 10
def __init__(self):
self.root = r'Images'
self.control_modifier = False
self.resized = 0
super().__init__()
self.main_widget = qtw.QWidget()
self.main_widget.setLayout(qtw.QVBoxLayout())
self.main_widget.layout().setSpacing(0)
self.setCentralWidget(self.main_widget)
# self.setWindowFlag(qtc.Qt.FramelessWindowHint)
self.create_main_window()
self.viewer()
self.menubar()
self.main_widget.layout().addWidget(self.menu_widget)
self.main_widget.layout().addWidget(self.main_window)
self.main_widget.layout().setContentsMargins(0,0,0,0)
self.show()
def menubar(self):
self.menu_widget = qtw.QWidget()
self.menu_widget.setLayout(qtw.QHBoxLayout())
self.menu_widget.layout().setContentsMargins(0,0,0,0)
self.menu_widget.layout().setSpacing(0)
self.menu_widget.layout().setAlignment(qtc.Qt.AlignLeft)
import_btn = qtw.QPushButton('Import', clicked=self.import_database)
explorer_btn = qtw.QPushButton('Explorer', clicked=self.show_hide_explorer)
import_btn.setFixedWidth(70)
explorer_btn.setFixedWidth(70)
self.menu_widget.layout().addWidget(import_btn)
self.menu_widget.layout().addWidget(explorer_btn)
import_btn.setFocusPolicy(qtc.Qt.ClickFocus)
explorer_btn.setFocusPolicy(qtc.Qt.ClickFocus)
def create_main_window(self):
self.main_window = qtw.QMainWindow()
self.dock = Dock('Explorer')
self.explorer = Explorer()
self.dock.setWidget(self.explorer)
self.dock.setTitleBarWidget(qtw.QWidget())
self.main_window.setFocusPolicy(qtc.Qt.StrongFocus)
self.main_window.setFocus()
self.main_window.addDockWidget(qtc.Qt.LeftDockWidgetArea, self.dock)
def viewer(self):
self.image_handler()
self.image_label = qtw.QLabel(alignment=qtc.Qt.AlignCenter)
self.curr_img_name = self.images[0]
self.curr_img_org = Image.open(os.path.join(self.root, self.curr_img_name))
self.curr_img_qt = ImageQt.ImageQt(self.curr_img_org)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
self.image_label.setPixmap(self.curr_img_pixmap)
self.image_scrollarea = ImageScrollArea(self)
self.main_window.setCentralWidget(self.image_scrollarea)
self.image_scrollarea.setWidget(self.image_label)
self.image_scrollarea.setWidgetResizable(True)
self.image_scrollarea.setVerticalScrollBarPolicy(qtc.Qt.ScrollBarAlwaysOff)
self.image_scrollarea.setHorizontalScrollBarPolicy(qtc.Qt.ScrollBarAlwaysOff)
self.image_scrollarea.setFocusPolicy(qtc.Qt.NoFocus)
def image_handler(self):
self.images = sorted([(int(image.split(os.extsep)[0]), image.split(os.extsep)[1]) for image in os.listdir(self.root)
if os.path.isfile(os.path.join(self.root, image)) and not image.endswith('.csv')])
self.images = ['.'.join((str(image[0]), image[1])) for image in self.images]
def resize_image(self):
self.curr_img = self.curr_img_org.copy()
self.curr_img_qt = ImageQt.ImageQt(self.curr_img)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
if self.resized < 0:
self.curr_img.thumbnail((
int(self.curr_img_org.width - (abs(self.resized)/100 * self.curr_img_org.width))
, int(self.curr_img_org.height - (abs(self.resized)/100 * self.curr_img_org.height)))
, Image.BOX)
self.curr_img_qt = ImageQt.ImageQt(self.curr_img)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
elif self.resized > 0:
self.curr_img = self.curr_img_org.copy()
self.curr_img = self.curr_img.resize((
int((abs(self.resized)/100 * self.curr_img_org.width) + self.curr_img_org.width)
, int((abs(self.resized)/100 * self.curr_img_org.height) + self.curr_img_org.height))
, Image.BOX)
self.curr_img_qt = ImageQt.ImageQt(self.curr_img)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
def change_image(self, direction):
current_index = self.images.index(self.curr_img_name)
if direction == qtc.Qt.Key_Left:
if current_index == 0:
return
self.curr_img_name = self.images[current_index-1]
self.curr_img_org = Image.open(os.path.join(self.root, self.curr_img_name))
self.curr_img_qt = ImageQt.ImageQt(self.curr_img_org)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
if self.resized != 0:
self.resize_image()
self.image_label.setPixmap(self.curr_img_pixmap)
self.image_scrollarea.scroll_value = 0
self.image_scrollarea.verticalScrollBar().setValue(self.image_scrollarea.scroll_value)
if direction == qtc.Qt.Key_Right:
if current_index == len(self.images)-1:
return
self.curr_img_name = self.images[current_index+1]
self.curr_img_org = Image.open(os.path.join(self.root, self.curr_img_name))
self.curr_img_qt = ImageQt.ImageQt(self.curr_img_org)
self.curr_img_pixmap = qtg.QPixmap.fromImage(self.curr_img_qt)
if self.resized != 0:
self.resize_image()
self.image_label.setPixmap(self.curr_img_pixmap)
self.image_scrollarea.scroll_value = 0
self.image_scrollarea.verticalScrollBar().setValue(self.image_scrollarea.scroll_value)
def import_database(self):
file_location = qtw.QFileDialog.getOpenFileName(self, 'Import database', os.path.abspath('.'), 'database files (*.db)')
self.explorer.set_database_location(file_location[0])
self.explorer.offset = 0
gallery_data_dict_list = self.explorer.get_items()
self.explorer.set_items(gallery_data_dict_list)
def show_hide_explorer(self):
if self.dock.isVisible():
self.dock.hide()
else:
self.dock.show()
def keyPressEvent(self, event):
if event.key() == qtc.Qt.Key_Left:
self.change_image(qtc.Qt.Key_Left)
if event.key() == qtc.Qt.Key_Right:
self.change_image(qtc.Qt.Key_Right)
if event.key() == qtc.Qt.Key_Up:
self.image_scrollarea.scroll_(120)
if event.key() == qtc.Qt.Key_Down:
self.image_scrollarea.scroll_(-120)
if event.key() == qtc.Qt.Key_Escape:
if self.dock.isVisible():
self.dock.hide()
if event.key() == qtc.Qt.Key_Control:
self.control_modifier = True
if self.control_modifier:
if event.key() == qtc.Qt.Key_E:
self.show_hide_explorer()
def keyReleaseEvent(self, event):
if event.key() == qtc.Qt.Key_Control:
self.control_modifier = False
def wheelEvent(self, event):
if self.control_modifier:
if event.angleDelta().y() == 120:
self.resized += 10
if self.resized >= 90: self.resized = 90
self.resize_image()
self.image_label.setPixmap(self.curr_img_pixmap)
elif event.angleDelta().y() == -120:
self.resized -= 10
if self.resized <= -90: self.resized = -90
self.resize_image()
self.image_label.setPixmap(self.curr_img_pixmap)
class ImageScrollArea(qtw.QScrollArea):
def __init__(self, main_window):
super().__init__()
self.main_window = main_window
self.scroll_value = self.verticalScrollBar().minimum()
self.scroll_constant = 100
def wheelEvent(self, event):
if self.main_window.control_modifier:
event.ignore()
else:
self.scroll_(event.angleDelta().y())
def scroll_(self, y):
if y == -120:
if self.scroll_value + self.scroll_constant >= self.verticalScrollBar().maximum():
self.scroll_value = self.verticalScrollBar().maximum()
else:
self.scroll_value += self.scroll_constant
elif y == 120:
if self.scroll_value - self.scroll_constant <= self.verticalScrollBar().minimum():
self.scroll_value = self.verticalScrollBar().minimum()
else:
self.scroll_value -= self.scroll_constant
self.verticalScrollBar().setValue(self.scroll_value)
class Dock(qtw.QDockWidget):
def __init__(self, title):
super().__init__()
self.setWindowTitle(title)
class Explorer(qtw.QWidget):
def __init__(self):
super().__init__()
# self.database_location = None
self.setLayout(qtw.QVBoxLayout())
self.search_box = qtw.QWidget()
self.search_box.setFixedHeight(int(0.15*self.height()))
self.search_box.setLayout(qtw.QFormLayout())
self.layout().addWidget(self.search_box)
search_edit = qtw.QLineEdit()
search_edit.setPlaceholderText('Enter search term(s)...')
search_bar = qtw.QHBoxLayout()
filter_options = ['tags', 'people', 'date', 'custom...']
filter_option_combobox = qtw.QComboBox()
filter_option_combobox.addItems(filter_options)
filter_option_combobox.setCurrentIndex(0)
filter_option_combobox.currentTextChanged.connect(self.custom_filters)
search_btn = qtw.QPushButton('Search')
search_bar.addWidget(filter_option_combobox)
search_bar.addWidget(search_btn)
self.search_box.layout().addRow(search_edit)
self.search_box.layout().addRow(search_bar)
self.browser()
self.layout().addWidget(self.browser_scrollarea)
def custom_filters(self, value):
if value == 'custom...':
pass
def browser(self):
self.browser_scrollarea = qtw.QScrollArea()
self.browser_scrollarea.setVerticalScrollBarPolicy(qtc.Qt.ScrollBarAlwaysOn)
self.browser_scrollarea.setHorizontalScrollBarPolicy(qtc.Qt.ScrollBarAlwaysOn)
def get_items(self):
self.DBB = DBBrowser()
self.DBB.set_database(database_location=self.database_location)
self.DBB.set_filters()
results = self.DBB.sqlite_select(get='*', order_by='date', order_in='ASC', table='Library')
self.offset += 10
gallery_data_dict_list = [{
'location' : result[0],
'date' : result[1],
'tags' : result[2]
} for result in results]
return gallery_data_dict_list
def set_items(self, gallery_data_dict_list):
self.browser_widget = qtw.QWidget()
self.browser_widget.setLayout(qtw.QGridLayout())
key = 0
for gallery_data_dict in gallery_data_dict_list:
if key > 3:
break
description = qtw.QScrollArea()
text = qtw.QLabel('texttexttexttexttexttexttexttexttexttexttexttexttexttexttexttexttex')
description.setWidget(text)
thumbnail = Image.open(os.path.join(gallery_data_dict['location'], os.listdir(gallery_data_dict['location'])[0]))
thumbnail.thumbnail((250,250))
thumbnail = ImageQt.ImageQt(thumbnail)
self.thumbnail_pixmap = qtg.QPixmap.fromImage(self.thumbnail)
self.thumb = qtw.QLabel(pixmap=self.thumbnail_pixmap)
# text.setFixedWidth(int(self.browser_scrollarea.width()*50/100))
self.browser_widget.layout().addWidget(self.thumb, key, 0)
self.browser_widget.layout().addWidget(description, key, 1)
key += 1
self.browser_scrollarea.setWidget(self.browser_widget)
def set_database_location(self, location):
self.database_location = location
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
mw = MainWindow()
sys.exit(app.exec_())
Edit:
Following #musicamante's comment, I tried a couple of things to reprex my question and it doesn't seem like anything but a random occurrence with a 99.999% fail rate. I don't say 100% because the same code that crashed a million times worked fine once. I tried stripping the code to nothing but just a QWidget with two widgets; a button to load the browser, and the browser. Nevertheless, it still crashed. I don't know how much more minimal I can go.
Then I realized, I was using PIL.Image and PIL.ImageQt instead of QPixmap. Changing all my images to use qt's pixmap instead fixed the problem, as far as my experience went. I tried using PIL.Image for the main viewer and QPixmap for the browser but it still crashed.
My question now is, is there a way I can make it work well with PIL? Because, unless I'm missing something, I feel like PIL.Image is a lot more feature rich for image manipulation than QPixmap. Namely resizing the image (PIL has a ton of algorithms but QPixmap only has Qt.FastTransformation and Qt.SmoothTransformation as far as I'm aware) and especially the .copy() function. Or alternatively, is there some other library?

disable arrow keys in Slider

When using scikit CollectionViewer (simple image browser) I'd like pressing arrow keys not to trigger going to prev/next image after slider got focus. I used eventFilter for that
from skimage.viewer import ImageViewer
from skimage.viewer.qt import Qt
from skimage.viewer.widgets import Slider
class SilentViewer(ImageViewer): #CollectionViewer with some modifications
def __init__(self, image_collection, update_on='move', **kwargs):
self.image_collection = image_collection
self.index = 0
self.num_images = len(self.image_collection)
first_image = image_collection[0]
super(SilentViewer, self).__init__(first_image)
slider_kws = dict(value=0, low=0, high=self.num_images - 1)
slider_kws['update_on'] = update_on
slider_kws['callback'] = self.update_index
slider_kws['value_type'] = 'int'
self.slider = Slider('frame', **slider_kws)
self.layout.addWidget(self.slider)
self.installEventFilter(self) #Modification to CollectionViewer №1
def update_index(self, name, index):
index = int(round(index))
if index == self.index:
return
index = max(index, 0)
index = min(index, self.num_images - 1)
self.index = index
self.slider.val = index
self.update_image(self.image_collection[index])
def eventFilter(self,obj,evt): #Modification to CollectionViewer №2
try:
print(evt.type(), evt.key(), evt.text())
if (evt.key() == Qt.Key_Left or
evt.key() == Qt.Key_Right or
evt.key() == Qt.Key_Up or
evt.key() == Qt.Key_Down):
print("Ignored arrow button")
return True
else:
return False
except:
print("Smth went wrong")
return False
#for testing reasons
from skimage import data
from skimage.transform import pyramid_gaussian
img = data.coins()
img_pyr = pyramid_gaussian(img, downscale=2, multichannel=False)
img_collection = tuple(img_pyr)
viewer = SilentViewer(img_collection)
viewer.show()
event filter seems to be working: key presses and other events trigger console output. However, arrow keys trigger image change. If I change to update_on='release' (see init), arrow keys do not trigger the image change, but make slider position change.
Could you please tell how can I make the arrow presses to be full consumed by the filter?
Analyzing the Slider source code, it can be seen that it is a container, that is, a widget that has other widgets (QLabel, QSlider and QLineEdit), so the filter must be installed on the internal QSlider and not on the container.
from skimage.viewer import ImageViewer
from skimage.viewer.qt import QtCore
from skimage.viewer.widgets import Slider
class SilentViewer(ImageViewer): # CollectionViewer with some modifications
def __init__(self, image_collection, update_on="move", **kwargs):
self.image_collection = image_collection
self.index = 0
self.num_images = len(self.image_collection)
print(self.num_images)
first_image = image_collection[0]
super(SilentViewer, self).__init__(first_image)
slider_kws = dict(value=0, low=0, high=self.num_images - 1)
slider_kws["update_on"] = update_on
slider_kws["callback"] = self.update_index
slider_kws["value_type"] = "int"
self.slider = Slider("frame", **slider_kws)
self.layout.addWidget(self.slider)
self.slider.slider.installEventFilter(self)
def update_index(self, name, index):
if index == self.index:
return
index = max(index, 0)
index = min(index, self.num_images - 1)
self.index = index
self.update_image(self.image_collection[index])
def eventFilter(self, obj, evt):
if obj is self.slider.slider and evt.type() == QtCore.QEvent.KeyPress:
if evt.key() in (
QtCore.Qt.Key_Left,
QtCore.Qt.Key_Right,
QtCore.Qt.Key_Up,
QtCore.Qt.Key_Down,
):
return True
return super(SilentViewer, self).eventFilter(obj, evt)
def main():
# for testing reasons
from skimage import data
from skimage.transform import pyramid_gaussian
img = data.coins()
img_pyr = pyramid_gaussian(img, downscale=2, multichannel=False)
img_collection = tuple(img_pyr)
viewer = SilentViewer(img_collection)
viewer.show()
if __name__ == "__main__":
main()

PyQt keep aspect ratio fixed

I'm working on a PyQt5 GUI, so far, I've just had experience with python scripts and did not delve into creating user interfaces.
The GUI will have to be used on different screens (maybe also some old 4:3 ratio screens) and will need to look nice in different sizes.
Now, my approach to make my life easier was to enforce a fixed aspect ratio of the window and resize the different elements according to window size.
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent= None):
super().__init__(parent)
self.form_widget = FormWidget(self)
self.setCentralWidget(self.form_widget)
self.resize(200, 400)
self.sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
self.sizePolicy.setHeightForWidth(True)
self.setSizePolicy(self.sizePolicy)
def heightForWidth(self, width):
return width * 2
class FormWidget(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__(parent)
def resizeEvent(self, event):
f = self.font()
temp = event.size().height()
f.setPixelSize(temp / 16)
self.setFont(f)
return super().resizeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Resizing the elements according to window size works fine, but window aspect ratio is not kept at all.
I copied this approach with heightForWidth from old PyQt4 threads. Doesn't this approach work anymore in PyQt5? Am I missing something?
If I understood your question, you should try using a layout inside the main window.
I did this:
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent= None):
super().__init__(parent)
self.central_widget = QtWidgets.QWidget()
self.central_layout = QtWidgets.QVBoxLayout()
self.setCentralWidget(self.central_widget)
self.central_widget.setLayout(self.central_layout)
# Lets create some widgets inside
self.label = QtWidgets.QLabel()
self.list_view = QtWidgets.QListView()
self.push_button = QtWidgets.QPushButton()
self.label.setText('Hi, this is a label. And the next one is a List View :')
self.push_button.setText('Push Button Here')
# Lets add the widgets
self.central_layout.addWidget(self.label)
self.central_layout.addWidget(self.list_view)
self.central_layout.addWidget(self.push_button)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
If you resize the window, the widgets inside it get resized.
First, answered by Marc and codeling in this question, heightForWidth is only supported for QGraphicsLayout's subclasses.
Second, how to make a fixed aspect ratio window (or top-level widget) in qt (or pyqt) is a question that have been asked for years. However, as far as I know, there is no standard way of doing so, and it is something surprisingly hard to achieve. In short, my way of doing this is use Qt.FramelessWindowHint to create a frameless window without system move and resize function, and implement custom move and resize.
Explain important mechanism:
move:
In mousePressEvent, keep the place where we last clicked on the widget(the draggable area).
In mouseMoveEvent, calculate the distance between the last clicked point and the current mouse location. Move the window according to this distance.
resize:
Find the increase or decrease step size of width and height by dividing the minimum width and height of the window by their highest common factor.
Use the step size to increase or decrease the window size to keep the aspect ratio.
A screenshot to show that it can resize according to the aspect ratio.
The following code should works with both PyQt5 and Pyside2.
from PyQt5.QtCore import Qt, QRect, QPoint, QEvent
from PyQt5.QtWidgets import (QLabel, QMainWindow, QApplication, QSizePolicy,
QVBoxLayout, QWidget, QHBoxLayout, QPushButton)
from enum import Enum
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowFlags(Qt.FramelessWindowHint)
self.createCostumTitleBar()
self.setContentsMargins(0, 0, 0, 0)
self.central = QWidget()
self.central.setStyleSheet("background-color: #f8ecdf")
self.centralLayout = QVBoxLayout()
self.central.setLayout(self.centralLayout)
self.centralLayout.addWidget(
self.costumsystemmenu, alignment=Qt.AlignTop)
self.centralLayout.setContentsMargins(0, 0, 0, 0)
self.setCentralWidget(self.central)
# Set the minimum size to avoid window being resized too small.
self.setMinimumSize(300, 400)
self.minheight = self.minimumHeight()
self.minwidth = self.minimumWidth()
self.resize(300, 400)
# make sure your minium size have the same aspect ratio as the step.
self.stepY = 4
self.stepX = 3
# install the event filter on this window.
self.installEventFilter(self)
self.grabarea.installEventFilter(self)
self.cursorpos = CursorPos.DEFAULT
self.iswindowpress = False
def createCostumTitleBar(self):
self.costumsystemmenu = QWidget()
self.costumsystemmenu.setStyleSheet("background-color: #ccc")
self.costumsystemmenu.setContentsMargins(0, 0, 0, 0)
self.costumsystemmenu.setMinimumHeight(30)
self.grabarea = QLabel("")
self.grabarea.setStyleSheet("background-color: #ccc")
self.grabarea.setSizePolicy(
QSizePolicy.Expanding, QSizePolicy.Preferred)
titlebarlayout = QHBoxLayout()
titlebarlayout.setContentsMargins(11, 11, 11, 11)
titlebarlayout.setSpacing(0)
self.closeButton = QPushButton("X")
self.closeButton.setSizePolicy(
QSizePolicy.Minimum, QSizePolicy.Preferred)
self.closeButton.clicked.connect(self.close)
self.costumsystemmenu.setLayout(titlebarlayout)
titlebarlayout.addWidget(self.grabarea)
titlebarlayout.addWidget(self.closeButton, alignment=Qt.AlignRight)
self.istitlebarpress = False
def eventFilter(self, object, event):
# The eventFilter() function must return true if the event
# should be filtered, (i.e. stopped); otherwise it must return false.
# https://doc.qt.io/qt-5/qobject.html#eventFilter
# check if the object is the mainwindow.
if object == self:
if event.type() == QEvent.HoverMove:
if not self.iswindowpress:
self.setCursorShape(event)
return True
elif event.type() == QEvent.MouseButtonPress:
self.iswindowpress = True
# Get the position of the cursor and map to the global coordinate of the widget.
self.globalpos = self.mapToGlobal(event.pos())
self.origingeometry = self.geometry()
return True
elif event.type() == QEvent.MouseButtonRelease:
self.iswindowpress = False
return True
elif event.type() == QEvent.MouseMove:
if self.cursorpos != CursorPos.DEFAULT and self.iswindowpress:
self.resizing(self.globalpos, event,
self.origingeometry, self.cursorpos)
return True
else:
return False
elif object == self.grabarea:
if event.type() == QEvent.MouseButtonPress:
if event.button() == Qt.LeftButton and self.iswindowpress == False:
self.oldpos = event.globalPos()
self.oldwindowpos = self.pos()
self.istitlebarpress = True
return True
elif event.type() == QEvent.MouseButtonRelease:
self.istitlebarpress = False
return True
elif event.type() == QEvent.MouseMove:
if (self.istitlebarpress):
distance = event.globalPos()-self.oldpos
newwindowpos = self.oldwindowpos + distance
self.move(newwindowpos)
return True
else:
return False
else:
return False
# Change the cursor shape when the cursor is over different part of the window.
def setCursorShape(self, event, handlersize=11):
rect = self.rect()
topLeft = rect.topLeft()
topRight = rect.topRight()
bottomLeft = rect.bottomLeft()
bottomRight = rect.bottomRight()
# get the position of the cursor
pos = event.pos()
# make the resize handle include some space outside the window,
# can avoid user move too fast and loss the handle.
# top handle
if pos in QRect(QPoint(topLeft.x()+handlersize, topLeft.y()-2*handlersize),
QPoint(topRight.x()-handlersize, topRight.y()+handlersize)):
self.setCursor(Qt.SizeVerCursor)
self.cursorpos = CursorPos.TOP
# bottom handle
elif pos in QRect(QPoint(bottomLeft.x()+handlersize, bottomLeft.y()-handlersize),
QPoint(bottomRight.x()-handlersize, bottomRight.y()+2*handlersize)):
self.setCursor(Qt.SizeVerCursor)
self.cursorpos = CursorPos.BOTTOM
# right handle
elif pos in QRect(QPoint(topRight.x()-handlersize, topRight.y()+handlersize),
QPoint(bottomRight.x()+2*handlersize, bottomRight.y()-handlersize)):
self.setCursor(Qt.SizeHorCursor)
self.cursorpos = CursorPos.RIGHT
# left handle
elif pos in QRect(QPoint(topLeft.x()-2*handlersize, topLeft.y()+handlersize),
QPoint(bottomLeft.x()+handlersize, bottomLeft.y()-handlersize)):
self.setCursor(Qt.SizeHorCursor)
self.cursorpos = CursorPos.LEFT
# topRight handle
elif pos in QRect(QPoint(topRight.x()-handlersize, topRight.y()-2*handlersize),
QPoint(topRight.x()+2*handlersize, topRight.y()+handlersize)):
self.setCursor(Qt.SizeBDiagCursor)
self.cursorpos = CursorPos.TOPRIGHT
# topLeft handle
elif pos in QRect(QPoint(topLeft.x()-2*handlersize, topLeft.y()-2*handlersize),
QPoint(topLeft.x()+handlersize, topLeft.y()+handlersize)):
self.setCursor(Qt.SizeFDiagCursor)
self.cursorpos = CursorPos.TOPLEFT
# bottomRight handle
elif pos in QRect(QPoint(bottomRight.x()-handlersize, bottomRight.y()-handlersize),
QPoint(bottomRight.x()+2*handlersize, bottomRight.y()+2*handlersize)):
self.setCursor(Qt.SizeFDiagCursor)
self.cursorpos = CursorPos.BOTTOMRIGHT
# bottomLeft handle
elif pos in QRect(QPoint(bottomLeft.x()-2*handlersize, bottomLeft.y()-handlersize),
QPoint(bottomLeft.x()+handlersize, bottomLeft.y()+2*handlersize)):
self.setCursor(Qt.SizeBDiagCursor)
self.cursorpos = CursorPos.BOTTOMLEFT
# Default is the arrow cursor.
else:
self.setCursor(Qt.ArrowCursor)
self.cursorpos = CursorPos.DEFAULT
def resizing(self, originpos, event, geo, cursorpos):
newpos = self.mapToGlobal(event.pos())
# find the distance between new and old cursor position.
dist = newpos - originpos
# calculate the steps to grow or srink.
if cursorpos in [CursorPos.TOP, CursorPos.BOTTOM,
CursorPos.TOPRIGHT,
CursorPos.BOTTOMLEFT, CursorPos.BOTTOMRIGHT]:
steps = dist.y()//self.stepY
elif cursorpos in [CursorPos.LEFT, CursorPos.TOPLEFT, CursorPos.RIGHT]:
steps = dist.x()//self.stepX
# if the distance moved is too stort, grow or srink by 1 step.
if steps == 0:
steps = -1 if dist.y() < 0 or dist.x() < 0 else 1
oldwidth = geo.width()
oldheight = geo.height()
oldX = geo.x()
oldY = geo.y()
if cursorpos in [CursorPos.TOP, CursorPos.TOPRIGHT]:
width = oldwidth - steps * self.stepX
height = oldheight - steps * self.stepY
newX = oldX
newY = oldY + (steps * self.stepY)
# check if the new size is within the size limit.
if height >= self.minheight and width >= self.minwidth:
self.setGeometry(newX, newY, width, height)
elif cursorpos in [CursorPos.BOTTOM, CursorPos.RIGHT, CursorPos.BOTTOMRIGHT]:
width = oldwidth + steps * self.stepX
height = oldheight + steps * self.stepY
self.resize(width, height)
elif cursorpos in [CursorPos.LEFT, CursorPos.BOTTOMLEFT]:
width = oldwidth - steps * self.stepX
height = oldheight - steps * self.stepY
newX = oldX + steps * self.stepX
newY = oldY
# check if the new size is within the size limit.
if height >= self.minheight and width >= self.minwidth:
self.setGeometry(newX, newY, width, height)
elif cursorpos == CursorPos.TOPLEFT:
width = oldwidth - steps * self.stepX
height = oldheight - steps * self.stepY
newX = oldX + steps * self.stepX
newY = oldY + steps * self.stepY
# check if the new size is within the size limit.
if height >= self.minheight and width >= self.minwidth:
self.setGeometry(newX, newY, width, height)
else:
pass
# cursor position
class CursorPos(Enum):
TOP = 1
BOTTOM = 2
RIGHT = 3
LEFT = 4
TOPRIGHT = 5
TOPLEFT = 6
BOTTOMRIGHT = 7
BOTTOMLEFT = 8
DEFAULT = 9
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Finally, I'd like to give special thanks to the authors and editors of this question, GLHF, DRPK, Elad Joseph, and SimoN SavioR. Without their contribution to the community, it wouldn't be possible to come up with this answer.

How in pyqt change text in QPlainTextEdit() if slider's value is 1, and don't change if the value is 0?

message = QtGui.QPlainTextEdit()
slider = QtGui.QSlider()
def slider_func():
if slider.value() == 0:
'''what to write here ?'''
if slider.value() == 1:
m = str(message.toPlainText())
translated = ''
i = len(m) - 1
while i >= 0:
translated = translated + m[i]
i = i - 1
message.setPlainText(translated)
QtCore.QObject.connect(slider, QtCore.SIGNAL('valueChanged(int)'),
slider_func)
When I set slider to the first tick, the text gets reversed. But when I set it to the begining the text is still reversed. I know that the problem is that once the text was changed it sets the text edit line. Any ideas how to solve it ? Example1, Example2
Here is an example of how to watch the value of the slider, and modify a QPlainText when the value is at certain values.
from PyQt4.QtGui import *
from PyQt4.Qt import *
import sys
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
self.setLayout(layout)
self.text = "Please enter some text"
self.edit = QPlainTextEdit(self.text)
layout.addWidget(self.edit)
slider = QSlider(Qt.Horizontal)
layout.addWidget(slider)
slider.valueChanged.connect(self.change_value)
def change_value(self, value):
if value == 0:
self.edit.setPlainText(self.text)
elif value == 1:
self.edit.setPlainText(self.text[::-1])
elif value > 1:
self.edit.setPlainText("Slider value is above 1, value: {0}".format(value))
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
When the QSlider.valueChanged signal is emitted, the new value is carried with it. So in my slot Window.change_value(self, value) the value positional argument will always contain the new QSlider value; which allows you to test against it.

Categories