计算一系列值的 RGB 值以创建热图

Posted

技术标签:

【中文标题】计算一系列值的 RGB 值以创建热图【英文标题】:Calculate RGB value for a range of values to create heat map 【发布时间】:2014-01-14 13:24:37 【问题描述】:

我正在尝试使用 python 创建热图。为此,我必须为可能值范围内的每个值分配一个 RGB 值。我想将颜色从蓝色(最小值)通过绿色更改为红色(最大值)。

下面的图片示例说明了我是如何想到颜色组合的:我们有一个从 1(纯蓝色)到 3(纯红色)的范围,2 介于两者之间,类似于绿色。

我阅读了线性插值并编写了一个函数(或多或少)处理最小值和最大值之间范围内某个值的计算并返回一个 RGB 元组。它使用ifelif 条件(这并不让我完全高兴):

def convert_to_rgb(minimum, maximum, value):
    minimum, maximum = float(minimum), float(maximum)    
    halfmax = (minimum + maximum) / 2
    if minimum <= value <= halfmax:
        r = 0
        g = int( 255./(halfmax - minimum) * (value - minimum))
        b = int( 255. + -255./(halfmax - minimum)  * (value - minimum))
        return (r,g,b)    
    elif halfmax < value <= maximum:
        r = int( 255./(maximum - halfmax) * (value - halfmax))
        g = int( 255. + -255./(maximum - halfmax)  * (value - halfmax))
        b = 0
        return (r,g,b)

但是我想知道是否可以使用if 条件为每个颜色值编写一个函数没有。有人有想法吗?非常感谢!

【问题讨论】:

【参考方案1】:

这是另一种方法,虽然不是尽可能短,但更通用,因为它没有针对您的特定颜色集进行硬编码。这意味着它还可用于在任意颜色的可变大小调色板上线性插值指定范围的值。

另请注意,颜色可能已在其他颜色空间中插值,得到的结果可能比其他颜色空间更令人愉悦。从我提交给一个名为 Range values to pseudocolor 的相关问题的两个单独答案获得的不同结果中就说明了这一点。

import sys
EPSILON = sys.float_info.epsilon  # Smallest possible difference.

def convert_to_rgb(minval, maxval, val, colors):
    # `colors` is a series of RGB colors delineating a series of
    # adjacent linear color gradients between each pair.

    # Determine where the given value falls proportionality within
    # the range from minval->maxval and scale that fractional value
    # by the total number in the `colors` palette.
    i_f = float(val-minval) / float(maxval-minval) * (len(colors)-1)

    # Determine the lower index of the pair of color indices this
    # value corresponds and its fractional distance between the lower
    # and the upper colors.
    i, f = int(i_f // 1), i_f % 1  # Split into whole & fractional parts.

    # Does it fall exactly on one of the color points?
    if f < EPSILON:
        return colors[i]
    else: # Return a color linearly interpolated in the range between it and 
          # the following one.
        (r1, g1, b1), (r2, g2, b2) = colors[i], colors[i+1]
        return int(r1 + f*(r2-r1)), int(g1 + f*(g2-g1)), int(b1 + f*(b2-b1))

if __name__ == '__main__':
    minval, maxval = 1, 3
    steps = 10
    delta = float(maxval-minval) / steps
    colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0)]  # [BLUE, GREEN, RED]
    print('  Val       R    G    B')
    for i in range(steps+1):
        val = minval + i*delta
        r, g, b = convert_to_rgb(minval, maxval, val, colors)
        print(':.3f -> (:3d, :3d, :3d)'.format(val, r, g, b))

数值输出:

  Val       R    G    B
1.000 -> (  0,   0, 255)
1.200 -> (  0,  50, 204)
1.400 -> (  0, 101, 153)
1.600 -> (  0, 153, 101)
1.800 -> (  0, 204,  50)
2.000 -> (  0, 255,   0)
2.200 -> ( 51, 203,   0)
2.400 -> (102, 152,   0)
2.600 -> (153, 101,   0)
2.800 -> (203,  51,   0)
3.000 -> (255,   0,   0)

这是可视化为水平渐变的输出:

【讨论】:

我用过这段代码,它工作得很好,即使是非常不同的颜色图(红色、橙色、白色)。这个解决方案可以在你的代码中使用 cmets 来改进,帮助我们理解这里的理论和实践。例如,找出上面的 float 和 int 之间的区别有什么意义? 查看此问题的一种方法是colors 指定一条穿过二维色彩空间的线,在该线上映射线性输入。 @Wes:减法是分离第一个线性插值(又名lerp)的浮点结果的整数和小数部分的过程中涉及的步骤之一。整数部分为i,小数部分为f。然后使用这两个值进行另一个 lerp,以计算调色板中 colors[i]colors[i+1] 之间的加权平均值(使用 f,它将位于 0-1.0 的范围内)。这是我多年前构想出来的用于制作平滑渐变的技术。 @Wes:对正在发生的事情的看法有一个问题是大多数色彩空间都是 3D(例如 RGB、YIQ 和 HLS),而不是 2D。 是的。一条穿过 3D 色彩空间的线。【参考方案2】:

“我们在对数尺度上感知光强度 – 指数强度斜坡将被视为线性 坡道”https://courses.cs.washington.edu/courses/cse455/09wi/Lects/lect11.pdf

来自https://en.wikipedia.org/wiki/RGB_color_model:“输入强度 RGB 值 (0.5, 0.5, 0.5) 仅输出大约 22% 的全亮度 (1.0, 1.0, 1.0),而不是 50%”

这会导致 @martineau 示例中 2.5 处的褐色污迹,它应该是黄色的,而 1.5 处的青色应该是为了获得适当的色调渐变。

因此,用于获取渐变的公式不一定是您想要的。 (抱歉没有直接回答你的问题)

但转换为 HSV 或 HLS 颜色空间模型并使用 H(用于色调)并将其用作输入,然后转换回 RGB 用于显示目的可能会很方便。 即:

colorsys.hsv_to_rgb(value, 1, 1)

https://docs.python.org/2/library/colorsys.html

【讨论】:

【参考方案3】:
def rgb(minimum, maximum, value):
    minimum, maximum = float(minimum), float(maximum)
    ratio = 2 * (value-minimum) / (maximum - minimum)
    b = int(max(0, 255*(1 - ratio)))
    r = int(max(0, 255*(ratio - 1)))
    g = 255 - b - r
    return r, g, b

【讨论】:

halfmax 应计算为(minimum - maximum) / 2value/halfmax 应为(value - minimum)/halfmax,否则只有当minimum 为1 且maximum 为3 时才能正常工作。见:@987654321 @【参考方案4】:

您通常可以消除带有两个值数组索引的if。 Python 缺少三元条件运算符,但这是可行的:

r = [red_curve_1, red_curve_2][value>=halfmax]
g = [green_curve_1, green_curve_2][value>=halfmax]
b = [blue_curve_1, blue_curve_2][value>=halfmax]

*_curve_1*_curve_2 表达式分别替换为中点左侧或右侧的常数或斜率或曲线。

我会把这些替换留给你,但是例如:

red_curve_1blue_curve_2 只是 0 green_curve_1255*(value-minimum)/(halfmax-minimum)

【讨论】:

这就是我所说的“条件索引”。顺便说一句,Python 确实有一个三元运算符,它称之为Conditional Expression。它允许像 r = red_curve_1 if value &gt;= halfmax else red_curve_2 这样的语句——尽管我认为使用它会更明显地表明该方法确实没有摆脱 OP 试图消除的 if 条件。 感谢关于条件表达式的提醒。它实际上比我建议的条件索引读起来不那么晦涩难懂。但正如你所说,OP 显然想摆脱if。 (条件表达式方法还具有在返回结果之前不对所有内容进行评估的优点。)

以上是关于计算一系列值的 RGB 值以创建热图的主要内容,如果未能解决你的问题,请参考以下文章

R 中热图/聚类默认值的差异(热图与热图.2)?

详解RGB系列--- 简介

无法在热图上设置值的格式

在R中创建连续热图

从R中的一系列分组变量中提取第一个值以计算旅行时间

从现有 json 列表中提取唯一值以创建唯一的列表视图构建器