- use css colors to convert color names into rgb values
This commit is contained in:
10
Cargo.toml
10
Cargo.toml
@@ -1,7 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiffwrite"
|
name = "tiffwrite"
|
||||||
version = "2025.5.0"
|
version = "2025.8.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
rust-version = "1.85.1"
|
||||||
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
authors = ["Wim Pomp <w.pomp@nki.nl>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Write BioFormats/ImageJ compatible tiffs with zstd compression in parallel."
|
description = "Write BioFormats/ImageJ compatible tiffs with zstd compression in parallel."
|
||||||
@@ -18,15 +19,16 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
|
css-color = "0.2.8"
|
||||||
flate2 = "1.1.1"
|
flate2 = "1.1.1"
|
||||||
ndarray = "0.16.1"
|
ndarray = "0.16.1"
|
||||||
num = "0.4.3"
|
num = "0.4.3"
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
zstd = "0.13.3"
|
zstd = "0.13.3"
|
||||||
numpy = { version = "0.24.0", optional = true }
|
numpy = { version = "0.25.0", optional = true }
|
||||||
|
|
||||||
[dependencies.pyo3]
|
[dependencies.pyo3]
|
||||||
version = "0.24.0"
|
version = "0.25.1"
|
||||||
features = ["extension-module", "abi3-py310", "generate-import-lib", "anyhow", "multiple-pymethods"]
|
features = ["extension-module", "abi3-py310", "generate-import-lib", "anyhow", "multiple-pymethods"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ from warnings import warn
|
|||||||
import colorcet
|
import colorcet
|
||||||
import matplotlib
|
import matplotlib
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from matplotlib import colors as mpl_colors
|
|
||||||
from numpy.typing import ArrayLike, DTypeLike
|
from numpy.typing import ArrayLike, DTypeLike
|
||||||
from tqdm.auto import tqdm
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
from . import tiffwrite_rs as rs # noqa
|
from . import tiffwrite_rs as rs # noqa
|
||||||
|
|
||||||
__all__ = ['IJTiffFile', 'IJTiffParallel', 'FrameInfo', 'Tag', 'tiffwrite']
|
__all__ = ["IJTiffFile", "IJTiffParallel", "FrameInfo", "Tag", "tiffwrite"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__version__ = version(Path(__file__).parent.name)
|
__version__ = version(Path(__file__).parent.name)
|
||||||
@@ -27,11 +26,11 @@ FrameInfo = tuple[ArrayLike, int, int, int]
|
|||||||
|
|
||||||
|
|
||||||
class Header:
|
class Header:
|
||||||
""" deprecated """
|
"""deprecated"""
|
||||||
|
|
||||||
|
|
||||||
class IFD(dict):
|
class IFD(dict):
|
||||||
""" deprecated """
|
"""deprecated"""
|
||||||
|
|
||||||
|
|
||||||
class TiffWriteWarning(UserWarning):
|
class TiffWriteWarning(UserWarning):
|
||||||
@@ -39,7 +38,7 @@ class TiffWriteWarning(UserWarning):
|
|||||||
|
|
||||||
|
|
||||||
class IJTiffFile(rs.IJTiffFile):
|
class IJTiffFile(rs.IJTiffFile):
|
||||||
""" Writes a tiff file in a format that the BioFormats reader in Fiji understands.
|
"""Writes a tiff file in a format that the BioFormats reader in Fiji understands.
|
||||||
Zstd compression is done in parallel using Rust.
|
Zstd compression is done in parallel using Rust.
|
||||||
path: path to the new tiff file
|
path: path to the new tiff file
|
||||||
dtype: datatype to use when saving to tiff
|
dtype: datatype to use when saving to tiff
|
||||||
@@ -53,17 +52,26 @@ class IJTiffFile(rs.IJTiffFile):
|
|||||||
extratags: other tags to be saved, example: (Tag.ascii(315, 'John Doe'), Tag.bytes(4567, [400, 500])
|
extratags: other tags to be saved, example: (Tag.ascii(315, 'John Doe'), Tag.bytes(4567, [400, 500])
|
||||||
or (Tag.ascii(33432, 'Made by me'),).
|
or (Tag.ascii(33432, 'Made by me'),).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(cls, path: str | Path, *args, **kwargs) -> IJTiffFile:
|
def __new__(cls, path: str | Path, *args, **kwargs) -> IJTiffFile:
|
||||||
return super().__new__(cls, str(path))
|
return super().__new__(cls, str(path))
|
||||||
|
|
||||||
def __init__(self, path: str | Path, *, dtype: DTypeLike = 'uint16',
|
def __init__(
|
||||||
colors: Sequence[str] = None, colormap: str = None, pxsize: float = None,
|
self,
|
||||||
deltaz: float = None, timeinterval: float = None,
|
path: str | Path,
|
||||||
compression: int | str | tuple[int, int] | tuple[str, int] = None, comment: str = None,
|
*,
|
||||||
extratags: Sequence[Tag] = None) -> None:
|
dtype: DTypeLike = "uint16",
|
||||||
|
colors: Sequence[str] = None,
|
||||||
|
colormap: str = None,
|
||||||
|
pxsize: float = None,
|
||||||
|
deltaz: float = None,
|
||||||
|
timeinterval: float = None,
|
||||||
|
compression: int | str | tuple[int, int] | tuple[str, int] = None,
|
||||||
|
comment: str = None,
|
||||||
|
extratags: Sequence[Tag] = None,
|
||||||
|
) -> None:
|
||||||
def get_codec(idx: int | str):
|
def get_codec(idx: int | str):
|
||||||
codecs = {'z': 50000, 'd': 8, 8: 8, 50000: 50000}
|
codecs = {"z": 50000, "d": 8, 8: 8, 50000: 50000}
|
||||||
if isinstance(idx, str):
|
if isinstance(idx, str):
|
||||||
return codecs.get(idx[0].lower(), 50000)
|
return codecs.get(idx[0].lower(), 50000)
|
||||||
else:
|
else:
|
||||||
@@ -78,7 +86,7 @@ class IJTiffFile(rs.IJTiffFile):
|
|||||||
compression = get_codec(compression), 22
|
compression = get_codec(compression), 22
|
||||||
self.set_compression(*compression)
|
self.set_compression(*compression)
|
||||||
if colors is not None:
|
if colors is not None:
|
||||||
self.colors = np.array([get_color(color) for color in colors])
|
self.colors = [str(color) for color in colors]
|
||||||
if colormap is not None:
|
if colormap is not None:
|
||||||
self.colormap = get_colormap(colormap)
|
self.colormap = get_colormap(colormap)
|
||||||
if pxsize is not None:
|
if pxsize is not None:
|
||||||
@@ -93,10 +101,13 @@ class IJTiffFile(rs.IJTiffFile):
|
|||||||
for extra_tag in extratags:
|
for extra_tag in extratags:
|
||||||
self.append_extra_tag(extra_tag, None)
|
self.append_extra_tag(extra_tag, None)
|
||||||
if self.dtype.itemsize == 1 and colors is not None:
|
if self.dtype.itemsize == 1 and colors is not None:
|
||||||
warn('Fiji will not interpret colors saved in an (u)int8 tif, save as (u)int16 instead.',
|
warn(
|
||||||
TiffWriteWarning, stacklevel=2)
|
"Fiji will not interpret colors saved in an (u)int8 tif, save as (u)int16 instead.",
|
||||||
|
TiffWriteWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
if colors is not None and colormap is not None:
|
if colors is not None and colormap is not None:
|
||||||
warn('Cannot have colors and colormap simultaneously.', TiffWriteWarning, stacklevel=2)
|
warn("Cannot have colors and colormap simultaneously.", TiffWriteWarning, stacklevel=2)
|
||||||
|
|
||||||
def __enter__(self) -> IJTiffFile:
|
def __enter__(self) -> IJTiffFile:
|
||||||
return self
|
return self
|
||||||
@@ -105,7 +116,7 @@ class IJTiffFile(rs.IJTiffFile):
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def save(self, frame: ArrayLike, c: int, z: int, t: int, extratags: Sequence[Tag] = None) -> None:
|
def save(self, frame: ArrayLike, c: int, z: int, t: int, extratags: Sequence[Tag] = None) -> None:
|
||||||
""" save a 2d numpy array to the tiff at channel=c, slice=z, time=t, with optional extra tif tags """
|
"""save a 2d numpy array to the tiff at channel=c, slice=z, time=t, with optional extra tif tags"""
|
||||||
frame = np.asarray(frame).astype(self.dtype)
|
frame = np.asarray(frame).astype(self.dtype)
|
||||||
match self.dtype:
|
match self.dtype:
|
||||||
case np.uint8:
|
case np.uint8:
|
||||||
@@ -129,41 +140,57 @@ class IJTiffFile(rs.IJTiffFile):
|
|||||||
case np.float64:
|
case np.float64:
|
||||||
self.save_f64(frame, c, z, t)
|
self.save_f64(frame, c, z, t)
|
||||||
case _:
|
case _:
|
||||||
raise TypeError(f'Cannot save type {self.dtype}')
|
raise TypeError(f"Cannot save type {self.dtype}")
|
||||||
if extratags is not None:
|
if extratags is not None:
|
||||||
for extra_tag in extratags:
|
for extra_tag in extratags:
|
||||||
self.append_extra_tag(extra_tag, (c, z, t))
|
self.append_extra_tag(extra_tag, (c, z, t))
|
||||||
|
|
||||||
|
|
||||||
def get_colormap(colormap: str) -> np.ndarray:
|
def get_colormap(colormap: str) -> np.ndarray:
|
||||||
if hasattr(colorcet, colormap.rstrip('_r')):
|
if hasattr(colorcet, colormap.rstrip("_r")):
|
||||||
cm = np.array([[int(''.join(i), 16) for i in zip(*[iter(s[1:])] * 2)]
|
cm = np.array(
|
||||||
for s in getattr(colorcet, colormap.rstrip('_r'))]).astype('uint8')
|
[[int("".join(i), 16) for i in zip(*[iter(s[1:])] * 2)] for s in getattr(colorcet, colormap.rstrip("_r"))]
|
||||||
if colormap.endswith('_r'):
|
).astype("uint8")
|
||||||
|
if colormap.endswith("_r"):
|
||||||
cm = cm[::-1]
|
cm = cm[::-1]
|
||||||
if colormap.startswith('glasbey') or colormap.endswith('glasbey'):
|
if colormap.startswith("glasbey") or colormap.endswith("glasbey"):
|
||||||
cm[0] = 255, 255, 255
|
cm[0] = 255, 255, 255
|
||||||
cm[-1] = 0, 0, 0
|
cm[-1] = 0, 0, 0
|
||||||
else:
|
else:
|
||||||
cmap = matplotlib.colormaps.get_cmap(colormap)
|
cmap = matplotlib.colormaps.get_cmap(colormap)
|
||||||
if cmap.N < 256:
|
if cmap.N < 256:
|
||||||
cm = (255 * np.vstack(((1, 1, 1),
|
cm = (
|
||||||
matplotlib.cm.ScalarMappable(matplotlib.colors.Normalize(1, 254),
|
255
|
||||||
cmap).to_rgba(np.arange(1, 254))[:, :3],
|
* np.vstack(
|
||||||
(0, 0, 0)))).astype('uint8')
|
(
|
||||||
|
(1, 1, 1),
|
||||||
|
matplotlib.cm.ScalarMappable(matplotlib.colors.Normalize(1, 254), cmap).to_rgba(
|
||||||
|
np.arange(1, 254)
|
||||||
|
)[:, :3],
|
||||||
|
(0, 0, 0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).astype("uint8")
|
||||||
else:
|
else:
|
||||||
cm = (255 * matplotlib.cm.ScalarMappable(matplotlib.colors.Normalize(0, 255), cmap)
|
cm = (
|
||||||
.to_rgba(np.arange(256))[:, :3]).astype('uint8')
|
255
|
||||||
|
* matplotlib.cm.ScalarMappable(matplotlib.colors.Normalize(0, 255), cmap).to_rgba(np.arange(256))[
|
||||||
|
:, :3
|
||||||
|
]
|
||||||
|
).astype("uint8")
|
||||||
return cm
|
return cm
|
||||||
|
|
||||||
|
|
||||||
def get_color(color: str) -> np.ndarray:
|
def tiffwrite(
|
||||||
return np.array([int(''.join(i), 16) for i in zip(*[iter(mpl_colors.to_hex(color)[1:])] * 2)]).astype('uint8')
|
file: str | Path,
|
||||||
|
data: np.ndarray,
|
||||||
|
axes: str = "TZCYX",
|
||||||
def tiffwrite(file: str | Path, data: np.ndarray, axes: str = 'TZCYX', dtype: DTypeLike = None, bar: bool = False,
|
dtype: DTypeLike = None,
|
||||||
*args: Any, **kwargs: Any) -> None:
|
bar: bool = False,
|
||||||
""" file: string; filename of the new tiff file
|
*args: Any,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""file: string; filename of the new tiff file
|
||||||
data: 2 to 5D numpy array
|
data: 2 to 5D numpy array
|
||||||
axes: string; order of dimensions in data, default: TZCYX for 5D, ZCYX for 4D, CYX for 3D, YX for 2D data
|
axes: string; order of dimensions in data, default: TZCYX for 5D, ZCYX for 4D, CYX for 3D, YX for 2D data
|
||||||
dtype: string; datatype to use when saving to tiff
|
dtype: string; datatype to use when saving to tiff
|
||||||
@@ -171,9 +198,9 @@ def tiffwrite(file: str | Path, data: np.ndarray, axes: str = 'TZCYX', dtype: DT
|
|||||||
other args: see IJTiffFile
|
other args: see IJTiffFile
|
||||||
"""
|
"""
|
||||||
|
|
||||||
axes = axes[-np.ndim(data):].upper()
|
axes = axes[-np.ndim(data) :].upper()
|
||||||
if not axes == 'CZTYX':
|
if not axes == "CZTYX":
|
||||||
axes_shuffle = [axes.find(i) for i in 'CZTYX']
|
axes_shuffle = [axes.find(i) for i in "CZTYX"]
|
||||||
axes_add = [i for i, j in enumerate(axes_shuffle) if j < 0]
|
axes_add = [i for i, j in enumerate(axes_shuffle) if j < 0]
|
||||||
axes_shuffle = [i for i in axes_shuffle if i >= 0]
|
axes_shuffle = [i for i in axes_shuffle if i >= 0]
|
||||||
data = np.transpose(data, axes_shuffle)
|
data = np.transpose(data, axes_shuffle)
|
||||||
@@ -182,8 +209,12 @@ def tiffwrite(file: str | Path, data: np.ndarray, axes: str = 'TZCYX', dtype: DT
|
|||||||
|
|
||||||
shape = data.shape[:3]
|
shape = data.shape[:3]
|
||||||
with IJTiffFile(file, dtype=data.dtype if dtype is None else dtype, *args, **kwargs) as f:
|
with IJTiffFile(file, dtype=data.dtype if dtype is None else dtype, *args, **kwargs) as f:
|
||||||
for n in tqdm(product(*[range(i) for i in shape]), total=np.prod(shape), # type: ignore
|
for n in tqdm(
|
||||||
desc='Saving tiff', disable=not bar):
|
product(*[range(i) for i in shape]),
|
||||||
|
total=np.prod(shape), # type: ignore
|
||||||
|
desc="Saving tiff",
|
||||||
|
disable=not bar,
|
||||||
|
):
|
||||||
f.save(data[n], *n)
|
f.save(data[n], *n)
|
||||||
|
|
||||||
|
|
||||||
@@ -193,7 +224,6 @@ try:
|
|||||||
|
|
||||||
from parfor import ParPool, Task
|
from parfor import ParPool, Task
|
||||||
|
|
||||||
|
|
||||||
class Pool(ParPool):
|
class Pool(ParPool):
|
||||||
def __init__(self, ijtifffile: IJTiffFile, parallel: Callable[[Any], Sequence[FrameInfo]]):
|
def __init__(self, ijtifffile: IJTiffFile, parallel: Callable[[Any], Sequence[FrameInfo]]):
|
||||||
self.ijtifffile = ijtifffile
|
self.ijtifffile = ijtifffile
|
||||||
@@ -211,14 +241,13 @@ try:
|
|||||||
super().close()
|
super().close()
|
||||||
self.ijtifffile.close()
|
self.ijtifffile.close()
|
||||||
|
|
||||||
|
|
||||||
class IJTiffParallel(metaclass=ABCMeta):
|
class IJTiffParallel(metaclass=ABCMeta):
|
||||||
""" wraps IJTiffFile.save in a parallel pool, the method 'parallel' needs to be overloaded """
|
"""wraps IJTiffFile.save in a parallel pool, the method 'parallel' needs to be overloaded"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def parallel(self, frame: Any) -> Sequence[FrameInfo]:
|
def parallel(self, frame: Any) -> Sequence[FrameInfo]:
|
||||||
""" does something with frame in a parallel process,
|
"""does something with frame in a parallel process,
|
||||||
and returns a sequence of frames and offsets to c, z and t to save in the tif """
|
and returns a sequence of frames and offsets to c, z and t to save in the tif"""
|
||||||
|
|
||||||
@wraps(IJTiffFile.__init__)
|
@wraps(IJTiffFile.__init__)
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
|||||||
@@ -42,3 +42,7 @@ module-name = "tiffwrite.tiffwrite_rs"
|
|||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
line_length = 119
|
line_length = 119
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 119
|
||||||
|
indent-width = 4
|
||||||
43
src/lib.rs
43
src/lib.rs
@@ -1,11 +1,12 @@
|
|||||||
#[cfg(feature = "python")]
|
#[cfg(feature = "python")]
|
||||||
mod py;
|
mod py;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Result, anyhow};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use css_color::Srgb;
|
||||||
use flate2::write::ZlibEncoder;
|
use flate2::write::ZlibEncoder;
|
||||||
use ndarray::{s, ArcArray2, AsArray, Ix2};
|
use ndarray::{ArcArray2, AsArray, Ix2, s};
|
||||||
use num::{traits::ToBytes, Complex, FromPrimitive, Rational32};
|
use num::{Complex, FromPrimitive, Rational32, traits::ToBytes};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
@@ -16,10 +17,10 @@ use std::time::Duration;
|
|||||||
use std::{cmp::Ordering, collections::HashMap};
|
use std::{cmp::Ordering, collections::HashMap};
|
||||||
use std::{
|
use std::{
|
||||||
thread,
|
thread,
|
||||||
thread::{available_parallelism, sleep, JoinHandle},
|
thread::{JoinHandle, available_parallelism, sleep},
|
||||||
};
|
};
|
||||||
use zstd::zstd_safe::CompressionLevel;
|
use zstd::zstd_safe::CompressionLevel;
|
||||||
use zstd::{stream::Encoder, DEFAULT_COMPRESSION_LEVEL};
|
use zstd::{DEFAULT_COMPRESSION_LEVEL, stream::Encoder};
|
||||||
|
|
||||||
const TAG_SIZE: usize = 20;
|
const TAG_SIZE: usize = 20;
|
||||||
const OFFSET_SIZE: usize = 8;
|
const OFFSET_SIZE: usize = 8;
|
||||||
@@ -707,6 +708,38 @@ impl IJTiffFile {
|
|||||||
self.compression = compression;
|
self.compression = compression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// set colors from css color names and #C01085
|
||||||
|
pub fn set_colors(&mut self, colors: &[String]) -> Result<()> {
|
||||||
|
self.colors = Colors::Colors(
|
||||||
|
colors
|
||||||
|
.iter()
|
||||||
|
.map(|c| {
|
||||||
|
let lc = c.to_lowercase();
|
||||||
|
let c = match lc.as_str() {
|
||||||
|
"r" => "#ff0000",
|
||||||
|
"g" => "#008000",
|
||||||
|
"b" => "#0000ff",
|
||||||
|
"c" => "#00bfbf",
|
||||||
|
"m" => "#bf00bf",
|
||||||
|
"y" => "#bfbf00",
|
||||||
|
"k" => "#000000",
|
||||||
|
"w" => "#ffffff",
|
||||||
|
_ => c,
|
||||||
|
};
|
||||||
|
match c.parse::<Srgb>() {
|
||||||
|
Ok(c) => Ok(vec![
|
||||||
|
(255.0 * c.red).round() as u8,
|
||||||
|
(255.0 * c.green).round() as u8,
|
||||||
|
(255.0 * c.blue).round() as u8,
|
||||||
|
]),
|
||||||
|
Err(_) => Err(anyhow!("could not parse color: {}", c)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// to be saved in description tag (270)
|
/// to be saved in description tag (270)
|
||||||
pub fn description(&self, c_size: usize, z_size: usize, t_size: usize) -> String {
|
pub fn description(&self, c_size: usize, z_size: usize, t_size: usize) -> String {
|
||||||
let mut desc: String = String::from("ImageJ=1.11a");
|
let mut desc: String = String::from("ImageJ=1.11a");
|
||||||
|
|||||||
11
src/py.rs
11
src/py.rs
@@ -183,7 +183,7 @@ impl PyIJTiffFile {
|
|||||||
return Err(PyValueError::new_err(format!(
|
return Err(PyValueError::new_err(format!(
|
||||||
"Unknown compression {}",
|
"Unknown compression {}",
|
||||||
compression
|
compression
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(ref mut ijtifffile) = self.ijtifffile {
|
if let Some(ref mut ijtifffile) = self.ijtifffile {
|
||||||
@@ -203,14 +203,9 @@ impl PyIJTiffFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[setter]
|
#[setter]
|
||||||
fn set_colors(&mut self, colors: PyReadonlyArray2<u8>) -> PyResult<()> {
|
fn set_colors(&mut self, colors: Vec<String>) -> PyResult<()> {
|
||||||
if let Some(ijtifffile) = &mut self.ijtifffile {
|
if let Some(ijtifffile) = &mut self.ijtifffile {
|
||||||
let a = colors.to_owned_array();
|
ijtifffile.set_colors(&colors)?;
|
||||||
ijtifffile.colors = Colors::Colors(
|
|
||||||
(0..a.shape()[0])
|
|
||||||
.map(|i| Vec::from(a.slice(s![i, ..]).as_slice().unwrap()))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from tiffwrite import IJTiffFile
|
|||||||
|
|
||||||
def test_mult(tmp_path: Path) -> None:
|
def test_mult(tmp_path: Path) -> None:
|
||||||
shape = (2, 3, 5)
|
shape = (2, 3, 5)
|
||||||
paths = [tmp_path / f'test{i}.tif' for i in range(6)]
|
paths = [tmp_path / f"test{i}.tif" for i in range(6)]
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
tifs = [stack.enter_context(IJTiffFile(path)) for path in paths] # noqa
|
tifs = [stack.enter_context(IJTiffFile(path)) for path in paths] # noqa
|
||||||
for c, z, t in tqdm(product(range(shape[0]), range(shape[1]), range(shape[2])), total=np.prod(shape)): # noqa
|
for c, z, t in tqdm(product(range(shape[0]), range(shape[1]), range(shape[2])), total=np.prod(shape)): # noqa
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ from tifffile import imread
|
|||||||
from tiffwrite import IJTiffFile
|
from tiffwrite import IJTiffFile
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('dtype', ('uint8', 'uint16', 'uint32', 'uint64',
|
@pytest.mark.parametrize(
|
||||||
'int8', 'int16', 'int32', 'int64', 'float32', 'float64'))
|
"dtype", ("uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64", "float32", "float64")
|
||||||
|
)
|
||||||
def test_single(tmp_path: Path, dtype) -> None:
|
def test_single(tmp_path: Path, dtype) -> None:
|
||||||
with IJTiffFile(tmp_path / 'test.tif', dtype=dtype, pxsize=0.1, deltaz=0.5, timeinterval=6.5) as tif:
|
with IJTiffFile(tmp_path / "test.tif", dtype=dtype, pxsize=0.1, deltaz=0.5, timeinterval=6.5) as tif:
|
||||||
a0, b0 = np.meshgrid(range(100), range(100))
|
a0, b0 = np.meshgrid(range(100), range(100))
|
||||||
a0[::2, :] = 0
|
a0[::2, :] = 0
|
||||||
b0[:, ::2] = 1
|
b0[:, ::2] = 1
|
||||||
@@ -23,6 +24,6 @@ def test_single(tmp_path: Path, dtype) -> None:
|
|||||||
tif.save(a1, 0, 0, 1)
|
tif.save(a1, 0, 0, 1)
|
||||||
tif.save(b1, 1, 0, 1)
|
tif.save(b1, 1, 0, 1)
|
||||||
|
|
||||||
t = imread(tmp_path / 'test.tif')
|
t = imread(tmp_path / "test.tif")
|
||||||
assert t.dtype == np.dtype(dtype), "data type does not match"
|
assert t.dtype == np.dtype(dtype), "data type does not match"
|
||||||
assert np.all(np.stack(((a0, b0), (a1, b1))) == t), "data does not match"
|
assert np.all(np.stack(((a0, b0), (a1, b1))) == t), "data does not match"
|
||||||
|
|||||||
Reference in New Issue
Block a user