Python Forum

Full Version: ImageTk Paste
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
I'm back again with my camera app! Trying to overlay/merge two images when saving them.
The error i get is
Error:
AttributeError: 'PhotoImage' object has no attribute 'load'
Please see code below:
from tkinter import *
import cv2
from PIL import Image, ImageTk, ImageEnhance
import time
from tkinter import filedialog
import numpy as np


class App:
    def __init__(self, video_source=0):
        self.overlay_img = None
        self.appName = "Kamera"
        self.window = Tk()
        self.window.title(self.appName)
        self.window.resizable(0, 0)
        # self.window.wm_iconbitmap("cam.ico")
        self.window['bg'] = 'black'
        self.video_source = video_source

        self.vid = MyVideoCapture(self.video_source)
        # self.label = Label(self.window, text=self.appName, font=15, bg='blue', fg='white').pack(side=TOP, fill=BOTH)
        self.canvas = Canvas(self.window, width=self.vid.width, height=self.vid.height, bg='red')
        self.canvas.pack()

        self.btn_snapshot = Button(self.window, text="Snapshot", width=5, command=self.snapshot)
        self.btn_snapshot.pack(side=LEFT, padx=10)

        self.btn_overlay = Button(self.window, text="Overlay", width=7, command=self.overlay)
        self.btn_overlay.pack(side=LEFT, padx=10)

        self.btn_settings = Button(self.window, text="Settings", width=5, command=self.settings)
        self.btn_settings.pack(side=LEFT, padx=10)

        self.slide_value = 200
        self.update()
        self.window.mainloop()

    def settings(self):
        self.newWindow = Toplevel(self.window)
        self.newWindow.title("Settings")
        self.newWindow.geometry("400x400")
        self.btn_flip = Button(self.newWindow, text="Mirror Image", width=10, command=self.flip_img)
        self.btn_flip.pack(side=LEFT, padx=10)
            # self.brightness_lbl = Label(self.newWindow, text="Image Brightness")
            # self.brightness_lbl.pack(anchor=NW)
        #var = IntVar()
        self.brightness = Scale(
        self.newWindow, length=200, from_=0, to=255, orient=HORIZONTAL, label="Image Brightness",
                command=self.slide)
        self.brightness.set(200)
        self.brightness.pack(anchor=NW)

    def slide(self, var):
        self.slide_value = self.brightness.get()
        print(self.slide_value)

    def flip_img(self):
        self.vid.flipped = not self.vid.flipped

    def overlay(self):
        file = filedialog.askopenfile(
            mode='rb', defaultextension='.png',title="Choose Overlay Image", filetypes=[("PNG Files", '*.png')])
        if file:
            self.overlay_img = ImageTk.PhotoImage(file=file)

    def snapshot(self):
        isTrue, frame = self.vid.getFrame()

        if isTrue:
            filename = filedialog.asksaveasfilename(
                defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
            # image = "IMG-" + time.strftime("%H-%M-%S-%d-%m") + ".jpg"
            cv2.imwrite(filename, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            # msg = Label(self.window, text='Image Saved' + image, bg='black', fg='green').place(x=430, y=510)

        else:
            messagebox.showerror("paint says", "unable to save image ,\n something went wrong")

        if isTrue and self.overlay_img:
            self.photo.paste(self.overlay_img, (0, 0))
            self.stamped_img = ImageTk.PhotoImage(self.photo)
            filename = filedialog.asksaveasfilename(
            defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
            filename = self.stamped_img

    def update(self):
        isTrue, frame = self.vid.getFrame()
        frame = cv2.normalize(frame, frame, 0, self.slide_value, cv2.NORM_MINMAX)

        if isTrue:
            self.photo = ImageTk.PhotoImage(image=Image.fromarray(frame))
            #self.canvas.tag_lower(self.photo)
            self.canvas.create_image(0, 0, image=self.photo, anchor=NW)

        if self.overlay_img:
            #self.canvas.tag_raise(self.overlay_img)
            self.canvas.create_image(0,0, image=self.overlay_img, anchor=NW)
            #self.stamped_img = self.photo.paste(self.overlay_img)
        self.window.after(100, self.update)


class MyVideoCapture:
    def __init__(self, video_source=0):
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open this camera \n select another video source", video_source)

        self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)

        self.flipped = True

    def getFrame(self):
        if self.vid.isOpened():
            isTrue, frame = self.vid.read()
            if isTrue and self.flipped:
                frame = cv2.flip(frame, 1)
            if isTrue:
                return (isTrue, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            else:
                return (isTrue, None)
        else:
            return (isTrue, None)

    def __del__(self):
        if self.vid.isOpened():
            self.vid.release()


if __name__ == "__main__":
    App()
There is no load in your code, and you haven't included the entire traceback (which would show where the error is being produced). Is this all the code?
Hello again, I don't see load used directly anywhere it must be called indirectly.
You should show the full error traceback as it indicates where it went wrong and more information.
(May-31-2021, 10:31 PM)Yoriz Wrote: [ -> ]Hello again, I don't see load used directly anywhere it must be called indirectly.
You should show the full error traceback as it indicates where it went wrong and more information.

It's in ImageTk.py:
Error:
File "/home/kieran/PycharmProjects/Kamera/venv/lib/python3.8/site-packages/PIL/ImageTk.py", line 165, in paste im.load() AttributeError: 'PhotoImage' object has no attribute 'load'
ImageTk.py
#
# The Python Imaging Library.
# $Id$
#
# a Tk display interface
#
# History:
# 96-04-08 fl   Created
# 96-09-06 fl   Added getimage method
# 96-11-01 fl   Rewritten, removed image attribute and crop method
# 97-05-09 fl   Use PyImagingPaste method instead of image type
# 97-05-12 fl   Minor tweaks to match the IFUNC95 interface
# 97-05-17 fl   Support the "pilbitmap" booster patch
# 97-06-05 fl   Added file= and data= argument to image constructors
# 98-03-09 fl   Added width and height methods to Image classes
# 98-07-02 fl   Use default mode for "P" images without palette attribute
# 98-07-02 fl   Explicitly destroy Tkinter image objects
# 99-07-24 fl   Support multiple Tk interpreters (from Greg Couch)
# 99-07-26 fl   Automatically hook into Tkinter (if possible)
# 99-08-15 fl   Hook uses _imagingtk instead of _imaging
#
# Copyright (c) 1997-1999 by Secret Labs AB
# Copyright (c) 1996-1997 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#

import tkinter
from io import BytesIO

from . import Image

# --------------------------------------------------------------------
# Check for Tkinter interface hooks

_pilbitmap_ok = None


def _pilbitmap_check():
    global _pilbitmap_ok
    if _pilbitmap_ok is None:
        try:
            im = Image.new("1", (1, 1))
            tkinter.BitmapImage(data=f"PIL:{im.im.id}")
            _pilbitmap_ok = 1
        except tkinter.TclError:
            _pilbitmap_ok = 0
    return _pilbitmap_ok


def _get_image_from_kw(kw):
    source = None
    if "file" in kw:
        source = kw.pop("file")
    elif "data" in kw:
        source = BytesIO(kw.pop("data"))
    if source:
        return Image.open(source)


# --------------------------------------------------------------------
# PhotoImage


class PhotoImage:
    """
    A Tkinter-compatible photo image.  This can be used
    everywhere Tkinter expects an image object.  If the image is an RGBA
    image, pixels having alpha 0 are treated as transparent.

    The constructor takes either a PIL image, or a mode and a size.
    Alternatively, you can use the ``file`` or ``data`` options to initialize
    the photo image object.

    :param image: Either a PIL image, or a mode string.  If a mode string is
                  used, a size must also be given.
    :param size: If the first argument is a mode string, this defines the size
                 of the image.
    :keyword file: A filename to load the image from (using
                   ``Image.open(file)``).
    :keyword data: An 8-bit string containing image data (as loaded from an
                   image file).
    """

    def __init__(self, image=None, size=None, **kw):

        # Tk compatibility: file or data
        if image is None:
            image = _get_image_from_kw(kw)

        if hasattr(image, "mode") and hasattr(image, "size"):
            # got an image instead of a mode
            mode = image.mode
            if mode == "P":
                # palette mapped data
                image.load()
                try:
                    mode = image.palette.mode
                except AttributeError:
                    mode = "RGB"  # default
            size = image.size
            kw["width"], kw["height"] = size
        else:
            mode = image
            image = None

        if mode not in ["1", "L", "RGB", "RGBA"]:
            mode = Image.getmodebase(mode)

        self.__mode = mode
        self.__size = size
        self.__photo = tkinter.PhotoImage(**kw)
        self.tk = self.__photo.tk
        if image:
            self.paste(image)

    def __del__(self):
        name = self.__photo.name
        self.__photo.name = None
        try:
            self.__photo.tk.call("image", "delete", name)
        except Exception:
            pass  # ignore internal errors

    def __str__(self):
        """
        Get the Tkinter photo image identifier.  This method is automatically
        called by Tkinter whenever a PhotoImage object is passed to a Tkinter
        method.

        :return: A Tkinter photo image identifier (a string).
        """
        return str(self.__photo)

    def width(self):
        """
        Get the width of the image.

        :return: The width, in pixels.
        """
        return self.__size[0]

    def height(self):
        """
        Get the height of the image.

        :return: The height, in pixels.
        """
        return self.__size[1]

    def paste(self, im, box=None):
        """
        Paste a PIL image into the photo image.  Note that this can
        be very slow if the photo image is displayed.

        :param im: A PIL image. The size must match the target region.  If the
                   mode does not match, the image is converted to the mode of
                   the bitmap image.
        :param box: A 4-tuple defining the left, upper, right, and lower pixel
                    coordinate. See :ref:`coordinate-system`. If None is given
                    instead of a tuple, all of the image is assumed.
        """

        # convert to blittable
        im.load()
        image = im.im
        if image.isblock() and im.mode == self.__mode:
            block = image
        else:
            block = image.new_block(self.__mode, im.size)
            image.convert2(block, image)  # convert directly between buffers

        tk = self.__photo.tk

        try:
            tk.call("PyImagingPhoto", self.__photo, block.id)
        except tkinter.TclError:
            # activate Tkinter hook
            try:
                from . import _imagingtk

                try:
                    if hasattr(tk, "interp"):
                        # Required for PyPy, which always has CFFI installed
                        from cffi import FFI

                        ffi = FFI()

                        # PyPy is using an FFI CDATA element
                        # (Pdb) self.tk.interp
                        #  <cdata 'Tcl_Interp *' 0x3061b50>
                        _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
                    else:
                        _imagingtk.tkinit(tk.interpaddr(), 1)
                except AttributeError:
                    _imagingtk.tkinit(id(tk), 0)
                tk.call("PyImagingPhoto", self.__photo, block.id)
            except (ImportError, AttributeError, tkinter.TclError):
                raise  # configuration problem; cannot attach to Tkinter


# --------------------------------------------------------------------
# BitmapImage


class BitmapImage:
    """
    A Tkinter-compatible bitmap image.  This can be used everywhere Tkinter
    expects an image object.

    The given image must have mode "1".  Pixels having value 0 are treated as
    transparent.  Options, if any, are passed on to Tkinter.  The most commonly
    used option is ``foreground``, which is used to specify the color for the
    non-transparent parts.  See the Tkinter documentation for information on
    how to specify colours.

    :param image: A PIL image.
    """

    def __init__(self, image=None, **kw):

        # Tk compatibility: file or data
        if image is None:
            image = _get_image_from_kw(kw)

        self.__mode = image.mode
        self.__size = image.size

        if _pilbitmap_check():
            # fast way (requires the pilbitmap booster patch)
            image.load()
            kw["data"] = f"PIL:{image.im.id}"
            self.__im = image  # must keep a reference
        else:
            # slow but safe way
            kw["data"] = image.tobitmap()
        self.__photo = tkinter.BitmapImage(**kw)

    def __del__(self):
        name = self.__photo.name
        self.__photo.name = None
        try:
            self.__photo.tk.call("image", "delete", name)
        except Exception:
            pass  # ignore internal errors

    def width(self):
        """
        Get the width of the image.

        :return: The width, in pixels.
        """
        return self.__size[0]

    def height(self):
        """
        Get the height of the image.

        :return: The height, in pixels.
        """
        return self.__size[1]

    def __str__(self):
        """
        Get the Tkinter bitmap image identifier.  This method is automatically
        called by Tkinter whenever a BitmapImage object is passed to a Tkinter
        method.

        :return: A Tkinter bitmap image identifier (a string).
        """
        return str(self.__photo)


def getimage(photo):
    """Copies the contents of a PhotoImage to a PIL image memory."""
    im = Image.new("RGBA", (photo.width(), photo.height()))
    block = im.im

    photo.tk.call("PyImagingPhotoGet", photo, block.id)

    return im


def _show(image, title):
    """Helper for the Image.show method."""

    class UI(tkinter.Label):
        def __init__(self, master, im):
            if im.mode == "1":
                self.image = BitmapImage(im, foreground="white", master=master)
            else:
                self.image = PhotoImage(im, master=master)
            super().__init__(master, image=self.image, bg="black", bd=0)

    if not tkinter._default_root:
        raise OSError("tkinter not initialized")
    top = tkinter.Toplevel()
    if title:
        top.title(title)
    UI(top, image).pack()
I think due to Namespace flooding with * imports you've learnt your lesson in why to not do it.

The code is expecting a PIL Image instead I think its got a tkinter Image which has no load method see the tk Image class below.
class Image:
    """Base class for images."""
    _last_id = 0

    def __init__(self, imgtype, name=None, cnf={}, master=None, **kw):
        self.name = None
        if not master:
            master = _get_default_root('create image')
        self.tk = getattr(master, 'tk', master)
        if not name:
            Image._last_id += 1
            name = "pyimage%r" % (Image._last_id,) # tk itself would use image<x>
        if kw and cnf: cnf = _cnfmerge((cnf, kw))
        elif kw: cnf = kw
        options = ()
        for k, v in cnf.items():
            if callable(v):
                v = self._register(v)
            options = options + ('-'+k, v)
        self.tk.call(('image', 'create', imgtype, name,) + options)
        self.name = name

    def __str__(self): return self.name

    def __del__(self):
        if self.name:
            try:
                self.tk.call('image', 'delete', self.name)
            except TclError:
                # May happen if the root was destroyed
                pass

    def __setitem__(self, key, value):
        self.tk.call(self.name, 'configure', '-'+key, value)

    def __getitem__(self, key):
        return self.tk.call(self.name, 'configure', '-'+key)

    def configure(self, **kw):
        """Configure the image."""
        res = ()
        for k, v in _cnfmerge(kw).items():
            if v is not None:
                if k[-1] == '_': k = k[:-1]
                if callable(v):
                    v = self._register(v)
                res = res + ('-'+k, v)
        self.tk.call((self.name, 'config') + res)

    config = configure

    def height(self):
        """Return the height of the image."""
        return self.tk.getint(
            self.tk.call('image', 'height', self.name))

    def type(self):
        """Return the type of the image, e.g. "photo" or "bitmap"."""
        return self.tk.call('image', 'type', self.name)

    def width(self):
        """Return the width of the image."""
        return self.tk.getint(
            self.tk.call('image', 'width', self.name))
Try removing from tkinter import * and using import tkinter as tk from now on.
Hi Yoriz!
I tried the above and subsequently had to add tk. before various objects such as
Canvas
and
Button
Still getting the same error:
Error:
/home/kieran/PycharmProjects/Kamera/venv/bin/python /home/kieran/PycharmProjects/Kamera/Kamera.py Exception in Tkinter callback Traceback (most recent call last): File "/usr/lib/python3.8/tkinter/__init__.py", line 1892, in __call__ return self.func(*args) File "/home/kieran/PycharmProjects/Kamera/Kamera.py", line 80, in snapshot self.photo.paste(self.overlay_img, (0, 0)) File "/home/kieran/PycharmProjects/Kamera/venv/lib/python3.8/site-packages/PIL/ImageTk.py", line 165, in paste im.load() AttributeError: 'PhotoImage' object has no attribute 'load' Process finished with exit code 0
Could this be a potential answer (which I don't fully understand): https://stackoverflow.com/questions/4013...il-imagetk

My current code:
import tkinter as tk
import cv2
from PIL import Image, ImageTk, ImageEnhance
import time
from tkinter import filedialog
import numpy as np


class App:
    def __init__(self, video_source=0):
        self.overlay_img = None
        self.appName = "Kamera"
        self.window = tk.Tk()
        self.window.title(self.appName)
        self.window.resizable(0, 0)
        # self.window.wm_iconbitmap("cam.ico")
        self.window['bg'] = 'black'
        self.video_source = video_source

        self.vid = MyVideoCapture(self.video_source)
        # self.label = Label(self.window, text=self.appName, font=15, bg='blue', fg='white').pack(side=TOP, fill=BOTH)
        self.canvas = tk.Canvas(self.window, width=self.vid.width, height=self.vid.height, bg='red')
        self.canvas.pack()

        self.btn_snapshot = tk.Button(self.window, text="Snapshot", width=5, command=self.snapshot)
        self.btn_snapshot.pack(side=tk.LEFT, padx=10)

        self.btn_overlay = tk.Button(self.window, text="Overlay", width=7, command=self.overlay)
        self.btn_overlay.pack(side=tk.LEFT, padx=10)

        self.btn_settings = tk.Button(self.window, text="Settings", width=5, command=self.settings)
        self.btn_settings.pack(side=tk.LEFT, padx=10)

        self.slide_value = 200
        self.update()
        self.window.mainloop()

    def settings(self):
        self.newWindow = Toplevel(self.window)
        self.newWindow.title("Settings")
        self.newWindow.geometry("400x400")
        self.btn_flip = Button(self.newWindow, text="Mirror Image", width=10, command=self.flip_img)
        self.btn_flip.pack(side=LEFT, padx=10)
            # self.brightness_lbl = Label(self.newWindow, text="Image Brightness")
            # self.brightness_lbl.pack(anchor=NW)
        #var = IntVar()
        self.brightness = Scale(
        self.newWindow, length=200, from_=0, to=255, orient=HORIZONTAL, label="Image Brightness",
                command=self.slide)
        self.brightness.set(200)
        self.brightness.pack(anchor=NW)

    def slide(self, var):
        self.slide_value = self.brightness.get()
        print(self.slide_value)

    def flip_img(self):
        self.vid.flipped = not self.vid.flipped

    def overlay(self):
        file = filedialog.askopenfile(
            mode='rb', defaultextension='.png',title="Choose Overlay Image", filetypes=[("PNG Files", '*.png')])
        if file:
            self.overlay_img = ImageTk.PhotoImage(file=file)

    def snapshot(self):
        isTrue, frame = self.vid.getFrame()

        if isTrue:
            filename = filedialog.asksaveasfilename(
                defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
            # image = "IMG-" + time.strftime("%H-%M-%S-%d-%m") + ".jpg"
            cv2.imwrite(filename, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            # msg = Label(self.window, text='Image Saved' + image, bg='black', fg='green').place(x=430, y=510)

        else:
            messagebox.showerror("paint says", "unable to save image ,\n something went wrong")

        if isTrue and self.overlay_img:
            self.photo.paste(self.overlay_img, (0, 0))
            self.stamped_img = ImageTk.PhotoImage(self.photo)
            filename = filedialog.asksaveasfilename(
            defaultextension='.jpg', title="Choose Filename", filetypes=[("JPEG Files", '*.jpg')])
            filename = self.stamped_img

    def update(self):
        isTrue, frame = self.vid.getFrame()
        frame = cv2.normalize(frame, frame, 0, self.slide_value, cv2.NORM_MINMAX)

        if isTrue:
            self.photo = ImageTk.PhotoImage(image=Image.fromarray(frame))
            #self.canvas.tag_lower(self.photo)
            self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)

        if self.overlay_img:
            #self.canvas.tag_raise(self.overlay_img)
            self.canvas.create_image(0,0, image=self.overlay_img, anchor=tk.NW)
            #self.stamped_img = self.photo.paste(self.overlay_img)
        self.window.after(100, self.update)


class MyVideoCapture:
    def __init__(self, video_source=0):
        self.vid = cv2.VideoCapture(video_source)
        if not self.vid.isOpened():
            raise ValueError("Unable to open this camera \n select another video source", video_source)

        self.width = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
        self.height = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)

        self.flipped = True

    def getFrame(self):
        if self.vid.isOpened():
            isTrue, frame = self.vid.read()
            if isTrue and self.flipped:
                frame = cv2.flip(frame, 1)
            if isTrue:
                return (isTrue, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
            else:
                return (isTrue, None)
        else:
            return (isTrue, None)

    def __del__(self):
        if self.vid.isOpened():
            self.vid.release()


if __name__ == "__main__":
    App()
O Wall I thought that was going to fix it but it's good that you got rid of the * imports Big Grin

self.overlay_img = ImageTk.PhotoImage(file=file)
self.overlay_img is a Tkinter-compatible photo image

self.photo = ImageTk.PhotoImage(image=Image.fromarray(frame))
self.photo is also a Tkinter-compatible photo image

When calling paste I think the passed in image has to be an actual PIL image.
self.photo.paste(self.overlay_img, (0, 0))

as self.overlay_img is used in the update method You probably need to keep self.overlay_img as it is
    def update(self):
        ...
        ...
        if self.overlay_img:
            #self.canvas.tag_raise(self.overlay_img)
            self.canvas.create_image(0,0, image=self.overlay_img, anchor=NW)


Here is what I hope will fix it Pray

When you make self.overlay_img try also making a pill image
    def overlay(self):
        file = filedialog.askopenfile(
            mode='rb', defaultextension='.png',title="Choose Overlay Image", filetypes=[("PNG Files", '*.png')])
        if file:
            self.overlay_img = ImageTk.PhotoImage(file=file)
            self.pil_overlay_img = Image.open(file)
Then change
if isTrue and self.overlay_img:
    self.photo.paste(self.overlay_img, (0, 0))
to
if isTrue and self.pil_overlay_img:
    self.photo.paste(self.pil_overlay_img, (0, 0))
This is from the documentation for PIL ImageTk module.

https://pillow.readthedocs.io/en/stable/...ageTk.html

Quote:paste(im, box=None)[source]
Paste a PIL image into the photo image. Note that this can be very slow if the photo image is displayed.

Parameters
im – A PIL image. The size must match the target region. If the mode does not match, the image is converted to the mode of the bitmap image.

box – A 4-tuple defining the left, upper, right, and lower pixel coordinate. See Coordinate System. If None is given instead of a tuple, all of the image is assumed.

Notice that im is a PIL image, not an ImageTk.PhotoImage. You need to load your overlay using
Quote:PIL.Image.open(fp, mode='r', formats=None)
Lesson learned on imports Smile

Unfortunately neither of the above work. I don't get an error but the image saves without the overlay on it.
The error is gone, that's progress Wink
Try it with both images being pillow images
Paste another image into an image with Python, Pillow
Pages: 1 2