2015-10-16 27 views
13

Ho una matrice di medie dimensioni (ad esempio 1500x3000) che voglio tracciare in scala poiché è un'immagine. Tuttavia, le scale verticale e orizzontale sono molto diverse. Per semplificare diciamo che c'è un metro/riga e 10/colonna. La trama dovrebbe quindi produrre un'immagine che è c. 1500x30000. Io uso l'estensione kwarg per le scale e aspect = 1 per evitare la deformazione. O utilizzando le finestre di stampa (QT4) e imshow() o usando savefig(), non sono mai riuscito a produrre l'immagine in scala e alla massima risoluzione.Tracciare a piena risoluzione con matplotlib.pyplot, imshow() e savefig()?

ho guardato a molte soluzioni proposte come indicato nella here, here o here e there o there nel caso in cui si trattava di un bug. Ho modificato il mio matplotlibrc e l'ho messo in ~/.config/matplotlib per provare a forzare le opzioni my display/savefig ma senza risultato. Ho anche provato con pcolormesh() ma senza successo. Io uso python 2.7 e matplotlib 1.3 dal repository di Ubuntu 14.04 e QT4Agg come back-end. Ho provato anche TkAgg, ma è lento e dà gli stessi risultati. Ho l'impressione che nell'asse delle x la risoluzione sia corretta, ma è decisamente sottocampionata nella direzione verticale. Ecco un pezzo di codice che dovrebbe simulare il mio problema.

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.colors 

R, C = 1500, 3000 
DATA = np.random.random((R, C)) 
DATA[::2, :] *= -1 # make every other line negative 
Yi, Xi = 1, 10 # increment 
CMP = 'seismic' 
ImageFormat ='pdf' 
Name = 'Image' 


DataRange = (np.absolute(DATA)).max() # I want my data centred on 0 
EXTENT = [0, Xi*C, 0 ,Yi*R] 
NORM = matplotlib.colors.Normalize(vmin =-DataRange, vmax= DataRange, clip =True) 

for i in range(1,4): 
    Fig=plt.figure(figsize=(45, 10), dpi = 100*i, tight_layout=True) 
    Fig.suptitle(Name+str(i)+'00DPI') 
    ax = Fig.add_subplot(1, 1, 1) 
    Plot = ax.imshow(DATA, cmap=plt.get_cmap(CMP), norm = NORM, extent = EXTENT, aspect = 1, interpolation='none') 
    ax.set_xlabel('metres') 
    ax.set_ylabel('metres') 
    Fig.savefig(Name+str(i)+'00DPI.'+ImageFormat, format = ImageFormat, dpi = Fig.dpi) 
plt.close() 

In imshow(), interpolazione = 'none' o 'più vicino' o 'bilineare' non cambia la risoluzione per qualche ragione anche se penso che si suppone almeno nella finestra di Qt4 se faccio spettacolo() invece di savefig(). Si noti che la risoluzione è la stessa nelle figure salvate qualunque cosa si è impostata in plt.figure (dpi =).

Sono fuori di idea e al limite delle mie conoscenze su come funzionano le cose con questo sistema. Qualsiasi aiuto è molto gradito.

Grazie in anticipo.

+1

Il salvataggio come SVG è un'opzione? 'plt.savefig (" test.svg ")' – Eric

+0

Non ho notato un miglioramento del salvataggio come svg in termini di risoluzione verticale. – Boorhin

+0

Ho modificato il codice in modo che l'immagine alternerà i valori positivi e negativi verticalmente. L'idea principale è che se le immagini vengono risolte per intero dovremmo essere in grado di distinguere le strisce orizzontali blu e rosse – Boorhin

risposta

1

Eseguendo il tuo esempio, tutto sembra a posto in matplotlib dopo lo zoom: non importa la risoluzione, i risultati sono gli stessi e vedo un pixel per unità di asse. Inoltre, provando con array più piccoli, i pdf (o altri formati) funzionano bene.

Questa è la mia spiegazione: quando si imposta la cifra dpi, si imposta il dpi dell'intera figura (non solo l'area dati). Sul mio sistema, questo risulta nell'area del grafico che occupa verticalmente circa il 20% dell'intera figura. Se si imposta 300 dpi e 10 in altezza, si ottiene per l'asse dei dati verticale un totale di 300x10x0.2 = 600 pixel, che non sono sufficienti per rappresentare 1500 punti, questo mi spiega perché l'output deve essere ricampionato. Si noti che la riduzione della larghezza talvolta funziona anche perché modifica la frazione di figura occupata dal grafico dei dati.

Quindi devi aumentare il dpi e impostare anche interpolation = 'none' (non dovrebbe importare se la risoluzione è impostata perfettamente, ma è importante se è abbastanza vicina). Inoltre, puoi regolare la posizione e le dimensioni del disegno per prendere una parte più grande della figura, ma tornando alle impostazioni di risoluzione ottimale, idealmente si desidera avere un numero di pixel sull'asse che è un multiplo dei punti dati, altrimenti deve avvenire una sorta di interpolazione (pensa come puoi tracciare due punti su tre pixel o viceversa).

Non so se ciò che segue è il modo migliore per farlo, ci potrebbero essere i metodi più adatti e proprietà in matplotlib, ma vorrei provare qualcosa di simile per calcolare il dpi ottimale:

vsize=ax.get_position().size[1] #fraction of figure occupied by axes 
axesdpi= int((Fig.get_size_inches()[1]*vsize)/R) #(or Yi*R according to what you want to do) 

Poi il codice (ridotto al primo anello), diventa:

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.colors 

R, C = 1500, 3000 
DATA = np.random.random((R, C)) 
DATA[::2, :] *= -1 # make every other line negative 
Yi, Xi = 1, 10 # increment 
CMP = 'seismic' 
ImageFormat ='pdf' 
Name = 'Image' 


DataRange = (np.absolute(DATA)).max() # I want my data centred on 0 
EXTENT = [0, Xi*C, 0 ,Yi*R] 
NORM = matplotlib.colors.Normalize(vmin =-DataRange, vmax= DataRange, clip =True) 

for i in (1,): 
    print i 
    Fig=plt.figure(figsize=(45, 10), dpi = 100*i, tight_layout=True) 
    Fig.suptitle(Name+str(i)+'00DPI') 
    ax = Fig.add_subplot(1, 1, 1) 
    Plot = ax.imshow(DATA, cmap=plt.get_cmap(CMP), norm = NORM, extent = EXTENT, aspect = 1, interpolation='none') 
    ax.set_xlabel('metres') 
    ax.set_ylabel('metres') 
    vsize=ax.get_position().size[1] #fraction of figure occupied by axes 
    axesdpi= int((Fig.get_size_inches()[1]*vsize)/R) #(or Yi*R according to what you want to do) 
    Fig.savefig(Name+str(axesdpi)+'DPI.'+ImageFormat, format = ImageFormat, dpi = axesdpi) 
    #plt.close() 

Questo funziona abbastanza per me.

1

In primo luogo, quando si salva come .pdf, si utilizza implicitamente il back-end in formato PDF, anche se è possibile specificare altri back-end nelle opzioni. Ciò significa che l'immagine viene salvata in formato vettoriale e dpi è quindi piuttosto privo di significato. In qualsiasi risoluzione, se carico il tuo PDF in un visualizzatore decente (ho usato inkscape, altri sono disponibili), puoi vedere chiaramente le strisce: ho effettivamente trovato più semplice osservare se hai impostato ogni seconda riga a zero. Tutti i PDF generati contengono informazioni complete per riprodurre le strisce e sono quindi virtualmente identici. Come si specifica figsize=(45, 10), tutti i PDF generati hanno suggerito dimensioni di visualizzazione 45 pollici x 10 pollici.

Se si specifica png come tipo di immagine, vedo una differenza nella dimensione del file in base al parametro dpi, che credo sia ciò che si aspetta. Se si guarda l'immagine a 100 dpi, ha 4500000, l'immagine a 200 dpi ha 18000000 pixel (4x come molti) e l'immagine a 300 dpi ha 40500000 (9 volte il numero). Noterai che 4500000 == 1500 x 3000 cioè un pixel per membro dell'array originale. Ne consegue, quindi, che le impostazioni dpi più grandi non ti danno davvero alcuna ulteriore definizione - invece, le tue strisce sono 2 o 3 larghe rispettivamente invece di 1.

I think quello che vuoi fare è effettivamente traccia ogni colonna 10 volte, in modo da ottenere un'immagine 1500 x 30000 pixel. Per fare questo, utilizzando tutti il ​​proprio codice, è possibile utilizzare np.repeat di fare qualcosa di simile al seguente:

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.colors 

R, C = 1500, 3000 
DATA = np.random.random((R, C)) 
DATA[::2, :] = 0 # make every other line plain white 
Yi, Xi = 1, 10 # increment 
DATA = np.repeat(DATA, Xi, axis=1) 
DATA = np.repeat(DATA, Yi) 

CMP = 'seismic' 
ImageFormat ='pdf' 
Name = 'Image' 


DataRange = (np.absolute(DATA)).max() # I want my data centred on 0 
EXTENT = [0, Xi*C, 0 ,Yi*R] 
NORM = matplotlib.colors.Normalize(vmin =-DataRange, vmax= DataRange, clip =True) 

for i in range(1,4): 
    Fig=plt.figure(figsize=(45, 10), dpi = 100*i, tight_layout=True) 
    Fig.suptitle(Name+str(i)+'00DPI') 
    ax = Fig.add_subplot(1, 1, 1) 
    Plot = ax.imshow(DATA, cmap=plt.get_cmap(CMP), norm = NORM, extent = EXTENT, aspect = 1, interpolation='none') 
    ax.set_xlabel('metres') 
    ax.set_ylabel('metres') 
    Fig.savefig(Name+str(i)+'00DPI.'+ImageFormat, format = ImageFormat, dpi = Fig.dpi) 
plt.close() 

Caveat: Questa soluzione ad alta intensità di una memoria - ci possono essere modi migliori là fuori.Se non è necessario l'uscita grafica vettoriale di pdf, è possibile modificare la variabile ImageFormat a png


Mi colpisce che l'altra cosa si potrebbe essere interessati con è quello di dare l'immagine del rapporto di formato appropriato (cioè 20 volte più largo che alto). Questo lo stai già facendo. Quindi, se guardi ciascuna rappresentazione di un pixel nello pdf, sono rettangolari (10 volte più larghe dell'altezza), non quadrate.

+0

Giusto per chiarire, l'incremento è una scala in metri. I dati vengono campionati ogni metro in verticale e ogni 10 metri in orizzontale. Quindi non voglio davvero ripetere i valori, preferisco un'interpolazione (come un filtro mediano). Tuttavia capisco che il mio codice al momento non è progettato per questo (ho rimosso quella parte per semplicità). Grazie per le spiegazioni, non conoscevo la funzione np.repeat. Userò anche un formato raster, è più significativo. È solo che preferisco il rendering dei font/assi in pdf, quindi posso modificarli in Inkscape per la pubblicazione. – Boorhin