Image not updating in QGraphicsScene
-
Good morning and thanks you all for your precious work here.
I'm struggling to make an image update in a QGraphicsScene.
In particular i would like to create two windows, the second one that opens when a button is pushed on the main window. I managed to do that, but what happened is that in the second window i would like to display an image (more or less like a live stream, but at a slow framerate) and in future I plan to add more graphical items, like lines and so on.
Here is a minimum reproducible example of the problem i'm facing:
import sys import numpy as np from PySide6.QtWidgets import QApplication, QGraphicsPixmapItem, QMainWindow, QVBoxLayout, QWidget, QGraphicsView, QGraphicsScene from PySide6.QtGui import QPixmap, QImage from PySide6.QtCore import QTimer def convert_16bit_to_8bit_for_display(imageArray, min_val, max_val): image_clipped = np.clip(imageArray, min_val, max_val) - min_val image_scaled = ((image_clipped / (max_val - min_val)) * 255.0).astype(np.uint8) return image_scaled class imageVisualizationWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Image Viewer") self.initImage() self.updatingTimer = QTimer() self.updatingTimer.timeout.connect(self.updateImage) self.generateLayout() def initImage(self): self.imageArray = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) self.imageArray8b = convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) self.view = QGraphicsView() self.scene = QGraphicsScene() self.qImage = QImage(self.imageArray8b.data, self.imageArray8b.shape[1], self.imageArray8b.shape[0], QImage.Format_Grayscale8) self.pixmapItem = self.scene.addPixmap(QPixmap.fromImage(self.qImage)) self.view.setScene(self.scene) def updateImage(self): newImage = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) self.imageArray = newImage.copy() self.updateImageDisplay() def updateImageDisplay(self): self.imageArray8b = convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) self.qImage = QImage(self.imageArray8b.data, self.imageArray8b.shape[1], self.imageArray8b.shape[0], QImage.Format_Grayscale8) self.pixmapItem.setPixmap(QPixmap.fromImage(self.qImage)) self.scene.update() def generateLayout(self): imageLayout = QVBoxLayout() imageLayout.addWidget(self.view) centralWidget = QWidget() centralWidget.setLayout(imageLayout) self.setCentralWidget(centralWidget) class MainWin(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Main WIndow") ###################################################### # Generate the image visualization window ###################################################### self.imageVisualizationWindow = imageVisualizationWindow(self) self.imageVisualizationWindow.initImage() self.imageVisualizationWindow.updatingTimer.start(500) self.imageVisualizationWindow.show() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWin() window.show() sys.exit(app.exec())Here i checked that the timer calls the updateImage() that calls updateImageDisplay(), and i checked that a new image is generated at each call, but the image does not update on the window.
The strage thing is that the same logic works here:
import sys import numpy as np from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QPushButton, QVBoxLayout, QWidget from PySide6.QtGui import QPixmap, QImage from PySide6.QtCore import QTimer def create_noisy_image(width, height): # Create a random noisy image noisy_image = np.random.randint(0, 256, (height, width), dtype=np.uint8) return noisy_image def update_pixmap_and_scene(pixmap_item, scene, new_image): # Convert the numpy array to QImage qimage = QImage(new_image.data, new_image.shape[1], new_image.shape[0], QImage.Format_Grayscale8) # Update the pixmap item with the new image pixmap_item.setPixmap(QPixmap.fromImage(qimage)) # Update the scene scene.update() def change_image(): new_noisy_image = create_noisy_image(width, height) update_pixmap_and_scene(pixmap_item, scene, new_noisy_image) if __name__ == "__main__": # Create the Qt Application app = QApplication(sys.argv) # Create a QWidget widget = QWidget() # Create a QVBoxLayout layout = QVBoxLayout(widget) # Create a QGraphicsView view = QGraphicsView() # Create a QGraphicsScene scene = QGraphicsScene() # Create a noisy image width = 100 height = 100 noisy_image = create_noisy_image(width, height) # Convert the numpy array to QImage qimage = QImage(noisy_image.data, noisy_image.shape[1], noisy_image.shape[0], QImage.Format_Grayscale8) # Create a QGraphicsPixmapItem with the QImage pixmap_item = scene.addPixmap(QPixmap.fromImage(qimage)) # Set the scene for the QGraphicsView view.setScene(scene) # Add the QGraphicsView to the layout layout.addWidget(view) # Create a button to change the image button = QPushButton("Change Image") button.clicked.connect(change_image) # Add the button to the layout layout.addWidget(button) # Set the layout for the widget widget.setLayout(layout) # Show the widget widget.show() timer = QTimer() timer.timeout.connect(change_image) timer.start(500) # Execute the application sys.exit(app.exec())Can somebody try to help me finding where i did something wrong?
Thanks to all in advance
-
Hi and welcome to devnet,
Not having a computer at hand right now to verify, let me just make some suggestions:
- your central widget only exist during ˋgenerateLayout` so I am not sure it is not getting garbage collected
- MainWin is currently relatively useless as it just contains your other widget. You could directly create your secondary widget in the main function.
-
Hi and welcome to devnet,
Not having a computer at hand right now to verify, let me just make some suggestions:
- your central widget only exist during ˋgenerateLayout` so I am not sure it is not getting garbage collected
- MainWin is currently relatively useless as it just contains your other widget. You could directly create your secondary widget in the main function.
@SGaist
Thanks for your feedback.Based on your suggestions i tried to reduce the example even more (avoiding nesting the windows creation) and making the central widget a property of the class:
import sys import numpy as np from PySide6.QtWidgets import QApplication, QGraphicsPixmapItem, QMainWindow, QVBoxLayout, QWidget, QGraphicsView, QGraphicsScene from PySide6.QtGui import QPixmap, QImage from PySide6.QtCore import QTimer def convert_16bit_to_8bit_for_display(imageArray, min_val, max_val): image_clipped = np.clip(imageArray, min_val, max_val) - min_val image_scaled = ((image_clipped / (max_val - min_val)) * 255.0).astype(np.uint8) return image_scaled class imageVisualizationWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Image Viewer") self.centralWidget = QWidget() self.imageLayout = QVBoxLayout() self.initImage() self.updatingTimer = QTimer() self.updatingTimer.timeout.connect(self.updateImage) self.generateLayout() def initImage(self): self.imageArray = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) self.imageArray8b = convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) self.view = QGraphicsView() self.scene = QGraphicsScene() self.qImage = QImage(self.imageArray8b.data, self.imageArray8b.shape[1], self.imageArray8b.shape[0], QImage.Format_Grayscale8) self.pixmapItem = self.scene.addPixmap(QPixmap.fromImage(self.qImage)) self.view.setScene(self.scene) def updateImage(self): newImage = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) self.imageArray = newImage.copy() self.updateImageDisplay() def updateImageDisplay(self): self.imageArray8b = convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) self.qImage = QImage(self.imageArray8b.data, self.imageArray8b.shape[1], self.imageArray8b.shape[0], QImage.Format_Grayscale8) self.pixmapItem.setPixmap(QPixmap.fromImage(self.qImage)) self.scene.update() def generateLayout(self): self.imageLayout.addWidget(self.view) self.centralWidget.setLayout(self.imageLayout) self.setCentralWidget(self.centralWidget) if __name__ == "__main__": app = QApplication(sys.argv) imageVisualizationWindow = imageVisualizationWindow() imageVisualizationWindow.initImage() imageVisualizationWindow.updatingTimer.start(500) imageVisualizationWindow.show() sys.exit(app.exec())But still it does not update the image.
-
@SGaist
Thanks for your feedback.Based on your suggestions i tried to reduce the example even more (avoiding nesting the windows creation) and making the central widget a property of the class:
import sys import numpy as np from PySide6.QtWidgets import QApplication, QGraphicsPixmapItem, QMainWindow, QVBoxLayout, QWidget, QGraphicsView, QGraphicsScene from PySide6.QtGui import QPixmap, QImage from PySide6.QtCore import QTimer def convert_16bit_to_8bit_for_display(imageArray, min_val, max_val): image_clipped = np.clip(imageArray, min_val, max_val) - min_val image_scaled = ((image_clipped / (max_val - min_val)) * 255.0).astype(np.uint8) return image_scaled class imageVisualizationWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Image Viewer") self.centralWidget = QWidget() self.imageLayout = QVBoxLayout() self.initImage() self.updatingTimer = QTimer() self.updatingTimer.timeout.connect(self.updateImage) self.generateLayout() def initImage(self): self.imageArray = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) self.imageArray8b = convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) self.view = QGraphicsView() self.scene = QGraphicsScene() self.qImage = QImage(self.imageArray8b.data, self.imageArray8b.shape[1], self.imageArray8b.shape[0], QImage.Format_Grayscale8) self.pixmapItem = self.scene.addPixmap(QPixmap.fromImage(self.qImage)) self.view.setScene(self.scene) def updateImage(self): newImage = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) self.imageArray = newImage.copy() self.updateImageDisplay() def updateImageDisplay(self): self.imageArray8b = convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) self.qImage = QImage(self.imageArray8b.data, self.imageArray8b.shape[1], self.imageArray8b.shape[0], QImage.Format_Grayscale8) self.pixmapItem.setPixmap(QPixmap.fromImage(self.qImage)) self.scene.update() def generateLayout(self): self.imageLayout.addWidget(self.view) self.centralWidget.setLayout(self.imageLayout) self.setCentralWidget(self.centralWidget) if __name__ == "__main__": app = QApplication(sys.argv) imageVisualizationWindow = imageVisualizationWindow() imageVisualizationWindow.initImage() imageVisualizationWindow.updatingTimer.start(500) imageVisualizationWindow.show() sys.exit(app.exec())But still it does not update the image.
I managed to make it work. But i don't understand why the other system was not working.
import sys import numpy as np from PySide6.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QVBoxLayout, QWidget, QPushButton from PySide6.QtGui import QPixmap, QImage from PySide6.QtCore import QTimer class ImageVisualizationWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Image Viewer") self.centralWidget = QWidget() self.setCentralWidget(self.centralWidget) self.layout = QVBoxLayout(self.centralWidget) self.view = QGraphicsView() self.scene = QGraphicsScene() self.view.setScene(self.scene) self.layout.addWidget(self.view) self.button = QPushButton("Change Image") self.layout.addWidget(self.button) self.button.clicked.connect(self.updateImage) self.updatingTimer = QTimer() self.updatingTimer.timeout.connect(self.updateImage) self.updatingTimer.start(500) self.initImage() def initImage(self): self.imageArray = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) image8b = self.convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) self.qImage = QImage(image8b.data, image8b.shape[1], image8b.shape[0], QImage.Format_Grayscale8) self.pixmap_item = self.scene.addPixmap(QPixmap.fromImage(self.qImage)) def updateImage(self): newImage = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) self.imageArray = newImage.copy() self.updateImageDisplay() def updateImageDisplay(self): image8b = self.convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) qImage = QImage(image8b.data, image8b.shape[1], image8b.shape[0], QImage.Format_Grayscale8) self.pixmap_item.setPixmap(QPixmap.fromImage(qImage)) def convert_16bit_to_8bit_for_display(self, imageArray, min_val, max_val): image_clipped = np.clip(imageArray, min_val, max_val) - min_val image_scaled = ((image_clipped / (max_val - min_val)) * 255.0).astype(np.uint8) return image_scaled if __name__ == "__main__": app = QApplication(sys.argv) window = ImageVisualizationWindow() window.show() sys.exit(app.exec())here the trick was to get the pixmapItem when adding it from the scene
self.pixmap_item = self.scene.addPixmap(QPixmap.fromImage(self.qImage))and then directly updating this pixmapItem in the updateImageDisplay()
self.pixmap_item.setPixmap(QPixmap.fromImage(qImage)) -
I managed to make it work. But i don't understand why the other system was not working.
import sys import numpy as np from PySide6.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QVBoxLayout, QWidget, QPushButton from PySide6.QtGui import QPixmap, QImage from PySide6.QtCore import QTimer class ImageVisualizationWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Image Viewer") self.centralWidget = QWidget() self.setCentralWidget(self.centralWidget) self.layout = QVBoxLayout(self.centralWidget) self.view = QGraphicsView() self.scene = QGraphicsScene() self.view.setScene(self.scene) self.layout.addWidget(self.view) self.button = QPushButton("Change Image") self.layout.addWidget(self.button) self.button.clicked.connect(self.updateImage) self.updatingTimer = QTimer() self.updatingTimer.timeout.connect(self.updateImage) self.updatingTimer.start(500) self.initImage() def initImage(self): self.imageArray = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) image8b = self.convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) self.qImage = QImage(image8b.data, image8b.shape[1], image8b.shape[0], QImage.Format_Grayscale8) self.pixmap_item = self.scene.addPixmap(QPixmap.fromImage(self.qImage)) def updateImage(self): newImage = np.random.randint(0, 16384, (256, 256), dtype=np.uint16) self.imageArray = newImage.copy() self.updateImageDisplay() def updateImageDisplay(self): image8b = self.convert_16bit_to_8bit_for_display(self.imageArray, 0, 16384) qImage = QImage(image8b.data, image8b.shape[1], image8b.shape[0], QImage.Format_Grayscale8) self.pixmap_item.setPixmap(QPixmap.fromImage(qImage)) def convert_16bit_to_8bit_for_display(self, imageArray, min_val, max_val): image_clipped = np.clip(imageArray, min_val, max_val) - min_val image_scaled = ((image_clipped / (max_val - min_val)) * 255.0).astype(np.uint8) return image_scaled if __name__ == "__main__": app = QApplication(sys.argv) window = ImageVisualizationWindow() window.show() sys.exit(app.exec())here the trick was to get the pixmapItem when adding it from the scene
self.pixmap_item = self.scene.addPixmap(QPixmap.fromImage(self.qImage))and then directly updating this pixmapItem in the updateImageDisplay()
self.pixmap_item.setPixmap(QPixmap.fromImage(qImage))@TommasoFurieriDO said in Image not updating in QGraphicsScene:
I managed to make it work. But i don't understand why the other system was not working.
It has taken me hours of painstaking edits to spot why this is! :(
In your "original", non-working you have:class imageVisualizationWindow(QMainWindow): def __init__(self, parent=None): self.initImage() ... if __name__ == "__main__": imageVisualizationWindow.initImage()That is two calls to
initImage()! So a separate set of view/scene/image etc. are created! Hence the behaviour, things are not what you think they are....In the "latest", working you have just the one call to
initImage().... -
@TommasoFurieriDO said in Image not updating in QGraphicsScene:
I managed to make it work. But i don't understand why the other system was not working.
It has taken me hours of painstaking edits to spot why this is! :(
In your "original", non-working you have:class imageVisualizationWindow(QMainWindow): def __init__(self, parent=None): self.initImage() ... if __name__ == "__main__": imageVisualizationWindow.initImage()That is two calls to
initImage()! So a separate set of view/scene/image etc. are created! Hence the behaviour, things are not what you think they are....In the "latest", working you have just the one call to
initImage()....@JonB Oh God.... i feel so stupid! Thanks for your effort! I feel i can close it!
-
T TommasoFurieriDO has marked this topic as solved on
-
@JonB Oh God.... i feel so stupid! Thanks for your effort! I feel i can close it!
@TommasoFurieriDO
I could not see it for looking. Working the "bad" one towards the "good" one, till it went from not working to working suddenly. I don't know how you came up with the order in which you create/initialise things, and you obviously altered it in the later incarnation (and I think it's much better now), but it was a bit hard to follow. I usually put all the widget creations in the contructor (or perhaps asetupUi()), yours was so "dotted around" neither of us spotted this! :)