I'm building a small image annotation app, but encountered some issues. In short, this app allows the user to free-hand-draw aliased pixel mask on an image.
For this, I create a
- QGraphicsView
|-- QGraphicsScene # for drawing the overlaid mask
|-- QGraphicsPixmapItem # for the underlying image
And I detect mousePressEvent
, mouseMoveEvent
, and mouseReleaseEvent
to obtain the QPoint
s by event.scenePos()
under the mouse and append them in a list. With this list, The mask is drew by doing:
for pts in This_list:
painter.setPen(pen)
painter.setBrush(Qt.green)
painter.drawPoint(pts)
However, the Qt won't register every QPoint
the mouse swept, if the mouse move faster, less points will be appended.
How can I get every single pixel that the mouse sweep over?
Solution 1: here is a solution, inspired by this post:
- we need two QImage, first for the underlying image to annotate, second a transparent canvas where we free-hand draw
- the main idea of this solution is to retrieve the pixels that has the same QColor of our QPen
- use this solution to scan pixels of the QImage then using a pointer to the bytes data.
A short code is the following:
from PySide6.QtGui import *
from PySide6.QtCore import *
from PySide6.QtWidgets import *
import sys
import numpy as np
# <... other code here... MainWindow, mousePressEvent>
def mouseReleaseEvent(self, event):
if (event.button() == Qt.LeftButton) & self.drawing:
# <... other code/algorithm here>
# get all pixels under the drawn mask/path
color_arr = self.getPixelColor(self.mask)
idx = self.getColorCoordinate(color_arr, QColor(255, 0, 0, 30))
# here is how to retrieve the QColor array of the self.mask
def getPixelColor(self, qimage:QImage, readOnly=False):
'''https://stackoverflow.com/questions/11360009/how-can-access-to-pixel-data-with-pyqt-qimage-scanline, this give a qcolor value array of type: [(255, 0, 0, 30), (255, 255, 255,0)...] 3+alpha color channel'''
ptr = qimage.bits()
if readOnly:
## get a read-only buffer to access the data
buf = memoryview(ptr)
## view the data as a read-only numpy array
arr = np.frombuffer(buf, dtype=np.ubyte).reshape(qimage.height(), qimage.width(), 4)
else:
## view the data as a writable numpy array
arr = np.asarray(ptr).reshape(qimage.height(), qimage.width(), 4) # replace 4 by 3 if you have a such as FORMAT_RGB32 3 channel QImage
return arr
# simple way to find all the indexed of pixel that has QColor expected
def getColorCoordinate(self, arr: np.array, qcolor: QColor):
return np.where((arr[:, :, 0] == qcolor.toTuple()[0]) &
(arr[:, :, 1] == qcolor.toTuple()[1]) &
(arr[:, :, 2] == qcolor.toTuple()[2]) # Note: one can only search for the target label color by ignoring the transparency
# (arr[:, :, 3] == qcolor.toTuple()[3]) # Note: uncomment this line to consider the transparency
)