From 4fceb5986748958d91c2cc595b08357fb32a0c30 Mon Sep 17 00:00:00 2001 From: Wim Pomp Date: Sun, 16 Oct 2022 14:10:12 +0200 Subject: [PATCH] - Windows compatibility (no forking -> no sharing open file descriptors) --- setup.py | 2 +- test.py | 14 ++++++++++++++ tiffwrite/__init__.py | 37 ++++++++++++++++++------------------- 3 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 test.py diff --git a/setup.py b/setup.py index 2a6f810..2c87984 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open('README.md', 'r') as fh: setuptools.setup( name='tiffwrite', - version='2022.10.1', + version='2022.10.2', author='Wim Pomp @ Lenstra lab NKI', author_email='w.pomp@nki.nl', description='Parallel tiff writer compatible with ImageJ.', diff --git a/test.py b/test.py new file mode 100644 index 0000000..e958cd7 --- /dev/null +++ b/test.py @@ -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() diff --git a/tiffwrite/__init__.py b/tiffwrite/__init__.py index 0e8e756..8fa317a 100755 --- a/tiffwrite/__init__.py +++ b/tiffwrite/__init__.py @@ -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.nframes = np.prod(self.shape[1:]) if self.colormap is None and self.colors is None else np.prod(self.shape) self.offsets = {} - self.fh = FileHandle(path, 'w+b') - self.namespace_manager = multiprocessing.Manager() - self.hashes = self.namespace_manager.dict() + self.fh = FileHandle(path) + manager = multiprocessing.Manager() + self.hashes = manager.dict() self.strips = {} self.ifds = {} self.frame_extra_tags = {} @@ -503,7 +503,6 @@ class IJTiffFile: with self.fh.lock() as fh: if len(self.frames_added) == 0: warn('At least one frame should be added to the tiff, removing file.') - fh.close() os.remove(self.path) else: 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) else: self.ifds[framenr].write_offset(self.header.offset - self.header.offsetsize) - fh.close() def __enter__(self): return self @@ -677,27 +675,28 @@ class PoolManager: class FileHandle: """ 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.mode = mode - self._lock = multiprocessing.RLock() - self._fh = open(name, mode, 0) - - def __enter__(self): - return self - - def __exit__(self, *args, **kwargs): - self.close() - - def close(self): - self._fh.close() + manager = multiprocessing.Manager() + self._lock = manager.RLock() + self._pos = manager.Value('i', 0) @contextmanager def lock(self): self._lock.acquire() + f = None try: - yield self._fh + f = open(self.name, 'rb+') + f.seek(self._pos.value) + yield f finally: + if f is not None: + self._pos.value = f.tell() + f.close() self._lock.release()