2009-11-05 23 views
8

¿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?Cómo trazar una imagen con un eje y no lineal con Matplotlib usando imshow?

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 mire esta imagen por favor (que es el tipo de resultado que quiero):

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

Como se puede ver la primera fila es 2 veces más pequeño que el de arriba, y así sucesivamente.

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

que tienen una gama bastante grande (10, 700,000) de los flotadores, que representa la transformada wavelet discreta coeficientes de un archivo de sonido. Estoy tratando de trazar el escalograma usando esos coeficientes. Podría copiar la matriz x veces hasta que obtenga el tamaño de fila de la imagen deseada, pero la memoria no puede contener tanta información ...

Respuesta

8

¿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 lagunas en los datos de las coordenadas inexistentes, a menos que haya una manera de proporcionar una función de transformación en lugar de sólo listas (nunca probado).

Editar:

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

Supongamos que tiene sus datos en una matriz, a. Se puede utilizar una transformación como ésta:

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 se transformará la primera coordenada de una matriz o una lista con la función mylog2 (que se pueden transformar como desee - es hecho en casa como una simplificación de log2) La ventaja es que puedes reutilizarlo para otra transformación en caso de que la necesites y también puedes controlarla fácilmente.

a continuación, asignar la matriz de ésta, lo cual no tiene una copia, sino una referencia local en la instancia:

b = arr(a) 

Ahora puede visualizar, 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í está una muestra (con una matriz que contiene valores aleatorios):

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

+0

-1: seleccionar qué tics aparecen en el eje y no resuelve la pregunta original. Con set_ticks(), el eje y permanece lineal, e imshow() aún dibuja la matriz linealmente. El póster original quiere "tamaño variable, píxeles rectangulares". – EOL

+0

Es por eso que digo que los datos tuvieron que ser adaptados, aún vale la pena mencionarlos para los ejes en sí mismos. – RedGlyph

+0

@EOL: ... y ahora con la trama correcta también. Entiendo lo que querías decir, pero a veces juntar piezas ayuda ;-) – RedGlyph

3

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

+0

Sí, pensé en la reducción de resolución pero ocasionalmente tengo que hacer un acercamiento bastante profundo a los últimos coeficientes de detalles y la pérdida de precisión es a menudo demasiado alta. Voy a ver NonUniformImage de todos modos, gracias por la sugerencia de que no estaba al tanto. – attwad

+0

Creo que esto funcionaría para dibujar la imagen que desea el cartel original, y que esto sería más eficiente que duplicar "píxeles" a través de una duplicación de filas. Sin embargo, obtener los ticks de eje correspondientes sigue siendo una pregunta abierta. – EOL

2

Si desea que ambos puedan ampliar y guardar memoria, puede hacer el dibujo "a mano". Matplotlib le permite dibujar rectángulos (que serían los "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 las extensiones de los ejes no son fijados por add_patch(), pero se les puede fijarse a los valores que desee (axes.set_xlim, ...).

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

+0

+1 para una alternativa eficiente, incluso si parece un poco más de trabajo, aceleraría la renderización (como se discutió en otra solución). – RedGlyph

+0

@RedGlyph ¡Gracias! Supongo que la solución de thrope dibuja rectángulos como este (con la opción de vecino más cercano), pero directamente a través de Matplotlib. – EOL

3

La mejor manera que he encontrado para hacer un escalograma usando matplotlib es usar imshow, similar a la implementación de specgram. Usar rectángulos es lento, porque tienes que hacer un glifo separado para cada valor. Del mismo modo, no querrás tener que hornear cosas en una matriz NumPy uniforme, porque probablemente te quedes sin memoria rápidamente, ya que tu nivel más alto va a ser tan largo como la mitad de tu 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 los valores de forma adaptativa por nivel.

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

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

Cuestiones relacionadas