Search code examples
pythonpyqtgraph

pyqtgraph image point selection


I'm trying to make a tool for my lab for manual image registration--where the user can select some points on two different images to align them. I made this in matplotlib, but zooming in/out was way too slow (I think because the images we're aligning are pretty high res). Is there a good way to do that in pyqtgraph? I just need to be able to select points on two image plots side by side and display where the point selections were.

Currently I have the images in ImageViews and I tried doing it with imv.scene.sigMouseClicked.connect(mouse_click), but in mouse_click(evt) evt.pos(), evt.scenePos(), and evt.screenPos() all gave coordinates that weren't in the image's coordinates. I also played around with doing the point selection with ROI free handles (since I could get the correct coordinates from those), but it doesn't seem like you could color the handles, which isn't a total deal-breaker I was wondering if there was a better option. Is there a better way to do this?

Edit:

The answer was great, I used it to make this pile of spaghetti: https://github.com/xkstein/ManualAlign

Figured I'd like it in case someone was looking for something similar and didn't want to hassle with coding a new one from scratch.


Solution

  • Your question is unclear about how you want the program to match the points, here I provide a simple solution to allow you (1) Show an image. (2) Add points to the image.

    The basic idea is to use a pg.GraphicsLayoutWidget, then add a pg.ImageItem and a pg.ScatterPlotItem, and each mouse click adds a point to the ScatterPlotItem. Code:

    import sys
    from PyQt5.QtCore import Qt
    from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout
    import pyqtgraph as pg
    import cv2 
    
    pg.setConfigOption('background', 'w')
    pg.setConfigOption('foreground', 'k')
    
    class ImagePlot(pg.GraphicsLayoutWidget):
    
        def __init__(self):
            super(ImagePlot, self).__init__()
            self.p1 = pg.PlotItem() 
            self.addItem(self.p1)
            self.p1.vb.invertY(True) # Images need inverted Y axis
    
            # Use ScatterPlotItem to draw points
            self.scatterItem = pg.ScatterPlotItem(
                size=10, 
                pen=pg.mkPen(None), 
                brush=pg.mkBrush(255, 0, 0),
                hoverable=True,
                hoverBrush=pg.mkBrush(0, 255, 255)
            )
            self.scatterItem.setZValue(2) # Ensure scatterPlotItem is always at top
            self.points = [] # Record Points
    
            self.p1.addItem(self.scatterItem)
    
    
        def setImage(self, image_path, size):
            
            self.p1.clear()
            self.p1.addItem(self.scatterItem)
            # pg.ImageItem.__init__ method takes input as an image array
            # I use opencv to load image, you can replace with other packages
            image = cv2.imread(image_path, 1)
            # resize image to some fixed size
            image = cv2.resize(image, size)
    
            self.image_item = pg.ImageItem(image)
            self.image_item.setOpts(axisOrder='row-major')
            self.p1.addItem(self.image_item)
    
    
        def mousePressEvent(self, event):
    
            point = self.p1.vb.mapSceneToView(event.pos()) # get the point clicked
            # Get pixel position of the mouse click
            x, y = int(point.x()), int(point.y())
    
            self.points.append([x, y])
            self.scatterItem.setPoints(pos=self.points)
            super().mousePressEvent(event)
    
        
    
    if __name__ == "__main__":
    
        QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
        app = QApplication([])
        win = QMainWindow()
    
        central_win = QWidget()
        layout = QHBoxLayout()
        central_win.setLayout(layout)
        win.setCentralWidget(central_win)
    
        image_plot1 = ImagePlot()
        image_plot2 = ImagePlot()
        layout.addWidget(image_plot1)
        layout.addWidget(image_plot2)
    
        image_plot1.setImage('/home/think/image1.png', (310, 200))
        image_plot2.setImage('/home/think/image2.jpeg', (310, 200))
        # You can access points by accessing image_plot1.points
        win.show()
    
        if (sys.flags.interactive != 1) or not hasattr(Qt.QtCore, "PYQT_VERSION"):
            QApplication.instance().exec_()
    

    The result looks like:

    enter image description here