KipCrossing / geotiff

A noGDAL tool for reading and writing geotiff files
GNU Lesser General Public License v2.1
213 stars 23 forks source link

File connection opened by read_box is not subsequently closed #69

Open MattArran opened 7 months ago

MattArran commented 7 months ago

Hi!

Thanks for the package- it's good to have a GeoTIFF reader that doesn't require struggling with GDAL. Unfortunately, I've been running into a bug in applying geotiff to my use case, in which I need to extract, process, and then remove GeoTIFFs, in Windows. Specifically, GeoTiff.read_box successfully reads data from a GeoTIF file but doesn't close the file connection, so that subsequent attempts to remove the file fail.

Example, using the downloaded GeoTiff https://download.osgeo.org/geotiff/samples/usgs/o41078a7.tif:

>>> from geotiff import GeoTiff
>>> import os
>>> geo_tiff = GeoTiff('o41078a7.tif')
>>> array = geo_tiff.read_box(((-78.85, 41.06), (-78.83, 41.04)))
>>> os.remove('o41078a7.tif')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'o41078a7.tif'

Examining the code, I've identified the problem as the processing of the ZarrTiffStore produced by tif.aszarr in __init__: while store.close() closes the connection, it's reopened by slicing of the Zarr array self._z and, while the Zarr array itself doesn't need closing, the store connection does:

>>> import os
>>> from tifffile import TiffFile
>>> import zarr
>>> tiff = TiffFile('o41078a7.tif')
>>> store = tiff.aszarr()
>>> zarray = zarr.open(store, mode='r')
>>> store.close()
>>> tiff.close()
>>> zarray[:4,:4]
array([[1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 1, 1, 1]], dtype=uint8)
>>> os.remove('o41078a7.tif')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'o41078a7.tif'
>>> store.close()
>>> os.remove('o41078a7.tif')
>>> os.path.isfile('o41078a7.tif')
False

Environment:

Windows 11 Enterprise requirements.txt

MattArran commented 7 months ago

I suspect the cleanest approach is to make GeoTiff a derived class of TiffFile, inheriting .close() and the with context managers. But I imagine there's a reason you've not followed that route, so will work on a quick fix with connections closed separately.