3

I was running a trial to see whether I can display an image and wait for a keypress with opencv3 as one process within an asynchronous program. It struck me that this was central to what I am wanting to do and I had better check it out first. So here's the code:

import asyncio
import cv2
import functools


def load_img(image):
    print(image)
    im = cv2.imread(image, cv2.IMREAD_GRAYSCALE)
    _, inv = cv2.threshold(im, 150, 255, cv2.THRESH_BINARY_INV)
    cv2.GaussianBlur(inv, (3, 3), 0)
    cv2.imshow('Async test', inv)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    return


async def test():
    tiff_img = 'Im1.tiff'
    await loop.run_in_executor(None, functools.partial(load_img, 
                                                       image=tiff_img))

    return


async def numbers():
    for number in range(200):
        await asyncio.sleep(0.5)
        print(number)
    return

if __name__ == '__main__':

    loop = asyncio.get_event_loop()
    single = asyncio.gather(test(), numbers())
    loop.run_until_complete(single)

Output is:

Im1.tiff
2018-01-26 15:22:11.091 python3.6[2784:215235] WARNING: nextEventMatchingMask should only be called from the Main Thread! This will throw an exception in the future.
0
1
2
3
4

Looking at the warning it is perhaps self explanatory.

Question is whether there is a way to achieve my aim or not ??

Chanonry
  • 423
  • 7
  • 19
  • So problem is about warning? Because other than that everything works. Also what OS and OpenCV version do you use? I tried your code on Windows with latest OpenCV and got no warning. – Mikhail Gerasimov Jan 26 '18 at 17:08
  • Sorry, I should have been more explicit. My objective is to display an image until the user presses any key. Synchronous code works but the async version does not display the image nor wait for a key press. opencv 3.3.1 macOS 10.13.12 pycharm 2017.3.1 – Chanonry Jan 27 '18 at 11:38

2 Answers2

1

Again, your code runs perfectly on Windows. It seems problem is related to macOS version of OpenCV and googling shows you're not only one.

I couldn't find any universal fix, but main idea of all discussions is:

the problem is that usually events and graphic function calls must be done in the main thread. So you have no choice here. Put that in the main thread.

Most obvious way to do it I can think right now is to use ProcessPoolExecutor instead of run_in_executor's default ThreadPoolExecutor.

Change your code like this:

from concurrent.futures import ProcessPoolExecutor
executor = ProcessPoolExecutor(1)

# ...

async def test():
    tiff_img = 'Im1.tiff'
    await loop.run_in_executor(
        executor, 
        functools.partial(load_img, image=tiff_img)
    )

Sorry I can't test it on macOS, but this version also works on Windows.


P.S. I didn't check what will happen if image doesn't exists or can't be loaded, just in case make sure you're able to do it without asyncio.

Mikhail Gerasimov
  • 36,989
  • 16
  • 116
  • 159
  • Excellent ! Works on macOS. Thanks for your help. Andy – Chanonry Jan 27 '18 at 16:11
  • On further testing I am not sure of the behaviour. The image is shown quickly but the window does not seem to be destroyed until the numbers task completes. I would have expected the window to close on the keypress and numbers to continue until the loop completes ? Is this how it behaves in your Windows environment ?? – Chanonry Jan 27 '18 at 22:06
  • @Chanonry "I would have expected the window to close on the keypress and numbers to continue until the loop completes" - that's what happens on Windows, yes. – Mikhail Gerasimov Jan 27 '18 at 22:16
0

So obviously a macOS issue. Seeking an alternative approach this was helpful How to show PIL images on the screen?.

For my purposes I did not use PIL but used a lower rated answer suggesting the macOS native image viewer. I replaced load_img() and test() above with

def sync_show(image):
    """
    Blocking
    macOS method of displaying image in a preview pane
    """
    os.system("open {}".format(image))
    return


async def test_show(img):
    """use ProcessPoolExecutor"""
    await loop.run_in_executor(executor, sync_show, img)
    return
Chanonry
  • 423
  • 7
  • 19