157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
from typing import Callable
|
|
import colorsys
|
|
import numpy as np
|
|
import numpy.typing as npt
|
|
|
|
"""
|
|
colorize transform fn (W/B: -∞..+∞ -> 0..1, R/B: -∞..+∞ -> -1..1)
|
|
---------------------------------------------------------------------------------------------
|
|
White/Black Linear v -> {|clamp(v, min, max)|-X}/(Y-X)
|
|
where
|
|
X = 0 if sign(min) != sign(max)
|
|
MIN(|min|,|max|) otherwise
|
|
Y = MAX(|min|,|max|)
|
|
Sigmoid v -> |sigmoid(v+offset, gain)-0.5|*2
|
|
Red/Blue Linear v -> [{clamp(v, min, max)-min}/(max-min)-0.5]*2
|
|
Sigmoid v -> {sigmoid(v+offset, gain)-0.5}*2
|
|
"""
|
|
|
|
def create_convert_linear_abs(min_: float, max_: float):
|
|
assert min_ < max_
|
|
X = 0.0 if np.sign(min_) != np.sign(max_) else \
|
|
min(np.abs(min_), np.abs(max_))
|
|
Y = max(np.abs(min_), np.abs(max_))
|
|
|
|
#@np.vectorize
|
|
def fn(array: npt.NDArray[np.float32]):
|
|
vs = (np.abs(np.clip(array, min_, max_)) - X) / (Y - X)
|
|
#assert np.all(((0.0 <= vs) & (vs <= 1.0)) | np.isnan(vs))
|
|
return vs
|
|
return fn
|
|
|
|
def create_convert_linear(min: float, max: float):
|
|
assert min < max
|
|
#@np.vectorize
|
|
def fn(array: npt.NDArray[np.float32]):
|
|
vs = ((np.clip(array, min, max) - min) / (max - min) - 0.5) * 2.0
|
|
#assert np.all(((-1.0 <= vs) & (vs <= 1.0)) | np.isnan(vs))
|
|
return vs
|
|
return fn
|
|
|
|
def convert_linear_auto01(array: npt.NDArray[np.float32]):
|
|
min_, max_ = np.min(array), np.max(array)
|
|
if np.isnan(min_) or np.isnan(max_) or min_ == max_:
|
|
return array
|
|
else:
|
|
return (array - min_) / (max_ - min_)
|
|
|
|
def convert_linear_auto11(array: npt.NDArray[np.float32]):
|
|
min_, max_ = np.min(array), np.max(array)
|
|
if np.isnan(min_) or np.isnan(max_) or min_ == max_:
|
|
return array
|
|
else:
|
|
return ((array - min_) / (max_ - min_) - 0.5) * 2.0
|
|
|
|
def create_convert_sigmoid_abs(gain: float, offset: float):
|
|
#@np.vectorize
|
|
def fn(array: npt.NDArray[np.float32]):
|
|
vs = 1.0 / (1.0 + np.exp(-gain * (array+offset)))
|
|
vs = np.abs(vs - 0.5) * 2.0
|
|
#assert np.all(((0.0 <= vs) & (vs <= 1.0)) | np.isnan(vs))
|
|
return vs
|
|
return fn
|
|
|
|
def create_convert_sigmoid(gain: float, offset: float):
|
|
#@np.vectorize
|
|
def fn(array: npt.NDArray[np.float32]):
|
|
vs = 1.0 / (1.0 + np.exp(-gain * (array+offset)))
|
|
vs = (vs - 0.5) * 2.0
|
|
#assert np.all(((-1.0 <= vs) & (vs <= 1.0)) | np.isnan(vs))
|
|
return vs
|
|
return fn
|
|
|
|
class Colorizer:
|
|
|
|
# v -> v
|
|
convert: Callable[[npt.NDArray[np.float32]], npt.NDArray[np.float32]]|None
|
|
|
|
# v -> pixel
|
|
colorize: Callable[[npt.NDArray[np.float32]], npt.NDArray[np.float32]]|None
|
|
|
|
format: str
|
|
|
|
def __init__(
|
|
self,
|
|
value1: str,
|
|
value2: str,
|
|
rgb: tuple[str,str,str],
|
|
hsl: tuple[str,str,str],
|
|
trans: str,
|
|
linear_minmax: tuple[float,float],
|
|
sigmoid_gain_offset: tuple[float,float]
|
|
):
|
|
assert value1 in ["White/Black", "Red/Blue", "Custom"]
|
|
if value1 == "White/Black":
|
|
self.format = "L"
|
|
colorize = None
|
|
elif value1 == "Red/Blue":
|
|
self.format = "RGB"
|
|
colorize = create_colorizer(colorize_red_blue_v)
|
|
else:
|
|
assert value2 in ["RGB", "HSL"]
|
|
if value2 == "RGB":
|
|
r, g, b = rgb
|
|
fn = eval(f"lambda v: ( ({r}), ({g}), ({b}) )", { "__builtins__": np }, {})
|
|
fv = np.vectorize(fn, otypes=[np.float32, np.float32, np.float32])
|
|
self.format ="RGB"
|
|
else:
|
|
h, s, l = hsl
|
|
fn = eval(f"lambda v: HLS2RGB( ({h}), ({l}), ({s}) )", { "__builtins__": np, "HLS2RGB": colorsys.hls_to_rgb }, {})
|
|
fv = np.vectorize(fn, otypes=[np.float32, np.float32, np.float32])
|
|
self.format = "RGB"
|
|
colorize = create_colorizer(fv)
|
|
|
|
assert trans in ["Auto [0,1]", "Auto [-1,1]", "Linear", "Sigmoid"]
|
|
if trans == "Auto [0,1]":
|
|
convert = convert_linear_auto01
|
|
elif trans == "Auto [-1,1]":
|
|
convert = convert_linear_auto11
|
|
elif trans == "Linear":
|
|
if value1 == "White/Black":
|
|
convert = create_convert_linear_abs(*linear_minmax)
|
|
else:
|
|
convert = create_convert_linear(*linear_minmax)
|
|
else:
|
|
if value1 == "White/Black":
|
|
convert = create_convert_sigmoid_abs(*sigmoid_gain_offset)
|
|
else:
|
|
convert = create_convert_sigmoid(*sigmoid_gain_offset)
|
|
|
|
self.convert = convert
|
|
self.colorize = colorize
|
|
|
|
def __call__(self, array: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
|
|
if self.convert is not None:
|
|
array = self.convert(array)
|
|
if self.colorize is not None:
|
|
array = self.colorize(array)
|
|
|
|
#assert np.all(((0.0 <= array) & (array <= 1.0)) | np.isnan(array))
|
|
|
|
return np.clip(array * 256, 0.0, 255.0).astype(np.uint8)
|
|
|
|
def colorize_red_blue_(v: float) -> tuple[float, float, float]:
|
|
# v = -∞..+∞ を
|
|
# v < 0 のとき青 (0, 0, v)
|
|
# v > 0 のとき赤 (v ,0, 0)
|
|
# にする
|
|
L = (v if v > 0.0 else 0.0, 0.0, np.abs(v) if v < 0.0 else 0.0)
|
|
return L
|
|
|
|
colorize_red_blue_v = np.vectorize(colorize_red_blue_, otypes=[np.float32, np.float32, np.float32])
|
|
|
|
def create_colorizer(fn: np.vectorize):
|
|
def colorizer(array: npt.NDArray[np.float32]):
|
|
return np.dstack(fn(array))
|
|
return colorizer
|