¿Cómo puedo trazar una matriz 2D como una imagen con Matplotlib teniendo la escala y relativa a la potencia de dos del valor y ?

Por ejemplo, la primera fila de mi matriz tendrá una altura en la imagen de 1, la segunda fila tendrá una altura de 4, etc. (las unidades son irrelevantes) No es fácil de explicar con palabras, así que mira esta imagen por favor (eso es el tipo de resultado que quiero):

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

Como puede ver, la primera fila es 2 veces más pequeña que la superior, y así sucesivamente.

Para aquellos interesados en por qué estoy tratando de hacer esto:

Tengo un conjunto bastante grande (10, 700000) de flotadores, que representan los coeficientes de transformación de wavelet discretos de un archivo de sonido. Estoy tratando de trazar el escalograma usando esos coeficientes. Podría copiar la matriz x veces hasta obtener el tamaño de fila de imagen deseado, pero la memoria no puede contener tanta información ...

10
attwad 5 nov. 2009 a las 11:32

4 respuestas

La mejor respuesta

¿Has intentado transformar el eje? Por ejemplo:

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

Esto significa que debe haber huecos en los datos para las coordenadas inexistentes, a menos que haya una manera de proporcionar una función de transformación en lugar de solo listas (nunca se ha intentado).

Editar :

Admito que fue solo una pista, no una solución completa. Esto es lo que quise decir con más detalles.

Supongamos que tiene sus datos en una matriz, a. Puedes usar una transformación como esta:

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)

Básicamente, transformará la primera coordenada de una matriz o lista con la función mylog2 (que puede transformar como lo desee: está hecha en casa como una simplificación de log2). La ventaja es que puede reutilizar eso para otra transformación si lo necesita, y también puede controlarlo fácilmente.

Luego, asigne su matriz a esta, que no hace una copia sino una referencia local en la instancia:

b = arr(a)

Ahora puede mostrarlo, por ejemplo:

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")

Aquí hay una muestra (con una matriz que contiene valores aleatorios):

texto alternativo http://img691.imageshack.us/img691/8883/clipboard01f.png

8
RedGlyph 5 nov. 2009 a las 12:10

Si desea que ambos puedan hacer zoom en y guardar memoria, puede hacer el dibujo "a mano". Matplotlib le permite dibujar rectángulos (serían sus "píxeles rectangulares"):

from matplotlib import patches
axes = subplot(111)
axes.add_patch(patches.Rectangle((0.2, 0.2), 0.5, 0.5))

Tenga en cuenta que add_patch () no establece la extensión de los ejes, pero puede establecerlos usted mismo en los valores que desee (axes.set_xlim, ...).

PD: Me parece que la respuesta de Thrope (matplotlib.image.NonUniformImage) realmente puede hacer lo que quieras, de una manera más simple que el método "manual" descrito aquí.

2
Eric O Lebigot 5 nov. 2009 a las 11:00

La mejor manera que he encontrado para hacer un escalograma usando matplotlib es usar imshow, similar a la implementación de specgram. El uso de rectángulos es lento porque tienes que hacer un glifo por separado para cada valor. Del mismo modo, no querrá tener que hornear cosas en una matriz NumPy uniforme, porque probablemente se quedará sin memoria rápidamente, ya que su nivel más alto será aproximadamente la mitad de su señal.

Aquí hay un ejemplo usando SciPy y 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()

También es posible que desee escalar valores de forma adaptativa por nivel.

Esto funciona bastante bien para mí. El único problema que tengo es que matplotlib crea un espacio delgado entre los niveles. Todavía estoy buscando una manera de arreglar esto.

PD - Aunque esta pregunta es bastante antigua ahora, pensé que respondería aquí, porque esta página apareció en Google cuando estaba buscando un método para crear escalogramas usando MPL.

3
Brandon Mechtley 22 jul. 2011 a las 10:42

Puede mirar matplotlib.image.NonUniformImage. Pero eso solo ayuda a tener un eje no uniforme: no creo que pueda trazar de forma adaptativa como lo desea (creo que cada punto de la imagen siempre tendrá la misma área, por lo que va a tiene que tener las filas más anchas varias veces). ¿Hay alguna razón por la que necesite trazar la matriz completa? Obviamente, el detalle completo no se mostrará en ningún gráfico, por lo que sugeriría reducir considerablemente la muestra de la matriz original para que pueda copiar las filas según sea necesario para obtener la imagen sin quedarse sin memoria.

3
robince 5 nov. 2009 a las 09:12