lazy pop up image loading for ipyleaflet - python-3.x

I have a lot of images from different locations.
I want to show them on a ipyleaflet map in a jupyter notebook.
Each image should have a marker, on click of the marker the image should be shown.
Since there are a lot of images i want the contents of the popup to be loaded in a lazy manner (as in, picture will only be loaded into memory when the marker is clicked and after it should be deleted again).
There must be some callback function i could hijack to load the image into memory only on click, but i cannot find it by the life of me. Hope to find some suggestions here.
The on_click method accepts a callback, but does not have access to the Marker object itself, such that i cannot change the popup itself.
Overwriting the `:
class LazyMarker(Marker):
def __init__(self, image_path, **kwargs):
self._image_path = image_path
super(LazyMarker, self).__init__(**kwargs)
def _handle_mouse_events(self, _, content, buffers):
self.popup = HTML()
self.popup.value = "lazy image loading test:" + load_image_function(self.path)
# rest of the original function
event_type = content.get('type', '')
if event_type == 'click':
self._click_callbacks(**content)
if event_type == 'dblclick':
self._dblclick_callbacks(**content)
[...]
seems also not to work. I'm very grateful for some suggestions! Thanks!

Answering my own question like here:
https://github.com/jupyter-widgets/ipyleaflet/issues/1092
m=Map(..)
def callback_with_popup_creation(fname):
def f(**kwargs):
marker_center = kwargs['coordinates']
html = HTML()
html.value="<img class=\"awesome image\" src=\"" + str(img_file_relative) + "\">"
popup = Popup(
location=marker_center,
child=html
)
m.add_layer(popup)
return f
for fname, lat, lon in data:
marker = Marker(location = (lat, lon))
marker.on_click(callback(fname))
hopefully that helps anyone with a similar issue.

Related

PyMuPDF insert image at bottom

I'm trying to read a PDF and insert an image at bottom(Footer) of each page in PDF. I've tried with PyMuPDF library.
Problem: Whatever the Rect (height, width) I give, it doesn't appear in the bottom, image keeps appearing only on the top half of the page (PDF page).
My code:
from fitz import fitz, Rect
doc = fitz.open("test_pdf2.pdf")
def add_footer(pdf):
img = open("footer4.jpg", "rb").read()
rect = Rect(0, 0, 570, 1230)
for i in range(0, pdf.pageCount):
page = pdf[i]
if not page._isWrapped:
page._wrapContents()
page.insertImage(rect, stream=img)
add_footer(doc)
doc.save('test_pdf5.pdf')
Processed_image with footer image in the middle of the page:
https://i.stack.imgur.com/HK9mm.png
Footer image:
https://i.stack.imgur.com/FRQYE.jpg
Please help!
Please let me know if this can be achieved by using any other library.
Some pages internally change the standard PDF geometry (which uses the bottom-left page point as (0, 0)) to something else, and at the same time do not encapsulate this doing properly as they should.
If you suspect this, use PyMuPDF methods to encapsulate the current page /Contents before you insert your items (text, images, etc.).
The easiest way is Page._wrapContents() which is also the friendly way to keep incremental PDF saves small. The MuPDF provided Page.cleanContents() does this and a lot more cleaning stuff, but bloats the incremental save delta, if this is a concern.
After doing little trial and error I was able to figure out the problem. I was missing out on proper dimensions of Rect, I had to give 0.85*h in second parameter.
below is the code:
from fitz import fitz, Rect
doc = fitz.open("test_pdf2.pdf")
w = 595
h = 842
def add_footer(pdf):
img = open("footer4.jpg", "rb").read()
rect = fitz.Rect(0, 0.85*h, w, h)
for i in range(0, pdf.pageCount):
page = pdf[i]
if not page._isWrapped:
page._wrapContents()
page.insertImage(rect, stream=img)
add_footer(doc)
doc.save('test_pdf5.pdf')
use:
wrapContents – Page.wrap_contents()
https://pymupdf.readthedocs.io/en/latest/znames.html?highlight=wrapContents
from fitz import fitz, Rect
doc = fitz.open("example.pdf")
w = 250
h = 400
def add_footer(pdf):
img = open("firmaJG.png", "rb").read()
rect = fitz.Rect(0, 0.85*h, w, h)
for i in range(0, pdf.pageCount):
page = pdf[i]
if not page._isWrapped:
page.wrap_contents()
page.insertImage(rect, stream=img)
add_footer(doc)
doc.save('test_pdf5.pdf')

PyQt QScrollArea doesn't display widgets

I am somewhat new to GUI programming and very new to PyQt, and I'm trying to build a GUI that displays a list of questions. I have created a QuestionBank class that subclasses QWidget and overrides the .show() method to display the list properly. I have tested this alone and it works correctly. However, the list of questions can be quite long, so I've been trying to make it scrollable. Rather than add a QScrollBar to the widget and then set up the event triggers by hand, I've been trying to my QuestionBank widget in a QScrollArea based on the syntax I've seen in examples online. While the scroll area shows up fine, it does not at all display the question bank but rather just shows a blank outline.
The QuestionBank class looks like this:
class QuestionBank(QWidget):
BUFFER = 10 # space between questions (can be modified)
def __init__(self, parent, questions):
# `parent` should be the QWidget that contains the QuestionBank, or None if
# QuestionBank is top level
# `questions` should be a list of MasterQuestion objects (not widgets)
QWidget.__init__(self, parent)
self.questions = [MasterQuestionWidget(self, q) for q in questions]
self.bottomEdge = 0
def show(self, y=BUFFER):
QWidget.show(self)
for q in self.questions:
# coordinates for each each question
q.move(QuestionBank.BUFFER, y)
q.show()
# update y-coordinate so that questions don't overlap
y += q.frameGeometry().height() + QuestionBank.BUFFER
self.bottomEdge = y + 3 * QuestionBank.BUFFER
# ... other methods down here
My code for showing the scroll bar looks like this:
app = QApplication(sys.argv)
frame = QScrollArea()
qs = QuestionBank(None, QFileManager.importQuestions())
qs.resize(350, 700)
frame.setGeometry(0, 0, 350, 300)
frame.setWidget(qs)
frame.show()
sys.exit(app.exec_())
I have tried many variants of this, including calling resize on frame instead of qs, getting rid of setGeometry, and setting the parent of qs to frame instead of None and I have no idea where I'm going wrong.
If it helps, I'm using PyQt5
Here is the question bank without the scroll area, to see what it is supposed to look like:
Here is the output of the code above with the scroll area:
This variation on the code is the only one that produces any output whatsoever, the rest just have blank windows. I'm convinced its something simple I'm missing, as the frame is obviously resizing correctly and it obviously knows what widget to display but its not showing the whole thing.
Any help is much appreciated, thank you in advance.

Python3 + Pillow + QT5: Crash when I resize a label containing an image

I'm getting a message box: "Python has stopped working" when I load an image into a QLabel in a window that is already visible. Selecting debug shows: an unhandled Win32 exception occurred in Python.exe.
If I load the image into the label before showing the window, it displays correctly.
Here's the stripped down code:
#!/usr/bin/etc python
import sys
import os
import stat
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PIL import *
from PIL.ImageQt import *
def update(label):
filename = r"C:\Users\me\Pictures\images\00000229.jpg"
im1 = Image.open(filename)
print ("Read ({},{})".format(im1.width, im1.height))
im2 = im1.rotate(90, expand=True)
print("Rotate ({},{})".format(im2.width, im2.height))
im2.thumbnail((1200,1200))
print("Thumbnail({},{})".format(im2.width, im2.height))
qimage = ImageQt(im2)
pixmap = QPixmap.fromImage(qimage)
label.setPixmap(pixmap)
app = QApplication(sys.argv)
desktop = QDesktopWidget()
deskGeometry = desktop.availableGeometry()
print("desktop ({},{})".format(deskGeometry.width(), deskGeometry.height()))
window = QFrame()
# If you let QT pick the sizes itself, it picks dumb ones, then complains
# 'cause they are dumb
window.setMinimumSize(300, 200)
window.setMaximumSize(deskGeometry.width(), deskGeometry.height())
label = QLabel()
#call update here: no crash
caption = QLabel()
caption.setText("Hello world")
box = QVBoxLayout()
box.addWidget(label)
box.addWidget(caption)
#call update here, no crash
window.setLayout(box)
#call update here, no crash
window.show()
#this call to update results in a crash
update(label)
#window.updateGeometry()
print("App: exec")
app.exec_()
Output:
desktop (3623,2160)
Read (1515,1051)
Rotate (1051,1515)
Thumbnail(832,1200)
App: exec
Do I need to do anything special to tell QT that the window size will be changing? Any suggestions for diagnosing the problem from here...
Update:
If I copy the body of the update function and paste it in place of the call to update, it no long crashes -- it works as expected.
From this I conclude that there is an object-lifetime issue. Somewhere behind the scenes QT and/or Pillow is keeping a pointer to an internal buffer rather than making a copy or "stealing" the buffer. When the object containing the buffer is deleted the pointer becomes invalid and "Bad Things Happen[TM]"
Now to determine who's being lazy...
I found a solution based on the observation mentioned in the Update that this appeared to be an object lifetime issue.
Changing the line in the update function from
pixmap = QPixmap.fromImage(qimage)
to
pixmap = QPixmap.fromImage(qimage).copy()
forces a copy of the pixmap. This copy apparently has its own data buffer rather than borrowing the buffer from the Image.
The Label then keeps a reference to the pixmap -- ensuring the lifetime of the buffer. The 'bug' seems to be that QPixmap.fromImage captures a pointer to the data in the Image, but does not keep a reference to the Image so if the Image gets garbage collected (which is likely 'cause it's a big object, the Label (and the pixmap) have a pointer to unallocated memory.
[This 'pointer to the buffer' stuff is sheer speculation on my part, but the bottom line is the program no longer crashes.]
In case anybody else stumbles upon this:
I struggled with a very similar issue and was able to solve it using slots and signals (based on this great article) since the .copy() solution did not work for me. My solution was to separate the creation of the QPixmap and the QLabel.setPixmap into different functions. When the function that creates the QPixmap finishes, it emits a signal that triggers the function that sets the pixmap to the label.
For instance, if you wanted to use threading:
class WorkerSignals(QObject):
ready = pyqtSignal()
class Worker(QRunnable):
def __init__(self, some_function, *args, **kwargs):
super(Worker, self).__init__()
self.some_function = some_function
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['ready'] = self.signals.ready
#pyqtSlot()
def run(self):
self.some_function(*self.args, **self.kwargs)
def make_pixmap(qimage, ready, **kwargs):
pixmap = QPixmap.fromImage(qimage) # qimage is some QImage
ready.emit()
def set_pixmap(pixmap):
label.setPixmap(pixmap) # 'label' is some QLabel
threadpool = QThreadPool
worker = Worker(make_pixmap, image)
worker.signals.ready.connect(set_pixmap)
threadpool.start(worker)
Obviously using threading is not necessary in this scenario, but this is just to show that it's possible in case you want to process and show images without hanging the rest of the GUI.
Edit: The crashes came back, disregard the above. Working on another fix.

Python load timer [duplicate]

So I have a .gif picture on a canvas in tkinter. I want this picture to change to another picture...but only for 3 seconds. and for it revert back to the original picture.
def startTurn(self):
newgif = PhotoImage(file = '2h.gif')
self.__leftImageCanvas.itemconfigure(self.__leftImage, image = newgif)
self.__leftImageCanvas.image = newgif
while self.cardTimer > 0:
time.sleep(1)
self.cardTimer -=1
oldgif = PhotoImage(file = 'b.gif')
self.__leftImageCanvas.itemconfigure(self.__leftImage, image = oldgif)
self.__leftImageCanvas.image = oldgif
this is a first attempt after a quick view of the timer. i know that this code does not make sense, but before i keep mindlessly trying to figure it out, i would much rather have more experienced input.
Tkinter widgets have a method named after which can be used to run a function after a specified period of time. To create an image and then change it three seconds later, you would do something like this:
def setImage(self, filename):
image = PhotoImage(file=filename)
self.__leftImageCanvas.itemconfigure(self.__leftImage, image=image)
self.__leftImageCanvas.image = image
def startTurn(self):
'''Set the image to "2h.gif", then change it to "b.gif" 3 seconds later'''
setImage("2h.gif")
self.after(3000, lambda: self.setImage("b.gif"))

tkinter python maximize window

I want to initialize a window as maximized, but I can't find out how to do it. I'm using python 3.3 and Tkinter 8.6 on windows 7. I guess the answer is just here: http://www.tcl.tk/man/tcl/TkCmd/wm.htm#m8
but I have no idea how to input it into my python script
Besides, I need to get the width and height of the window (both as maximised and if the user re-scale it afterwards), but I guess I can just find that out myself.
You can do it by calling
root.state('zoomed')
If you want to set the fullscreen attribute to True, it is as easy as:
root = Tk()
root.attributes('-fullscreen', True)
However, it doesn't show the title bar. If you want to keep it visible, you can resize the Tk element with the geometry() method:
root = Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (w, h))
With winfo_width() and winfo_height() you can get the width and height or the window, and also you can bind an event handler to the <Configure> event:
def resize(event):
print("New size is: {}x{}".format(event.width, event.height))
root.bind("<Configure>", resize)
To show maximized window with title bar use the 'zoomed' attribute
root = Tk()
root.attributes('-zoomed', True)
I've found this on other website:
import Tkinter
MyRoot = Tkinter.Tk()
MyRoot.state("zoomed")
MyRoot.mainloop()
This solved my problem.
The first approach is to use the root.state('zoomed'), but is not supposed to be universally available. It works on Windows, and on my Ubuntu machine. However, under my Arch machine it doesn't.
The second is to first get the maxsize, and then set geometry manually, like:
m = root.maxsize()
root.geometry('{}x{}+0+0'.format(*m))
This works on most machines, but not on all. For example, under my Arch the maxsize() returns (1425, 870), while the real geometry of maximized window should be (1440, 848). So, you also couldn't rely on it.
And the third, in my opinion the best approach is to use root.wm_attributes('-zoomed', 1). It is universally available and seems to be the safest. On some machines in could zoom only by width or by height, but comparing to previous method, this one would never give you a window partly ouside of the screen.
Finally, if you want a fullscreen, not just zoomed window, use root.wm_attributes('-fullscreen', 1). It provides a native link to window manager's behavior, thus working much better, than playing with overrideredirect and setting geometry by hand (which on some platforms could lead to unmanaged window, which could be closed only by its own interface or killing the process, won't show on the taskbar, etc...)
The most pythonic is" root.wm_state('zoomed'), as mentioned by #J.F.Sebastian
I recently ran into a similar issue where a library I was supporting needed to add Windows 10 as a development target also. Thanks to the information I found here, This is what we're doing now:
class INI_Link:
"""A connector class between a value stored in an ini file, and a value stored elsewhere that can be get and set with two helper functions."""
def __init__(self, getter, setter, varname, inigroup="Settings", inifile=''):
"""Remember that getter is called first to provide the default value.
Then the ini value is read if available, if not the default value is used."""
self._get = getter
self._set = setter
self._save = lambda value :inidb(inifile)[inigroup].__setitem__(varname, getter())
self._load = lambda :inidb(inifile)[inigroup].get(varname, getter())
#first load
self._lastvalue = self._load()
print(self._lastvalue)
self._set(self._lastvalue)
self._callbacks=[]
def trace(self, callback, mode='w'):
"""this only traces for .value.set() not for changes to the underlying value in either location.
if you never touch this again until .commit() at the end of your program, then it will never trigger until then.
call .probe() to force to check for changes without returning anything."""
self.callbacks.append(callback)
def probe(self):
"""check for changes, if there have been any, allert all traces."""
self._monitor(self._get())
def __get__(self):
value = self._get()
self._monitor(value)
return value
def __set__(self, value):
self._set(value)
self._save(value)
self._monitor(value)
def _monitor(value):
"helper to dispatch callbacks"
if value != self._lastvalue:
self._lastvalue = value
for cb in self._callbacks:
try:
cb()
except:
pass
def commit(self):
"""Call this right before getter is no longer useful."""
self._save(self._get())
And then in the main window class's __init__()
self._geometry = INI_Link(self.tkroot.geometry, self.tkroot.geometry, "window_geometry")
try:
#umbuntu and others, not arch
self._zoomed = INI_Link(lambda:self.tkroot.wm_attributes('-zoomed'),
lambda z: self.tkroot.wm_attributes('-zoomed', z)
, "window_zoomed")
except:
#windows and others, not umbuntu
self._zoomed = INI_Link(lambda: self.tkroot.state() == 'zoomed',
lambda z: self.tkroot.state(['normal','zoomed'][z])
, "window_zoomed")
and then when the window is being closed:
#save zoomed state.
self._zoomed.commit()
try:
if self.tkroot.wm_attributes('-zoomed'):
self.tkroot.wm_attributes('-zoomed', False)
self.tkroot.update()
except:
if self.tkroot.state() != 'normal':
self.tkroot.state('normal')
self.tkroot.update()
#save window size in normal state
self._geometry.commit()
With TkAgg as backend this is the only combination that maximized the window without fullscreen:
win_manager = plt.get_current_fig_manager()
win_manager.window.state('zoomed')
win_manager.full_screen_toggle()

Resources