• Re: Tkinter and cv2: "not responding" popup when imshow launched from t

    From Thomas Passin@21:1/5 to John O'Hagan on Tue Mar 14 08:07:19 2023
    On 3/14/2023 6:54 AM, John O'Hagan wrote:
    Hi list

    I'm trying to use cv2 to display images created as numpy arrays, from
    within a tkinter app (which does other things with the arrays before
    they are displayed as images). The arrays are colour-coded
    visualisations of genomes and can be over a billion elements in size,
    and I've found the PIL methods to display images in tkinter are too
    slow and memory-heavy.

    Here is minimal code that demonstrates the problem in the subject line:

    import cv2
    from tkinter import *

    images=['a.jpg', 'b.jpg', 'c.jpg'] #change to image paths

    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)
    counter=[0]
    def show():
    cv2.imshow('W', cv2.imread(images[counter[0] % len(images)]))
    cv2.waitKey(1)
    counter[0] += 1

    root=Tk()
    root.wm_attributes("-topmost", 1)
    Button(root, text=' Show ', command=show).pack()
    mainloop()

    It works up to a point - I can cycle through the images by clicking the button - but if I mouse-click on the displayed image (e.g. to use the
    zooming and panning features of cv2), nothing happens, and a few
    seconds later the image greys out and a popup appears saying "'Unknown'
    is not responding" and giving the option of waiting or forcing close
    (but sometimes these options are greyed out too). Clicking "wait", if available, closes the popup but it comes back a few seconds later. If I
    then click on the tkinter window titlebar, the popup changes to "'Tk'
    is not responding". Clicking on the button still works and after a few
    clicks the popup closes.

    This happens under both x11 and wayland, but under wayland, I  also get
    this error:

    "QSocketNotifier: Can only be used with threads started with QThread qt.qpa.wayland: Wayland does not support QWindow::requestActivate()"

    and only every second button press displays a new image ,with only
    every second image displayed.

    I think this particular popup is a Gnome thing, but AIUI most DEs have something similar to detect stuck apps. But the app is not stuck.

    I suspect this is some kind of interaction between the call to
    cv2.waitKey (which is necessary but I've never understood why!) and the tkinter event loop, but it's beyond my knowledge.

    Any suggestions about causes or workarounds?


    I don't know anything about the specifics here, but with billions of
    elements you will not be able to show more than a small fraction on the
    screen. So I think that a progressive disclosure approach would pay
    off. Sample the arrays down to a more workable size before creating the
    screen images. If you want to zoom in, resample them and recreate new
    images that only cover the zoomed in region in more detail.

    It would also be useful to cache the generated images so they can be re-displayed without needing to be regenerated each time.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Weatherby,Gerard@21:1/5 to All on Tue Mar 14 13:52:04 2023
    Assuming you’re using opencv-python, I’d post query at https://github.com/opencv/opencv-python/issues.

    From: Python-list <python-list-bounces+gweatherby=uchc.edu@python.org> on behalf of John O'Hagan <research@johnohagan.com>
    Date: Tuesday, March 14, 2023 at 6:56 AM
    To: Python list <python-list@python.org>
    Subject: Tkinter and cv2: "not responding" popup when imshow launched from tk app
    *** Attention: This is an external email. Use caution responding, opening attachments or clicking on links. ***

    Hi list

    I'm trying to use cv2 to display images created as numpy arrays, from
    within a tkinter app (which does other things with the arrays before
    they are displayed as images). The arrays are colour-coded
    visualisations of genomes and can be over a billion elements in size,
    and I've found the PIL methods to display images in tkinter are too
    slow and memory-heavy.

    Here is minimal code that demonstrates the problem in the subject line:

    import cv2
    from tkinter import *

    images=['a.jpg', 'b.jpg', 'c.jpg'] #change to image paths

    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)
    counter=[0]
    def show():
    cv2.imshow('W', cv2.imread(images[counter[0] % len(images)]))
    cv2.waitKey(1)
    counter[0] += 1

    root=Tk()
    root.wm_attributes("-topmost", 1)
    Button(root, text=' Show ', command=show).pack()
    mainloop()

    It works up to a point - I can cycle through the images by clicking the
    button - but if I mouse-click on the displayed image (e.g. to use the
    zooming and panning features of cv2), nothing happens, and a few
    seconds later the image greys out and a popup appears saying "'Unknown'
    is not responding" and giving the option of waiting or forcing close
    (but sometimes these options are greyed out too). Clicking "wait", if available, closes the popup but it comes back a few seconds later. If I
    then click on the tkinter window titlebar, the popup changes to "'Tk'
    is not responding". Clicking on the button still works and after a few
    clicks the popup closes.

    This happens under both x11 and wayland, but under wayland, I also get
    this error:

    "QSocketNotifier: Can only be used with threads started with QThread qt.qpa.wayland: Wayland does not support QWindow::requestActivate()"

    and only every second button press displays a new image ,with only
    every second image displayed.

    I think this particular popup is a Gnome thing, but AIUI most DEs have something similar to detect stuck apps. But the app is not stuck.

    I suspect this is some kind of interaction between the call to
    cv2.waitKey (which is necessary but I've never understood why!) and the
    tkinter event loop, but it's beyond my knowledge.

    Any suggestions about causes or workarounds?

    Thanks

    --

    John


    -- https://urldefense.com/v3/__https://mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!iHFg1AtgcwsfEeHXaH_Nasebf9SGreVlDs-DevEIQbFiwUQThx-_rah1QkSHRJEotJFyd-d6OCQ3GuQa1MxvsnGA$<https://urldefense.com/v3/__https:/mail.python.org/mailman/listinfo/
    python-list__;!!Cn_UX_p3!iHFg1AtgcwsfEeHXaH_Nasebf9SGreVlDs-DevEIQbFiwUQThx-_rah1QkSHRJEotJFyd-d6OCQ3GuQa1MxvsnGA$>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From aapost@21:1/5 to John O'Hagan on Tue Mar 14 16:22:55 2023
    On 3/14/23 06:54, John O'Hagan wrote:
    Hi list

    I'm trying to use cv2 to display images created as numpy arrays, from
    within a tkinter app (which does other things with the arrays before
    they are displayed as images). The arrays are colour-coded
    visualisations of genomes and can be over a billion elements in size,
    and I've found the PIL methods to display images in tkinter are too
    slow and memory-heavy.

    Here is minimal code that demonstrates the problem in the subject line:

    import cv2
    from tkinter import *

    images=['a.jpg', 'b.jpg', 'c.jpg'] #change to image paths

    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)
    counter=[0]
    def show():
    cv2.imshow('W', cv2.imread(images[counter[0] % len(images)]))
    cv2.waitKey(1)
    counter[0] += 1

    root=Tk()
    root.wm_attributes("-topmost", 1)
    Button(root, text=' Show ', command=show).pack()
    mainloop()

    It works up to a point - I can cycle through the images by clicking the button - but if I mouse-click on the displayed image (e.g. to use the
    zooming and panning features of cv2), nothing happens, and a few
    seconds later the image greys out and a popup appears saying "'Unknown'
    is not responding" and giving the option of waiting or forcing close
    (but sometimes these options are greyed out too). Clicking "wait", if available, closes the popup but it comes back a few seconds later. If I
    then click on the tkinter window titlebar, the popup changes to "'Tk'
    is not responding". Clicking on the button still works and after a few
    clicks the popup closes.

    This happens under both x11 and wayland, but under wayland, I  also get
    this error:

    "QSocketNotifier: Can only be used with threads started with QThread qt.qpa.wayland: Wayland does not support QWindow::requestActivate()"

    and only every second button press displays a new image ,with only
    every second image displayed.

    I think this particular popup is a Gnome thing, but AIUI most DEs have something similar to detect stuck apps. But the app is not stuck.

    I suspect this is some kind of interaction between the call to
    cv2.waitKey (which is necessary but I've never understood why!) and the tkinter event loop, but it's beyond my knowledge.

    Any suggestions about causes or workarounds?

    Thanks

    --

    John




    I don't get any of the zoom/panning behavior with waitKey(1). But a
    couple notes from the web:


    https://docs.opencv.org/2.4/modules/highgui/doc/user_interface.html

    Note

    This function should be followed by waitKey function which displays the
    image for specified milliseconds. Otherwise, it won’t display the image.
    For example, waitKey(0) will display the window infinitely until any
    keypress (it is suitable for image display). waitKey(25) will display a
    frame for 25 ms, after which display will be automatically closed. (If
    you put it in a loop to read videos, it will display the video
    frame-by-frame)

    https://pythonexamples.org/python-opencv-imshow/

    cv2.waitKey(0) is important for holding the execution of the python
    program at this statement, so that the image window stays visible. If
    you do not provide this statement, cv2.imshow() executes in fraction of
    a second and the program closes all the windows it opened, which makes
    it almost impossible to see the image on the window.




    if I change waitKey to 0, I get the ability to zoom/pan, but it places
    the tk window in a blocked state because it is waiting on cv2 to return.
    If I hit the ESC key, it releases the wait and gives control back to the
    tk window, allowing me to press show again to continue to the next image.

    I can try to change waitKey to a high ms like 10000000 and have zoom/pan
    for that amount of time before it gives control back to tk (not a
    sensical approach).

    The fact that you can still see the image after tk takes back control is
    I believe just a matter of design, some examples show
    cv2.destroyAllWindows() after waitKey(0) to clean that up, but of course
    if you are reusing the same window, that destroys the target.

    You can resolve that if you move
    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

    to inside the show, and destroy it after the wait, and make waitKey 0,
    this allows creation/cleanup of that window per image

    Hitting ESC when done zooming/panning on each image to get back to tk.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From John O'Hagan@21:1/5 to Thomas Passin on Wed Mar 15 13:00:44 2023
    On Tue, 2023-03-14 at 08:07 -0400, Thomas Passin wrote:
    On 3/14/2023 6:54 AM, John O'Hagan wrote:
    Hi list

    I'm trying to use cv2 to display images created as numpy arrays,
    from
    within a tkinter app (which does other things with the arrays
    before
    they are displayed as images). The arrays are colour-coded
    visualisations of genomes and can be over a billion elements in
    size,
    and I've found the PIL methods to display images in tkinter are too
    slow and memory-heavy.

    Here is minimal code that demonstrates the problem in the subject
    line:

    import cv2
    from tkinter import *

    images=['a.jpg', 'b.jpg', 'c.jpg'] #change to image paths

    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)
    counter=[0]
    def show():
        cv2.imshow('W', cv2.imread(images[counter[0] % len(images)]))
        cv2.waitKey(1)
        counter[0] += 1

    root=Tk()
    root.wm_attributes("-topmost", 1)
    Button(root, text=' Show ', command=show).pack()
    mainloop()

    It works up to a point - I can cycle through the images by clicking
    the
    button - but if I mouse-click on the displayed image (e.g. to use
    the
    zooming and panning features of cv2), nothing happens, and a few
    seconds later the image greys out and a popup appears saying
    "'Unknown'
    is not responding" and giving the option of waiting or forcing
    close
    (but sometimes these options are greyed out too). Clicking "wait",
    if
    available, closes the popup but it comes back a few seconds later.
    If I
    then click on the tkinter window titlebar, the popup changes
    to "'Tk'
    is not responding". Clicking on the button still works and after a
    few
    clicks the popup closes.

    [...]

    I don't know anything about the specifics here, but with billions of elements you will not be able to show more than a small fraction on
    the
    screen.  

    Hi Thomas

    Thanks for your reply.

    In the real app I use interpolating methods to fit the whole image on
    the screen. In cv2:

    cv2.imshow('W', cv2.resize(array, (width, height))

    It's very quick, fractions of a second even for a billion+ sized array.
    The PIL/Tk equivalent:

    im = ImageTk.PhotoImage(Image.fromarray(array).resize((width, height))) canvas.create_image(width/2, height/2, image=im)

    did the same thing but was very, very slow for such large arrays (15
    minutes or more per image, with memory heavily swapped out).

    Having said all that, the specific problem I'm having isn't related to
    the size of the arrays. The code I posted above triggers the problem
    even with small images.

    So I think that a progressive disclosure approach would pay
    off.  Sample the arrays down to a more workable size before creating
    the
    screen images.  If you want to zoom in, resample them and recreate
    new
    images that only cover the zoomed in region in more detail.

    It would also be useful to cache the generated images so they can be re-displayed without needing to be regenerated each time.

    This is exactly the approach I took in the first draft (except the
    caching idea)! I wasn't using interpolating methods and was limited to
    a minimum of one pixel per array element, and therefore limited in how
    much of the array could be displayed. Even this got pretty slow in
    tkinter if fully zoomed out, although the caching would have helped
    with that. But the project brief requires the whole genome to be
    visible by default.

    Thanks

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From John O'Hagan@21:1/5 to Gerard on Wed Mar 15 13:40:51 2023
    On Tue, 2023-03-14 at 13:52 +0000, Weatherby,Gerard wrote:
    Assuming you’re using opencv-python, I’d post query at https://github.com/opencv/opencv-python/issues.


    Thanks Gerard

    I'm using the python3-opencv package from Debian testing. Is that
    github the appropriate place for this query?

    Thanks

    --

    John



    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From John O'Hagan@21:1/5 to aapost on Wed Mar 15 22:37:30 2023
    On Tue, 2023-03-14 at 16:22 -0400, aapost wrote:
    On 3/14/23 06:54, John O'Hagan wrote:

    [...]

    Here is minimal code that demonstrates the problem in the subject
    line:

    import cv2
    from tkinter import *

    images=['a.jpg', 'b.jpg', 'c.jpg'] #change to image paths

    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)
    counter=[0]
    def show():
        cv2.imshow('W', cv2.imread(images[counter[0] % len(images)]))
        cv2.waitKey(1)
        counter[0] += 1

    root=Tk()
    root.wm_attributes("-topmost", 1)
    Button(root, text=' Show ', command=show).pack()
    mainloop()

    It works up to a point - I can cycle through the images by clicking
    the
    button - but if I mouse-click on the displayed image (e.g. to use
    the
    zooming and panning features of cv2), nothing happens, and a few
    seconds later the image greys out and a popup appears saying
    "'Unknown'
    is not responding" and giving the option of waiting or forcing
    close
    (but sometimes these options are greyed out too). Clicking "wait",
    if
    available, closes the popup but it comes back a few seconds later.
    If I
    then click on the tkinter window titlebar, the popup changes
    to "'Tk'
    is not responding". Clicking on the button still works and after a
    few
    clicks the popup closes.

    [...]

    I think this particular popup is a Gnome thing, but AIUI most DEs
    have
    something similar to detect stuck apps. But the app is not stuck.

    I suspect this is some kind of interaction between the call to
    cv2.waitKey (which is necessary but I've never understood why!) and
    the
    tkinter event loop, but it's beyond my knowledge.

    Any suggestions about causes or workarounds?

    Thanks

    --

    John




    I don't get any of the zoom/panning behavior with waitKey(1). But a
    couple notes from the web:


    https://docs.opencv.org/2.4/modules/highgui/doc/user_interface.html

    Note

    This function should be followed by waitKey function which displays
    the
    image for specified milliseconds. Otherwise, it won’t display the
    image.
    For example, waitKey(0) will display the window infinitely until any keypress (it is suitable for image display). waitKey(25) will display
    a
    frame for 25 ms, after which display will be automatically closed.
    (If
    you put it in a loop to read videos, it will display the video frame-by-frame)

    https://pythonexamples.org/python-opencv-imshow/

    cv2.waitKey(0) is important for holding the execution of the python
    program at this statement, so that the image window stays visible. If
    you do not provide this statement, cv2.imshow() executes in fraction
    of
    a second and the program closes all the windows it opened, which
    makes
    it almost impossible to see the image on the window.

    if I change waitKey to 0, I get the ability to zoom/pan, but it
    places
    the tk window in a blocked state because it is waiting on cv2 to
    return.
    If I hit the ESC key, it releases the wait and gives control back to
    the
    tk window, allowing me to press show again to continue to the next
    image.

    I can try to change waitKey to a high ms like 10000000 and have
    zoom/pan
    for that amount of time before it gives control back to tk (not a
    sensical approach).

    The fact that you can still see the image after tk takes back control
    is
    I believe just a matter of design, some examples show cv2.destroyAllWindows() after waitKey(0) to clean that up, but of
    course
    if you are reusing the same window, that destroys the target.

    You can resolve that if you move
    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)

    to inside the show, and destroy it after the wait, and make waitKey
    0,
    this allows creation/cleanup of that window per image

    Hitting ESC when done zooming/panning on each image to get back to
    tk.

    Thanks for your reply.

    I'm afraid in the real app it won't be practical to have no gui control
    while viewing images.

    But your suggestions have made me realise that my issue has nothing to
    do with tkinter, it seems to be the way imshow is supposed to work.
    This code gives the same behaviour described above:

    cv2.imshow('W', array)
    cv2.waitKey(100)
    time.sleep(20)

    I've seen the docs you mention that say the window will close if no
    keypress happens within the waitKey time, but that doesn't seem to be
    what happens as you say. After waitKey returns, the image remains and everything works, but for some reason the desktop complains about an unresponsive app.

    You can use cv2 to display video with:

    while 1:
    frame = camera.read() #pseudocode
    cv2.imshow('W', frame)
    cv2.waitKey(1)

    (In fact this used to work without waitKey if you called cv2.startWindowThread() after creating the window, but that stopped
    working at some point.)

    The fact that this works, presumably because the image is being
    replaced often enough, suggested the following workaround to my
    problem, using a call to after() to re-display the current image before
    the desktop starts complaining:

    images=[cv2.imread(i) for i in ('a.jpg', 'b.jpg', 'c.jpg')]

    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)

    counter=[0]

    image = [None]

    def show():
    im = images[counter[0] % 3]
    cv2.imshow('W', im)
    cv2.waitKey(1)
    image[:] = [im]
    counter[0] += 1

    root=Tk()
    root.wm_attributes("-topmost", 1)
    b=Button(root, text=' Show ', command=show)
    b.pack()

    def update():
    if image[0] is not None:
    cv2.imshow('W', image[0])
    cv2.waitKey(1)
    root.after(1000, update)

    update()

    mainloop()


    Not ideal, but I'm starting to think that despite the "highgui" label, cv2.imshow is not really suited for integration into a gui. Which is a
    shame because it's so damn fast! 

    Thanks

    John

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From aapost@21:1/5 to John O'Hagan on Wed Mar 15 15:19:46 2023
    On 3/15/23 07:37, John O'Hagan wrote:
    On Tue, 2023-03-14 at 16:22 -0400, aapost wrote:
    On 3/14/23 06:54, John O'Hagan wrote:
    It works up to a point - I can cycle through the images by clicking
    the
    button - but if I mouse-click on the displayed image (e.g. to use
    the
    zooming and panning features of cv2), nothing happens, and a few
    seconds later the image greys out and a popup appears saying
    "'Unknown'
    is not responding" and giving the option of waiting or forcing
    close
    (but sometimes these options are greyed out too). Clicking "wait",
    if
    available, closes the popup but it comes back a few seconds later.
    If I
    then click on the tkinter window titlebar, the popup changes
    to "'Tk'
    is not responding". Clicking on the button still works and after a
    few
    clicks the popup closes.

    [...]

    I think this particular popup is a Gnome thing, but AIUI most DEs
    have
    something similar to detect stuck apps. But the app is not stuck.

    I suspect this is some kind of interaction between the call to
    cv2.waitKey (which is necessary but I've never understood why!) and
    the
    tkinter event loop, but it's beyond my knowledge.

    Any suggestions about causes or workarounds?

    Thanks

    --

    John




    I don't get any of the zoom/panning behavior with waitKey(1). But a
    couple notes from the web:


    https://docs.opencv.org/2.4/modules/highgui/doc/user_interface.html

    Note

    This function should be followed by waitKey function which displays
    the
    image for specified milliseconds. Otherwise, it won’t display the
    image.
    For example, waitKey(0) will display the window infinitely until any
    keypress (it is suitable for image display). waitKey(25) will display
    a
    frame for 25 ms, after which display will be automatically closed.
    (If
    you put it in a loop to read videos, it will display the video
    frame-by-frame)

    https://pythonexamples.org/python-opencv-imshow/

    cv2.waitKey(0) is important for holding the execution of the python
    program at this statement, so that the image window stays visible. If
    you do not provide this statement, cv2.imshow() executes in fraction
    of
    a second and the program closes all the windows it opened, which
    makes
    it almost impossible to see the image on the window.

    if I change waitKey to 0, I get the ability to zoom/pan, but it
    places
    the tk window in a blocked state because it is waiting on cv2 to
    return.
    If I hit the ESC key, it releases the wait and gives control back to
    the
    tk window, allowing me to press show again to continue to the next
    image.

    I can try to change waitKey to a high ms like 10000000 and have
    zoom/pan
    for that amount of time before it gives control back to tk (not a
    sensical approach).

    The fact that you can still see the image after tk takes back control
    is
    I believe just a matter of design, some examples show
    cv2.destroyAllWindows() after waitKey(0) to clean that up, but of
    course
    if you are reusing the same window, that destroys the target.

    You can resolve that if you move
    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)

    to inside the show, and destroy it after the wait, and make waitKey
    0,
    this allows creation/cleanup of that window per image

    Hitting ESC when done zooming/panning on each image to get back to
    tk.

    Thanks for your reply.

    I'm afraid in the real app it won't be practical to have no gui control
    while viewing images.

    But your suggestions have made me realise that my issue has nothing to
    do with tkinter, it seems to be the way imshow is supposed to work.
    This code gives the same behaviour described above:

    cv2.imshow('W', array)
    cv2.waitKey(100)
    time.sleep(20)

    I've seen the docs you mention that say the window will close if no
    keypress happens within the waitKey time, but that doesn't seem to be
    what happens as you say. After waitKey returns, the image remains and everything works, but for some reason the desktop complains about an unresponsive app.

    You can use cv2 to display video with:

    while 1:
    frame = camera.read() #pseudocode
    cv2.imshow('W', frame)
    cv2.waitKey(1)

    (In fact this used to work without waitKey if you called cv2.startWindowThread() after creating the window, but that stopped
    working at some point.)

    The fact that this works, presumably because the image is being
    replaced often enough, suggested the following workaround to my
    problem, using a call to after() to re-display the current image before
    the desktop starts complaining:

    images=[cv2.imread(i) for i in ('a.jpg', 'b.jpg', 'c.jpg')]

    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)

    counter=[0]

    image = [None]

    def show():
    im = images[counter[0] % 3]
    cv2.imshow('W', im)
    cv2.waitKey(1)
    image[:] = [im]
    counter[0] += 1

    root=Tk()
    root.wm_attributes("-topmost", 1)
    b=Button(root, text=' Show ', command=show)
    b.pack()

    def update():
    if image[0] is not None:
    cv2.imshow('W', image[0])
    cv2.waitKey(1)
    root.after(1000, update)

    update()

    mainloop()


    Not ideal, but I'm starting to think that despite the "highgui" label, cv2.imshow is not really suited for integration into a gui. Which is a
    shame because it's so damn fast!



    Yep, it was just a solution for the extent of the snippet. I don't have
    any easy fixes for the single thread blocking issue as I have not needed
    to investigate any UI blocking situations yet (if anyone has experience
    in that they can chime in, or you may need to ask about
    blocking/threading solutions/examples in regard to imshow on a cv2
    specific forum, some sort of entirely different approach may be needed).

    To clarify the docs, it does not say "close if no key presses", it says
    2 different things. 2nd part being, Setting the ms will close the window
    after the time regardless. The wording of the "displaying the window indefinitely until any keypress" - when 0, does not say anything about
    closing, so the until a keypress is unclear as to what that means, as
    any keypress when it is 0 definitely does not close it. They do activate
    the pan/zoom, and ability to ESC though..

    Also to clarify (just in case), waitKey is in ms, and time.sleep is in
    seconds, so waitKey 100 is 0.1, the equivalent to time.sleep(20) would
    be waitKey(20000)

    The likely reason the desktop is complaining about an unresponsive app
    is that even though you still see that image, even at 100 ms (0.1s) the
    window you are clicking on by that time is technically already dead and
    closed, just not cleaned up.

    Additionally at least one of the error messages you were getting were in
    regard to qt (one of the graphical options under the hood), my
    understanding is that qt has some added controls for imshow that show up
    in a toolbar above the image (I don't have them so I don't see them). A
    sort of hypothetical guess, but it might be that those features are
    getting launched, but when they don't have the live imshow to operate
    on, they choke and the OS steps in (could be wrong).

    Does the update solve your problem in practice though? That is just a
    loop that makes the imshow live again for 1 ms, then pauses for 1
    second, then runs recursively. Potentially indefinitely delaying
    anything from being flagged as stuck. I assume that doesn't give you the pan/zoom ability either (at least it doesn't on mine).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From aapost@21:1/5 to John O'Hagan on Wed Mar 15 17:29:49 2023
    On 3/15/23 07:37, John O'Hagan wrote:
    On Tue, 2023-03-14 at 16:22 -0400, aapost wrote:
    On 3/14/23 06:54, John O'Hagan wrote:


    Doing a quick read, tkinter is not threadsafe, so diving in to a
    threading solution is probably not the best approach.

    But just to throw out another possible solution to see if you can find a
    "good enough" work around that fits your desired behavior.

    You could spawn the imshow as it's own program:

    file2: sp.py

    import sys
    import cv2
    import tkinter as tk
    def show(img, root):
    cv2.namedWindow(str(root), cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty(str(root), cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
    cv2.imshow(str(root), cv2.imread(img))
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    exit()
    root=tk.Tk()
    show(sys.argv[1], root)
    tk.mainloop()


    file1:main.py

    import cv2
    import tkinter as tk
    import subprocess
    images=['c.jpg', 'b.jpg', 'c.jpg']
    control = {
    "counter" : 0,
    "pid": None
    }
    def show(control):
    if control["pid"]:
    control["pid"].kill()
    control["pid"] = subprocess.Popen(["python", "sp.py", images[control["counter"] % len(images)]])
    control["counter"] += 1
    root=tk.Tk()
    root.wm_attributes("-topmost", 1)
    tk.Button(root, text=' Show ', command=lambda: show(control)).pack() tk.mainloop()


    refactor/design as needed, you track the pid, kill the existing on each subsequent show press.

    caveats would be that since it is an entirely separate program, if you
    close the main window, the other window will still linger until it is
    also closed. You could try to catch the close to run a .kill() on the subprocess if you want right before the exit, etc.

    But this gives control back to the main GUI since they are now
    independent of each other and not within the same thread.

    If they need to talk to each other in some way more than that, I am sure
    deeper design solutions could be thought up.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From aapost@21:1/5 to John O'Hagan on Thu Mar 16 04:21:30 2023
    On 3/15/23 07:37, John O'Hagan wrote:
    On Tue, 2023-03-14 at 16:22 -0400, aapost wrote:
    On 3/14/23 06:54, John O'Hagan wrote:

    [...]



    Read an alternative description of the waitKey behavior

    For example, waitKey(0) will display the window infinitely until any
    keypress (it is suitable for image display). waitKey(25) will display a
    frame and wait approximately 25 ms for a key press (suitable for
    displaying a video frame-by-frame). To remove the window, use cv::destroyWindow.

    I went back to double check and I stand corrected on the "any keypress"
    part. Any keypress on the 'keyboard' does break the wait (as I
    incorrectly concluded and assumed only ESC did).

    It is still slightly ambiguous in explaining that when using 25ms,
    keypress or not, the wait breaks at 25ms (or before that if you press a keyboard key). For my setup the window is stale at that point, no
    controls, just a stale frame behind the tkinter window that needs to be destroyed or reused.

    Whether that is the exact behavior on all set-ups isn't clear, the note
    further uses the ambiguous phrasing "might".

    Note: This function should be followed by a call to cv::waitKey or cv::pollKey to perform GUI housekeeping tasks that are necessary to
    actually show the given image and make the window respond to mouse and
    keyboard events. Otherwise, it won’t display the image and the window
    might lock up.

    It seems with the several variations, behavior varies between them, like
    one comment saying startWindowThread when using c++ and gtk allows you
    to not use waitKey (not the case here.. it seems -- changing my language
    to not misspeak, lol).

    I haven't come across any examples beyond imshow running stand-alone as
    in my solution suggestion 2. But waitKey does return a keyvalue for the
    keypess to allow extending the functionality, so you can grab it, do
    something, and go right back to waiting, I haven't seen any use of this
    though.

    You can also leave out reference to tkinter all together when using startWindowThread:

    import sys
    import cv2

    cv2.startWindowThread()
    cv2.namedWindow("W", cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty("W", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) cv2.imshow("W", cv2.imread(sys.argv[1]))
    while(1):
    a = cv2.waitKey(0)
    if a == 27:#ESC
    break
    #elif a == something else, do something
    cv2.destroyAllWindows()
    exit()

    But it still blocks if integrated in to the main tkinter thread, and
    appears to use tkinter under the hood. And as tkinter is considered thread-unsafe, the startWindowThread would only be ok when spawned as a separate process like the subprocess example.

    Anyway, apologies for the mistake on the any key part.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From John O'Hagan@21:1/5 to aapost on Sat Mar 18 13:56:04 2023
    On Thu, 2023-03-16 at 04:21 -0400, aapost wrote:
    On 3/15/23 07:37, John O'Hagan wrote:
    On Tue, 2023-03-14 at 16:22 -0400, aapost wrote:
    On 3/14/23 06:54, John O'Hagan wrote:

    [...]



    Read an alternative description of the waitKey behavior

    ;For example, waitKey(0) will display the window infinitely until
    any
    keypress (it is suitable for image display). waitKey(25) will display
    a
    frame and wait approximately 25 ms for a key press (suitable for
    displaying a video frame-by-frame). To remove the window, use cv::destroyWindow.

    I went back to double check and I stand corrected on the "any
    keypress"
    part. Any keypress on the 'keyboard' does break the wait (as I
    incorrectly concluded and assumed only ESC did).

    It is still slightly ambiguous in explaining that when using 25ms,
    keypress or not, the wait breaks at 25ms (or before that if you press
    a
    keyboard key). For my setup the window is stale at that point, no
    controls, just a stale frame behind the tkinter window that needs to
    be
    destroyed or reused.

    Whether that is the exact behavior on all set-ups isn't clear, the
    note
    further uses the ambiguous phrasing "might".

    ;Note: This function should be followed by a call to cv::waitKey or cv::pollKey to perform GUI housekeeping tasks that are necessary to
    actually show the given image and make the window respond to mouse
    and
    keyboard events. Otherwise, it won’t display the image and the window
    might lock up.

    It seems with the several variations, behavior varies between them,
    like
    one comment saying startWindowThread when using c++ and gtk allows
    you
    to not use waitKey (not the case here.. it seems -- changing my
    language
    to not misspeak, lol).

    I haven't come across any examples beyond imshow running stand-alone
    as
    in my solution suggestion 2. But waitKey does return a keyvalue for
    the
    keypess to allow extending the functionality, so you can grab it, do something, and go right back to waiting, I haven't seen any use of
    this
    though.

    You can also leave out reference to tkinter all together when using startWindowThread:

    import sys
    import cv2

    cv2.startWindowThread()
    cv2.namedWindow("W", cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty("W", cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)
    cv2.imshow("W", cv2.imread(sys.argv[1]))
    while(1):
         a = cv2.waitKey(0)
         if a == 27:#ESC
             break
         #elif a == something else, do something
    cv2.destroyAllWindows()
    exit()

    But it still blocks if integrated in to the main tkinter thread, and
    appears to use tkinter under the hood. And as tkinter is considered thread-unsafe, the startWindowThread would only be ok when spawned as
    a
    separate process like the subprocess example.


    Thanks for this and your other detailed replies, which I won't go into
    because your comment above led me to a simple workaround using
    threading, with a caveat. 

    This may be controversial, but AIUI it's fine to use threads in tkinter
    as long as you don't touch the same widget from different threads. I do
    it all the time!  So I tried opening and waiting for the window in a
    thread like this:

    def window_thread():
    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN,
    cv2.WINDOW_FULLSCREEN)
    cv2.waitKey(0)

    threading.Thread(target=window_thread, daemon=True).start()


    This works - the images can be displayed by calling imshow() via
    tkinter buttons in the main thread, and all the cv2 panning and zooming
    works too. 

    But the fly in the ointment is that any keypress while the focus is on
    the cv2 window exits the thread. I think this would apply to your
    subprocess example too.

    I tried:

    - putting the waitKey call in a while loop as in your example above,
    hoping it would just keep waiting, but the second call to waitKey
    doesn't respond to keypresses
    - putting the whole window_thread code in a while loop with a call to destroyAllWindows, hoping that this would re-create the window, but it
    doesn't
    - restarting the window thread after waitKey, which has the same
    results as above and
    - using a new name for the namedWindow each time

    In all cases, any subsequent calls to imshow block. I have no idea
    why. 

    For now it looks like I'll have to tell my users not to touch the
    keyboard while the image is in focus! Not ideal.

    If there is another nice fast pythonic way to display an image from a
    very large array _programatically_, I'd love to know about it.

    --

    John

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From John O'Hagan@21:1/5 to John O'Hagan on Sun Mar 19 11:08:54 2023
    On Tue, 2023-03-14 at 21:54 +1100, John O'Hagan wrote:

    [...]

    Here is minimal code that demonstrates the problem in the subject
    line:

    import cv2
    from tkinter import *

    images=['a.jpg', 'b.jpg', 'c.jpg'] #change to image paths

    cv2.namedWindow('W', cv2.WND_PROP_FULLSCREEN)
    cv2.setWindowProperty('W', cv2.WND_PROP_FULLSCREEN, 
    cv2.WINDOW_FULLSCREEN)
    counter=[0]
    def show():
    cv2.imshow('W', cv2.imread(images[counter[0] % len(images)]))
    cv2.waitKey(1)
    counter[0] += 1

    root=Tk()
    root.wm_attributes("-topmost", 1)
    Button(root, text=' Show ', command=show).pack()
    mainloop()

    It works up to a point - I can cycle through the images by clicking
    the button - but if I mouse-click on the displayed image (e.g. to use
    the zooming and panning features of cv2), nothing happens, and a few
    seconds later the image greys out and a popup appears saying
    "'Unknown' is not responding" and giving the option of waiting or
    forcing close (but sometimes these options are greyed out too).
    Clicking "wait", if available, closes the popup but it comes back a
    few seconds later. If I then click on the tkinter window titlebar,
    the popup changes to "'Tk' is not responding". Clicking on the button
    still works and after a few clicks the popup closes.

    [...]

    For anyone interested (and there are a lot of questions about this type
    of issue out there), here's the nearest I came to a satisfactory
    solution. In the real code I put a method like:

    def window_thread():
    cv2.namedWindow('W') #window properties omited
    while 1: # really a flag for clean exit
    cv2.imshow('W', self.image)
    cv2.waitKey(40)

    and call this as a thread from __init__. This updates the image every
    40 ms regardless of whether it has been changed or not, in the manner
    of a video feed. Seems unnecessary, but surprisingly it doesn't seem to
    use any significant resources and seems to be the only way to keep both
    the tkinter GUI and the cv2 window responsive. What I'd really like is cv2.wait(until_i_say_so()) - i.e. to wait for a flag that can be set programatically, but it doesn't seem to be available in cv2.

    Thanks to those who replied.

    --

    John



    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)