14

换句话说,我想制作一个颜色随2 个变量变化的热图(或曲面图)。(具体来说,亮度 = 幅度,色调 = 相位。)有没有本地方法可以做到这一点?类似地块的一些示例:

使用两个颜色条,一个用于幅度,一个用于相位

使用颜色条表示幅度,使用圆形图例表示相位

使用 2D 颜色条来指示两个变量的变化

我想要做什么的几个很好的例子。

更多来自天文学的例子,但带有非感知色调

编辑:这就是我所做的:https ://github.com/endolith/complex_colormap

4

4 回答 4

9

imshow可以采用 [r, g, b] 条目的数组。因此,您可以将绝对值转换为强度和相位 - 转换为色调。

我将使用复数作为示例,因为它最有意义。如果需要,您可以随时添加numpy数组Z = X + 1j * Y

因此,对于您的数据Z,您可以使用例如

imshow(complex_array_to_rgb(Z))

哪里(编辑:多亏了这个建议,让它变得更快更好)

def complex_array_to_rgb(X, theme='dark', rmax=None):
    '''Takes an array of complex number and converts it to an array of [r, g, b],
    where phase gives hue and saturaton/value are given by the absolute value.
    Especially for use with imshow for complex plots.'''
    absmax = rmax or np.abs(X).max()
    Y = np.zeros(X.shape + (3,), dtype='float')
    Y[..., 0] = np.angle(X) / (2 * pi) % 1
    if theme == 'light':
        Y[..., 1] = np.clip(np.abs(X) / absmax, 0, 1)
        Y[..., 2] = 1
    elif theme == 'dark':
        Y[..., 1] = 1
        Y[..., 2] = np.clip(np.abs(X) / absmax, 0, 1)
    Y = matplotlib.colors.hsv_to_rgb(Y)
    return Y

因此,例如:

Z = np.array([[3*(x + 1j*y)**3 + 1/(x + 1j*y)**2
              for x in arange(-1,1,0.05)] for y in arange(-1,1,0.05)])
imshow(complex_array_to_rgb(Z, rmax=5), extent=(-1,1,-1,1))

在此处输入图像描述

imshow(complex_array_to_rgb(Z, rmax=5, theme='light'), extent=(-1,1,-1,1))

在此处输入图像描述

于 2013-06-14T16:42:23.313 回答
6

imshow将采用 NxMx3 (rbg) 或 NxMx4 (grba) 数组,因此您可以“手动”进行颜色映射。

您可以通过子类化将您的矢量映射到缩放器并非常Normalize巧妙地布置自定义颜色图来获得一些牵引力(但我认为这最终将不得不对您的一个维度进行分类)。

我做过类似的事情(pdf链接,见第24页的图),但代码在MATLAB(并埋在我的档案中的某个地方)。

我同意双变量颜色图会很有用(主要用于表示非常密集的矢量场,无论你做什么,你都会在小溪中前进)。我认为明显的扩展是让颜色图采用复杂的参数。它需要专门的子类Normalizeand Colormap,如果我认为实现它需要做很多工作,我会来回走动。我怀疑如果你让它手动工作,那将只是一个 api 争论的问题。

于 2013-03-15T15:06:28.290 回答
0

我创建了一个易于使用的 2D 颜色图类,它采用 2 个 NumPy 数组并将它们映射到基于参考图像的 RGB 图像。

我以@GjjvdBurg 的回答为起点。通过一些工作,这仍然可以改进,并可能变成一个合适的 Python 模块 - 如果你愿意,请随意这样做,我授予你所有学分。

TL;博士:

# read reference image
cmap_2d = ColorMap2D('const_chroma.jpeg', reverse_x=True)  # , xclip=(0,0.9))

# map the data x and y to the RGB space, defined by the image
rgb = cmap_2d(data_x, data_y)

# generate a colorbar image
cbar_rgb = cmap_2d.generate_cbar()

ColorMap2D班级:

class ColorMap2D:
    def __init__(self, filename: str, transpose=False, reverse_x=False, reverse_y=False, xclip=None, yclip=None):
        """
        Maps two 2D array to an RGB color space based on a given reference image.
        Args:
            filename (str): reference image to read the x-y colors from
            rotate (bool): if True, transpose the reference image (swap x and y axes)
            reverse_x (bool): if True, reverse the x scale on the reference
            reverse_y (bool): if True, reverse the y scale on the reference
            xclip (tuple): clip the image to this portion on the x scale; (0,1) is the whole image
            yclip  (tuple): clip the image to this portion on the y scale; (0,1) is the whole image
        """
        self._colormap_file = filename or COLORMAP_FILE
        self._img = plt.imread(self._colormap_file)
        if transpose:
            self._img = self._img.transpose()
        if reverse_x:
            self._img = self._img[::-1,:,:]
        if reverse_y:
            self._img = self._img[:,::-1,:]
        if xclip is not None:
            imin, imax = map(lambda x: int(self._img.shape[0] * x), xclip)
            self._img = self._img[imin:imax,:,:]
        if yclip is not None:
            imin, imax = map(lambda x: int(self._img.shape[1] * x), yclip)
            self._img = self._img[:,imin:imax,:]
        if issubclass(self._img.dtype.type, np.integer):
            self._img = self._img / 255.0

        self._width = len(self._img)
        self._height = len(self._img[0])

        self._range_x = (0, 1)
        self._range_y = (0, 1)


    @staticmethod
    def _scale_to_range(u: np.ndarray, u_min: float, u_max: float) -> np.ndarray:
        return (u - u_min) / (u_max - u_min)

    def _map_to_x(self, val: np.ndarray) -> np.ndarray:
        xmin, xmax = self._range_x
        val = self._scale_to_range(val, xmin, xmax)
        rescaled = (val * (self._width - 1))
        return rescaled.astype(int)

    def _map_to_y(self, val: np.ndarray) -> np.ndarray:
        ymin, ymax = self._range_y
        val = self._scale_to_range(val, ymin, ymax)
        rescaled = (val * (self._height - 1))
        return rescaled.astype(int)

    def __call__(self, val_x, val_y):
        """
        Take val_x and val_y, and associate the RGB values 
        from the reference picture to each item. val_x and val_y 
        must have the same shape.
        """
        if val_x.shape != val_y.shape:
            raise ValueError(f'x and y array must have the same shape, but have {val_x.shape} and {val_y.shape}.')
        self._range_x = (np.amin(val_x), np.amax(val_x))
        self._range_y = (np.amin(val_y), np.amax(val_y))
        x_indices = self._map_to_x(val_x)
        y_indices = self._map_to_y(val_y)
        i_xy = np.stack((x_indices, y_indices), axis=-1)
        rgb = np.zeros((*val_x.shape, 3))
        for indices in np.ndindex(val_x.shape):
            img_indices = tuple(i_xy[indices])
            rgb[indices] = self._img[img_indices]
        return rgb

    def generate_cbar(self, nx=100, ny=100):
        "generate an image that can be used as a 2D colorbar"
        x = np.linspace(0, 1, nx)
        y = np.linspace(0, 1, ny)
        return self.__call__(*np.meshgrid(x, y))


用法:

完整示例,使用从此处获取的恒定色度参考作为屏幕截图:

# generate data
x = y = np.linspace(-2, 2, 300)
xx, yy = np.meshgrid(x, y)
ampl = np.exp(-(xx ** 2 + yy ** 2))
phase = (xx ** 2 - yy ** 2) * 6 * np.pi
data = ampl * np.exp(1j * phase)
data_x, data_y = np.abs(data), np.angle(data)

# Here is the 2D colormap part
cmap_2d = ColorMap2D('const_chroma.jpeg', reverse_x=True)  # , xclip=(0,0.9))
rgb = cmap_2d(data_x, data_y)
cbar_rgb = cmap_2d.generate_cbar()

# plot the data
fig, plot_ax = plt.subplots(figsize=(8, 6))
plot_extent = (x.min(), x.max(), y.min(), y.max())
plot_ax.imshow(rgb, aspect='auto', extent=plot_extent,  origin='lower')
plot_ax.set_xlabel('x')
plot_ax.set_ylabel('y')
plot_ax.set_title('data')

#  create a 2D colorbar and make it fancy
plt.subplots_adjust(left=0.1, right=0.65)
bar_ax = fig.add_axes([0.68, 0.15, 0.15, 0.3])
cmap_extent = (data_x.min(), data_x.max(), data_y.min(), data_y.max())
bar_ax.imshow(cbar_rgb, extent=cmap_extent, aspect='auto',  origin='lower',)
bar_ax.set_xlabel('amplitude')
bar_ax.set_ylabel('phase')
bar_ax.yaxis.tick_right()
bar_ax.yaxis.set_label_position('right')
for item in ([bar_ax.title, bar_ax.xaxis.label, bar_ax.yaxis.label] +
             bar_ax.get_xticklabels() + bar_ax.get_yticklabels()):
    item.set_fontsize(7)
plt.show()

示例图

于 2021-08-30T09:08:26.740 回答
0

我知道这是一个旧帖子,但想帮助其他可能迟到的人。下面是从sage实现 complex_to_rgb 的 python 函数。注意:这个实现不是最优的,但它是可读的。见链接:(例子)(源代码

代码:

import numpy as np

def complex_to_rgb(z_values):
    
    width = z_values.shape[0]
    height = z_values.shape[1]
    rgb = np.zeros(shape=(width, height, 3))
    
    for i in range(width):
        
        row = z_values[i]
        
        for j in range(height):
            
            # define value, real(value), imag(value)
            zz = row[j]
            x = np.real(zz)
            y = np.imag(zz)
            
            # define magnitued and argument
            magnitude = np.hypot(x, y)
            arg = np.arctan2(y, x)
            
            # define lighness
            lightness = np.arctan(np.log(np.sqrt(magnitude) + 1)) * (4 / np.pi) - 1
            
            if lightness < 0:
                bot = 0
                top = 1 + lightness
            else:
                bot = lightness
                top = 1
            
            # define hue
            hue = 3 * arg / np.pi
            
            if hue < 0:
                hue += 6

            # set ihue and use it to define rgb values based on cases
            ihue = int(hue)

            # case 1
            if ihue == 0:
                r = top
                g = bot + hue * (top - bot)
                b = bot

            # case 2
            elif ihue == 1:
                r = bot + (2 - hue) * (top - bot)
                g = top
                b = bot

            # case 3
            elif ihue == 2:
                r = bot
                g = top
                b = bot + (hue - 2) * (top - bot)

            # case 4
            elif ihue == 3:
                r = bot
                g = bot + (4 - hue) * (top - bot)
                b = top

            # case 5
            elif ihue == 4:
                r = bot + (hue - 4) * (top - bot)
                g = bot
                b = top

            # case 6
            else:
                r = top
                g = bot
                b = bot + (6 - hue) * (top - bot)
                
            # set rgb array values
            rgb[i, j, 0] = r
            rgb[i, j, 1] = g
            rgb[i, j, 2] = b

    return rgb
于 2022-03-04T04:33:28.907 回答