As can be seen from PIL's code at https://pillow.readthedocs.io/en/5.3.x/_modules/PIL/ImageShow.html , the show method in images will ultimately call os.system with a heuristically determined command to display the image. The problem, as can be seen here, is, that the return value of the os.system call is ignored, so, even when the image is not displayed, the program will never know that.
You could try monkey-patching os.system to raise an error if the original os.system returns anything other than zero, and then you'd have an exception.
However, upon attempting it this way, I noticed that for my default viewer, it returns "0" as exit code anyway, even with a broken display, so it is not detectable with "system". You'd have to monkeypatch os.system with a function that would make a proper subprocess.Popen call and check not only the exit-code, but also the sdterr of the called command - it's starting to get tricky.
So, a second, simpler, and possibly safer route, is to have your program check the contents of the DISPLAY environment variable. If it is empty or invalid, any attempt to use the X display will fail:
import os
if not os.environ.get("DISPLAY"):
raise RuntimeError("Display is not properly set. Aborting!")
Why is it that way? Historically PIL's (the original PIL, not the currently maintained Pillow) show code was a simple, hardcoded call to xv - it was probably found to be only useful for debugging purposes by the original library owners. Although today it is more sophisticated, looking for a suitable external viewer among a couple of (still hardcoded) options, is not a "first class UI" in any way. I think it is proper for your described use-case, but for anything beyond that, maybe the first option would be to move to matplotlib to display the images in a UI which is better controlled from the Python code, without having to resort to write full GUI code with Qt or Tkinter.