- Windows compatibility (no forking -> no sharing open file descriptors)

This commit is contained in:
Wim Pomp
2022-10-16 14:10:12 +02:00
parent 86e4b28499
commit 4fceb59867
3 changed files with 33 additions and 20 deletions

View File

@@ -5,7 +5,7 @@ with open('README.md', 'r') as fh:
setuptools.setup( setuptools.setup(
name='tiffwrite', name='tiffwrite',
version='2022.10.1', version='2022.10.2',
author='Wim Pomp @ Lenstra lab NKI', author='Wim Pomp @ Lenstra lab NKI',
author_email='w.pomp@nki.nl', author_email='w.pomp@nki.nl',
description='Parallel tiff writer compatible with ImageJ.', description='Parallel tiff writer compatible with ImageJ.',

14
test.py Normal file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/python
import tiffwrite
import numpy as np
from itertools import product
def test():
with tiffwrite.IJTiffWriter('test.tif', (3, 4, 5)) as tif:
for c, z, t in product(range(3), range(4), range(5)):
tif.save(np.random.randint(0, 255, (64, 64)), c, z, t)
if __name__ == '__main__':
test()

View File

@@ -357,9 +357,9 @@ class IJTiffFile:
self.spp = self.shape[0] if self.colormap is None and self.colors is None else 1 # samples/pixel self.spp = self.shape[0] if self.colormap is None and self.colors is None else 1 # samples/pixel
self.nframes = np.prod(self.shape[1:]) if self.colormap is None and self.colors is None else np.prod(self.shape) self.nframes = np.prod(self.shape[1:]) if self.colormap is None and self.colors is None else np.prod(self.shape)
self.offsets = {} self.offsets = {}
self.fh = FileHandle(path, 'w+b') self.fh = FileHandle(path)
self.namespace_manager = multiprocessing.Manager() manager = multiprocessing.Manager()
self.hashes = self.namespace_manager.dict() self.hashes = manager.dict()
self.strips = {} self.strips = {}
self.ifds = {} self.ifds = {}
self.frame_extra_tags = {} self.frame_extra_tags = {}
@@ -503,7 +503,6 @@ class IJTiffFile:
with self.fh.lock() as fh: with self.fh.lock() as fh:
if len(self.frames_added) == 0: if len(self.frames_added) == 0:
warn('At least one frame should be added to the tiff, removing file.') warn('At least one frame should be added to the tiff, removing file.')
fh.close()
os.remove(self.path) os.remove(self.path)
else: else:
if len(self.frames_written) < np.prod(self.shape): # add empty frames if needed if len(self.frames_written) < np.prod(self.shape): # add empty frames if needed
@@ -541,7 +540,6 @@ class IJTiffFile:
self.ifds[framenr].write_offset(self.ifds[framenr - 1].where_to_write_next_ifd_offset) self.ifds[framenr].write_offset(self.ifds[framenr - 1].where_to_write_next_ifd_offset)
else: else:
self.ifds[framenr].write_offset(self.header.offset - self.header.offsetsize) self.ifds[framenr].write_offset(self.header.offset - self.header.offsetsize)
fh.close()
def __enter__(self): def __enter__(self):
return self return self
@@ -677,27 +675,28 @@ class PoolManager:
class FileHandle: class FileHandle:
""" Process safe file handle """ """ Process safe file handle """
def __init__(self, name, mode='rb'): def __init__(self, name):
if os.path.exists(name):
os.remove(name)
with open(name, 'xb'):
pass
self.name = name self.name = name
self.mode = mode manager = multiprocessing.Manager()
self._lock = multiprocessing.RLock() self._lock = manager.RLock()
self._fh = open(name, mode, 0) self._pos = manager.Value('i', 0)
def __enter__(self):
return self
def __exit__(self, *args, **kwargs):
self.close()
def close(self):
self._fh.close()
@contextmanager @contextmanager
def lock(self): def lock(self):
self._lock.acquire() self._lock.acquire()
f = None
try: try:
yield self._fh f = open(self.name, 'rb+')
f.seek(self._pos.value)
yield f
finally: finally:
if f is not None:
self._pos.value = f.tell()
f.close()
self._lock.release() self._lock.release()