FLIR has a custom image format for some of their cameras, for which I have made an image plugin. This works fine for single images, but for files with multiple frames, it fails to load the subsequent frames because the file pointer is closed.
This works:
import flir
from PIL import Image, ImageSequence
infile = 'example.seq'
for i in range(3):
im = Image.open(infile)
im.seek(i)
im.show()
print(im.tell())
Output:
Loading frame 0 at offset 0
0
Loading frame 0 at offset 0
Loading frame 1 at offset 166620
1
Loading frame 0 at offset 0
Loading frame 2 at offset 333112
2
But this doesn't work:
import flir
from PIL import Image, ImageSequence
infile = 'example.seq'
im = Image.open(infile)
for i in range(3):
im.seek(i)
im.show()
print(im.tell())
Output:
Loading frame 0 at offset 0
0
Traceback (most recent call last):
in <module>
im.seek(i)
File "flir.py", line 86, in seek
self.open_rel(offset)
File "flir.py", line 36, in open_rel
self.fp.seek(offset)
^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'seek'
Here's an example of seeking with the webp decoder. This doesn't complain of the iteration, but shows the last frame every time even though I only attempt to seek the first three.
from PIL import Image, ImageSequence
im = Image.open('animated_hopper.webp')
for i in range(3):
im.seek(i)
im.show()
print(im.tell()) #Prints the last frame every time?
Output:
3
3
3
I have tried writing my own load_seek()
and load_read()
functions, but this does not help when the file pointer has been closed.
I have tried using the ImageSequence.Iterator, but this method just calls seek() anyway and has the same result. https://pillow.readthedocs.io/en/stable/_modules/PIL/ImageSequence.html#Iterator
Here's the source code for my plugin: https://gist.github.com/kamocat/aa93f3a062723a99666af2235c95b5ab I will see about uploading a sample image.
EDIT:
Image.Open
accepts a file pointer as an alternative to a file path. I thought if I opened the file myself, Pillow wouldn't close it. Unfortunately this did not change the matter.import flir
from PIL import Image, ImageSequence
infile = 'example.seq'
with open(infile, 'rb') as f:
im = Image.open(f)
for i in range(3):
im.seek(i)
im.show()
print(im.tell())
ImageFile.seek()
and that it should return itself.
Accordingly I tried:def seek(self, pos: int) -> ImageFile.ImageFile:
#FIXME: This depends on known image size
# This is currently necessary because the file pointer is closed between frames
if pos == self.tell():
return self
if pos > 0:
offset = pos * self.stride + 0x80
else:
offset = 0
self.open_rel(offset)
return self
but this also did not work.
It turns out there are two reasons for this, both stemming from the same piece of code in ImageFile.py
if self._exclusive_fp and self._close_exclusive_fp_after_loading:
self.fp.close()
self.fp = None
If a file path is passed into Image.open()
then it will be closed, but even if a file is opened explicitly it will be closed by the garbage collector after self.fp
is assigned to None.
The solution takes three changes:
_open()
, set __close_exclusive_fp_after_loading=self.is_animated
open_rel
, preserve the file pointer with self._fp = self.fp
seek()
, restore the file pointer with self.fp = self._fp