Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image.open on Ubuntu 20.04 gives the error "No images found" in the viewer #5945

Closed
abarker opened this issue Jan 9, 2022 · 37 comments
Closed
Labels

Comments

@abarker
Copy link

abarker commented Jan 9, 2022

The viewer is opened, but a "No images found" error is displayed. Pillow version pillow-9.0.0. This may be the same bug: https://stackoverflow.com/questions/63579415/why-does-pil-fail-to-display-images-with-error-no-images-found-in-file-tmp

Code to reproduce:

from PIL import Image

im = Image.open(r"/var/lib/app-info/icons/ubuntu-focal-updates-universe/48x48/inkscape_inkscape.png")
im.show()

Changing the code in the UnixViewer class in ImageShow.py seems to fix it. I'm not sure why the current code is writing the filename to a file and then reading it back from stdin, using shell=True in Popen. Deleting the rm -f $im" command in the Popen call seems to fix the problem. It also seems to work using cat | xargs -L1 in the Popen command instead of setting the shell variable im.

This simpler code replacement also seems to work, without setting shell=True:

def show_file(self, file, **options):
    """Display given file"""
    command = self.get_command_ex(file, **options)[0]
    subprocess.Popen([command, file])
    return 1
@radarhere
Copy link
Member

Why is the filename being written to the file? That was added in #3450 for the sake of security - so that nothing in the filename would be unintentionally interpreted as command line syntax.

The rm -f code is there to clean up after ourselves - to remove the temporary file once it has been passed to the image viewer.

A question - what viewer is being opened?

@radarhere radarhere added the Linux label Jan 9, 2022
@MayankFawkes
Copy link

Same .show() is not working for me too, it says file not found and i even checked manually there is no file in /tmp dir
i use ubuntu 20.04 with python 3.8.10

Installing collected packages: pillow
  Attempting uninstall: pillow
    Found existing installation: Pillow 9.0.0
    Uninstalling Pillow-9.0.0:
      Successfully uninstalled Pillow-9.0.0
Successfully installed pillow-8.4.0

@radarhere
Copy link
Member

What image viewer is being opened?

@MayankFawkes
Copy link

MayankFawkes commented Jan 9, 2022

Ubuntu default image viewer eog, i even checked myself while keeping the viewer open ls /tmp but i cant found any image file there, seems pillow is not creating a tmp image.

@abarker
Copy link
Author

abarker commented Jan 9, 2022

It is calling xdg-open, which opens GNOME image viewer.

The full error message is: No images found in “file:///tmp/tmp7zxoxrke.PNG”. This is the same file that is being passed in as file. Adding sleep 3; before the rm seems to make it work.

@MayankFawkes
Copy link

ye i guess pillow deleting the file way more faster than it the viewer opening it. adding sleep might work.

@radarhere
Copy link
Member

The full error message is: No images found in “file:///tmp/tmp7zxoxrke.PNG”. This is the same file that is being passed in as file. Adding sleep 3; before the rm seems to make it work.

Thanks for testing that out. Would you be able to test out #5950 to see if that fixes the problem for you?

@hugovk
Copy link
Member

hugovk commented Jan 10, 2022

Would be interesting to hear which image viewer is used in Pillow 8.4.0, before xdg-open was added in latest 9.0.0.

I don't have Linux to test this, but one theory on what may be happening.

Explicit image viewer

  1. Pillow tells the viewer to open an image
  2. The viewer opens the image and returns back to Pillow
  3. Pillow deletes the image

xdg-open

  1. Pillow tells xdg-open to open an image
  2. xdg-open receives the instruction and returns back to Pillow
    3a. In parallel: Pillow deletes the image
    3b. In parallel: xdg-open tells the default viewer to open the image. Has it been deleted yet?

@MayankFawkes
Copy link

MayankFawkes commented Jan 10, 2022

agreed

@inactivist
Copy link

inactivist commented Jan 10, 2022

Thanks for testing that out. Would you be able to test out #5950 to see if that fixes the problem for you?

This seems to solve the problem on my 20.04 test environment.

@abarker
Copy link
Author

abarker commented Jan 10, 2022

@radarhere, I ran #5950 and it worked, but the program behavior has changed. It now opens the image using ImageMagick rather than using GNOME image viewer as before. It is no longer calling xdg-open.

This might be masking the problem with xdg-open and GNOME image viewer.

@radarhere
Copy link
Member

Thanks for testing.

@abarker if the image is still needed after we've finished calling xdg-open, then there's no way for us to know how long it is needed for. sleep 3 might fix the problem for you, but for someone with a slower machine, it might need to be sleep 4. This is a race condition. The suggestion in my PR prefers the known success of opening ImageMagick on your machine over the possible failure involved in opening GNOME image viewer.

@drunksaint
Copy link

this fix works for me too

@Lunaphied
Copy link

I'm not sure if the best place to discuss this is here or in the PR; after debugging this exact same issue I don't think this is the best approach. The issue is indeed that xdg-open does not block the next command and launches the registered handler in the background. While the race condition is not ideal, every single other platform viewer encounters the same issue and solves this with a delay; Windows ends up including a ping command simply to introduce this delay.

I think most users would expect and desire that the default image viewer application is prioritized for this usage. Ideally you would let the specific ordering be supplied as an optional argument and valid options would be attempted in the supplied order. In the (albeit somewhat unlikely event) that none of the other viewers are installed, users will still encounter completely broken behavior when xdg-open gets used.

I think the best solution here is to both introduce the same sleep 20 already used by the MacViewer code into the UnixViewer code and hope for the best. Due to the way Linux works you're basically only racing the application loading enough to open a handle on the temporary file used, it doesn't even have to read data out of the image file.

@alv2017
Copy link

alv2017 commented Jan 14, 2022

I'm experiencing the same error on "20.04.3 LTS (Focal Fossa)". The difference is that I'm trying to open a *.jpg file.

  1. The script:
from PIL import Image

if __name__ == "__main__":
    # open image
    im = Image.open("images/vintage-cars.jpg")

    # show image
    im.show()
  1. When I run the script, I get an error message: "No images found in “file:///tmp/tmpt8lwgjrw.PNG”."

  2. Please note, that I'm trying to open a *.jpg file, and not a *.png

  3. When I apply the fix Moved xdg-open to lowest priority #5950, the image opens with ImageMagic, but again as *.png

@radarhere
Copy link
Member

@alv2017 by default, Pillow will always attempt to show your image as a PNG. This isn't a bug, but a feature - just because Pillow supports an image format doesn't mean that the format would then be supported by a given image viewer. PNG is a common format that a given image viewer will be able to handle. Yes, JPEG is also common, but a line had to be drawn somewhere, this was simplest, and so you have it.

If you would like to change the format that your image is shown in,

>>> from PIL import Image, ImageShow
>>> for _viewer in ImageShow._viewers:
...     _viewer.format = "JPEG"
... 
>>> im = Image.new("RGB", (100, 100))
>>> im.show()

If you disagree with my reasoning, please create a new issue about that specifically, to keep this thread neat.

@alv2017
Copy link

alv2017 commented Jan 19, 2022

@radarhere thanks for the answer,
I opened a new issue:
#5976
and I would like to work on it.

@BrettRyland
Copy link

So, I've just run into this issue after upgrading to Pillow 9.0.0 from 8.1.2. I use KDE and the default viewer with xdg-open is gwenview (which has this issue) vs the default with display being ImageMagick.
However, I don't want to use a full-fledged image browsing program to display images with Pillow, I want a quick and simple viewer, which is what ImageMagick provides.
Ideally, the preferred viewer ought to be configurable via a config file in the user's home directory (e.g., ~/.config/Pillow/settings.cfg). The current method of subclassing the Viewer class, as suggested by the docs, and registering it as another viewer is far too complicated for such a simple configuration issue.

@radarhere
Copy link
Member

This should now be resolved in Pillow 9.0.1. Let us know if you are still experiencing a problem.

@modwizcode @abarker this was fixed by adding a delay after all, so xdg-open should still be used in Pillow 9.0.1, like 9.0.0.

@BrettRyland I'd like to relocate discussion of your comment to #5976 if you don't mind

@BrettRyland
Copy link

BrettRyland commented Feb 3, 2022

Hi, sorry but this needs re-opening as the "fix" doesn't work. You need to monitor the process and only remove the file once the process using it closes. After the 20s delay runs out, the image is removed and the viewer (gwenview in this case) automatically updates to account for the image disappearing.
I've made a short clip of this happening https://youtu.be/9dT3_gUdKPI

@radarhere radarhere reopened this Feb 3, 2022
@BrettRyland
Copy link

Since you appear to be using subprocess.Popen to launch the viewer, you ought to be able to use the poll and wait commands in subprocess to check for the process exiting. This should work for any process that doesn't fork (e.g., ImageMagick), but won't for any that do (e.g., xdg-open). A quick search indicates that for forked processes, getting the PID of the child process is non-trivial.

@hugovk
Copy link
Member

hugovk commented Feb 3, 2022

Would it be a bad idea to stop auto-deleting temporary files and leave it to the operating system or Pillow user to take care of it?

Windows apparently doesn't clear the %TEMP% directory, so that could be an exception.

https://superuser.com/a/296827/83235

macOS: /tmp deleted every three days or so.

https://superuser.com/a/187105/83235

Linux: old answers but it varies from boot to a number of hours.

https://serverfault.com/q/377348/169566

@BrettRyland
Copy link

BrettRyland commented Feb 3, 2022

Based on this stackoverflow post, it's possible to query which command would be run by xdg-open, which avoids the issue of it forking and then allows waiting on the subprocess:

import os
import subprocess
from xdg import BaseDirectory, DesktopEntry
def xdg_query(command, parameter):
    p = subprocess.Popen(
        ["xdg-mime", "query", command, parameter],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    output, errors = p.communicate()
    if p.returncode or errors:
        raise XDGError(
            "xdg-mime returned error code %d: %s" % (p.returncode, errors.strip())
        )
    return output.strip()
desktop_filename = xdg_query('default', 'image/png')
res = BaseDirectory.load_data_paths('applications', desktop_filename.decode())
desktop_file = next(res) if res else None
entry = DesktopEntry.DesktopEntry(desktop_file) if desktop_file is not None and os.path.exists(desktop_file) else None

image_filename = 'repos/arctic-ice/data/unmarked/20190831-flight1_transparent_mosaic_group1.tif'
p = subprocess.Popen([entry.getExec().split(' ')[0],image_filename])
p.wait()

(Sorry the code's a bit messy, I was just getting it to work in the ipython prompt.)

Edit: Also, the .split(' ')[0] was to remove the %U from the command to be executed, which would probably need handling better if this is used.

@alv2017
Copy link

alv2017 commented Feb 3, 2022

https://github.com/alv2017/Pillow/tree/test_unix_viewers

please have a look (I have activated the workflows)

The solution is user-oriented, i.e. if the user created a Python program that shows the images,
the viewing processes will be managed by the Python program, and the program will be closed,
as soon as the images in the viewers are closed. The viewer processes are non-blocking.

@alv2017
Copy link

alv2017 commented Feb 3, 2022

@radarhere, @hugovk I noticed that this error is repeatedly showing up:

================================== FAILURES ===================================
_________________________________ test_sanity _________________________________

tmp_path = WindowsPath('C:/Users/runneradmin/AppData/Local/Temp/pytest-of-runneradmin/pytest-0/test_sanity8')

    @pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
    def test_sanity(tmp_path):
        # Segfault test
        app = QApplication([])
        ex = Example()
        assert app  # Silence warning
        assert ex  # Silence warning
    
        for mode in ("1", "RGB", "RGBA", "L", "P"):
            # to QPixmap
            im = hopper(mode)
            data = ImageQt.toqpixmap(im)
    
            assert isinstance(data, QPixmap)
            assert not data.isNull()
    
            # Test saving the file
            tempfile = str(tmp_path / f"temp_{mode}.png")
            data.save(tempfile)
    
            # Render the image
            qimage = ImageQt.ImageQt(im)
            data = QPixmap.fromImage(qimage)
            qt_format = QImage.Format if ImageQt.qt_version == "6" else QImage
            qimage = QImage(128, 128, qt_format.Format_ARGB32)
            painter = QPainter(qimage)
            image_label = QLabel()
            image_label.setPixmap(data)
            image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128))
            painter.end()
            rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png")
            qimage.save(rendered_tempfile)
            assert_image_equal_tofile(im.convert("RGBA"), rendered_tempfile)
    
            # from QPixmap
>           roundtrip(hopper(mode))

Tests/test_qt_image_qapplication.py:85: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Tests/test_qt_image_qapplication.py:47: in roundtrip
    assert_image_equal(result, expected.convert("RGB"))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

a = <PIL.PpmImagePlugin.PpmImageFile image mode=RGB size=128x128 at 0x23980B9D790>
b = <PIL.Image.Image image mode=RGB size=128x128 at 0x23980B9D7F0>, msg = None

    def assert_image_equal(a, b, msg=None):
        assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
        assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
        if a.tobytes() != b.tobytes():
            if HAS_UPLOADER:
                try:
                    url = test_image_results.upload(a, b)
                    logger.error(f"Url for test images: {url}")
                except Exception:
                    pass
    
>           assert False, msg or "got different content"
E           AssertionError: got different content
E           assert False

Tests/helper.py:98: AssertionError
------------------------------ Captured log call ------------------------------
ERROR    Tests.helper:helper.py:94 Url for test images: D:/a/Pillow/Pillow/Tests/errors/tmplryrz69l

@hugovk
Copy link
Member

hugovk commented Feb 3, 2022

Yep, one or two of the Qt tests are a bit flaky, we should consider something to rerun those (maybe with a https://pypi.org/project/flaky/ decorator) or perhaps even skip them on MinGW. (Or figure out the root cause and fix that...)

@alv2017
Copy link

alv2017 commented Feb 3, 2022

@hugovk this error shows up when: 1) a.mode == b.mode; 2) a.size == b.size; 3) a.tobytes() != b.tobytes();

@alv2017
Copy link

alv2017 commented Feb 3, 2022

@radarhere, @hugovk another problem I face (it is showing up from time to time on Windows setups):

Run actions/setup-python@v2
Successfully setup CPython (3.9.9)
C:\hostedtoolcache\windows\Python\3.9.9\x86\Scripts\pip.exe cache dir
Error: The process 'C:\hostedtoolcache\windows\Python\3.9.9\x86\Scripts\pip.exe' failed with exit code 3221225477

@nulano
Copy link
Contributor

nulano commented Feb 3, 2022

@radarhere, @hugovk another problem I face (it is showing up from time to time on Windows setups):

Run actions/setup-python@v2
Successfully setup CPython (3.9.9)
C:\hostedtoolcache\windows\Python\3.9.9\x86\Scripts\pip.exe cache dir
Error: The process 'C:\hostedtoolcache\windows\Python\3.9.9\x86\Scripts\pip.exe' failed with exit code 3221225477

Does not seem to be a Pillow issue, also reported here: actions/runner-images#5009

@MayankFawkes
Copy link

Well it seems 9.0.1 still have some bugs now the show command is working but it is not terminating the shell after closing the file viewer while 8.4.0 working so well.

@radarhere
Copy link
Member

another problem I face (it is showing up from time to time on Windows setups):

Run actions/setup-python@v2
Successfully setup CPython (3.9.9)
C:\hostedtoolcache\windows\Python\3.9.9\x86\Scripts\pip.exe cache dir
Error: The process 'C:\hostedtoolcache\windows\Python\3.9.9\x86\Scripts\pip.exe' failed with exit code 3221225477

Does not seem to be a Pillow issue, also reported here: actions/virtual-environments#5009

@alv2017 this should now been fixed

@radarhere
Copy link
Member

Well it seems 9.0.1 still have some bugs now the show command is working but it is not terminating the shell after closing the file viewer while 8.4.0 working so well.

@MayankFawkes that's interesting. What file viewer are you using?

@radarhere
Copy link
Member

#6045 has now been merged, so in the next release of Pillow, temporary image files will no longer be removed on Unix.
@alv2017 approves of closing this.

@BrettRyland are you ok for this to be resolved?

@BrettRyland
Copy link

BrettRyland commented Feb 19, 2022

It resolves the issue, but I would still prefer to be able to easily configure which viewer to be used instead of relying on the system default for opening image files as I think there is a significant difference in the purpose of an image viewer vs an image editor or gallery viewer and the system default does not provide a distinction between these.

@radarhere
Copy link
Member

Closing. The discussion about selecting the viewer can be continued in #5976

@nulano
Copy link
Contributor

nulano commented Mar 29, 2022

FWIW I just ran into this on Windows 10 after installing a new image editor. The first time I used im.show() the application selector showed up asking me to confirm I wish to continue using the builtin Photos app. After confirming, the Photos app opened with an error message "It appears that the file was moved or renamed." However, having made my selection, the next time I used im.show() the image opened in Photos without errors. I don't think there is a need to change the behaviour on Windows (at least with the Photos app), since this will only happen the first time it is used. Just wanted to note this here for future readers.

@hl037
Copy link

hl037 commented Apr 1, 2022

I know this is closed, but another "good" solution would be to delete the temporary image file when the image shown is garbage collected. And yet another would be to delete it when the interpreter shutsdown.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.