File size: 4,203 Bytes
5a1821b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0e1a816
5a1821b
0e1a816
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b93ba5
0e1a816
 
 
 
5a1821b
4b93ba5
af887b9
 
 
 
 
 
 
 
4b93ba5
0e1a816
 
 
 
 
 
 
 
 
 
 
 
af887b9
 
 
0e1a816
 
 
af887b9
 
 
 
5a1821b
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""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)