- start some color(map) support in python

- compress_frame function in python for backwards compatibility
- save all extra tags in a single hashmap
- construct tags from references
- store frames by c, z, t
- save px_size in tiff
- some getters and setters in py.rs
This commit is contained in:
Wim Pomp
2024-10-10 15:28:14 +02:00
parent 7678585bba
commit 1197806a6f
5 changed files with 681 additions and 293 deletions

View File

@@ -4,7 +4,9 @@ from itertools import product
from pathlib import Path from pathlib import Path
from typing import Any, Sequence from typing import Any, Sequence
import colorcet
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
@@ -27,29 +29,29 @@ class Tag(rs.Tag):
Strip = tuple[list[int], list[int]] Strip = tuple[list[int], list[int]]
CZT = tuple[int, int, int] CZT = tuple[int, int, int]
FrameInfo = tuple[IFD, Strip, CZT] FrameInfo = tuple[np.ndarray, None, CZT]
class IJTiffFile(rs.IJTiffFile): class IJTiffFile(rs.IJTiffFile):
def __new__(cls, path: str | Path, shape: tuple[int, int, int], dtype: DTypeLike = 'uint16', def __new__(cls, path: str | Path, shape: tuple[int, int, int], dtype: DTypeLike = 'uint16',
colors: Sequence[str] = None, colormap: str = None, pxsize: float = None, colors: Sequence[str] = None, colormap: str = None, pxsize: float = None,
deltaz: float = None, timeinterval: float = None, comment: str = None, deltaz: float = None, timeinterval: float = None, comment: str = None,
**extratags: Tag.Value | Tag) -> IJTiffFile: **extratags: Tag) -> IJTiffFile:
new = super().__new__(cls, str(path), shape) new = super().__new__(cls, str(path), shape)
if colors is not None: if colors is not None:
new = new.with_colors(colors) new.colors = np.array([get_color(color) for color in colors])
if colormap is not None: if colormap is not None:
new = new.with_colormap(colormap) new.colormap = get_colormap(colormap)
if pxsize is not None: if pxsize is not None:
new = new.with_pxsize(pxsize) new.px_size = float(pxsize)
if deltaz is not None: if deltaz is not None:
new = new.with_deltaz(deltaz) new.delta_z = float(deltaz)
if timeinterval is not None: if timeinterval is not None:
new = new.with_timeinterval(timeinterval) new.time_interval = float(timeinterval)
if comment is not None: if comment is not None:
new = new.with_comment(comment) new.comment = comment
if extratags: for extra_tag in extratags:
new = new.extend_extratags(extratags) new.append_extra_tag(extra_tag, None)
return new return new
def __init__(self, path: str | Path, shape: tuple[int, int, int], dtype: DTypeLike = 'uint16', # noqa def __init__(self, path: str | Path, shape: tuple[int, int, int], dtype: DTypeLike = 'uint16', # noqa
@@ -66,31 +68,44 @@ class IJTiffFile(rs.IJTiffFile):
self.close() self.close()
def save(self, frame: ArrayLike, c: int, z: int, t: int) -> None: def save(self, frame: ArrayLike, c: int, z: int, t: int) -> None:
for frame, _, (cn, zn, tn) in self.compress_frame(frame):
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:
self.save_u8(frame, c, z, t) self.save_u8(frame, c + cn, z + zn, t + tn)
case np.uint16: case np.uint16:
self.save_u16(frame, c, z, t) self.save_u16(frame, c + cn, z + zn, t + tn)
case np.uint32: case np.uint32:
self.save_u32(frame, c, z, t) self.save_u32(frame, c + cn, z + zn, t + tn)
case np.uint64: case np.uint64:
self.save_u64(frame, c, z, t) self.save_u64(frame, c + cn, z + zn, t + tn)
case np.int8: case np.int8:
self.save_i8(frame, c, z, t) self.save_i8(frame, c + cn, z + zn, t + tn)
case np.int16: case np.int16:
self.save_i16(frame, c, z, t) self.save_i16(frame, c + cn, z + zn, t + tn)
case np.int32: case np.int32:
self.save_i32(frame, c, z, t) self.save_i32(frame, c + cn, z + zn, t + tn)
case np.int64: case np.int64:
self.save_i64(frame, c, z, t) self.save_i64(frame, c + cn, z + zn, t + tn)
case np.float32: case np.float32:
self.save_f32(frame, c, z, t) self.save_f32(frame, c + cn, z + zn, t + tn)
case np.float64: case np.float64:
self.save_f64(frame, c, z, t) self.save_f64(frame, c + cn, z + zn, t + tn)
case _: case _:
raise TypeError(f'Cannot save type {self.dtype}') raise TypeError(f'Cannot save type {self.dtype}')
def compress_frame(self, frame: ArrayLike) -> tuple[FrameInfo]: # noqa
return (frame, None, (0, 0, 0)),
def get_colormap(colormap: str) -> np.ndarray:
colormap = getattr(colorcet, colormap)
colormap[0] = '#ffffff'
colormap[-1] = '#000000'
return np.array([[int(''.join(i), 16) for i in zip(*[iter(s[1:])] * 2)] for s in colormap]).astype('uint8')
def get_color(color: str) -> np.ndarray:
return np.array([int(''.join(i), 16) for i in zip(*[iter(mpl_colors.to_hex(color)[1:])] * 2)]).astype('uint8')
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:

View File

@@ -12,7 +12,7 @@ classifiers = [
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
] ]
dependencies = ["numpy", "tqdm"] dependencies = ["colorcet", "matplotlib", "numpy", "tqdm"]
[project.optional-dependencies] [project.optional-dependencies]
test = ["pytest"] test = ["pytest"]

View File

@@ -1,31 +1,29 @@
// #[cfg(not(feature = "nopython"))] // #[cfg(not(feature = "nopython"))]
mod py; mod py;
use anyhow::Result;
use chrono::Utc;
use ndarray::{s, Array2};
use num::traits::ToBytes;
use num::{Complex, FromPrimitive, Rational32, Zero};
use rayon::prelude::*;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use anyhow::Result;
use num::{Complex, Rational32, Zero};
use ndarray::{s, Array2};
use num::traits::ToBytes;
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{DefaultHasher, Hash, Hasher};
use std::io::{Read, Seek, SeekFrom, Write};
use std::thread; use std::thread;
use std::thread::JoinHandle; use std::thread::JoinHandle;
use chrono::Utc;
use zstd::stream::encode_all; use zstd::stream::encode_all;
use rayon::prelude::*;
const TAG_SIZE: usize = 20; const TAG_SIZE: usize = 20;
const OFFSET_SIZE: usize = 8; const OFFSET_SIZE: usize = 8;
const OFFSET: u64 = 16; const OFFSET: u64 = 16;
const COMPRESSION: u16 = 50000; const COMPRESSION: u16 = 50000;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct IFD { struct IFD {
tags: Vec<Tag> tags: Vec<Tag>,
} }
impl IFD { impl IFD {
@@ -39,12 +37,6 @@ impl IFD {
} }
} }
fn extend_tags(&mut self, tags: Vec<Tag>) {
for tag in tags {
self.push_tag(tag);
}
}
fn write(&mut self, ijtifffile: &mut IJTiffFile, where_to_write_offset: u64) -> Result<u64> { fn write(&mut self, ijtifffile: &mut IJTiffFile, where_to_write_offset: u64) -> Result<u64> {
self.tags.sort(); self.tags.sort();
ijtifffile.file.seek(SeekFrom::End(0))?; ijtifffile.file.seek(SeekFrom::End(0))?;
@@ -52,7 +44,9 @@ impl IFD {
ijtifffile.file.write(&[0])?; ijtifffile.file.write(&[0])?;
} }
let offset = ijtifffile.file.stream_position()?; let offset = ijtifffile.file.stream_position()?;
ijtifffile.file.write(&(self.tags.len() as u64).to_le_bytes())?; ijtifffile
.file
.write(&(self.tags.len() as u64).to_le_bytes())?;
for tag in self.tags.iter_mut() { for tag in self.tags.iter_mut() {
tag.write_tag(ijtifffile)?; tag.write_tag(ijtifffile)?;
@@ -62,19 +56,20 @@ impl IFD {
for tag in self.tags.iter() { for tag in self.tags.iter() {
tag.write_data(ijtifffile)?; tag.write_data(ijtifffile)?;
} }
ijtifffile.file.seek(SeekFrom::Start(where_to_write_offset))?; ijtifffile
.file
.seek(SeekFrom::Start(where_to_write_offset))?;
ijtifffile.file.write(&offset.to_le_bytes())?; ijtifffile.file.write(&offset.to_le_bytes())?;
Ok(where_to_write_next_ifd_offset) Ok(where_to_write_next_ifd_offset)
} }
} }
#[derive(Clone, Debug, Eq)] #[derive(Clone, Debug, Eq)]
pub struct Tag { pub struct Tag {
code: u16, code: u16,
bytes: Vec<u8>, bytes: Vec<u8>,
ttype: u16, ttype: u16,
offset: u64 offset: u64,
} }
impl PartialOrd<Self> for Tag { impl PartialOrd<Self> for Tag {
@@ -97,11 +92,16 @@ impl PartialEq for Tag {
impl Tag { impl Tag {
pub fn new(code: u16, bytes: Vec<u8>, ttype: u16) -> Self { pub fn new(code: u16, bytes: Vec<u8>, ttype: u16) -> Self {
Tag { code, bytes, ttype, offset: 0 } Tag {
code,
bytes,
ttype,
offset: 0,
}
} }
pub fn byte(code: u16, byte: Vec<u8>) -> Self { pub fn byte(code: u16, byte: &Vec<u8>) -> Self {
Tag::new(code, byte, 1) Tag::new(code, byte.to_owned(), 1)
} }
pub fn ascii(code: u16, ascii: &str) -> Self { pub fn ascii(code: u16, ascii: &str) -> Self {
@@ -110,74 +110,191 @@ impl Tag {
Tag::new(code, bytes, 2) Tag::new(code, bytes, 2)
} }
pub fn short(code: u16, short: Vec<u16>) -> Self { pub fn short(code: u16, short: &Vec<u16>) -> Self {
Tag::new(code, short.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 3) Tag::new(
code,
short
.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
3,
)
} }
pub fn long(code: u16, long: Vec<u32>) -> Self { pub fn long(code: u16, long: &Vec<u32>) -> Self {
Tag::new(code, long.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 4) Tag::new(
code,
long.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
4,
)
} }
pub fn rational(code: u16, rational: Vec<Rational32>) -> Self { pub fn rational(code: u16, rational: &Vec<Rational32>) -> Self {
Tag::new(code, rational.into_iter().map(|x| Tag::new(
u32::try_from(*x.denom()).unwrap().to_le_bytes().into_iter().chain( code,
u32::try_from(*x.numer()).unwrap().to_le_bytes()).collect::<Vec<_>>() rational
).flatten().collect(), 5) .into_iter()
.map(|x| {
u32::try_from(*x.denom())
.unwrap()
.to_le_bytes()
.into_iter()
.chain(u32::try_from(*x.numer()).unwrap().to_le_bytes())
.collect::<Vec<_>>()
})
.flatten()
.collect(),
5,
)
} }
pub fn sbyte(code: u16, sbyte: Vec<i8>) -> Self { pub fn sbyte(code: u16, sbyte: &Vec<i8>) -> Self {
Tag::new(code, sbyte.iter().map(|x| x.to_le_bytes()).flatten().collect(), 6) Tag::new(
code,
sbyte.iter().map(|x| x.to_le_bytes()).flatten().collect(),
6,
)
} }
pub fn sshort(code: u16, sshort: Vec<i16>) -> Self { pub fn sshort(code: u16, sshort: &Vec<i16>) -> Self {
Tag::new(code, sshort.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 8) Tag::new(
code,
sshort
.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
8,
)
} }
pub fn slong(code: u16, slong: Vec<i32>) -> Self { pub fn slong(code: u16, slong: &Vec<i32>) -> Self {
Tag::new(code, slong.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 9) Tag::new(
code,
slong
.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
9,
)
} }
pub fn srational(code: u16, srational: Vec<Rational32>) -> Self { pub fn srational(code: u16, srational: &Vec<Rational32>) -> Self {
Tag::new(code, srational.into_iter().map(|x| Tag::new(
i32::try_from(*x.denom()).unwrap().to_le_bytes().into_iter().chain( code,
i32::try_from(*x.numer()).unwrap().to_le_bytes()).collect::<Vec<_>>() srational
).flatten().collect(), 10) .into_iter()
.map(|x| {
i32::try_from(*x.denom())
.unwrap()
.to_le_bytes()
.into_iter()
.chain(i32::try_from(*x.numer()).unwrap().to_le_bytes())
.collect::<Vec<_>>()
})
.flatten()
.collect(),
10,
)
} }
pub fn float(code: u16, float: Vec<f32>) -> Self { pub fn float(code: u16, float: &Vec<f32>) -> Self {
Tag::new(code, float.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 11) Tag::new(
code,
float
.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
11,
)
} }
pub fn double(code: u16, double: Vec<f64>) -> Self { pub fn double(code: u16, double: &Vec<f64>) -> Self {
Tag::new(code, double.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 12) Tag::new(
code,
double
.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
12,
)
} }
pub fn ifd(code: u16, ifd: Vec<u32>) -> Self { pub fn ifd(code: u16, ifd: &Vec<u32>) -> Self {
Tag::new(code, ifd.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 13) Tag::new(
code,
ifd.into_iter().map(|x| x.to_le_bytes()).flatten().collect(),
13,
)
} }
pub fn unicode(code: u16, unicode: &str) -> Self { pub fn unicode(code: u16, unicode: &str) -> Self {
let mut bytes: Vec<u8> = unicode.encode_utf16().map(|x| x.to_le_bytes()).flatten().collect(); let mut bytes: Vec<u8> = unicode
.encode_utf16()
.map(|x| x.to_le_bytes())
.flatten()
.collect();
bytes.push(0); bytes.push(0);
Tag::new(code, bytes, 14) Tag::new(code, bytes, 14)
} }
pub fn complex(code: u16, complex: Vec<Complex<f32>>) -> Self { pub fn complex(code: u16, complex: &Vec<Complex<f32>>) -> Self {
Tag::new(code, complex.into_iter().map(|x| Tag::new(
x.re.to_le_bytes().into_iter().chain(x.im.to_le_bytes()).collect::<Vec<_>>() code,
).flatten().collect(), 15) complex
.into_iter()
.map(|x| {
x.re.to_le_bytes()
.into_iter()
.chain(x.im.to_le_bytes())
.collect::<Vec<_>>()
})
.flatten()
.collect(),
15,
)
} }
pub fn long8(code: u16, long8: Vec<u64>) -> Self { pub fn long8(code: u16, long8: &Vec<u64>) -> Self {
Tag::new(code, long8.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 16) Tag::new(
code,
long8
.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
16,
)
} }
pub fn slong8(code: u16, slong8: Vec<i64>) -> Self { pub fn slong8(code: u16, slong8: &Vec<i64>) -> Self {
Tag::new(code, slong8.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 17) Tag::new(
code,
slong8
.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
17,
)
} }
pub fn ifd8(code: u16, ifd8: Vec<u64>) -> Self { pub fn ifd8(code: u16, ifd8: &Vec<u64>) -> Self {
Tag::new(code, ifd8.into_iter().map(|x| x.to_le_bytes()).flatten().collect(), 18) Tag::new(
code,
ifd8.into_iter()
.map(|x| x.to_le_bytes())
.flatten()
.collect(),
18,
)
} }
pub fn count(&self) -> u64 { pub fn count(&self) -> u64 {
@@ -226,7 +343,8 @@ impl Tag {
ijtifffile.file.seek(SeekFrom::End(0))?; ijtifffile.file.seek(SeekFrom::End(0))?;
let offset = ijtifffile.write(&self.bytes)?; let offset = ijtifffile.write(&self.bytes)?;
ijtifffile.file.seek(SeekFrom::Start( ijtifffile.file.seek(SeekFrom::Start(
self.offset + (TAG_SIZE - OFFSET_SIZE) as u64))?; self.offset + (TAG_SIZE - OFFSET_SIZE) as u64,
))?;
ijtifffile.file.write(&offset.to_le_bytes())?; ijtifffile.file.write(&offset.to_le_bytes())?;
if ijtifffile.file.stream_position()? % 2 == 1 { if ijtifffile.file.stream_position()? % 2 == 1 {
ijtifffile.file.write(&[0u8])?; ijtifffile.file.write(&[0u8])?;
@@ -236,17 +354,16 @@ impl Tag {
} }
} }
#[derive(Debug)]
struct CompressedFrame { struct CompressedFrame {
bytes: Vec<Vec<u8>>, bytes: Vec<Vec<u8>>,
image_width: u32, image_width: u32,
image_length: u32, image_length: u32,
tile_size: usize, tile_size: usize,
bits_per_sample: u16, bits_per_sample: u16,
sample_format: u16 sample_format: u16,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct Frame { struct Frame {
tileoffsets: Vec<u64>, tileoffsets: Vec<u64>,
@@ -261,17 +378,28 @@ struct Frame {
impl Frame { impl Frame {
fn new( fn new(
tileoffsets: Vec<u64>, tilebytecounts: Vec<u64>, image_width: u32, image_length: u32, tileoffsets: Vec<u64>,
bits_per_sample: u16, sample_format: u16, tile_width: u16, tile_length: u16 tilebytecounts: Vec<u64>,
image_width: u32,
image_length: u32,
bits_per_sample: u16,
sample_format: u16,
tile_width: u16,
tile_length: u16,
) -> Self { ) -> Self {
Frame { Frame {
tileoffsets, tilebytecounts, image_width, image_length, bits_per_sample, tileoffsets,
sample_format, tile_width, tile_length tilebytecounts,
image_width,
image_length,
bits_per_sample,
sample_format,
tile_width,
tile_length,
} }
} }
} }
pub trait Bytes { pub trait Bytes {
const BITS_PER_SAMPLE: u16; const BITS_PER_SAMPLE: u16;
const SAMPLE_FORMAT: u16; const SAMPLE_FORMAT: u16;
@@ -279,7 +407,6 @@ pub trait Bytes {
fn bytes(&self) -> Vec<u8>; fn bytes(&self) -> Vec<u8>;
} }
macro_rules! bytes_impl { macro_rules! bytes_impl {
($T:ty, $bits_per_sample:expr, $sample_format:expr) => { ($T:ty, $bits_per_sample:expr, $sample_format:expr) => {
impl Bytes for $T { impl Bytes for $T {
@@ -315,23 +442,26 @@ 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);
#[derive(Clone, Debug)]
pub enum Colors {
None,
Colors(Vec<Vec<u8>>),
Colormap(Vec<Vec<u8>>),
}
#[derive(Debug)] #[derive(Debug)]
pub struct IJTiffFile { pub struct IJTiffFile {
file: File, file: File,
frames: HashMap<(usize, u8), Frame>, frames: HashMap<(usize, usize, usize), Frame>,
hashes: HashMap<u64, u64>, hashes: HashMap<u64, u64>,
threads: HashMap<(usize, u8), JoinHandle<CompressedFrame>>, threads: HashMap<(usize, usize, usize), JoinHandle<CompressedFrame>>,
pub shape: (usize, usize, usize), pub shape: (usize, usize, usize),
pub n_frames: usize, pub colors: Colors,
pub samples_per_pixel: u8,
pub colormap: Option<Vec<u16>>,
pub colors: Option<Vec<(u8, u8, u8)>>,
pub comment: Option<String>, pub comment: Option<String>,
pub px_size: Option<f64>,
pub delta_z: Option<f64>, pub delta_z: Option<f64>,
pub timeinterval: Option<f64>, pub time_interval: Option<f64>,
pub extra_tags: Vec<Tag>, pub extra_tags: HashMap<Option<(usize, usize, usize)>, Vec<Tag>>,
pub extra_tags_frame: HashMap<usize, Vec<Tag>>
} }
impl Drop for IJTiffFile { impl Drop for IJTiffFile {
@@ -344,28 +474,35 @@ impl Drop for IJTiffFile {
impl IJTiffFile { impl IJTiffFile {
pub fn new(path: &str, shape: (usize, usize, usize)) -> Result<Self> { pub fn new(path: &str, shape: (usize, usize, usize)) -> Result<Self> {
let mut file = OpenOptions::new().create(true).truncate(true) let mut file = OpenOptions::new()
.write(true).read(true).open(path)?; .create(true)
.truncate(true)
.write(true)
.read(true)
.open(path)?;
file.write(b"II")?; file.write(b"II")?;
file.write(&43u16.to_le_bytes())?; file.write(&43u16.to_le_bytes())?;
file.write(&8u16.to_le_bytes())?; file.write(&8u16.to_le_bytes())?;
file.write(&0u16.to_le_bytes())?; file.write(&0u16.to_le_bytes())?;
file.write(&OFFSET.to_le_bytes())?; file.write(&OFFSET.to_le_bytes())?;
let colormap: Option<Vec<(u8, u8, u8)>> = None; Ok(IJTiffFile {
let (spp, n_frames) = if let None = &colormap { file,
(shape.0 as u8, shape.1 * shape.2) frames: HashMap::new(),
} else { hashes: HashMap::new(),
(1, shape.0 * shape.1 * shape.2) threads: HashMap::new(),
}; shape,
Ok(IJTiffFile { file, frames: HashMap::new(), hashes: HashMap::new(), colors: Colors::None,
threads: HashMap::new(), shape, n_frames, comment: None,
samples_per_pixel: spp, colormap: None, colors: None, comment: None, delta_z: None, px_size: None,
timeinterval: None, extra_tags: Vec::new(), extra_tags_frame: HashMap::new() } ) delta_z: None,
time_interval: None,
extra_tags: HashMap::new(),
})
} }
pub fn description(&self) -> String { pub fn description(&self) -> String {
let mut desc: String = String::from("ImageJ=1.11a"); let mut desc: String = String::from("ImageJ=1.11a");
if let (None, None) = (self.colormap.as_ref(), self.colors.as_ref()) { if let Colors::None = self.colors {
desc += &format!("\nimages={}", self.shape.0); desc += &format!("\nimages={}", self.shape.0);
desc += &format!("\nslices={}", self.shape.1); desc += &format!("\nslices={}", self.shape.1);
desc += &format!("\nframes={}", self.shape.2); desc += &format!("\nframes={}", self.shape.2);
@@ -384,7 +521,7 @@ impl IJTiffFile {
if let Some(delta_z) = self.delta_z { if let Some(delta_z) = self.delta_z {
desc += &format!("\nspacing={}", delta_z); desc += &format!("\nspacing={}", delta_z);
} }
if let Some(timeinterval) = self.timeinterval { if let Some(timeinterval) = self.time_interval {
desc += &format!("\ninterval={}", timeinterval); desc += &format!("\ninterval={}", timeinterval);
} }
if let Some(comment) = &self.comment { if let Some(comment) = &self.comment {
@@ -393,11 +530,27 @@ impl IJTiffFile {
desc desc
} }
fn get_frame_number(&self, c: usize, z: usize, t: usize) -> (usize, u8) { fn get_czt(&self, frame_number: usize, channel: u8) -> (usize, usize, usize) {
if let (None, None) = (self.colormap.as_ref(), self.colors.as_ref()) { if let Colors::None = self.colors {
(z + t * self.shape.1, c as u8) (
channel as usize,
frame_number % self.shape.1,
frame_number / self.shape.1,
)
} else { } else {
(c + z * self.shape.0 + t * self.shape.0 * self.shape.1, 0) (
frame_number % self.shape.0,
frame_number / self.shape.0 % self.shape.1,
frame_number / self.shape.0 / self.shape.1,
)
}
}
fn spp_and_n_frames(&self) -> (u8, usize) {
if let Colors::None = &self.colors {
(self.shape.0 as u8, self.shape.1 * self.shape.2)
} else {
(1, self.shape.0 * self.shape.1 * self.shape.2)
} }
} }
@@ -419,7 +572,9 @@ impl IJTiffFile {
fn write(&mut self, bytes: &Vec<u8>) -> Result<u64> { fn write(&mut self, bytes: &Vec<u8>) -> Result<u64> {
let hash = IJTiffFile::hash(&bytes); let hash = IJTiffFile::hash(&bytes);
if self.hashes.contains_key(&hash) && self.hash_check(&bytes, *self.hashes.get(&hash).unwrap())? { if self.hashes.contains_key(&hash)
&& self.hash_check(&bytes, *self.hashes.get(&hash).unwrap())?
{
Ok(*self.hashes.get(&hash).unwrap()) Ok(*self.hashes.get(&hash).unwrap())
} else { } else {
if self.file.stream_position()? % 2 == 1 { if self.file.stream_position()? % 2 == 1 {
@@ -432,64 +587,88 @@ impl IJTiffFile {
} }
} }
pub fn save<T>(&mut self, frame: Array2<T>, c: usize, z: usize, t: usize, pub fn save<T>(&mut self, frame: Array2<T>, c: usize, z: usize, t: usize) -> Result<()>
extra_tags: Option<Vec<Tag>>) -> Result<()> where
where T: Bytes + Clone + Send + Sync + Zero + 'static { T: Bytes + Clone + Send + Sync + Zero + 'static,
let key = self.get_frame_number(c, z, t); {
if let Some(extra_tags) = extra_tags { self.compress_frame(frame.reversed_axes(), c, z, t)?;
if let Some(extra_tags_frame) = self.extra_tags_frame.get_mut(&key.0) {
extra_tags_frame.extend(extra_tags);
} else {
self.extra_tags_frame.insert(key.0, extra_tags);
}
}
self.compress_frame(frame.reversed_axes(), key)?;
Ok(()) Ok(())
} }
fn compress_frame<T>(&mut self, frame: Array2<T>, key: (usize, u8)) -> Result<()> fn compress_frame<T>(&mut self, frame: Array2<T>, c: usize, z: usize, t: usize) -> Result<()>
where T: Bytes + Clone + Zero + Send + 'static { where
T: Bytes + Clone + Zero + Send + 'static,
{
fn compress<T>(frame: Array2<T>) -> CompressedFrame fn compress<T>(frame: Array2<T>) -> CompressedFrame
where T: Bytes + Clone + Zero { where
T: Bytes + Clone + Zero,
{
let image_width = frame.shape()[0] as u32; let image_width = frame.shape()[0] as u32;
let image_length = frame.shape()[1] as u32; let image_length = frame.shape()[1] as u32;
let tile_size = 2usize.pow(((image_width as f64 * image_length as f64 / 64f64 let tile_size = 2usize
).log2() / 2f64).round() as u32).max(16).min(1024); .pow(
((image_width as f64 * image_length as f64 / 64f64).log2() / 2f64).round()
as u32,
)
.max(16)
.min(1024);
let tiles = IJTiffFile::tile(frame.reversed_axes(), tile_size); let tiles = IJTiffFile::tile(frame.reversed_axes(), tile_size);
let byte_tiles: Vec<Vec<u8>> = tiles.into_iter().map( let byte_tiles: Vec<Vec<u8>> = tiles
|tile| tile.map(|x| x.bytes()).into_iter().flatten().collect() .into_iter()
).collect(); .map(|tile| tile.map(|x| x.bytes()).into_iter().flatten().collect())
let bytes = byte_tiles.into_par_iter().map(|x| encode_all(&*x, 3).unwrap()).collect::<Vec<_>>(); .collect();
CompressedFrame { bytes, image_width, image_length, tile_size, let bytes = byte_tiles
bits_per_sample: T::BITS_PER_SAMPLE, sample_format: T::SAMPLE_FORMAT } .into_par_iter()
.map(|x| encode_all(&*x, 3).unwrap())
.collect::<Vec<_>>();
CompressedFrame {
bytes,
image_width,
image_length,
tile_size,
bits_per_sample: T::BITS_PER_SAMPLE,
sample_format: T::SAMPLE_FORMAT,
} }
self.threads.insert(key, thread::spawn(move || compress(frame)));
for key in self.threads.keys().cloned().collect::<Vec<(usize, u8)>>() {
if self.threads[&key].is_finished() {
} }
self.threads
.insert((c, z, t), thread::spawn(move || compress(frame)));
for key in self
.threads
.keys()
.cloned()
.collect::<Vec<(usize, usize, usize)>>()
{
if self.threads[&key].is_finished() {}
} }
for key in self.threads.keys().cloned().collect::<Vec<(usize, u8)>>() { for (c, z, t) in self.threads.keys().cloned().collect::<Vec<_>>() {
if self.threads[&key].is_finished() { if self.threads[&(c, z, t)].is_finished() {
if let Some(thread) = self.threads.remove(&key) { if let Some(thread) = self.threads.remove(&(c, z, t)) {
self.write_frame(thread.join().unwrap(), key)?; self.write_frame(thread.join().unwrap(), c, z, t)?;
} }
} }
} }
Ok(()) Ok(())
} }
fn write_frame(&mut self, frame: CompressedFrame, key: (usize, u8)) -> Result<()> { fn write_frame(&mut self, frame: CompressedFrame, c: usize, z: usize, t: usize) -> Result<()> {
let mut tileoffsets = Vec::new(); let mut tileoffsets = Vec::new();
let mut tilebytecounts = Vec::new(); let mut tilebytecounts = Vec::new();
for tile in frame.bytes { for tile in frame.bytes {
tilebytecounts.push(tile.len() as u64); tilebytecounts.push(tile.len() as u64);
tileoffsets.push(self.write(&tile)?); tileoffsets.push(self.write(&tile)?);
} }
let frame = Frame::new(tileoffsets, tilebytecounts, frame.image_width, frame.image_length, let frame = Frame::new(
frame.bits_per_sample, frame.sample_format, frame.tile_size as u16, frame.tile_size as u16); tileoffsets,
self.frames.insert(key, frame); tilebytecounts,
frame.image_width,
frame.image_length,
frame.bits_per_sample,
frame.sample_format,
frame.tile_size as u16,
frame.tile_size as u16,
);
self.frames.insert((c, z, t), frame);
Ok(()) Ok(())
} }
@@ -499,59 +678,74 @@ impl IJTiffFile {
let (n, m) = (shape[0] / size, shape[1] / size); let (n, m) = (shape[0] / size, shape[1] / size);
for i in 0..n { for i in 0..n {
for j in 0..m { for j in 0..m {
tiles.push(frame.slice( tiles.push(
s![i * size..(i + 1) * size, j * size..(j + 1) * size]).to_owned()); frame
.slice(s![i * size..(i + 1) * size, j * size..(j + 1) * size])
.to_owned(),
);
} }
if shape[1] % size != 0 { if shape[1] % size != 0 {
let mut tile = Array2::<T>::zeros((size, size)); let mut tile = Array2::<T>::zeros((size, size));
tile.slice_mut( tile.slice_mut(s![.., ..shape[1] - m * size])
s![.., ..shape[1] - m * size] .assign(&frame.slice(s![i * size..(i + 1) * size, m * size..]));
).assign(&frame.slice(s![i * size..(i + 1) * size, m * size..]));
tiles.push(tile); tiles.push(tile);
} }
} }
if shape[0] % size != 0 { if shape[0] % size != 0 {
for j in 0..m { for j in 0..m {
let mut tile = Array2::<T>::zeros((size, size)); let mut tile = Array2::<T>::zeros((size, size));
tile.slice_mut( tile.slice_mut(s![..shape[0] - n * size, ..])
s![..shape[0] - n * size, ..] .assign(&frame.slice(s![n * size.., j * size..(j + 1) * size]));
).assign(&frame.slice(s![n * size.., j * size..(j + 1) * size]));
tiles.push(tile); tiles.push(tile);
} }
if shape[1] % size != 0 { if shape[1] % size != 0 {
let mut tile = Array2::<T>::zeros((size, size)); let mut tile = Array2::<T>::zeros((size, size));
tile.slice_mut( tile.slice_mut(s![..shape[0] - n * size, ..shape[1] - m * size])
s![..shape[0] - n * size, ..shape[1] - m * size] .assign(&frame.slice(s![n * size.., m * size..]));
).assign(&frame.slice(s![n * size.., m * size..]));
tiles.push(tile); tiles.push(tile);
} }
} }
tiles tiles
} }
fn get_colormap(&self, _colormap: &Vec<u16>) -> Result<Vec<u16>> { fn get_colormap(&self, colormap: &Vec<Vec<u8>>, bits_per_sample: u16) -> Vec<u16> {
todo!(); if bits_per_sample == 8 {
colormap
.iter()
.flatten()
.map(|x| (*x as u16) * 256)
.collect()
} else {
colormap
.iter()
.map(|x| vec![x; 256])
.flatten()
.flatten()
.map(|x| (*x as u16) * 256)
.collect()
}
} }
fn get_color(&self, _colors: (u8, u8, u8)) -> Result<Vec<u16>> { fn get_color(&self, _colors: &Vec<u8>, _bits_per_sample: u16) -> Result<Vec<u16>> {
todo!(); todo!();
} }
fn close(&mut self) -> Result<()> { fn close(&mut self) -> Result<()> {
for key in self.threads.keys().cloned().collect::<Vec<(usize, u8)>>() { for (c, z, t) in self.threads.keys().cloned().collect::<Vec<_>>() {
if let Some(thread) = self.threads.remove(&key) { if let Some(thread) = self.threads.remove(&(c, z, t)) {
self.write_frame(thread.join().unwrap(), key)?; self.write_frame(thread.join().unwrap(), c, z, t)?;
} }
} }
let mut where_to_write_next_ifd_offset = OFFSET - OFFSET_SIZE as u64; let mut where_to_write_next_ifd_offset = OFFSET - OFFSET_SIZE as u64;
let mut warn = false; let mut warn = false;
for frame_number in 0..self.n_frames { let (samples_per_pixel, n_frames) = self.spp_and_n_frames();
if let Some(frame) = self.frames.get(&(frame_number, 0)) { for frame_number in 0..n_frames {
if let Some(frame) = self.frames.get(&self.get_czt(frame_number, 0)) {
let mut tileoffsets = Vec::new(); let mut tileoffsets = Vec::new();
let mut tilebytecounts = Vec::new(); let mut tilebytecounts = Vec::new();
let mut frame_count = 0; let mut frame_count = 0;
for channel in 0..self.samples_per_pixel { for channel in 0..samples_per_pixel {
if let Some(frame_n) = self.frames.get(&(frame_number, channel)) { if let Some(frame_n) = self.frames.get(&self.get_czt(frame_number, channel)) {
tileoffsets.extend(frame_n.tileoffsets.iter()); tileoffsets.extend(frame_n.tileoffsets.iter());
tilebytecounts.extend(frame_n.tilebytecounts.iter()); tilebytecounts.extend(frame_n.tilebytecounts.iter());
frame_count += 1; frame_count += 1;
@@ -560,52 +754,80 @@ impl IJTiffFile {
} }
} }
let mut ifd = IFD::new(); let mut ifd = IFD::new();
ifd.push_tag(Tag::long(256, vec![frame.image_width])); ifd.push_tag(Tag::long(256, &vec![frame.image_width]));
ifd.push_tag(Tag::long(257, vec![frame.image_length])); ifd.push_tag(Tag::long(257, &vec![frame.image_length]));
ifd.push_tag(Tag::short(258, vec![frame.bits_per_sample; frame_count])); ifd.push_tag(Tag::short(258, &vec![frame.bits_per_sample; frame_count]));
ifd.push_tag(Tag::short(259, vec![COMPRESSION])); ifd.push_tag(Tag::short(259, &vec![COMPRESSION]));
ifd.push_tag(Tag::ascii(270, &self.description())); ifd.push_tag(Tag::ascii(270, &self.description()));
ifd.push_tag(Tag::short(277, vec![frame_count as u16])); ifd.push_tag(Tag::short(277, &vec![frame_count as u16]));
ifd.push_tag(Tag::ascii(305, "tiffwrite_rs")); ifd.push_tag(Tag::ascii(305, "tiffwrite_rs"));
ifd.push_tag(Tag::short(322, vec![frame.tile_width])); ifd.push_tag(Tag::short(322, &vec![frame.tile_width]));
ifd.push_tag(Tag::short(323, vec![frame.tile_length])); ifd.push_tag(Tag::short(323, &vec![frame.tile_length]));
ifd.push_tag(Tag::long8(324, tileoffsets)); ifd.push_tag(Tag::long8(324, &tileoffsets));
ifd.push_tag(Tag::long8(325, tilebytecounts)); ifd.push_tag(Tag::long8(325, &tilebytecounts));
ifd.push_tag(Tag::short(339, vec![frame.sample_format])); ifd.push_tag(Tag::short(339, &vec![frame.sample_format]));
if let Some(px_size) = self.px_size {
let r = vec![Rational32::from_f64(px_size).unwrap()];
ifd.push_tag(Tag::rational(282, &r));
ifd.push_tag(Tag::rational(283, &r));
ifd.push_tag(Tag::short(296, &vec![1]));
}
if frame_number == 0 { if frame_number == 0 {
if let Some(colormap) = &self.colormap { if let Colors::Colormap(colormap) = &self.colors {
ifd.push_tag(Tag::short(320, self.get_colormap(colormap)?)); ifd.push_tag(Tag::short(
ifd.push_tag(Tag::short(262, vec![3])); // PhotometricInterpretation PHOTOMETRIC_PALETTE 320,
} else if let None = self.colors { &self.get_colormap(colormap, frame.bits_per_sample),
ifd.push_tag(Tag::short(262, vec![1])); // PhotometricInterpretation PHOTOMETRIC_PALETTE ));
ifd.push_tag(Tag::short(262, &vec![3]));
} else if let Colors::None = self.colors {
ifd.push_tag(Tag::short(262, &vec![1]));
} }
} }
if frame_number < self.samples_per_pixel as usize { if frame_number < samples_per_pixel as usize {
if let Some(color) = &self.colors { if let Colors::Colors(colors) = &self.colors {
ifd.push_tag(Tag::short(320, self.get_color(color[frame_number])?)); ifd.push_tag(Tag::short(
ifd.push_tag(Tag::short(262, vec![3])); // PhotometricInterpretation PHOTOMETRIC_PALETTE 320,
&self.get_color(&colors[frame_number], frame.bits_per_sample)?,
));
ifd.push_tag(Tag::short(262, &vec![3]));
} }
} }
if let (None, None) = (&self.colormap, &self.colors) { if let Colors::None = &self.colors {
if self.shape.0 > 1 { if self.shape.0 > 1 {
ifd.push_tag(Tag::short(284, vec![2])) ifd.push_tag(Tag::short(284, &vec![2]))
} }
} }
if self.extra_tags_frame.contains_key(&frame_number) { for channel in 0..samples_per_pixel {
ifd.extend_tags(self.extra_tags_frame[&frame_number].to_owned()); let czt = self.get_czt(frame_number, channel);
if let Some(extra_tags) = self.extra_tags.get(&Some(czt)) {
for tag in extra_tags {
ifd.push_tag(tag.to_owned())
} }
ifd.extend_tags(self.extra_tags.to_owned()); }
ifd.push_tag(Tag::ascii(306, &format!("{}", Utc::now().format("%Y:%m:%d %H:%M:%S")))); }
if let Some(extra_tags) = self.extra_tags.get(&None) {
for tag in extra_tags {
ifd.push_tag(tag.to_owned())
}
}
ifd.push_tag(Tag::ascii(
306,
&format!("{}", Utc::now().format("%Y:%m:%d %H:%M:%S")),
));
where_to_write_next_ifd_offset = ifd.write(self, where_to_write_next_ifd_offset)?; where_to_write_next_ifd_offset = ifd.write(self, where_to_write_next_ifd_offset)?;
} else { } else {
warn = true; warn = true;
} }
if warn { if warn {
println!("Some frames were not added to the tif file, either you forgot them, \ println!(
or an error occurred and the tif file was closed prematurely.") "Some frames were not added to the tif file, either you forgot them, \
or an error occurred and the tif file was closed prematurely."
)
} }
} }
self.file.seek(SeekFrom::Start(where_to_write_next_ifd_offset))?; self.file
.seek(SeekFrom::Start(where_to_write_next_ifd_offset))?;
self.file.write(&0u64.to_le_bytes())?; self.file.write(&0u64.to_le_bytes())?;
Ok(()) Ok(())
} }

View File

@@ -2,7 +2,6 @@ use anyhow::Result;
use ndarray::{s, Array2}; use ndarray::{s, Array2};
use tiffwrite::IJTiffFile; use tiffwrite::IJTiffFile;
fn main() -> Result<()> { fn main() -> Result<()> {
println!("Hello World!"); println!("Hello World!");
let mut f = IJTiffFile::new("foo.tif", (2, 1, 1))?; let mut f = IJTiffFile::new("foo.tif", (2, 1, 1))?;
@@ -12,12 +11,12 @@ fn main() -> Result<()> {
arr[[i, j]] = i as u16; arr[[i, j]] = i as u16;
} }
} }
f.save(arr.to_owned(), 0, 0, 0, None)?; f.save(arr.to_owned(), 0, 0, 0)?;
let mut arr = Array2::<u16>::zeros((100, 100)); let mut arr = Array2::<u16>::zeros((100, 100));
arr.slice_mut(s![64.., ..64]).fill(1); arr.slice_mut(s![64.., ..64]).fill(1);
arr.slice_mut(s![..64, 64..]).fill(2); arr.slice_mut(s![..64, 64..]).fill(2);
arr.slice_mut(s![64.., 64..]).fill(3); arr.slice_mut(s![64.., 64..]).fill(3);
f.save(arr.to_owned(), 1, 0,0, None)?; f.save(arr.to_owned(), 1, 0, 0)?;
Ok(()) Ok(())
} }

256
src/py.rs
View File

@@ -1,101 +1,153 @@
use crate::{Colors, IJTiffFile, Tag};
use ndarray::s;
use num::{Complex, FromPrimitive, Rational32};
use numpy::{PyArrayMethods, PyReadonlyArray2};
use pyo3::prelude::*; use pyo3::prelude::*;
use crate::{IJTiffFile, Tag};
use num::{Complex, Rational32, FromPrimitive};
use numpy::{PyReadonlyArray2, PyArrayMethods};
#[pyclass(subclass)] #[pyclass(subclass)]
#[pyo3(name = "Tag")] #[pyo3(name = "Tag")]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct PyTag { struct PyTag {
tag: Tag tag: Tag,
} }
#[pymethods] #[pymethods]
impl PyTag { impl PyTag {
#[staticmethod] #[staticmethod]
fn byte(code: u16, byte: Vec<u8>) -> Self { fn byte(code: u16, byte: Vec<u8>) -> Self {
PyTag { tag: Tag::byte(code, byte) } PyTag {
tag: Tag::byte(code, &byte),
}
} }
#[staticmethod] #[staticmethod]
fn ascii(code: u16, ascii: &str) -> Self { fn ascii(code: u16, ascii: &str) -> Self {
PyTag { tag: Tag::ascii(code, ascii) } PyTag {
tag: Tag::ascii(code, ascii),
}
} }
#[staticmethod] #[staticmethod]
fn short(code: u16, short: Vec<u16>) -> Self { fn short(code: u16, short: Vec<u16>) -> Self {
PyTag { tag: Tag::short(code, short) } PyTag {
tag: Tag::short(code, &short),
}
} }
#[staticmethod] #[staticmethod]
fn long(code: u16, long: Vec<u32>) -> Self { fn long(code: u16, long: Vec<u32>) -> Self {
PyTag { tag: Tag::long(code, long) } PyTag {
tag: Tag::long(code, &long),
}
} }
#[staticmethod] #[staticmethod]
fn rational(code: u16, rational: Vec<f64>) -> Self { fn rational(code: u16, rational: Vec<f64>) -> Self {
PyTag { tag: Tag::rational(code, rational.into_iter().map(|x| Rational32::from_f64(x).unwrap()).collect()) } PyTag {
tag: Tag::rational(
code,
&rational
.into_iter()
.map(|x| Rational32::from_f64(x).unwrap())
.collect(),
),
}
} }
#[staticmethod] #[staticmethod]
fn sbyte(code: u16, sbyte: Vec<i8>) -> Self { fn sbyte(code: u16, sbyte: Vec<i8>) -> Self {
PyTag { tag: Tag::sbyte(code, sbyte) } PyTag {
tag: Tag::sbyte(code, &sbyte),
}
} }
#[staticmethod] #[staticmethod]
fn sshort(code: u16, sshort: Vec<i16>) -> Self { fn sshort(code: u16, sshort: Vec<i16>) -> Self {
PyTag { tag: Tag::sshort(code, sshort) } PyTag {
tag: Tag::sshort(code, &sshort),
}
} }
#[staticmethod] #[staticmethod]
fn slong(code: u16, slong: Vec<i32>) -> Self { fn slong(code: u16, slong: Vec<i32>) -> Self {
PyTag { tag: Tag::slong(code, slong) } PyTag {
tag: Tag::slong(code, &slong),
}
} }
#[staticmethod] #[staticmethod]
fn srational(code: u16, srational: Vec<f64>) -> Self { fn srational(code: u16, srational: Vec<f64>) -> Self {
PyTag { tag: Tag::srational(code, srational.into_iter().map(|x| Rational32::from_f64(x).unwrap()).collect()) } PyTag {
tag: Tag::srational(
code,
&srational
.into_iter()
.map(|x| Rational32::from_f64(x).unwrap())
.collect(),
),
}
} }
#[staticmethod] #[staticmethod]
fn float(code: u16, float: Vec<f32>) -> Self { fn float(code: u16, float: Vec<f32>) -> Self {
PyTag { tag: Tag::float(code, float) } PyTag {
tag: Tag::float(code, &float),
}
} }
#[staticmethod] #[staticmethod]
fn double(code: u16, double: Vec<f64>) -> Self { fn double(code: u16, double: Vec<f64>) -> Self {
PyTag { tag: Tag::double(code, double) } PyTag {
tag: Tag::double(code, &double),
}
} }
#[staticmethod] #[staticmethod]
fn ifd(code: u16, ifd: Vec<u32>) -> Self { fn ifd(code: u16, ifd: Vec<u32>) -> Self {
PyTag { tag: Tag::ifd(code, ifd) } PyTag {
tag: Tag::ifd(code, &ifd),
}
} }
#[staticmethod] #[staticmethod]
fn unicode(code: u16, unicode: &str) -> Self { fn unicode(code: u16, unicode: &str) -> Self {
PyTag { tag: Tag::unicode(code, unicode) } PyTag {
tag: Tag::unicode(code, unicode),
}
} }
#[staticmethod] #[staticmethod]
fn complex(code: u16, complex: Vec<(f32, f32)>) -> Self { fn complex(code: u16, complex: Vec<(f32, f32)>) -> Self {
PyTag { tag: Tag::complex(code, complex.into_iter().map(|(x, y)| Complex { re: x, im: y }).collect()) } PyTag {
tag: Tag::complex(
code,
&complex
.into_iter()
.map(|(x, y)| Complex { re: x, im: y })
.collect(),
),
}
} }
#[staticmethod] #[staticmethod]
fn long8(code: u16, long8: Vec<u64>) -> Self { fn long8(code: u16, long8: Vec<u64>) -> Self {
PyTag { tag: Tag::long8(code, long8) } PyTag {
tag: Tag::long8(code, &long8),
}
} }
#[staticmethod] #[staticmethod]
fn slong8(code: u16, slong8: Vec<i64>) -> Self { fn slong8(code: u16, slong8: Vec<i64>) -> Self {
PyTag { tag: Tag::slong8(code, slong8) } PyTag {
tag: Tag::slong8(code, &slong8),
}
} }
#[staticmethod] #[staticmethod]
fn ifd8(code: u16, ifd8: Vec<u64>) -> Self { fn ifd8(code: u16, ifd8: Vec<u64>) -> Self {
PyTag { tag: Tag::ifd8(code, ifd8) } PyTag {
tag: Tag::ifd8(code, &ifd8),
}
} }
fn count(&self) -> u64 { fn count(&self) -> u64 {
@@ -103,56 +155,158 @@ impl PyTag {
} }
} }
#[pyclass(subclass)] #[pyclass(subclass)]
#[pyo3(name = "IJTiffFile")] #[pyo3(name = "IJTiffFile")]
#[derive(Debug)] #[derive(Debug)]
struct PyIJTiffFile { struct PyIJTiffFile {
ijtifffile: Option<IJTiffFile> ijtifffile: Option<IJTiffFile>,
} }
#[pymethods] #[pymethods]
impl PyIJTiffFile { impl PyIJTiffFile {
#[new] #[new]
fn new(path: &str, shape: (usize, usize, usize)) -> PyResult<Self> { fn new(path: &str, shape: (usize, usize, usize)) -> PyResult<Self> {
Ok(PyIJTiffFile { ijtifffile: Some(IJTiffFile::new(path, shape)?) } ) Ok(PyIJTiffFile {
ijtifffile: Some(IJTiffFile::new(path, shape)?),
})
} }
fn with_colors(&mut self, colors: (u8, u8, u8)) -> Self { #[getter]
todo!() fn get_colors(&self) -> PyResult<Option<Vec<Vec<u8>>>> {
if let Some(ijtifffile) = &self.ijtifffile {
if let Colors::Colors(colors) = &ijtifffile.colors {
return Ok(Some(colors.to_owned()));
}
}
Ok(None)
} }
fn with_colormap(&mut self, colormap: Vec<(u8, u8, u8)>) -> Self { #[setter]
todo!() fn set_colors(&mut self, colors: PyReadonlyArray2<u8>) -> PyResult<()> {
if let Some(ijtifffile) = &mut self.ijtifffile {
let a = colors.to_owned_array();
ijtifffile.colors = Colors::Colors(
(0..a.shape()[0])
.map(|i| Vec::from(a.slice(s![i, ..]).as_slice().unwrap()))
.collect(),
);
}
Ok(())
} }
fn with_px_size(&mut self, pxsize: f64) -> Self { #[getter]
todo!() fn get_colormap(&mut self) -> PyResult<Option<Vec<Vec<u8>>>> {
if let Some(ijtifffile) = &self.ijtifffile {
if let Colors::Colormap(colormap) = &ijtifffile.colors {
return Ok(Some(colormap.to_owned()));
}
}
Ok(None)
} }
fn with_delta_z(&mut self, delta_z: f64) -> Self { #[setter]
todo!() fn set_colormap(&mut self, colormap: PyReadonlyArray2<u8>) -> PyResult<()> {
if let Some(ijtifffile) = &mut self.ijtifffile {
let a = colormap.to_owned_array();
ijtifffile.colors = Colors::Colormap(
(0..a.shape()[0])
.map(|i| Vec::from(a.slice(s![i, ..]).as_slice().unwrap()))
.collect(),
);
}
Ok(())
} }
fn with_time_interval(&mut self, time_interval: f64) -> Self { #[getter]
todo!() fn get_px_size(&self) -> PyResult<Option<f64>> {
if let Some(ijtifffile) = &self.ijtifffile {
Ok(ijtifffile.px_size)
} else {
Ok(None)
}
} }
fn with_comments(&mut self, comments: String) -> Self { #[setter]
todo!() fn set_px_size(&mut self, px_size: f64) -> PyResult<()> {
if let Some(ijtifffile) = &mut self.ijtifffile {
ijtifffile.px_size = Some(px_size);
}
Ok(())
} }
fn append_extra_tag(&mut self, tag: PyTag) { #[getter]
fn get_delta_z(&self) -> PyResult<Option<f64>> {
if let Some(ijtifffile) = &self.ijtifffile {
Ok(ijtifffile.delta_z)
} else {
Ok(None)
}
}
#[setter]
fn set_delta_z(&mut self, delta_z: f64) -> PyResult<()> {
if let Some(ijtifffile) = &mut self.ijtifffile {
ijtifffile.delta_z = Some(delta_z);
}
Ok(())
}
#[getter]
fn get_time_interval(&self) -> PyResult<Option<f64>> {
if let Some(ijtifffile) = &self.ijtifffile {
Ok(ijtifffile.time_interval)
} else {
Ok(None)
}
}
#[setter]
fn set_time_interval(&mut self, time_interval: f64) -> PyResult<()> {
if let Some(ijtifffile) = &mut self.ijtifffile {
ijtifffile.time_interval = Some(time_interval);
}
Ok(())
}
#[getter]
fn get_comment(&self) -> PyResult<Option<String>> {
if let Some(ijtifffile) = &self.ijtifffile {
Ok(ijtifffile.comment.clone())
} else {
Ok(None)
}
}
#[setter]
fn set_comment(&mut self, comment: &str) -> PyResult<()> {
if let Some(ijtifffile) = &mut self.ijtifffile {
ijtifffile.comment = Some(String::from(comment));
}
Ok(())
}
fn append_extra_tag(&mut self, tag: PyTag, czt: Option<(usize, usize, usize)>) {
if let Some(ijtifffile) = self.ijtifffile.as_mut() { if let Some(ijtifffile) = self.ijtifffile.as_mut() {
ijtifffile.extra_tags.push(tag.tag); if let Some(extra_tags) = ijtifffile.extra_tags.get_mut(&czt) {
extra_tags.push(tag.tag)
}
} }
} }
fn extend_extra_tags(&mut self, tags: Vec<PyTag>) { fn get_tags(&self, czt: Option<(usize, usize, usize)>) -> PyResult<Vec<PyTag>> {
if let Some(ijtifffile) = self.ijtifffile.as_mut() { if let Some(ijtifffile) = &self.ijtifffile {
ijtifffile.extra_tags.extend(tags.into_iter().map(|x| x.tag)); if let Some(extra_tags) = ijtifffile.extra_tags.get(&czt) {
let v = extra_tags
.iter()
.map(|tag| PyTag {
tag: tag.to_owned(),
})
.collect();
return Ok(v);
} }
} }
Ok(Vec::new())
}
fn close(&mut self) -> PyResult<()> { fn close(&mut self) -> PyResult<()> {
self.ijtifffile.take(); self.ijtifffile.take();
@@ -160,20 +314,19 @@ impl PyIJTiffFile {
} }
} }
macro_rules! impl_save { macro_rules! impl_save {
($T:ty, $t:ident) => { ($T:ty, $t:ident) => {
#[pymethods] #[pymethods]
impl PyIJTiffFile { impl PyIJTiffFile {
fn $t(&mut self, frame: PyReadonlyArray2<$T>, c: usize, t: usize, z: usize, fn $t(
extra_tags: Option<Vec<PyTag>>) -> PyResult<()> { &mut self,
let extra_tags = if let Some(extra_tags) = extra_tags { frame: PyReadonlyArray2<$T>,
Some(extra_tags.into_iter().map(|x| x.tag).collect()) c: usize,
} else { t: usize,
None z: usize,
}; ) -> PyResult<()> {
if let Some(ijtifffile) = self.ijtifffile.as_mut() { if let Some(ijtifffile) = self.ijtifffile.as_mut() {
ijtifffile.save(frame.to_owned_array(), c, t, z, extra_tags)?; ijtifffile.save(frame.to_owned_array(), c, t, z)?;
} }
Ok(()) Ok(())
} }
@@ -192,7 +345,6 @@ impl_save!(i64, save_i64);
impl_save!(f32, save_f32); impl_save!(f32, save_f32);
impl_save!(f64, save_f64); impl_save!(f64, save_f64);
#[pymodule] #[pymodule]
#[pyo3(name = "tiffwrite_rs")] #[pyo3(name = "tiffwrite_rs")]
fn tiffwrite_rs(m: &Bound<'_, PyModule>) -> PyResult<()> { fn tiffwrite_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {