gnuplot: como plotar um elemento de matriz 2D por pixel sem margens

9

Estou tentando usar o gnuplot 5.0 para plotar uma matriz de dados 2D sem margens, bordas ou eixos ... apenas uma imagem 2D (.png ou .jpg) representando alguns dados. Eu gostaria de ter cada elemento da matriz para corresponder a exatamente um pixel da imagem com nenhuma escala / interpolação etc e não pixels brancos extras nas bordas.

Até agora, quando tento definir as margens como 0 e mesmo usando a pixelsbandeira, ainda resta uma linha de pixels brancos nas bordas direita e superior da imagem.

Como posso obter apenas um arquivo de imagem com representação pixel por pixel de uma matriz de dados e nada a mais?

Script gnuplot:

#!/usr/bin/gnuplot --persist

set terminal png size 400, 200

set size ratio -1
set lmargin at screen 0
set rmargin at screen 1
set tmargin at screen 0
set bmargin at screen 1

unset colorbox
unset tics
unset xtics
unset ytics
unset border
unset key

set output "pic.png"

plot "T.dat" binary array=400x200 format="%f" with image pixels notitle

Dados de exemplo do Fortran 90:

program main
implicit none
integer, parameter :: nx = 400
integer, parameter :: ny = 200
real, dimension (:,:), allocatable :: T
allocate (T(nx,ny))

T(:,:)=0.500
T(2,2)=5.
T(nx-1,ny-1)=5.
T(2,ny-1)=5.
T(nx-1,2)=5.

open(3, file="T.dat", access="stream")
write(3) T(:,:)
close(3)

end program main

pixels extras

HotDogCannon
fonte
seria aceitável se os dados estiverem no x y zformato de lista?
theozh

Respostas:

5

Alguns terminais gnuplot implementam "with image" criando um arquivo png separado contendo a imagem e depois vinculando-a dentro da plotagem resultante. Usar esse arquivo de imagem png separado diretamente evitará problemas de layout da página, margens, etc. Aqui eu uso o terminal da tela. O enredo em si é jogado fora; tudo o que mantemos é o arquivo png criado com o conteúdo desejado.

gnuplot> set term canvas name 'myplot'
Terminal type is now 'canvas'
Options are ' rounded size 600,400 enhanced fsize 10 lw 1 fontscale 1 standalone'
gnuplot> set output '/dev/null'
gnuplot> plot "T.dat" binary array=400x200 format="%f" with image 
   linking image 1 to external file myplot_image_01.png
gnuplot> quit

$identify myplot_image_01.png
myplot_image_01.png PNG 400x200 400x200+0+0 8-bit sRGB 348B 0.000u 0:00.000
Ethan
fonte
Isso é curto, rápido e funciona! As únicas coisas que ainda não consegui: 1. evitar a saída no Windows, 2. dar meu próprio nome sem índice ao arquivo png ou pelo menos parar de aumentar o índice do arquivo png toda vez que você recriar a plotagem.
theozh 6/12/19
Não posso ajudar com a saída do Windows. Você está certo sobre o balcão no terminal de tela; nunca redefine. Mas você pode usar o mesmo truque set term tikz externalimagese esse terminal reinicia o contador em cada "período definido". Você não pode usar set output "/dev/null"com o tikz, mas se isso não funcionar para suprimir a saída para você, talvez você não se importe. Os terminais tkcanvas e svg são outras possibilidades, mas o mecanismo png externo depende da versão do gnuplot e das opções de compilação.
Ethan
Isso funciona muito bem! Um pouco de renomeação após o fato é necessário, mas no final recebo um arquivo de imagem com pixels exatos, como eu estava procurando. Obrigado!
HotDogCannon 6/12/19
3

Não use o gnuplot.

Em vez disso, escreva um script que leia seus dados e os converta em um dos formatos Portable Anymap . Aqui está um exemplo em Python:

#!/usr/bin/env python3
import math
import struct

width = 400
height = 200
levels = 255

raw_datum_fmt = '=d' # native, binary double-precision float
raw_datum_size = struct.calcsize(raw_datum_fmt)

with open('T.dat', 'rb') as f:
    print("P2")
    print("{} {}".format(width, height))
    print("{}".format(levels))

    raw_data = f.read(width * height * raw_datum_size)

    for y in range(height):
        for x in range(width):
            raw_datum, = struct.unpack_from(raw_datum_fmt, raw_data, (y * width + x) * raw_datum_size)
            datum = math.floor(raw_datum * levels) # assume a number in the range [0, 1]
            print("{:>3} ".format(datum), end='')
        print()

Se você pode modificar o programa que gera o arquivo de dados, pode até pular a etapa acima e gerar os dados diretamente no formato PNM.

De qualquer forma, você pode usar o ImageMagick para converter a imagem em um formato de sua escolha:

./convert.py | convert - pic.png
user3840170
fonte
11
De fato, usar o Gnuplot para esse tipo de tarefa é como usar um livro para pregar algo. Pode funcionar, mas os livros não são feitos para esta tarefa. Minha ferramenta preferida seria o GNU Octave para permanecer no domínio "GNU" :) A imwritefunção pode ser usada para salvar dados 2D como uma imagem PNG.
blerontin
Esta é uma rota interessante e, definitivamente, um backup valioso, mas se não for usar gnuplot, utilizarei apenas matplotlib(veja minha resposta abaixo). Essa é uma grande contribuição, mas tecnicamente a pergunta / recompensa pede uma gnuplotsolução, por mais imprópria que pareça ser para essa tarefa em particular.
HotDogCannon 06/12/19
3

Essa deve ser uma tarefa fácil, no entanto, aparentemente não é. A seguir pode ser uma solução (complicada) porque todas as outras tentativas falharam. Suspeito que algumas bibliotecas de gráficos tenham um problema que você provavelmente não pode resolver como usuário do gnuplot.

Você mencionou que os dados da matriz ASCII também estão ok. O "truque" aqui é plotar dados with linesonde os dados são "interrompidos" por linhas vazias, basicamente desenhando pontos únicos. Verifique isso no caso de você precisar colocar seu arquivo de dados 1: 1 em um bloco de dados .

No entanto, se já não é estranho o suficiente, parece funcionar para pnge gifterminal, mas não para pngcairoou wxt. Eu acho que a solução alternativa é provavelmente lenta e ineficiente, mas pelo menos cria a saída desejada. Não tenho certeza se existe um limite de tamanho. Testado com 100x100 pixels com Win7, gnuplot 5.2.6. Comentários e melhorias são bem-vindos.

Código:

### pixel image from matrix data without strange white border
reset session

SizeX = 100
SizeY = 100
set terminal png size SizeX,SizeY
set output "tbPixelImage.png"

# generate some random matrix data
set print $Data2
    do for [y=1:SizeY] {
        Line = ''
        do for [x=1:SizeX] {
            Line = Line.sprintf(" %9d",int(rand(0)*0x01000000))  # random color
        }
        print Line
    }
set print
# print $Data2

# convert matrix data into x y z data with empty lines inbetween
set print $Data3
    do for [y=1:SizeY] {
        do for [x=1:SizeX] {
            print sprintf("%g %g %s", x, y, word($Data2[y],x))
            print ""
        }
    }
set print
# print $Data3

set margins 0,0,0,0
unset colorbox
unset border
unset key
unset tics

set xrange[1:SizeX]
set yrange[1:SizeY]

plot $Data3 u 1:2:3 w l lw 1 lc rgb var notitle

set output
### end of code

Resultado: (100x100 pixels)

insira a descrição da imagem aqui

(ampliada com fundo preto):

insira a descrição da imagem aqui

Imagem com 400x200 pixels (leva cerca de 22 segundos no meu laptop de 8 anos).

insira a descrição da imagem aqui

theozh
fonte
e se SizeX e SizeY não forem iguais?
HotDogCannon 30/11/19
desculpe, eu misturei xe y. Ainda está funcionando se SizeXe SizeYnão é igual. Vou corrigir o código.
theozh 30/11/19
OK, abordagem interessante, mas como leio primeiro os dados binários do Fortran para um array ou fluxo como o seu Data2?
HotDogCannon 6/12/19
você poderia, de alguma forma, fornecer um arquivo binário de 400 x 200 com dados de ponto flutuante para testar?
theozh
1

O que acabei realmente usando para obter o que eu precisava, mesmo que a pergunta / recompensa pedisse uma gnuplotsolução:

matplotlibtem uma função matplotlib.pyplot.imsave, que faz o que eu estava procurando ... ou seja, plotando 'apenas pixels de dados' e sem extras como bordas, margens, eixos, etc. Originalmente, eu só sabia sobre matplotlib.pyplot.imshow e precisava faça muitos truques para eliminar todos os extras do arquivo de imagem e impedir qualquer interpolação / suavização, etc. (e, portanto, ativada gnuplotem um determinado ponto). Com imsaveisso é bastante fácil, então volto a usar matplotlibuma solução fácil e ainda flexível (em termos de mapa de cores, escala, etc.) para plotagens 'exatas em pixels'. Aqui está um exemplo:

#!/usr/bin/env python3

import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

nx = 400
ny = 200

data = np.fromfile('T.dat', dtype=np.float32, count=nx*ny)
data = data.reshape((nx,ny), order='F')
matplotlib.image.imsave('T.png', np.transpose(data), origin='lower', format='png')
HotDogCannon
fonte
1

OK, aqui está outra solução possível (eu a separei da minha primeira abordagem complicada). Ele cria a trama imediatamente, em menos de um segundo. Não é necessário renomear nem criar um arquivo inútil.

Eu acho que a chave é usar term pnge ps 0.1.

Não tenho uma prova, mas acho ps 1que seria ca. 6 pixels de largura e criaria sobreposição e / ou pixels brancos no canto. Novamente, por qualquer motivo, parece funcionar, term pngmas não com term pngcairo.

O que eu testei (Win7, gnuplot 5.2.6) é um arquivo binário com o padrão 00 00 FFrepetido todo (não consigo exibir bytes nulos aqui). Como o gnuplot aparentemente lê 4 bytes por item da matriz ( format="%d"), isso leva a um padrão RGB alternado se eu estiver plotando with lc rgb var.

Da mesma forma (espero), podemos descobrir como ler format="%f"e usá-lo junto com uma paleta de cores. Eu acho que é isso que você está procurando, certo? Mais resultados, comentários, melhorias e explicações são bem-vindos.

Código:

### pixel image from matrix data without strange white border
reset session

SizeX = 400
SizeY = 200
set terminal png size SizeX,SizeY
set output "tbPixelImage.png"

set margins 0,0,0,0
unset colorbox
unset border
unset key
unset tics

set xrange[0:SizeX-1]
set yrange[0:SizeY-1]

plot "tbBinary.dat" binary array=(SizeX,SizeY) format="%d" w p pt 5 ps 0.1 lc rgb var
### end of code

Resultado:

insira a descrição da imagem aqui

theozh
fonte