如何用imshow在Matplotlib中绘制非线性y轴的图像?

11 投票
4 回答
9343 浏览
提问于 2025-04-15 15:41

我想用Matplotlib把一个二维数组画成图片,并且让y轴的刻度是y值的平方的关系。

举个例子,我的数组第一行在图片中的高度是1,第二行的高度是4,依此类推(单位不重要)。用文字说不太好理解,所以请看这张图片(这就是我想要的效果):

alt text http://support.sas.com/rnd/app/da/new/802ce/iml/chap1/images/wavex1k.gif

从图中可以看到,第一行的高度是上面一行的两倍,依此类推。

对于那些想知道我为什么要这样做的人:

我有一个很大的数组(10, 700000),里面是浮点数,表示一个音频文件的离散小波变换系数。我想用这些系数画出标度图。虽然我可以把数组复制多次,直到得到想要的图片行高,但内存装不下这么多信息……

4 个回答

3

你可以看看 matplotlib.image.NonUniformImage。不过这个工具只是帮你处理不均匀的坐标轴而已——我觉得你想要的那种自适应绘图可能实现不了(因为图像中的每个点的面积总是相同的,所以你可能需要重复使用更宽的行)。你真的需要绘制完整的数组吗?显然,完整的细节在任何图表中都不会完全显示出来,所以我建议你大幅度减少原始矩阵的数据量,这样你就可以根据需要复制行,得到图像而不会耗尽内存。

4

我发现用matplotlib制作标度图的最好方法是使用imshow,这和specgram的实现方式类似。用矩形来做会很慢,因为你需要为每个值单独制作一个图形。同样,你也不想把所有东西都放进一个统一的NumPy数组里,因为这样很快就会耗尽内存,特别是你的最高级别大约会和信号的一半一样长。

这里有一个使用SciPy和PyWavelets的例子:

from pylab import *
import pywt
import scipy.io.wavfile as wavfile

# Find the highest power of two less than or equal to the input.
def lepow2(x):
    return 2 ** floor(log2(x))

# Make a scalogram given an MRA tree.
def scalogram(data):
    bottom = 0

    vmin = min(map(lambda x: min(abs(x)), data))
    vmax = max(map(lambda x: max(abs(x)), data))

    gca().set_autoscale_on(False)

    for row in range(0, len(data)):
        scale = 2.0 ** (row - len(data))

        imshow(
            array([abs(data[row])]),
            interpolation = 'nearest',
            vmin = vmin,
            vmax = vmax,
            extent = [0, 1, bottom, bottom + scale])

        bottom += scale

# Load the signal, take the first channel, limit length to a power of 2 for simplicity.
rate, signal = wavfile.read('kitten.wav')
signal = signal[0:lepow2(len(signal)),0]
tree = pywt.wavedec(signal, 'db5')

# Plotting.
gray()
scalogram(tree)
show()

你可能还想根据每个级别自适应地调整值。

这个方法对我来说效果不错。唯一的问题是matplotlib在级别之间会留下一条非常细的空隙。我还在寻找解决这个问题的方法。

附言 - 尽管这个问题现在已经有点旧了,但我还是想在这里回应一下,因为当我在谷歌上寻找使用MPL创建标度图的方法时,这个页面出现了。

8

你有没有尝试过转换坐标轴?比如说:

ax = subplot(111)
ax.yaxis.set_ticks([0, 2, 4, 8])
imshow(data)

这意味着在那些不存在的坐标位置,数据中必须有空缺,除非有办法提供一个转换函数,而不仅仅是列表(我从来没有尝试过)。

编辑

我承认这只是一个线索,并不是完整的解决方案。下面我会详细解释我的意思。

假设你的数据在一个数组中,叫做 a。你可以使用这样的转换:

class arr(object):
    @staticmethod
    def mylog2(x):
        lx = 0
        while x > 1:
            x >>= 1
            lx += 1
        return lx
    def __init__(self, array):
        self.array = array
    def __getitem__(self, index):
        return self.array[arr.mylog2(index+1)]
    def __len__(self):
        return 1 << len(self.array)

基本上,它会用 mylog2 函数来转换数组或列表的第一个坐标(你可以根据自己的需要修改这个函数 - 它是一个简化版的 log2 函数)。这样做的好处是,如果你需要的话,可以重复使用这个转换,而且你也能很容易地控制它。

然后把你的数组映射到这个新的数组上,这样不会复制数据,而是创建一个在当前实例中的本地引用:

b = arr(a)

现在你可以显示它,比如说:

ax = subplot(111)
ax.yaxis.set_ticks([16, 8, 4, 2, 1, 0])
axis([-0.5, 4.5, 31.5, 0.5])
imshow(b, interpolation="nearest")

这里有一个示例(包含随机值的数组):

alt text http://img691.imageshack.us/img691/8883/clipboard01f.png

撰写回答