How to display images (np.hstack) in tkinter frame

I’m trying to build a tkinter GUI to show frames from 2 captures side-by-side.
This code works.

import cv2, numpy as np
from tkinter import NW, Tk, Canvas, PhotoImage

def photo_image(img):
    h, w = img.shape[:2]
    data = f'P6 {w} {h} 255 '.encode() + img[..., ::-1].tobytes()
    return PhotoImage(width=w, height=h, data=data, format='PPM')
def update():
    ret1, img1 = cap1.read()
    ret2, img2 = cap2.read()
    if ret1:
        photo = photo_image(np.hstack((img1, img2)))
        canvas.create_image(0, 0, image=photo, anchor=NW)
        canvas.image = photo
    root.after(15, update)
root = Tk()
root.title("Video")
cap1 = cv2.VideoCapture('/tmp/fto-20241229_184824_Sports2D.mp4')
cap2 = cv2.VideoCapture('/tmp/dtl-20241229_184824_Sports2D.mp4')
canvas = Canvas(root, width=1650, height=700)
canvas.pack()
update()
root.mainloop()
cap1.release()
cap2.release()

But when I try to implement the same code in a class I get the error
when I slide through the frames. ie
pyimage2" doesn’t exist
pyimage3" doesn’t exist
pyimage4" doesn’t exist

_tkinter.TclError: image "pyimage2" doesn't exist
import tkinter as tk
import cv2
import numpy as np
from tkinter import *
from tkinter.ttk import *

class Application(tk.Tk):
    def __init__(self, width, height, video1, video2):
        super().__init__()

        self.root = tk.Tk()
        self.root.option_add("*tearOff", False)
        self.root.title("Golf Swing Analysis")
        self.photo = None
        self.style = Style()
        self.width = width
        self.height = height
        self.cap1 = cv2.VideoCapture(video1)
        self.cap2 = cv2.VideoCapture(video2)
        self.cap1.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
        self.cap1.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
        self.cap2.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
        self.cap2.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)

        # Create toolbar
        toolbar = tk.Frame(self.root, bd=1, height=50, relief=tk.RAISED)
        toolbar.pack(side=tk.TOP, fill=tk.X)

        self.paned = PanedWindow(self.root)
        self.paned.pack()
        self.pane1 = Frame(self.paned)
        self.paned.add(self.pane1, weight=1)
        self.pane1.pack()
        self.notebook = Notebook(self.pane1)
        self.notebook.pack()

        self.tab1 = Frame(self.notebook)
        self.notebook.add(self.tab1, text="Swing View")
        self.tab2 = Frame(self.notebook)
        self.label2 = Label(self.tab2, text="Reference", justify="center")
        self.label2.pack()
        self.notebook.add(self.tab2, text="Reference Swing")

        # Add buttons to toolbar
        backBtn = tk.Button(toolbar, text='Previous', command=self.previous_file)
        backBtn.pack(side=tk.LEFT)
        nextBtn = tk.Button(toolbar, text="Next", command=self.next_file)
        nextBtn.pack(side=tk.LEFT)

        self.sep = Separator(orient="vertical")
        self.sep.pack(side=tk.LEFT)
        label = Label(toolbar, text="Drawing Tools: ")
        label.pack(side="left")
        self.var = tk.IntVar()
        drawOn = Radiobutton(toolbar, text="enable", variable=self.var, value=1, command=self.toggle_radio)
        drawOn.pack(side="left")
        drawOff = Radiobutton(toolbar, text="disable", variable=self.var, value=0, command=self.toggle_radio)
        drawOff.pack(side="left")
        plotBtn = tk.Button(toolbar, text="Show Plots", command=self.show_plot)
        plotBtn.pack(side=tk.LEFT)
        exportBtn = tk.Button(toolbar, text="Export", command=self.export_results)
        exportBtn.pack(side=tk.LEFT)
        quitBtn = tk.Button(toolbar, text="Quit", command=self.exit_app)
        quitBtn.pack(side=tk.RIGHT)

        label = Label(toolbar, text="Frame: ")
        label.pack(side="left")
        self.scale = tk.Scale(toolbar, from_=0, to=240, orient="horizontal", length=240, command=self.slider_changed)
        self.scale.pack(side=tk.LEFT)

        self.root.bind("<Left>", lambda e: self.scale.set(self.scale.get()-1))
        self.root.bind("<Right>", lambda e: self.scale.set(self.scale.get()+1))

        self.canvas = Canvas(self.tab1, width=1610, height=640, highlightbackground="red", highlightthickness=2)
        self.canvas.pack()

        self.notebook.pack(expand=True, fill="both", padx=5, pady=5)
        self.root.after(0, self.update)

    def next_file(self):
        print("Open next swing")

    def previous_file(self):
        print("Open previous swing")

    def export_results(self):
        print("Exporting results")

    def show_plot(self):
        print("Plotting results")

    def exit_app(self):
        self.root.destroy()

    def toggle_radio(self):
        if self.var.get() == 1:
            print("drawings enabled")
        elif self.var.get() == 0:
            print("drawings disabled")

    def slider_changed(self,value):
        self.cap1.set(cv2.CAP_PROP_POS_FRAMES, int(value))
        self.cap2.set(cv2.CAP_PROP_POS_FRAMES, int(value))
        self.update()

    def photo_image(self,img):
        h, w = img.shape[:2]
        data = f'P6 {w} {h} 255 '.encode() + img[..., ::-1].tobytes()
        return PhotoImage(width=w, height=h, data=data, format='PPM')

    def update(self):
        ret1, img1 = self.cap1.read()
        ret2, img2 = self.cap2.read()

        if ret1 and ret2:
            photo = self.photo_image(np.hstack((img1, img2)))
            self.canvas.create_image(0, 0, image=photo, anchor=NW)
            self.canvas.image = photo
        
        self.root.after(15, self.update)

    def __del__(self):
        self.cap1.release()
        self.cap2.release()

if __name__ == "__main__":
    video1='/tmp/fto-20241229_184824_Sports2D.mp4'
    video2='/tmp/dtl-20241229_184824_Sports2D.mp4'
    app = Application(800,600,video1,video2)
    app.mainloop()

Nevermind, it’s working. Was not really an opencv but a Tkinter question anyway.

demo