python-3.xpyqtgraphroi

Is there a way to prevent a rectangular pyqtgraph ROI to not move left-right?


I have this snippet from part of a code I'm working on with pyqtgraph ROI. I have also set boundaries in which the ROI cannot cross on all sides. When I move either of the vertical scale handles, it gives the ROI an opportunity to move left-right if click inside of it. Is there a way to prevent this from happening? I only want it to move up-down. This is kind of like setting another bound within the original bound to the ROI depending on the current X start position and X stop position.

I tried modifying the self.maxBound variable in the ROI class using signals but it became messy, and the ROI kept ignoring the right bound.

Any help will be greatly appriciated

from PyQt5.QtWidgets import (
    QApplication,
    QMainWindow,
    QGridLayout,
    QWidget)
import pyqtgraph as pg
import sys
import numpy as np
from PyQt5.QtCore import QRectF, QRect


class TestROI(QMainWindow):

    imageWidth = 1000
    imageHardLimitTop = 1048
    imageHardLimitBottom = 2047
    shallowestPixel = 1400
    deepestPixel = 1699
    ROITopBottomMargin = 20
    shaderDarkness = 185
    shaderColor = (0, 0, 50, shaderDarkness)
    imageHardLimitDepth = imageHardLimitBottom - imageHardLimitTop + 1

    def __init__(self):
        super(TestROI, self).__init__()

        self.mainWidget = QWidget()
        self.imagePlotCanvas = pg.GraphicsLayoutWidget()
        self.graphGridLayout = QGridLayout(self.mainWidget)
        self.graphGridLayout.addWidget(self.imagePlotCanvas)
        self.setCentralWidget(self.mainWidget)

        self.showMaximized()

        # Place for 2D images:
        self.plotArea = self.imagePlotCanvas.addPlot()
        self.plotArea.setRange(
            QRect(0, self.imageHardLimitTop, self.imageWidth, self.imageHardLimitDepth))
        self.plotArea.setLimits(xMin=-1000, xMax=2000)
        self.plotArea.setAspectLocked(True)
        self.plotArea.invertY(b=True)
        self.plotArea.showGrid(x=True, y=True, alpha=1.0)
        self.plotArea.showButtons()

        self.twoDImageROI = pg.ROI(pos=[0, self.shallowestPixel],
                                      size=[
                                          self.imageWidth, self.deepestPixel - self.shallowestPixel + 1],
                                      removable=False,
                                      maxBounds=QRectF(-1, self.imageHardLimitTop-1,
                                                       self.imageWidth + 2, self.imageHardLimitDepth+1),
                                      scaleSnap=False, translateSnap=False)
        self.twoDImageROI.setZValue(20)


        self.shadedTopRegion = pg.LinearRegionItem(
            [self.imageHardLimitTop, self.shallowestPixel], orientation=pg.LinearRegionItem.Horizontal, movable=False)
        self.shadedTopRegion.setBrush(color=self.shaderColor)
        self.shadedTopRegion.setZValue(10)
        self.plotArea.addItem(self.shadedTopRegion)

        self.shadedBottomRegion = pg.LinearRegionItem(
            [self.deepestPixel + 1, self.imageHardLimitBottom+1], orientation=pg.LinearRegionItem.Horizontal, movable=False)
        self.shadedBottomRegion.setBrush(color=self.shaderColor)
        self.shadedBottomRegion.setZValue(10)
        self.plotArea.addItem(self.shadedBottomRegion)

        # self.twoDImageROI.setAcceptedMouseButtons(Qt.LeftButton)
        self.twoDImageROI.sigRegionChanged.connect(self.imageROIChanged)

        # Shaded Region on the left and right to cover up the above two vertical lines when out of image bound.
        self.shadedLeftRegion = pg.LinearRegionItem(
            [-1000, 0], orientation=pg.LinearRegionItem.Vertical, movable=False)
        self.shadedLeftRegion.setBrush(color=(0, 0, 0))
        self.shadedLeftRegion.setZValue(20)
        self.plotArea.addItem(self.shadedLeftRegion)

        self.shadedRightRegion = pg.LinearRegionItem(
            [1000, 2000], orientation=pg.LinearRegionItem.Vertical, movable=False)
        self.shadedRightRegion.setBrush(color=(0, 0, 0))
        self.shadedRightRegion.setZValue(20)
        self.plotArea.addItem(self.shadedRightRegion)

        self.twoDImageROI.addScaleHandle([0.5, 0.0], [0.5, 1.0], index=0)
        self.twoDImageROI.addScaleHandle([0.5, 1.0], [0.5, 0.0], index=1)
        self.twoDImageROI.addScaleHandle([1.0, 0.5], [0.5, 0.5])
        self.twoDImageROI.addScaleHandle([0.0, 0.5], [0.5, 0.5])

        self.twoDOCMImage = pg.ImageItem(border='c')
        self.twoDOCMImage.setParentItem(self.plotArea)

        self.plotArea.addItem(self.twoDOCMImage)
        self.plotArea.addItem(self.twoDImageROI)

        zeroImage = np.zeros(self.imageWidth * self.imageHardLimitDepth)\
            .reshape((self.imageWidth, self.imageHardLimitDepth))
        self.twoDOCMImage.setImage(zeroImage)
        startingRect = QRect(0, self.imageHardLimitTop,
                             self.imageWidth, self.imageHardLimitDepth)
        self.twoDOCMImage.setRect(startingRect)

    def imageROIChanged(self):
        x, top = self.twoDImageROI.pos()
        top = int(round(top, 0))

        w, h = self.twoDImageROI.size()
        h = int(round(h, 0))

        bot = top + h - 1

        self.shallowestPixel = top
        self.deepestPixel = bot

        self.updateLinearRegion()

    def updateLinearRegion(self):
        x, top = self.twoDImageROI.pos()
        top = int(round(top, 0))

        w, h = self.twoDImageROI.size()
        h = int(round(h, 0))

        bot = top + h - 1

        self.shadedTopRegion.setRegion([self.imageHardLimitTop, top])
        self.shadedBottomRegion.setRegion([bot, self.imageHardLimitBottom+1])


if __name__ == '__main__':

    app = QApplication(sys.argv)
    main_window = TestROI()
    main_window.show()
    sys.exit(app.exec_())

Solution

  • I believe that issue You are talking about is this 1px wiggle from left to right when moving ROI up and down. This effect is due to the translation of real ROI position to plot X, Y values. There will always be rounding and thus ROI will always wiggle a bit left and right.

    To prevent this, You can manually set constant x position of Your ROI. For that, You have to override setPos of ROI class.

    Here is example ROI class, that You can use:

    class ConstantXROI(ROI):
        constant_x = 0
    
        def setPos(self, pos, y=None, update=True, finish=True):
            pos.setX(self.constant_x)
            super().setPos(pos, y=y, update=update, finish=finish)
    

    Then in Your code, just use:

    self.twoDImageROI = ConstantXROI(...)