Forum Archive

PIL Image and Closing DataStreams/ImageFile

stephen

good day Pythonistas!


I was having an issue with Image|pillow|PIL (your choice 😅) with File Context for Image.open(), Image.load() and Image.close().

>I was reciveing 19 warnings for: ↴

__warningregistry__[("unclosed file <_io܂BufferedReader name='my_image܂png'>", <class 'ResourceWarning'>, 8)]

  • This Warning was located at the end (bottom) of the Console Inspector

Example of my use at the time


from PIL import Image

img = Image.open('my_image.png')
img.load() # This will have been my first issue besides bad practice
img = img.resize((256, 256), 1) # The 1 in pos2 is for AA, original was 3600x3600
img.save('resized.png', 'png') # pos2 not needed for str type filename but good practice

Example Corrected

⒈ General Implementation ↴


from PIL import Image

with Image.open('my_image.png') as img:
    img = img.resize((256, 256), 1)
    img.save('resized.png', 'png')

⒉ Dated (I believe) Implementation ↴


from PIL import Image

try:
    img = Image.open('my_image.png')
    img = img.resize((256, 256), 1)
    img.save('resized.png', 'png')

finally:
    img.close()

⒊ Alternative (this is what I went with) Implementation ↴


from PIL import Image

try:
    with open('my_image.png', 'rb') as f:
    img = Image.open(f)
    img = img.resize((256, 256), 1)
    img.save('resized.png', 'png')


finally:
    f.close()
    img.close()

This is how I understand whats going on..

  1. img.open(fn) Opens the image BUT does not load any of the data

  2. img.load() Loads the pixel data to the stream

  3. Calling on an operation to the Image Object the first time ALSO load pixel data to the stream. In this case img.resize((w, h), filter)
  4. img.save(fn, format) Closes Image.

problem was both img.load() and img.resize((w, h), filter) each loaded thier own separate DataStream.. i assume the auto-implemented steam was the primary.

Finally

If your running your PIL Image through operations... one shouldn't call load() explicitly if you working with Single Paged Images.

---

This worked wonderfully... till i found that ☝︎ still remained...

my_image.tiff ...

Turns out that "tiff format will always be treated as multipage..

" 𝄆𝄞♫♪⁼𝄫 ... should of read the brochure 🧐..."

I tried many Approches to this part.. And after hours of wonderfull Warning Messages, decided i didnt NEED the tiff file so i changed format to png and problem "disapeared"..

---

According to everything I gathered, both of my issues are very common while using Pill across the whole Python comunity. so I came here fore hopes that my first segment will help someone and that even though I removed my problem I would like to try to understand what is happening with the tiff inside PIL to produce such an issue..

Thank You!


Here is my current snippet for anyone that wanted or needed



class Loop(scene.Scene, metaclass=GCore):
    def __init__(self, *args, **kwargs):
        s.Scene.__init__(self, *args, **kwargs)
        super().setup()
        GCore.PopulateCache(self)


    def setup(self):
        try:
            ```
            cache starts with allnthe values being Str paths
            pulled from .txt file.

            and then here wevchange that to out Texture objects to use in 
            the next initialization stage.
            ```
            for k,v in self.cache.items():

                with open(v, 'rb') as f:

                    img = Image.open(f) 

                    img=img.resize((int(img.size[0]/2*self.ScaleMod), 
                                    int(img.size[1]/2*self.ScaleMod)), 1)

                    ```
                    Convert Image back to Byte Data so we can implement
                    the scale for Retina Screens with from_data in ui.Image
                    ```
                    iodata = io.BytesIO()   
                    img.save(iodata, 'png')

                    ns.cache[k]=scene.Texture(ui.Image.from_data(iodata.getvalue(), 
                                                    scene.get_screen_scale()))      
        ```
        finally close all image and datastream objects.
        then add the scene.Texture to the Cache Dict.
        ```                                     
        finally:
            iodata.close()

            f.close()
            img.close()
            del img
            del f
            del iodata

            for x in locals():
                print(x)

---


mikael

@stephen, I think your examples share an issue where you assign the resized and copied image to a variable with the same name as the original image, thus losing the reference to the original and then closing the wrong thing.

stephen

Hello there @mikael !

I see what your saying but what you cannot see is originally (and before i fixed the duplicate streams) i had somthing similar to this:


img = Image.open('my_image.png')
img.load() 
img_rs = img.resize((256, 256), 1)

with io.BytesIO()   as iodata:
    img_rs.save(iodata, 'png')

    texture=scene.Texture(ui.Image.from_data(iodata.getvalue(), ...

...

i changed it to img=img... to reduce code knowing ill never need that exact ref to original again during this loop session.reason is i place the Texture object in a cache dict and from here on is called from there 🤓🤓

EDIT

@mikael
I also forgot to include my finally block that handles any mishaps once caching is complete..

    finally:
            iodata.close()

            f.close()
            img.close()
            del img
            del f
            del iodata

            for x in locals():
                print(x)