458 lines
20 KiB
Python
458 lines
20 KiB
Python
import numpy as np
|
|
from PIL import Image, ImageFilter
|
|
import glob
|
|
import hashlib
|
|
import json
|
|
import folder_paths
|
|
import os
|
|
|
|
_HIST_CH_DEFS = {
|
|
"RGB": [(0, (1.0, 0.22, 0.22)), (1, (0.22, 1.0, 0.22)), (2, (0.22, 0.44, 1.0))],
|
|
"RED": [(0, (1.0, 0.22, 0.22))],
|
|
"GREEN": [(1, (0.22, 1.0, 0.22))],
|
|
"BLUE": [(2, (0.22, 0.44, 1.0))],
|
|
}
|
|
|
|
# All valid style names
|
|
VALID_STYLES = {
|
|
"gradient", # original — filled area with top-to-bottom brightness fade
|
|
"bars", # original — flat filled area
|
|
"lines", # original — thin outline only (2px)
|
|
"glow", # original — bars + gaussian bloom
|
|
"waveform", # center-line oscilloscope, mirrored above/below midpoint
|
|
"heatmap", # single-channel density map using perceptual colour ramp
|
|
"stacked", # R/G/B channels stacked (not overlapping)
|
|
"dots", # vertical dot columns, density proportional to count
|
|
"step", # raw unsmoothed step function — shows true comb pattern
|
|
"luma", # gradient + luminosity curve overlaid in white
|
|
"log", # gradient with log-scale Y axis
|
|
"parade", # R | G | B panels side by side (ignores channel arg)
|
|
"percentile", # gradient + vertical lines at 10/25/50/75/90 percentiles
|
|
"inverse", # light background variant of gradient
|
|
}
|
|
|
|
|
|
def _get_raw(arr: np.ndarray, ch_idx: int, precision: bool) -> np.ndarray:
|
|
"""Return 256-bin histogram for one channel."""
|
|
if precision:
|
|
arr_16 = arr[:, :, ch_idx] * (65535.0 / 255.0)
|
|
raw_16, _ = np.histogram(arr_16, bins=65536, range=(0, 65536))
|
|
return raw_16.reshape(256, 256).sum(axis=1).astype(np.float32)
|
|
else:
|
|
raw, _ = np.histogram(arr[:, :, ch_idx], bins=256, range=(0, 256))
|
|
return raw.astype(np.float32)
|
|
|
|
|
|
def _make_smooth(sigma: float):
|
|
"""Return a closure that gaussian-smooths a 256-element array."""
|
|
size = max(int(sigma * 4) | 1, 3)
|
|
kx = np.arange(size) - size // 2
|
|
k = np.exp(-0.5 * (kx / sigma) ** 2); k /= k.sum()
|
|
def _smooth(h: np.ndarray) -> np.ndarray:
|
|
return np.convolve(h.astype(np.float32), k, mode='same')
|
|
return _smooth
|
|
|
|
|
|
def _normalise(h: np.ndarray, smooth_fn, sqrt: bool) -> np.ndarray:
|
|
sm = smooth_fn(h)
|
|
if sqrt:
|
|
sm = np.sqrt(np.maximum(sm, 0))
|
|
return sm / (sm.max() or 1.0)
|
|
|
|
|
|
def _log_normalise(h: np.ndarray, smooth_fn) -> np.ndarray:
|
|
sm = smooth_fn(h)
|
|
sm = np.log1p(np.maximum(sm, 0))
|
|
return sm / (sm.max() or 1.0)
|
|
|
|
|
|
def _dark_canvas(h: int, w: int) -> np.ndarray:
|
|
canvas = np.full((h, w, 3), 18.0 / 255.0, dtype=np.float32)
|
|
for frac in (0.25, 0.5, 0.75):
|
|
canvas[int((1.0 - frac) * (h - 1)), :] = 0.32
|
|
canvas[:, int(frac * (w - 1)), :] = 0.32
|
|
return canvas
|
|
|
|
|
|
def _light_canvas(h: int, w: int) -> np.ndarray:
|
|
canvas = np.full((h, w, 3), 0.92, dtype=np.float32)
|
|
for frac in (0.25, 0.5, 0.75):
|
|
canvas[int((1.0 - frac) * (h - 1)), :] = 0.72
|
|
canvas[:, int(frac * (w - 1)), :] = 0.72
|
|
return canvas
|
|
|
|
|
|
def _cols_heights(norm256: np.ndarray, hist_w: int, hist_h: int):
|
|
"""Interpolate normalised 256-bin curve to hist_w display columns."""
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
cols = np.interp(x_idx, np.arange(256), norm256)
|
|
heights = (cols * (hist_h - 1)).astype(int)
|
|
return cols, heights
|
|
|
|
|
|
def _draw_gradient(canvas, heights, color, hist_h, hist_w):
|
|
row_idx = np.arange(hist_h).reshape(-1, 1)
|
|
fill_mask = row_idx >= (hist_h - heights)
|
|
safe_h = np.maximum(heights, 1).astype(np.float32)
|
|
dist_b = (hist_h - 1 - row_idx).astype(np.float32)
|
|
grad = np.clip(0.28 + 0.72 * (dist_b / safe_h), 0.0, 1.0)
|
|
for ci, cv in enumerate(color):
|
|
canvas[:, :, ci] = np.where(
|
|
fill_mask, np.maximum(canvas[:, :, ci], grad * cv), canvas[:, :, ci])
|
|
|
|
|
|
def _draw_bars(canvas, heights, color, hist_h, hist_w, alpha=1.0):
|
|
row_idx = np.arange(hist_h).reshape(-1, 1)
|
|
fill_mask = row_idx >= (hist_h - heights)
|
|
for ci, cv in enumerate(color):
|
|
canvas[:, :, ci] = np.where(
|
|
fill_mask, np.maximum(canvas[:, :, ci], cv * alpha), canvas[:, :, ci])
|
|
|
|
|
|
def _draw_lines(canvas, heights, color, hist_h, hist_w):
|
|
for dy in range(2):
|
|
rs = np.clip(hist_h - heights - dy, 0, hist_h - 1)
|
|
xs = np.arange(hist_w)[heights > 0]
|
|
for ci, cv in enumerate(color):
|
|
canvas[rs[xs], xs, ci] = np.maximum(canvas[rs[xs], xs, ci], cv)
|
|
|
|
def rasterix_histogram_render(
|
|
pil_img: Image.Image,
|
|
channel: str = "RGB",
|
|
style: str = "bars",
|
|
precision: bool = False,
|
|
) -> Image.Image:
|
|
|
|
if style not in VALID_STYLES:
|
|
raise ValueError(f"style must be one of {sorted(VALID_STYLES)}, got '{style}'")
|
|
|
|
arr = np.array(pil_img.convert("RGB"), dtype=np.float32)
|
|
hist_h = 192
|
|
hist_w = 512
|
|
sqrt_norm = False
|
|
|
|
sigma = 0.75 if style in ("bars", "step", "dots") else 1.0
|
|
smooth = _make_smooth(sigma)
|
|
channels = _HIST_CH_DEFS.get(channel, _HIST_CH_DEFS["RGB"])
|
|
|
|
if style == "parade":
|
|
panel_w = hist_w // 3 # 341 px each; total = 1023 px
|
|
parade_w = panel_w * 3
|
|
canvas = np.full((hist_h, parade_w, 3), 18.0 / 255.0, dtype=np.float32)
|
|
for p in range(3):
|
|
ox = p * panel_w
|
|
for frac in (0.25, 0.5, 0.75):
|
|
canvas[int((1.0-frac)*(hist_h-1)), ox:ox+panel_w] = 0.32
|
|
canvas[:, ox + int(frac*(panel_w-1)), :] = 0.32
|
|
if p > 0:
|
|
canvas[:, ox, :] = 0.45
|
|
for p, (ch_idx, color) in enumerate(_HIST_CH_DEFS["RGB"]):
|
|
ox = p * panel_w
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norm = _normalise(raw, smooth, sqrt_norm)
|
|
_, heights = _cols_heights(norm, panel_w, hist_h)
|
|
sub = canvas[:, ox:ox+panel_w, :]
|
|
_draw_gradient(sub, heights, color, hist_h, panel_w)
|
|
canvas[:, ox:ox+panel_w, :] = sub
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "inverse":
|
|
canvas = _light_canvas(hist_h, hist_w)
|
|
inv_colors = {0: (0.75, 0.10, 0.10), 1: (0.10, 0.65, 0.10), 2: (0.10, 0.25, 0.85)}
|
|
draw_channels = [(idx, inv_colors.get(idx, col)) for idx, col in channels]
|
|
else:
|
|
canvas = _dark_canvas(hist_h, hist_w)
|
|
draw_channels = channels
|
|
|
|
raws = {ch_idx: _get_raw(arr, ch_idx, precision) for ch_idx, _ in channels}
|
|
luma_raw = None
|
|
if style in ("luma", "heatmap"):
|
|
luma_arr = (0.299 * arr[:,:,0] + 0.587 * arr[:,:,1] + 0.114 * arr[:,:,2])
|
|
if precision:
|
|
l16 = luma_arr * (65535.0/255.0)
|
|
r16, _ = np.histogram(l16, bins=65536, range=(0,65536))
|
|
luma_raw = r16.reshape(256,256).sum(axis=1).astype(np.float32)
|
|
else:
|
|
luma_raw, _ = np.histogram(luma_arr, bins=256, range=(0,256))
|
|
luma_raw = luma_raw.astype(np.float32)
|
|
|
|
if style == "heatmap":
|
|
norm = _normalise(luma_raw, smooth, sqrt_norm)
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
cols = np.interp(x_idx, np.arange(256), norm)
|
|
ramp_t = np.array([0.0, 0.33, 0.66, 1.0])
|
|
ramp_r = np.array([0.0, 0.05, 0.0, 1.0])
|
|
ramp_g = np.array([0.0, 0.05, 0.85, 1.0])
|
|
ramp_b = np.array([0.0, 0.55, 0.85, 1.0])
|
|
cr = np.interp(cols, ramp_t, ramp_r)
|
|
cg = np.interp(cols, ramp_t, ramp_g)
|
|
cb = np.interp(cols, ramp_t, ramp_b)
|
|
row_idx = np.arange(hist_h).reshape(-1, 1)
|
|
heights = (cols * (hist_h - 1)).astype(int)
|
|
fill_mask = row_idx >= (hist_h - heights)
|
|
safe_h = np.maximum(heights, 1).astype(np.float32)
|
|
dist_b = (hist_h - 1 - row_idx).astype(np.float32)
|
|
grad = np.clip(0.15 + 0.85 * (dist_b / safe_h), 0.0, 1.0)
|
|
for ci, cramp in enumerate([cr, cg, cb]):
|
|
canvas[:, :, ci] = np.where(fill_mask,
|
|
np.maximum(canvas[:, :, ci], grad * cramp), canvas[:, :, ci])
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "stacked":
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
row_idx = np.arange(hist_h).reshape(-1, 1)
|
|
norms = []
|
|
for ch_idx, _ in _HIST_CH_DEFS["RGB"]:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norms.append(np.interp(x_idx, np.arange(256), _normalise(raw, smooth, sqrt_norm)))
|
|
norms = np.array(norms) # (3, hist_w)
|
|
total = norms.sum(axis=0) + 1e-6
|
|
fracs = norms / total # (3, hist_w), each col sums to 1
|
|
cum_h = np.zeros(hist_w, dtype=np.float32)
|
|
for layer, (ch_idx, color) in enumerate(_HIST_CH_DEFS["RGB"]):
|
|
layer_h = (fracs[layer] * (hist_h - 1) * norms.max(axis=0) / norms.max()).astype(int)
|
|
floor_h = cum_h.astype(int)
|
|
top_h = (cum_h + layer_h).astype(int)
|
|
for x in range(hist_w):
|
|
if layer_h[x] > 0:
|
|
y_lo = hist_h - 1 - top_h[x]
|
|
y_hi = hist_h - 1 - floor_h[x]
|
|
y_lo = np.clip(y_lo, 0, hist_h-1)
|
|
y_hi = np.clip(y_hi, 0, hist_h-1)
|
|
if y_lo <= y_hi:
|
|
for ci, cv in enumerate(color):
|
|
canvas[y_lo:y_hi+1, x, ci] = np.maximum(
|
|
canvas[y_lo:y_hi+1, x, ci], cv * 0.85)
|
|
cum_h += layer_h
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "step":
|
|
row_idx = np.arange(hist_h).reshape(-1, 1)
|
|
column_bin = np.minimum((np.arange(hist_w) * 256) // hist_w, 255).astype(np.int32)
|
|
for ch_idx, color in draw_channels:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
if sqrt_norm:
|
|
norm256 = np.sqrt(np.maximum(raw, 0)); norm256 /= (norm256.max() or 1.0)
|
|
else:
|
|
norm256 = raw / (raw.max() or 1.0)
|
|
heights = (norm256[column_bin] * (hist_h - 1)).astype(int)
|
|
fill_mask = row_idx >= (hist_h - heights)
|
|
for ci, cv in enumerate(color):
|
|
canvas[:, :, ci] = np.where(
|
|
fill_mask, np.maximum(canvas[:, :, ci], cv * 0.9), canvas[:, :, ci])
|
|
y = np.clip(hist_h - 1 - heights, 0, hist_h - 1)
|
|
x = np.arange(hist_w)
|
|
canvas[y, x, :] = np.maximum(canvas[y, x, :], 0.95)
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "waveform":
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
mid = hist_h // 2
|
|
for ch_idx, color in draw_channels:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norm = _normalise(raw, smooth, sqrt_norm)
|
|
cols = np.interp(x_idx, np.arange(256), norm)
|
|
amp = (cols * (mid - 2)).astype(int) # half-amplitude
|
|
for x in range(hist_w):
|
|
if amp[x] == 0: continue
|
|
y_lo = np.clip(mid - amp[x], 0, hist_h-1)
|
|
y_hi = np.clip(mid + amp[x], 0, hist_h-1)
|
|
for y in range(y_lo, y_hi+1):
|
|
dist = abs(y - mid) / max(amp[x], 1)
|
|
brightness = max(0.25, 1.0 - dist * 0.7)
|
|
for ci, cv in enumerate(color):
|
|
canvas[y, x, ci] = max(canvas[y, x, ci], cv * brightness)
|
|
canvas[mid, :, :] = np.maximum(canvas[mid, :, :], 0.28)
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "dots":
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
n_dots = 32 # max dots per column
|
|
for ch_idx, color in draw_channels:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norm = _normalise(raw, smooth, sqrt_norm)
|
|
cols = np.interp(x_idx, np.arange(256), norm)
|
|
for x in range(hist_w):
|
|
n = max(1, int(cols[x] * n_dots))
|
|
positions = np.linspace(hist_h - 2, int((1.0 - cols[x]) * (hist_h - 1)), n)
|
|
for pos in positions:
|
|
y = int(np.clip(pos, 0, hist_h - 1))
|
|
brightness = 0.5 + 0.5 * (1.0 - pos / hist_h)
|
|
for ci, cv in enumerate(color):
|
|
canvas[y, x, ci] = max(canvas[y, x, ci], cv * brightness)
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "log":
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
for ch_idx, color in draw_channels:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norm = _log_normalise(raw, smooth)
|
|
_, heights = _cols_heights(norm, hist_w, hist_h)
|
|
_draw_gradient(canvas, heights, color, hist_h, hist_w)
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "luma":
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
for ch_idx, color in draw_channels:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norm = _normalise(raw, smooth, sqrt_norm)
|
|
_, heights = _cols_heights(norm, hist_w, hist_h)
|
|
dimmed = tuple(v * 0.55 for v in color)
|
|
_draw_gradient(canvas, heights, dimmed, hist_h, hist_w)
|
|
norm_luma = _normalise(luma_raw, smooth, sqrt_norm)
|
|
cols_luma = np.interp(x_idx, np.arange(256), norm_luma)
|
|
heights_luma = (cols_luma * (hist_h - 1)).astype(int)
|
|
white = (1.0, 1.0, 1.0)
|
|
_draw_lines(canvas, heights_luma, white, hist_h, hist_w)
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "percentile":
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
for ch_idx, color in draw_channels:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norm = _normalise(raw, smooth, sqrt_norm)
|
|
_, heights = _cols_heights(norm, hist_w, hist_h)
|
|
_draw_gradient(canvas, heights, color, hist_h, hist_w)
|
|
if len(draw_channels) == 3:
|
|
lum = 0.299*arr[:,:,0] + 0.587*arr[:,:,1] + 0.114*arr[:,:,2]
|
|
flat = lum.ravel()
|
|
else:
|
|
flat = arr[:, :, draw_channels[0][0]].ravel()
|
|
pcts = [10, 25, 50, 75, 90]
|
|
pvals = np.percentile(flat, pcts)
|
|
pct_colors = [
|
|
(0.60, 0.60, 0.60), # 10th — grey
|
|
(0.85, 0.85, 0.30), # 25th — yellow
|
|
(1.00, 1.00, 1.00), # 50th — white (median)
|
|
(0.85, 0.85, 0.30), # 75th — yellow
|
|
(0.60, 0.60, 0.60), # 90th — grey
|
|
]
|
|
for pval, pcol in zip(pvals, pct_colors):
|
|
x_pos = int(np.interp(pval, [0, 255], [0, hist_w - 1]))
|
|
x_pos = np.clip(x_pos, 0, hist_w - 1)
|
|
for ci, cv in enumerate(pcol):
|
|
canvas[:, x_pos, ci] = cv
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
if style == "inverse":
|
|
x_idx = np.linspace(0, 255, hist_w)
|
|
for ch_idx, color in draw_channels:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norm = _normalise(raw, smooth, sqrt_norm)
|
|
cols = np.interp(x_idx, np.arange(256), norm)
|
|
heights = (cols * (hist_h - 1)).astype(int)
|
|
row_idx = np.arange(hist_h).reshape(-1, 1)
|
|
fill_mask = row_idx >= (hist_h - heights)
|
|
safe_h = np.maximum(heights, 1).astype(np.float32)
|
|
dist_b = (hist_h - 1 - row_idx).astype(np.float32)
|
|
grad = np.clip(0.15 + 0.85 * (1.0 - dist_b / safe_h), 0.0, 1.0)
|
|
for ci, cv in enumerate(color):
|
|
canvas[:, :, ci] = np.where(
|
|
fill_mask,
|
|
np.minimum(canvas[:, :, ci], 1.0 - grad * cv * 0.7),
|
|
canvas[:, :, ci])
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
return result
|
|
|
|
for ch_idx, color in draw_channels:
|
|
raw = _get_raw(arr, ch_idx, precision)
|
|
norm = _normalise(raw, smooth, sqrt_norm)
|
|
_, heights = _cols_heights(norm, hist_w, hist_h)
|
|
|
|
if style in ("gradient", "inverse"):
|
|
_draw_gradient(canvas, heights, color, hist_h, hist_w)
|
|
elif style == "lines":
|
|
_draw_lines(canvas, heights, color, hist_h, hist_w)
|
|
else: # bars, glow
|
|
_draw_bars(canvas, heights, color, hist_h, hist_w)
|
|
|
|
result = Image.fromarray(
|
|
np.clip(canvas * 255, 0, 255).astype(np.uint8), mode="RGB")
|
|
|
|
if style == "glow":
|
|
bloom = result.filter(ImageFilter.GaussianBlur(radius=5))
|
|
result = Image.fromarray(
|
|
np.clip(
|
|
np.array(result, dtype=np.float32) +
|
|
np.array(bloom, dtype=np.float32) * 0.55,
|
|
0, 255).astype(np.uint8), mode="RGB")
|
|
|
|
return result
|
|
|
|
def _safe_node_id(node_id):
|
|
safe = str(node_id or "global")
|
|
return "".join(ch if ch.isalnum() or ch in ("-", "_") else "_" for ch in safe)
|
|
|
|
def rasterix_hist_cache_paths(node_id=None):
|
|
hist_dir = folder_paths.get_temp_directory()
|
|
os.makedirs(hist_dir, exist_ok=True)
|
|
safe_id = _safe_node_id(node_id)
|
|
return hist_dir, os.path.join(hist_dir, f"rasterix_hist_cache_{safe_id}_input.png"), os.path.join(hist_dir, f"rasterix_hist_cache_{safe_id}_output.png"),
|
|
|
|
def rasterix_hist_meta_path(node_id=None):
|
|
hist_dir, _, _ = rasterix_hist_cache_paths(node_id=node_id)
|
|
return os.path.join(hist_dir, f"rasterix_hist_cache_meta_{_safe_node_id(node_id)}.json")
|
|
|
|
def rasterix_hist_signature(pil_img):
|
|
return hashlib.sha1(pil_img.tobytes()).hexdigest()
|
|
|
|
def rasterix_hist_cache_store(pil_input, pil_output, precision, node_id=None):
|
|
hist_dir, in_path, out_path = rasterix_hist_cache_paths(node_id=node_id)
|
|
meta_path = rasterix_hist_meta_path(node_id=node_id)
|
|
safe_id = _safe_node_id(node_id)
|
|
current_key = f"{rasterix_hist_signature(pil_input)}::{rasterix_hist_signature(pil_output)}::{'16' if precision else '8'}"
|
|
|
|
previous_key = None
|
|
if os.path.isfile(meta_path):
|
|
try:
|
|
with open(meta_path, 'r', encoding='utf-8') as f:
|
|
previous_key = (json.load(f) or {}).get("cache_key")
|
|
except Exception:
|
|
previous_key = None
|
|
|
|
if current_key != previous_key:
|
|
for old_hist in glob.glob(os.path.join(hist_dir, f"History_{safe_id}_*.png")):
|
|
try:
|
|
os.remove(old_hist)
|
|
except OSError:
|
|
pass
|
|
|
|
pil_input.save(in_path, compress_level=1)
|
|
pil_output.save(out_path, compress_level=1)
|
|
with open(meta_path, 'w', encoding='utf-8') as f:
|
|
json.dump({"cache_key": current_key}, f)
|
|
|
|
def rasterix_hist_render_path(node_id, histogram_source, histogram_channel, histogram_style):
|
|
hist_dir, _, _ = rasterix_hist_cache_paths(node_id=node_id)
|
|
source_prefix = "input" if histogram_source else "output"
|
|
safe_id = _safe_node_id(node_id)
|
|
history_filename = f"History_{safe_id}_{source_prefix}_{histogram_channel.lower()}_{histogram_style}.png"
|
|
return os.path.join(hist_dir, history_filename)
|
|
|
|
def rasterix_hist_render_selected(pil_input, pil_output, precision, histogram_source, histogram_channel, histogram_style, node_id=None):
|
|
target_file = rasterix_hist_render_path(node_id, histogram_source, histogram_channel, histogram_style)
|
|
if os.path.isfile(target_file):
|
|
return Image.open(target_file).convert("RGB")
|
|
source_image = pil_input if histogram_source else pil_output
|
|
rendered = rasterix_histogram_render(source_image, histogram_channel, histogram_style, precision)
|
|
rendered.save(target_file, compress_level=1)
|
|
return rendered |