- bugfix: make sure all bytes are processed by the zstd encoder

- bump dependencies
This commit is contained in:
Wim Pomp
2024-12-23 15:29:08 +01:00
parent 49f8ab4115
commit 7fdfb7c9dc
4 changed files with 43 additions and 13 deletions

View File

@@ -1,6 +1,6 @@
name: PyTest name: PyTest
on: workflow_call on: [push, pull_request, workflow_call]
jobs: jobs:
pytest: pytest:

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "tiffwrite" name = "tiffwrite"
version = "2024.11.0" version = "2024.12.0"
edition = "2021" edition = "2021"
authors = ["Wim Pomp <w.pomp@nki.nl>"] authors = ["Wim Pomp <w.pomp@nki.nl>"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
@@ -14,16 +14,16 @@ name = "tiffwrite"
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
anyhow = "1.0.89" anyhow = "1.0.95"
chrono = "0.4.38" chrono = "0.4.39"
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.2" zstd = "0.13.2"
numpy = { version = "0.22.0", optional = true } numpy = { version = "0.23.0", optional = true }
[dependencies.pyo3] [dependencies.pyo3]
version = "0.22.5" version = "0.23.3"
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

View File

@@ -23,12 +23,14 @@ const OFFSET_SIZE: usize = 8;
const OFFSET: u64 = 16; const OFFSET: u64 = 16;
const COMPRESSION: u16 = 50000; const COMPRESSION: u16 = 50000;
/// Image File Directory
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct IFD { struct IFD {
tags: HashSet<Tag>, tags: HashSet<Tag>,
} }
impl IFD { impl IFD {
/// new IFD with empty set of tags
pub fn new() -> Self { pub fn new() -> Self {
IFD { IFD {
tags: HashSet::new(), tags: HashSet::new(),
@@ -61,6 +63,7 @@ impl IFD {
} }
} }
/// Tiff tag, use one of the constructors to get a tag of a specific type
#[derive(Clone, Debug, Eq)] #[derive(Clone, Debug, Eq)]
pub struct Tag { pub struct Tag {
code: u16, code: u16,
@@ -317,6 +320,7 @@ impl Tag {
} }
} }
/// get the number of values in the tag
pub fn count(&self) -> u64 { pub fn count(&self) -> u64 {
let c = match self.ttype { let c = match self.ttype {
1 => self.bytes.len(), // BYTE 1 => self.bytes.len(), // BYTE
@@ -478,6 +482,16 @@ impl CompressedFrame {
} }
} }
/// loop until all bytes are encoded
fn write(encoder: &mut Encoder<&mut Vec<u8>>, buf: &[u8]) -> Result<()> {
let b = buf.len();
let mut w = 0;
while w < b {
w += encoder.write(&buf[w..])?;
}
Ok(())
}
fn compress_tile<T>( fn compress_tile<T>(
frame: ArcArray2<T>, frame: ArcArray2<T>,
slice: (usize, usize, usize, usize), slice: (usize, usize, usize, usize),
@@ -495,7 +509,8 @@ impl CompressedFrame {
encoder.set_pledged_src_size(Some((bytes_per_sample * tile_width * tile_length) as u64))?; encoder.set_pledged_src_size(Some((bytes_per_sample * tile_width * tile_length) as u64))?;
let shape = (slice.1 - slice.0, slice.3 - slice.2); let shape = (slice.1 - slice.0, slice.3 - slice.2);
for i in 0..shape.0 { for i in 0..shape.0 {
encoder.write( CompressedFrame::write(
&mut encoder,
&frame &frame
.slice(s![slice.0..slice.1, slice.2..slice.3]) .slice(s![slice.0..slice.1, slice.2..slice.3])
.slice(s![i, ..]) .slice(s![i, ..])
@@ -504,12 +519,15 @@ impl CompressedFrame {
.flatten() .flatten()
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
)?; )?;
encoder.write(&vec![0u8; bytes_per_sample * (tile_width - shape.1)])?; CompressedFrame::write(
&mut encoder,
&vec![0u8; bytes_per_sample * (tile_width - shape.1)],
)?;
} }
encoder.write(&vec![ CompressedFrame::write(
0u8; &mut encoder,
bytes_per_sample * tile_width * (tile_length - shape.0) &vec![0u8; bytes_per_sample * tile_width * (tile_length - shape.0)],
])?; )?;
encoder.finish()?; encoder.finish()?;
Ok(dest) Ok(dest)
} }
@@ -551,6 +569,7 @@ impl Frame {
} }
} }
/// trait to convert numbers to bytes
pub trait Bytes { pub trait Bytes {
const BITS_PER_SAMPLE: u16; const BITS_PER_SAMPLE: u16;
const SAMPLE_FORMAT: u16; const SAMPLE_FORMAT: u16;
@@ -593,6 +612,7 @@ bytes_impl!(isize, 32, 2);
bytes_impl!(f32, 32, 3); bytes_impl!(f32, 32, 3);
bytes_impl!(f64, 64, 3); bytes_impl!(f64, 64, 3);
/// what colormap to save in the tiff; None, Colors: gradient from black to color, or full Colormap
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Colors { pub enum Colors {
None, None,
@@ -600,18 +620,24 @@ pub enum Colors {
Colormap(Vec<Vec<u8>>), Colormap(Vec<Vec<u8>>),
} }
/// save 2d arrays in a tif file compatible with Fiji/ImageJ
#[derive(Debug)] #[derive(Debug)]
pub struct IJTiffFile { pub struct IJTiffFile {
file: File, file: File,
frames: HashMap<(usize, usize, usize), Frame>, frames: HashMap<(usize, usize, usize), Frame>,
hashes: HashMap<u64, u64>, hashes: HashMap<u64, u64>,
threads: HashMap<(usize, usize, usize), JoinHandle<CompressedFrame>>, threads: HashMap<(usize, usize, usize), JoinHandle<CompressedFrame>>,
/// zstd: -7 ..= 22
pub compression_level: i32, pub compression_level: i32,
pub colors: Colors, pub colors: Colors,
pub comment: Option<String>, pub comment: Option<String>,
/// um per pixel
pub px_size: Option<f64>, pub px_size: Option<f64>,
/// um per slice
pub delta_z: Option<f64>, pub delta_z: Option<f64>,
/// s per frame
pub time_interval: Option<f64>, pub time_interval: Option<f64>,
/// extra tags; per frame: key = Some((c, z, t)), global: key = None
pub extra_tags: HashMap<Option<(usize, usize, usize)>, Vec<Tag>>, pub extra_tags: HashMap<Option<(usize, usize, usize)>, Vec<Tag>>,
} }
@@ -624,7 +650,8 @@ impl Drop for IJTiffFile {
} }
impl IJTiffFile { impl IJTiffFile {
/// create new tifffile from path string /// create new tifffile from path string, use it's save() method to save frames
/// the file is finalized when it goes out of scope
pub fn new(path: &str) -> Result<Self> { pub fn new(path: &str) -> Result<Self> {
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.create(true) .create(true)
@@ -746,6 +773,7 @@ impl IJTiffFile {
} }
} }
/// save a 2d array to the tiff file at channel c, slice z, and time t
pub fn save<'a, A, T>(&mut self, frame: A, c: usize, z: usize, t: usize) -> Result<()> pub fn save<'a, A, T>(&mut self, frame: A, c: usize, z: usize, t: usize) -> Result<()>
where where
A: AsArray<'a, T, Ix2>, A: AsArray<'a, T, Ix2>,

View File

@@ -11,6 +11,7 @@ struct PyTag {
tag: Tag, tag: Tag,
} }
/// Tiff tag, use one of the constructors to get a tag of a specific type
#[pymethods] #[pymethods]
impl PyTag { impl PyTag {
#[staticmethod] #[staticmethod]
@@ -150,6 +151,7 @@ impl PyTag {
} }
} }
/// get the number of values in the tag
fn count(&self) -> u64 { fn count(&self) -> u64 {
self.tag.count() self.tag.count()
} }