- bugfix: make sure all bytes are processed by the zstd encoder
- bump dependencies
This commit is contained in:
2
.github/workflows/pytest.yml
vendored
2
.github/workflows/pytest.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: PyTest
|
name: PyTest
|
||||||
|
|
||||||
on: workflow_call
|
on: [push, pull_request, workflow_call]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pytest:
|
pytest:
|
||||||
|
|||||||
10
Cargo.toml
10
Cargo.toml
@@ -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
|
||||||
|
|
||||||
|
|||||||
42
src/lib.rs
42
src/lib.rs
@@ -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>,
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user