- add colormap from string to rust code

This commit is contained in:
Wim Pomp
2025-08-23 23:13:57 +02:00
parent 067368e06c
commit 7ea0d1c093
5 changed files with 38 additions and 53 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "tiffwrite" name = "tiffwrite"
version = "2025.8.0" version = "2025.8.1"
edition = "2024" edition = "2024"
rust-version = "1.85.1" rust-version = "1.85.1"
authors = ["Wim Pomp <w.pomp@nki.nl>"] authors = ["Wim Pomp <w.pomp@nki.nl>"]
@@ -18,6 +18,8 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
anyhow = "1.0.98" anyhow = "1.0.98"
colorcet = "0.2.1"
colorgrad = "0.7.2"
chrono = "0.4.41" chrono = "0.4.41"
css-color = "0.2.8" css-color = "0.2.8"
flate2 = "1.1.1" flate2 = "1.1.1"

View File

@@ -6,8 +6,6 @@ from pathlib import Path
from typing import Any, Callable, Sequence from typing import Any, Callable, Sequence
from warnings import warn from warnings import warn
import colorcet
import matplotlib
import numpy as np import numpy as np
from numpy.typing import ArrayLike, DTypeLike from numpy.typing import ArrayLike, DTypeLike
from tqdm.auto import tqdm from tqdm.auto import tqdm
@@ -77,6 +75,9 @@ class IJTiffFile(rs.IJTiffFile):
else: else:
return codecs.get(int(idx), 50000) return codecs.get(int(idx), 50000)
if colors is not None and colormap is not None:
warn("Cannot have colors and colormap simultaneously.", TiffWriteWarning, stacklevel=2)
self.path = Path(path) self.path = Path(path)
self.dtype = np.dtype(dtype) self.dtype = np.dtype(dtype)
if compression is not None: if compression is not None:
@@ -88,7 +89,7 @@ class IJTiffFile(rs.IJTiffFile):
if colors is not None: if colors is not None:
self.colors = [str(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 = str(colormap)
if pxsize is not None: if pxsize is not None:
self.px_size = float(pxsize) self.px_size = float(pxsize)
if deltaz is not None: if deltaz is not None:
@@ -96,7 +97,7 @@ class IJTiffFile(rs.IJTiffFile):
if timeinterval is not None: if timeinterval is not None:
self.time_interval = float(timeinterval) self.time_interval = float(timeinterval)
if comment is not None: if comment is not None:
self.comment = comment self.comment = str(comment)
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, None) self.append_extra_tag(extra_tag, None)
@@ -106,8 +107,6 @@ class IJTiffFile(rs.IJTiffFile):
TiffWriteWarning, TiffWriteWarning,
stacklevel=2, stacklevel=2,
) )
if colors is not None and colormap is not None:
warn("Cannot have colors and colormap simultaneously.", TiffWriteWarning, stacklevel=2)
def __enter__(self) -> IJTiffFile: def __enter__(self) -> IJTiffFile:
return self return self
@@ -146,41 +145,6 @@ class IJTiffFile(rs.IJTiffFile):
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:
if hasattr(colorcet, colormap.rstrip("_r")):
cm = np.array(
[[int("".join(i), 16) for i in zip(*[iter(s[1:])] * 2)] for s in getattr(colorcet, colormap.rstrip("_r"))]
).astype("uint8")
if colormap.endswith("_r"):
cm = cm[::-1]
if colormap.startswith("glasbey") or colormap.endswith("glasbey"):
cm[0] = 255, 255, 255
cm[-1] = 0, 0, 0
else:
cmap = matplotlib.colormaps.get_cmap(colormap)
if cmap.N < 256:
cm = (
255
* np.vstack(
(
(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:
cm = (
255
* matplotlib.cm.ScalarMappable(matplotlib.colors.Normalize(0, 255), cmap).to_rgba(np.arange(256))[
:, :3
]
).astype("uint8")
return cm
def tiffwrite( def tiffwrite(
file: str | Path, file: str | Path,
data: np.ndarray, data: np.ndarray,

View File

@@ -26,7 +26,7 @@ classifiers = [
"Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.14",
] ]
dependencies = ["colorcet", "matplotlib", "numpy", "tqdm"] dependencies = ["numpy", "tqdm"]
[project.optional-dependencies] [project.optional-dependencies]
test = ["pytest", "tifffile", "imagecodecs"] test = ["pytest", "tifffile", "imagecodecs"]

View File

@@ -3,6 +3,8 @@ mod py;
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use chrono::Utc; use chrono::Utc;
use colorcet::ColorMap;
use colorgrad::{Gradient, LinearGradient};
use css_color::Srgb; use css_color::Srgb;
use flate2::write::ZlibEncoder; use flate2::write::ZlibEncoder;
use ndarray::{ArcArray2, AsArray, Ix2, s}; use ndarray::{ArcArray2, AsArray, Ix2, s};
@@ -709,11 +711,12 @@ impl IJTiffFile {
} }
/// set colors from css color names and #C01085 /// set colors from css color names and #C01085
pub fn set_colors(&mut self, colors: &[String]) -> Result<()> { pub fn set_colors<S: AsRef<str>>(&mut self, colors: &[S]) -> Result<()> {
self.colors = Colors::Colors( self.colors = Colors::Colors(
colors colors
.iter() .iter()
.map(|c| { .map(|c| {
let c = c.as_ref();
let lc = c.to_lowercase(); let lc = c.to_lowercase();
let c = match lc.as_str() { let c = match lc.as_str() {
"r" => "#ff0000", "r" => "#ff0000",
@@ -740,6 +743,28 @@ impl IJTiffFile {
Ok(()) Ok(())
} }
pub fn set_colormap<S: AsRef<str>>(&mut self, name: S) -> Result<()> {
let name = name.as_ref();
let colormap: LinearGradient = name.parse::<ColorMap>()?.try_into()?;
let mut colormap = colormap
.colors(256)
.into_iter()
.map(|c| {
vec![
(c.r * 255.0).round() as u8,
(c.g * 255.0).round() as u8,
(c.b * 255.0).round() as u8,
]
})
.collect::<Vec<_>>();
if name.starts_with("glasbey") || name.ends_with("glasbey") {
colormap[0] = vec![255, 255, 255];
colormap[255] = vec![0, 0, 0];
}
self.colors = Colors::Colormap(colormap);
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");

View File

@@ -1,7 +1,6 @@
use crate::{Colors, Compression, IJTiffFile, Tag}; use crate::{Colors, Compression, IJTiffFile, Tag};
use ndarray::s;
use num::{Complex, FromPrimitive, Rational32}; use num::{Complex, FromPrimitive, Rational32};
use numpy::{PyArrayMethods, PyReadonlyArray2}; use numpy::PyReadonlyArray2;
use pyo3::exceptions::PyValueError; use pyo3::exceptions::PyValueError;
use pyo3::prelude::*; use pyo3::prelude::*;
@@ -221,14 +220,9 @@ impl PyIJTiffFile {
} }
#[setter] #[setter]
fn set_colormap(&mut self, colormap: PyReadonlyArray2<u8>) -> PyResult<()> { fn set_colormap(&mut self, colormap: &str) -> PyResult<()> {
if let Some(ijtifffile) = &mut self.ijtifffile { if let Some(ijtifffile) = &mut self.ijtifffile {
let a = colormap.to_owned_array(); ijtifffile.set_colormap(colormap)?;
ijtifffile.colors = Colors::Colormap(
(0..a.shape()[0])
.map(|i| Vec::from(a.slice(s![i, ..]).as_slice().unwrap()))
.collect(),
);
} }
Ok(()) Ok(())
} }