Issue with "dynamic" padding in matplotlib - python

I've been trying to set a fixed size padding (in pixels) on my matplotlib figure. I've never been able to find a solution that suits my needs on the internet so I had to make a little workaround for this, which is done with the following code :
def resizeEvent(self,e):
windowWidth=self.geometry().width()
windowHeight=self.geometry().height()
#print(windowWidth,windowHeight)
figure.subplots_adjust(left=100.0/windowWidth,right=1-100.0/windowWidth, top=1-100.0/windowHeight,bottom=100.0/windowHeight)
It works fine when manually resizing the window (we have a padding of 100px on every side).
Unfortunatly, when clicking Maximize, the padding (in 0 to 1) seems to be equal to it's previous value, even if the print returns the correct window size (1920px).
A second click to Restore Down will then set the padding to the value we should have when we maximized it.
I don't really get what's happening here, I must be missing something...
Tell me if you need more information such as more code.
Thank you for your kind help :)

I've had similar issues in the past (the figure not resizing at start up) and moved the recalculation of the figure subplots to the draw method. Something like this (I use tight_layout but you get the idea):
class PlotWidget(FigureCanvas):
def __init__(self):
self.__resizing_needed = True
def draw(self):
if self.__resizing_needed:
self.__resizing_needed = False
try:
self.figure.tight_layout()
except ValueError:
# Set the minimumSize of this widget to prevent this
print('Plot too small.')
super(PlotWidget, self).draw()
def resizeEvent(self, event):
self.__resizing_needed = True
super(PlotWidget, self).resizeEvent(event)

Related

Pyqt5 image coordinates

I display images with Qlabel.I need image coordinates/pixel coordinates but, I use mouseclickevent its show me only Qlabel coordinates.
for examples my image is 800*753 and my Qlabel geometry is (701,451).I reads coordinates in (701,451) but I need image coordinates in (800*753)
def resimac(self):
filename= QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
self.image=QtGui.QImage(filename[0])
self.pixmap=QtGui.QPixmap.fromImage(self.image)
self.resim1.setPixmap(self.pixmap)
self.resim1.mousePressEvent=self.getPixel
def getPixel(self, event):
x = event.pos().x()
y = event.pos().y()
print("X=",x," y= ",y)
Since you didn't provide a minimal, reproducible example, I'm going to assume that you're probably setting the scaledContents property, but that could also be not true (in case you set a maximum or fixed size for the label).
There are some other serious issues about your answer, I'll address them at the end of this answer.
The point has to be mapped to the pixmap coordinates
When setting a pixmap to a QLabel, Qt automatically resizes the label to its contents.
Well, it does it unless the label has some size constrains: a maximum/fixed size that is smaller than the pixmap, and/or the QLabel has the scaledContents property set to True as written above. Note that this also happens if any of its ancestors has some size constraints (for example, the main window has a maximum size, or it's maximized to a screen smaller than the space the window needs).
In any of those cases, the mousePressEvent will obviously give you the coordinates based on the widget, not on the pixmap.
First of all, even if it doesn't seem to be that important, you'll have to consider that every widget can have some contents margins: the widget will still receive events that happen inside the area of those margins, even if they are outside its actual contents, so you'll have to consider that aspect, and ensure that the event happens within the real geometry of the widget contents (in this case, the pixmap). If that's true, you'll have to translate the event position to that rectangle to get its position according to the pixmap.
Then, if the scaledContents property is true, the image will be scaled to the current available size of the label (which also means that its aspect ratio will not be maintained), so you'll need to scale the position.
This is just a matter of math: compute the proportion between the image size and the (contents of the) label, then multiply the value using that proportion.
# click on the horizontal center of the widget
mouseX = 100
pixmapWidth = 400
widgetWidth = 200
xRatio = pixmapWidth / widgetWidth
# xRatio = 2.0
pixmapX = mouseX * xRatio
# the resulting "x" is the horizontal center of the pixmap
# pixmapX = 200
On the other hand, if the contents are not scaled you'll have to consider the QLabel alignment property; it is usually aligned on the left and vertically centered, but that depends on the OS, the style currently in use and the localization (consider right-to-left writing languages). This means that if the image is smaller than the available size, there will be some empty space within its margins, and you'll have to be aware of that.
In the following example I'm trying to take care about all of that (I'd have to be honest, I'm not 100% sure, as there might be some 1-pixel tolerance due to various reasons, most regarding integer-based coordinates and DPI awareness).
Note that instead of overwriting mousePressEvent as you did, I'm using an event filter, I'll explain the reason for it afterwards.
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout(self)
self.getImageButton = QtWidgets.QPushButton('Select')
layout.addWidget(self.getImageButton)
self.getImageButton.clicked.connect(self.resimac)
self.resim1 = QtWidgets.QLabel()
layout.addWidget(self.resim1)
self.resim1.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
# I'm assuming the following...
self.resim1.setScaledContents(True)
self.resim1.setFixedSize(701,451)
# install an event filter to "capture" mouse events (amongst others)
self.resim1.installEventFilter(self)
def resimac(self):
filename, filter = QtWidgets.QFileDialog.getOpenFileName(None, 'Resim Yükle', '.', 'Image Files (*.png *.jpg *.jpeg *.bmp *.tif)')
if not filename:
return
self.resim1.setPixmap(QtGui.QPixmap(filename))
def eventFilter(self, source, event):
# if the source is our QLabel, it has a valid pixmap, and the event is
# a left click, proceed in trying to get the event position
if (source == self.resim1 and source.pixmap() and not source.pixmap().isNull() and
event.type() == QtCore.QEvent.MouseButtonPress and
event.button() == QtCore.Qt.LeftButton):
self.getClickedPosition(event.pos())
return super().eventFilter(source, event)
def getClickedPosition(self, pos):
# consider the widget contents margins
contentsRect = QtCore.QRectF(self.resim1.contentsRect())
if pos not in contentsRect:
# outside widget margins, ignore!
return
# adjust the position to the contents margins
pos -= contentsRect.topLeft()
pixmapRect = self.resim1.pixmap().rect()
if self.resim1.hasScaledContents():
x = pos.x() * pixmapRect.width() / contentsRect.width()
y = pos.y() * pixmapRect.height() / contentsRect.height()
pos = QtCore.QPoint(x, y)
else:
align = self.resim1.alignment()
# for historical reasons, QRect (which is based on integer values),
# returns right() as (left+width-1) and bottom as (top+height-1),
# and so their opposite functions set/moveRight and set/moveBottom
# take that into consideration; using a QRectF can prevent that; see:
# https://doc.qt.io/qt-5/qrect.html#right
# https://doc.qt.io/qt-5/qrect.html#bottom
pixmapRect = QtCore.QRectF(pixmapRect)
# the pixmap is not left aligned, align it correctly
if align & QtCore.Qt.AlignRight:
pixmapRect.moveRight(contentsRect.x() + contentsRect.width())
elif align & QtCore.Qt.AlignHCenter:
pixmapRect.moveLeft(contentsRect.center().x() - pixmapRect.width() / 2)
# the pixmap is not top aligned (note that the default for QLabel is
# Qt.AlignVCenter, the vertical center)
if align & QtCore.Qt.AlignBottom:
pixmapRect.moveBottom(contentsRect.y() + contentsRect.height())
elif align & QtCore.Qt.AlignVCenter:
pixmapRect.moveTop(contentsRect.center().y() - pixmapRect.height() / 2)
if not pos in pixmapRect:
# outside image margins, ignore!
return
# translate coordinates to the image position and convert it back to
# a QPoint, which is integer based
pos = (pos - pixmapRect.topLeft()).toPoint()
print('X={}, Y={}'.format(pos.x(), pos.y()))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
Now. A couple of suggestions.
Don't overwrite existing child object methods with [other] object's instance attributes
There are various reasons for which this is not a good idea, and, while dealing with Qt, the most important of them is that Qt uses function caching for virtual functions; this means that as soon as a virtual is called the first time, that function will always be called in the future. While your approach could work in simple cases (especially if the overwriting happens within the parent's __init__), it's usually prone to unexpected behavior that's difficult to debug if you're not very careful.
And that's exactly your case: I suppose that resimac is not called upon parent instantiation and until after some other event (possibly a clicked button) happens. But if the user, for some reason, clicks on the label before a new pixmap is loaded, your supposedly overwritten method will never get called: at that time, you've not overwritten it yet, so the user clicks the label, Qt calls the QLabel's base class mousePressEvent implementation, and then that method will always be called from that point on, no matter if you try to overwrite it.
To work around that, you have at least 3 options:
use an event filter (as the example above); an event filter is something that "captures" events of a widgets and allows you to observe (and interact) with it; you can also decide to propagate that event to the widget's parent or not (that's mostly the case of key/mouse events: if a widget isn't "interested" about one of those events, it "tells" its parent to care about it); this is the simplest method, but it can become hard to implement and debug for complex cases;
subclass the widget and manually add it to your GUI within your code;
subclass it and "promote" the widget if you're using Qt's Designer;
You don't need to use a QImage for a QLabel.
This is not that an issue, it's just a suggestion: QPixmap already uses (sort of) fromImage within its C++ code when constructing it with a path as an argument, so there's no need for that.
Always, always provide usable, Minimal Reproducible Example code.
See:
https://stackoverflow.com/help/how-to-ask
https://stackoverflow.com/help/minimal-reproducible-example
It could take time, even hours to get an "MRE", but it's worth it: there'll always somebody that could answer you, but doesn't want to or couldn't dig into your code for various reasons (mostly because it's incomplete, vague, inusable, lacking context, or even too expanded). If, for any reason, there'll be just that one user, you'll be losing your occasion to solve your problem. Be patient, carefully prepare your questions, and you'll probably get plenty of interactions and useful insight from it.

Kivy drawn items 'disappearing' (or possible render order problem)

(this is a repost from https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/kivy-users/0B5GYWkTIwE/-OQYp_ykCQAJ)
Very occasionally our kivy-based UI glitches in a very odd way: the system remains responsive and the displays of buttons and text work but it seems that all "drawn" items disappear, i.e. items from kivy.graphics.
Here is the main control screen in it's proper state:
and when it goes wrong it changes to:
All the items rendered with the kivy.graphics objects have gone!
This is using 1.9.1-1build3 (in Ubuntu) and 1.9.1-dev (from kivypie on an RPi)
Does anyone have a clue as to what might be causing this or how to track it down? It seems to happen on touch events, but it is so rare it's been impossible so far to find any commonality and there is no error report output to stdout/stderr or the .kivy/log/
Here's a section of code showing how elements are being drawn:
class LineWidget(GUIWidget):
"""A line widget. A simple sequence of points"""
def __init__(self, points, width, **kwargs):
GUIWidget.__init__(self, **kwargs)
self.points = points
with self.canvas:
self.line_color = theme.system.line.Color()
self.line = Line(width=width)
self.bind(size=self.update_rect, pos=self.update_rect)
def update_rect(self, instance, value):
"""Update the point positions."""
self.pos = instance.pos
if self.points:
self.line.points = self.get_points(self.points)
def set_color(self, color):
"""Change the color"""
self.line_color.rgb = color
and the blue/grey background is rendered:
with self.canvas.before:
if background_color is None:
background_color = theme.primary.black
self._background_color = Color(*background_color)
self._background_rectangle = Rectangle(size=self.size, pos=self.pos)
In my post to the kivy group, it has been suggested that maybe some race condition has happened resulting in the native kivy objects being rendered on top of mine and to try using canvas.after, but unless I can find a way to provoke the problem I'll never know if this has worked.
Is it possible that the render order (Z-index) has changed?
Is it possible there is some race condition?
The problem happens so rarely it has been impossible so far to
provoke the problem (100s of random clicks on widgets is the only
way) - is there some programmatic method to try and force the issue?
what debug could I put in to try and find out what might be
happening?
Thanks,

Selecting points on a PlotWidget with Qt

I'm new to python/pyside/pyqtgraph and I'm kind of stuck in my program.
So, I have an numpy.ndarray representing 10000 values, and I plot it in a PlotWidget using the plot method.
The result is ok but now I want to allow the user to select points of the curve so I can save the X axis of the point and use it later on.
What I would like to do is creating a QPushButton which when clicked it waits for the user to select two points on the curve by left-clicking and then save the X axis. Seems pretty simple conceptually but I don't find the good way of doing it.
I would be really pleased if you could give me an example or something, I'm also open to any suggestion that deviate from this use case.
I can resume the code by this lines :
self.myWidget = pyqtgraph.PlotWidget()
self.myWidget.plot(myValues) # myValues is the numpy array
self.select2PointsButton = QtGui.QPushButton()
self.select2PointsButton.clicked.connect(self.waitForSelection)
def waitForSelection(self):
# Wait for a click left on the curve to select first point then save the X-axis
# Do it again to select the second point
Thanks,
Morgan
Edit after Zet4 answer :
Thank you for your answer it helped me get started.
In the end, I made a subclass of PlotWidget :
class PltWidget(pg.PlotWidget):
def __init__(self, parent=None):
super(PltWidget, self).__init__(parent)
self.selectionMode = False
def mousePressEvent(self, ev):
if self.selectionMode:
if ev.button() == QtCore.Qt.LeftButton:
# How do I get the X axis ?
else:
super(PltWidget, self).mousePressEvent(ev)
Then I use it in my window, connecting the button signal with the slot changing the boolean of my PltWidget :
..... # Other attributes and connections of my Window
self.T0Button = QtGui.QPushButton()
self.graphicsLeft = PltWidget()
self.T0Button.clicked.connect(self.selectT0)
def selectT0(self):
self.graphicsLeft.selectionMode = not(self.graphicsLeft.selectionMode)
I'll probably use your buffer strategy to command two selections from the user.
However, I still need to know how do I get the X axis of the PlotWidget from where I clicked. If anyone using pyqtgraph know the answer, please let me know.
Thanks.
My apologize, i'm not a pyqt expert, but your problem seems too be more conceptual than technical.
You can use your QPushButton.clicked (in your code, the waitForSelection function) to change the functional state of your object (allow or disable point selection).
So you need to :
create a function that intercept the click on your pushButton (your waitForSelection function)
create a function that intercept left click on your graphical object (i'll assume you name it onLeftClick)
a functional state handler : a boolean is the easiest way for it ( isSelectedMode ).
a buffer that represent a point. ( buffer here, it can be your X-axis as you say'ed )
Your waitForSelection function will only inverse the state of isSelectedMode. It will also clear the buffer, before you don't need it anymore. pseudocode :
if isSelectedMode == true
buffer = null;
isSelectedMode = !isSelectedMode;
The onLeftClick will do the harder of the work, see this pseudocode :
if isSelectedMode == true
if buffer != null // No point save, so this click is the first one
//Here you save your data in the buffer
else
// One point is saved so that's the second one.
// Do what you want with it and buffer
else
// The selection mode is not active.
This only thing that missing is the way of getting your X-axis from your left click.
I hope this can help you.
Best regards

Drawing window border in Python xlib

I'm working on a window manager written using python's xlib bindings and I'm (initially) attempting to mimic dwm's behavior in a more pythonic way. I've gotten much of what I need, but I'm having trouble using X's built in window border functionality to indicate window focus.
Assuming I've got an instance of Xlib's window class and that I'm reading the documentation correctly, this should do what I want to do (at least for now) - set the window border of a preexisting window to a garish color and set the border width to 2px.
def set_active_border(self, window):
border_color = self.colormap.alloc_named_color(\
"#ff00ff").pixel
window.change_attributes(None,border_pixel=border_color,
border_width = 2 )
self.dpy.sync()
However, I get nothing from this - I can add print statements to prove that my program is indeed running the callback function that I associated with the event, but I get absolutely no color change on the border. Can anyone identify what exactly I'm missing here? I can pastebin a more complete example, if it will help. I'm not exactly sure it will though as this is the only bit that handles the border.
Looks like this was complete PEBKAC. I've found an answer. Basically, I was doing this:
def set_active_border(self, window):
border_color = self.colormap.alloc_named_color(
"#ff00ff"
).pixel
window.configure(border_width=2)
window.change_attributes(
None,
border_pixel=border_color,
border_width=2)
self.dpy.sync()
Apparently this was confusing X enough that it was doing nothing. The solution that I've stumbled upon was to remove the border_width portion from the window.change_attributes() call, like so:
def set_active_border(self, window):
border_color = self.colormap.alloc_named_color(
"#ff00ff"
).pixel
window.configure(border_width=2)
window.change_attributes(
None,
border_pixel=border_color
)
self.dpy.sync()
I hope this helps someone later on down the road!

Can I make a custom pygtk tooltip stay on when the cursor is over it?

Please look at the following snippet :
import gtk
def callback(widget, x, y, keyboard_mode, tooltip):
hbox = gtk.HBox(False, 8)
button = gtk.Button('Exit Tooltip')
label = gtk.Label('Tooltip text')
hbox.pack_start(label)
hbox.pack_start(button)
hbox.show_all()
tooltip.set_custom(hbox)
return True
label = gtk.Label('Test label')
label.set_has_tooltip(True)
label.connect('query-tooltip', callback)
Here I've created a custom tooltip with a close button in it. Now I want it to stay until i click that close button. Searching google was not that helpful. besides I would also like to know the signals/events that are emitted when a tooltip is being closed.
Similar problems are handled smoothly for JQuery/JavaScript/Ajax tooltips etc. but
for gtk/pygtk there is no luck :(
Thanks in advance ...
I had this issue as well, and as far as I know, there isn't any way to determine how long a tooltip stays up.
What I did (and recommend to you) is that you make your own "tooltip" and set it's background color to yellow, or whatever color you want, via an eventbox. Make sure you don't show it yet. This is just a simplified code, as you will need to position and size this in your project yourself.
color = gtk.gdk.rgb_get_colormap().alloc_color('black')
ebTooltip = gtk.EventBox()
btnTooltip = gtk.Button("Close")
ebTooltip.add(btnTooltip)
ebTooltip.modify_bg(gtk.STATE_NORMAL, color)
Now, you just need to hide and show this eventbox via your callbacks. To show it, call...
ebTooltip.show()
And, to hide it (probably on the "clicked" event of your close button)...
ebTooltip.hide()
Hope that solves your issue!

Categories