- added some type aliases
- an overridden compress_frame in a class subclassing IJTiffWrite can now write multiple frames
This commit is contained in:
4
.github/workflows/pytest.yml
vendored
4
.github/workflows/pytest.yml
vendored
@@ -8,12 +8,12 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10", "3.12"]
|
python-version: ["3.10", "3.12"]
|
||||||
os: [ubuntu-20.04, windows-2019, macOS-11]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install
|
- name: Install
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ or
|
|||||||
tiffwrite('file.tif', image, 'TCXY')
|
tiffwrite('file.tif', image, 'TCXY')
|
||||||
|
|
||||||
## Write one frame at a time
|
## Write one frame at a time
|
||||||
from itertools import product
|
|
||||||
from tiffwrite import IJTiffFile
|
from tiffwrite import IJTiffFile
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@@ -76,7 +75,6 @@ or
|
|||||||
tif.save(np.random.randint(0, 10, (32, 32)), c, z, t)
|
tif.save(np.random.randint(0, 10, (32, 32)), c, z, t)
|
||||||
|
|
||||||
## Saving multiple tiffs simultaneously
|
## Saving multiple tiffs simultaneously
|
||||||
from itertools import product
|
|
||||||
from tiffwrite import IJTiffFile
|
from tiffwrite import IJTiffFile
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@@ -89,7 +87,7 @@ or
|
|||||||
tif_b.save(np.random.randint(0, 10, (32, 32)), c, z, t)
|
tif_b.save(np.random.randint(0, 10, (32, 32)), c, z, t)
|
||||||
|
|
||||||
## Tricks & tips
|
## 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.
|
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
|
- Using the colormap parameter you can make ImageJ open the file and apply the colormap. colormap='glasbey' is very
|
||||||
useful.
|
useful.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "tiffwrite"
|
name = "tiffwrite"
|
||||||
version = "2024.4.0"
|
version = "2024.10.0"
|
||||||
description = "Parallel tiff writer compatible with ImageJ."
|
description = "Parallel tiff writer compatible with ImageJ."
|
||||||
authors = ["Wim Pomp, Lenstra lab NKI <w.pomp@nki.nl>"]
|
authors = ["Wim Pomp, Lenstra lab NKI <w.pomp@nki.nl>"]
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
@@ -16,7 +16,7 @@ numpy = "*"
|
|||||||
tqdm = "*"
|
tqdm = "*"
|
||||||
colorcet = "*"
|
colorcet = "*"
|
||||||
matplotlib = "*"
|
matplotlib = "*"
|
||||||
parfor = ">=2024.3.0"
|
parfor = ">=2024.9.2"
|
||||||
pytest = { version = "*", optional = true }
|
pytest = { version = "*", optional = true }
|
||||||
mypy = { version = "*", optional = true }
|
mypy = { version = "*", optional = true }
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ except Exception: # noqa
|
|||||||
__version__ = "unknown"
|
__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,
|
def tiffwrite(file: str | Path, data: np.ndarray, axes: str = 'TZCXY', dtype: DTypeLike = None, bar: bool = False,
|
||||||
*args: Any, **kwargs: Any) -> None:
|
*args: Any, **kwargs: Any) -> None:
|
||||||
""" file: string; filename of the new tiff file
|
""" 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.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.frame_extra_tags: dict[tuple[int, int, int], dict[int, Tag]] = {}
|
||||||
self.fh = FileHandle(self.path)
|
self.fh = FileHandle(self.path)
|
||||||
|
self.pool = ParPool(self.compress_frame) # type: ignore
|
||||||
self.hashes = PoolSingleton().manager.dict()
|
self.hashes = PoolSingleton().manager.dict()
|
||||||
self.pool = ParPool(self.compress_frame)
|
|
||||||
self.main_process = True
|
self.main_process = True
|
||||||
|
|
||||||
with self.fh.lock() as fh: # noqa
|
with self.fh.lock() as fh: # noqa
|
||||||
@@ -477,13 +482,14 @@ class IJTiffFile:
|
|||||||
if self.main_process:
|
if self.main_process:
|
||||||
ifds, strips = {}, {}
|
ifds, strips = {}, {}
|
||||||
for n in list(self.pool.tasks):
|
for n in list(self.pool.tasks):
|
||||||
framenr, channel = self.get_frame_number(n)
|
for ifd, strip, delta in self.pool[n]:
|
||||||
ifds[framenr], strips[(framenr, channel)] = 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()
|
self.pool.close()
|
||||||
with self.fh.lock() as fh: # noqa
|
with self.fh.lock() as fh: # noqa
|
||||||
for n, tags in self.frame_extra_tags.items():
|
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)
|
ifds[framenr].update(tags)
|
||||||
if 0 in ifds and self.colormap is not None:
|
if 0 in ifds and self.colormap is not None:
|
||||||
ifds[0][320] = Tag('SHORT', self.colormap_bytes)
|
ifds[0][320] = Tag('SHORT', self.colormap_bytes)
|
||||||
@@ -547,14 +553,17 @@ class IJTiffFile:
|
|||||||
fh.write(bvalue)
|
fh.write(bvalue)
|
||||||
return offset
|
return offset
|
||||||
|
|
||||||
def compress_frame(self, frame: np.ndarray) -> tuple[IFD, tuple[list[int], list[int]]]:
|
def compress_frame(self, frame: np.ndarray) -> Sequence[FrameInfo]:
|
||||||
""" This is run in a different process"""
|
""" 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))
|
stripbytecounts, ifd, chunks = self.get_chunks(self.ij_tiff_frame(frame))
|
||||||
stripbyteoffsets = []
|
stripbyteoffsets = []
|
||||||
with self.fh.lock() as fh: # noqa
|
with self.fh.lock() as fh: # noqa
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
stripbyteoffsets.append(self.write(fh, chunk))
|
stripbyteoffsets.append(self.write(fh, chunk))
|
||||||
return ifd, (stripbyteoffsets, stripbytecounts)
|
return (ifd, (stripbyteoffsets, stripbytecounts), (0, 0, 0)),
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_chunks(frame: bytes) -> tuple[list[int], IFD, list[bytes]]:
|
def get_chunks(frame: bytes) -> tuple[list[int], IFD, list[bytes]]:
|
||||||
|
|||||||
Reference in New Issue
Block a user