From a733d8a820712fc2db3049d9a663eabd2f5d2ebb Mon Sep 17 00:00:00 2001 From: Wim Pomp Date: Wed, 2 Oct 2024 13:58:12 +0200 Subject: [PATCH] - added some type aliases - an overridden compress_frame in a class subclassing IJTiffWrite can now write multiple frames --- .github/workflows/pytest.yml | 4 ++-- README.md | 4 +--- pyproject.toml | 4 ++-- tiffwrite/__init__.py | 23 ++++++++++++++++------- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 59903e7..c61decd 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -8,12 +8,12 @@ jobs: strategy: matrix: python-version: ["3.10", "3.12"] - os: [ubuntu-20.04, windows-2019, macOS-11] + os: [ubuntu-latest, windows-latest, macOS-latest] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install diff --git a/README.md b/README.md index 752ffac..bb9af0a 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,6 @@ or tiffwrite('file.tif', image, 'TCXY') ## Write one frame at a time - from itertools import product from tiffwrite import IJTiffFile import numpy as np @@ -76,7 +75,6 @@ or tif.save(np.random.randint(0, 10, (32, 32)), c, z, t) ## Saving multiple tiffs simultaneously - from itertools import product from tiffwrite import IJTiffFile import numpy as np @@ -89,7 +87,7 @@ or tif_b.save(np.random.randint(0, 10, (32, 32)), c, z, t) ## Tricks & tips -- The order of feeding frames to IJTiffFile is unimportant, IJTiffFile will order de ifd's such that the file will +- The order of feeding frames to IJTiffFile is unimportant, IJTiffFile will order the ifd's such that the file will be opened as a correctly ordered hyperstack. - Using the colormap parameter you can make ImageJ open the file and apply the colormap. colormap='glasbey' is very useful. diff --git a/pyproject.toml b/pyproject.toml index f24c6ed..dd29aba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tiffwrite" -version = "2024.4.0" +version = "2024.10.0" description = "Parallel tiff writer compatible with ImageJ." authors = ["Wim Pomp, Lenstra lab NKI "] license = "GPL-3.0-or-later" @@ -16,7 +16,7 @@ numpy = "*" tqdm = "*" colorcet = "*" matplotlib = "*" -parfor = ">=2024.3.0" +parfor = ">=2024.9.2" pytest = { version = "*", optional = true } mypy = { version = "*", optional = true } diff --git a/tiffwrite/__init__.py b/tiffwrite/__init__.py index f2dfcff..65d9660 100755 --- a/tiffwrite/__init__.py +++ b/tiffwrite/__init__.py @@ -31,6 +31,11 @@ except Exception: # noqa __version__ = "unknown" +type Strip = tuple[list[int], list[int]] +type CZT = tuple[int, int, int] +type FrameInfo = tuple[IFD, Strip, CZT] + + def tiffwrite(file: str | Path, data: np.ndarray, axes: str = 'TZCXY', dtype: DTypeLike = None, bar: bool = False, *args: Any, **kwargs: Any) -> None: """ file: string; filename of the new tiff file @@ -389,8 +394,8 @@ class IJTiffFile: self.nframes = np.prod(self.shape[1:]) if self.colormap is None and self.colors is None else np.prod(self.shape) self.frame_extra_tags: dict[tuple[int, int, int], dict[int, Tag]] = {} self.fh = FileHandle(self.path) + self.pool = ParPool(self.compress_frame) # type: ignore self.hashes = PoolSingleton().manager.dict() - self.pool = ParPool(self.compress_frame) self.main_process = True with self.fh.lock() as fh: # noqa @@ -477,13 +482,14 @@ class IJTiffFile: if self.main_process: ifds, strips = {}, {} for n in list(self.pool.tasks): - framenr, channel = self.get_frame_number(n) - ifds[framenr], strips[(framenr, channel)] = self.pool[n] + for ifd, strip, delta in self.pool[n]: + framenr, channel = self.get_frame_number(tuple(i + j for i, j in zip(n, delta))) # type: ignore + ifds[framenr], strips[(framenr, channel)] = ifd, strip self.pool.close() with self.fh.lock() as fh: # noqa for n, tags in self.frame_extra_tags.items(): - framenr, channel = self.get_frame_number(n) + framenr, _ = self.get_frame_number(n) ifds[framenr].update(tags) if 0 in ifds and self.colormap is not None: ifds[0][320] = Tag('SHORT', self.colormap_bytes) @@ -547,14 +553,17 @@ class IJTiffFile: fh.write(bvalue) return offset - def compress_frame(self, frame: np.ndarray) -> tuple[IFD, tuple[list[int], list[int]]]: - """ This is run in a different process""" + def compress_frame(self, frame: np.ndarray) -> Sequence[FrameInfo]: + """ This is run in a different process. Turns an image into bytes, writes them and returns the ifd, strip info + and czt delta. When subclassing IJTiffWrite this can be overridden to write one or more (using czt delta) + frames. + """ stripbytecounts, ifd, chunks = self.get_chunks(self.ij_tiff_frame(frame)) stripbyteoffsets = [] with self.fh.lock() as fh: # noqa for chunk in chunks: stripbyteoffsets.append(self.write(fh, chunk)) - return ifd, (stripbyteoffsets, stripbytecounts) + return (ifd, (stripbyteoffsets, stripbytecounts), (0, 0, 0)), @staticmethod def get_chunks(frame: bytes) -> tuple[list[int], IFD, list[bytes]]: