Shape2Force / S2FApp /utils /display.py
kaveh's picture
updated UI
0e1a816
raw
history blame
4.2 kB
"""Display utilities for heatmaps and colormaps."""
import numpy as np
import cv2
from config.constants import COLORMAPS, COLORMAP_N_SAMPLES
def cv_colormap_to_plotly_colorscale(colormap_name, n_samples=None):
"""Build a Plotly colorscale from OpenCV colormap so UI matches download/PDF exactly."""
n = n_samples or COLORMAP_N_SAMPLES
cv2_cmap = COLORMAPS.get(colormap_name, cv2.COLORMAP_JET)
gradient = np.linspace(0, 255, n, dtype=np.uint8).reshape(1, -1)
rgb = cv2.applyColorMap(gradient, cv2_cmap)
rgb = cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB)
scale = []
for i in range(n):
r, g, b = rgb[0, i]
scale.append([i / (n - 1), f"rgb({r},{g},{b})"])
return scale
def build_range_colorscale(colormap_name, clip_min, clip_max, n_range_samples=32):
"""
Build a Plotly colorscale for Range mode: normal gradient in [clip_min, clip_max],
the "zero" color everywhere else (0 → clip_min and clip_max → 1).
"""
cv2_cmap = COLORMAPS.get(colormap_name, cv2.COLORMAP_JET)
zero_px = np.array([[0]], dtype=np.uint8)
zero_rgb = cv2.applyColorMap(zero_px, cv2_cmap)
zero_rgb = cv2.cvtColor(zero_rgb, cv2.COLOR_BGR2RGB)
zr, zg, zb = zero_rgb[0, 0]
zero_color = f"rgb({zr},{zg},{zb})"
eps = 0.0005
scale = []
scale.append([0.0, zero_color])
if clip_min > eps:
scale.append([clip_min - eps, zero_color])
positions = np.linspace(clip_min, clip_max, n_range_samples)
pixel_vals = np.clip((positions * 255).astype(np.uint8), 0, 255).reshape(1, -1)
rgb = cv2.applyColorMap(pixel_vals, cv2_cmap)
rgb = cv2.cvtColor(rgb, cv2.COLOR_BGR2RGB)
for i, pos in enumerate(positions):
r, g, b = rgb[0, i]
scale.append([float(pos), f"rgb({r},{g},{b})"])
if clip_max < 1.0 - eps:
scale.append([clip_max + eps, zero_color])
scale.append([1.0, zero_color])
return scale
def apply_display_scale(heatmap, mode, min_percentile=0, max_percentile=100,
clip_min=0, clip_max=1, clip_bounds=False):
"""
Apply display scaling. Display only—does not change underlying values.
- Default: full 0–1 range as-is.
- Range: keep original values inside [clip_min, clip_max].
clip_bounds=False → zero out outside. clip_bounds=True → clamp to bounds.
- Rescale: map [clip_min, clip_max] → [0, 1].
clip_bounds=False → zero out outside. clip_bounds=True → clamp to bounds first.
"""
if mode == "Default" or mode == "Auto" or mode == "Full":
return np.clip(heatmap, 0, 1).astype(np.float32)
if mode == "Percentile":
pmin = float(np.percentile(heatmap, min_percentile))
pmax = float(np.percentile(heatmap, max_percentile))
if pmax > pmin:
out = (heatmap.astype(np.float32) - pmin) / (pmax - pmin)
return np.clip(out, 0, 1).astype(np.float32)
return np.clip(heatmap, 0, 1).astype(np.float32)
if mode == "Range" or mode == "Filter" or mode == "Threshold":
# Range: filter (discard outside) + rescale [clip_min, clip_max] → [0, 1] so max shows as red
vmin, vmax = float(clip_min), float(clip_max)
if vmax > vmin:
h = heatmap.astype(np.float32)
if clip_bounds:
return np.clip(h, vmin, vmax).astype(np.float32)
mask = (h >= vmin) & (h <= vmax)
out = np.zeros_like(h)
out[mask] = (h[mask] - vmin) / (vmax - vmin)
return np.clip(out, 0, 1).astype(np.float32)
return np.clip(heatmap, 0, 1).astype(np.float32)
if mode == "Rescale":
vmin, vmax = float(clip_min), float(clip_max)
if vmax > vmin:
h = heatmap.astype(np.float32)
if clip_bounds:
clamped = np.clip(h, vmin, vmax)
return ((clamped - vmin) / (vmax - vmin)).astype(np.float32)
mask = (h >= vmin) & (h <= vmax)
out = np.zeros_like(h)
out[mask] = (h[mask] - vmin) / (vmax - vmin)
return np.clip(out, 0, 1).astype(np.float32)
return np.clip(heatmap, 0, 1).astype(np.float32)
return np.clip(heatmap, 0, 1).astype(np.float32)