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

View File

@@ -6,8 +6,6 @@ from pathlib import Path
from typing import Any, Callable, Sequence
from warnings import warn
import colorcet
import matplotlib
import numpy as np
from numpy.typing import ArrayLike, DTypeLike
from tqdm.auto import tqdm
@@ -77,6 +75,9 @@ class IJTiffFile(rs.IJTiffFile):
else:
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.dtype = np.dtype(dtype)
if compression is not None:
@@ -88,7 +89,7 @@ class IJTiffFile(rs.IJTiffFile):
if colors is not None:
self.colors = [str(color) for color in colors]
if colormap is not None:
self.colormap = get_colormap(colormap)
self.colormap = str(colormap)
if pxsize is not None:
self.px_size = float(pxsize)
if deltaz is not None:
@@ -96,7 +97,7 @@ class IJTiffFile(rs.IJTiffFile):
if timeinterval is not None:
self.time_interval = float(timeinterval)
if comment is not None:
self.comment = comment
self.comment = str(comment)
if extratags is not None:
for extra_tag in extratags:
self.append_extra_tag(extra_tag, None)
@@ -106,8 +107,6 @@ class IJTiffFile(rs.IJTiffFile):
TiffWriteWarning,
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:
return self
@@ -146,41 +145,6 @@ class IJTiffFile(rs.IJTiffFile):
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(
file: str | Path,
data: np.ndarray,

View File

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

View File

@@ -3,6 +3,8 @@ mod py;
use anyhow::{Result, anyhow};
use chrono::Utc;
use colorcet::ColorMap;
use colorgrad::{Gradient, LinearGradient};
use css_color::Srgb;
use flate2::write::ZlibEncoder;
use ndarray::{ArcArray2, AsArray, Ix2, s};
@@ -709,11 +711,12 @@ impl IJTiffFile {
}
/// 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(
colors
.iter()
.map(|c| {
let c = c.as_ref();
let lc = c.to_lowercase();
let c = match lc.as_str() {
"r" => "#ff0000",
@@ -740,6 +743,28 @@ impl IJTiffFile {
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)
pub fn description(&self, c_size: usize, z_size: usize, t_size: usize) -> String {
let mut desc: String = String::from("ImageJ=1.11a");

View File

@@ -1,7 +1,6 @@
use crate::{Colors, Compression, IJTiffFile, Tag};
use ndarray::s;
use num::{Complex, FromPrimitive, Rational32};
use numpy::{PyArrayMethods, PyReadonlyArray2};
use numpy::PyReadonlyArray2;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
@@ -221,14 +220,9 @@ impl PyIJTiffFile {
}
#[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 {
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(),
);
ijtifffile.set_colormap(colormap)?;
}
Ok(())
}