gnuplot: jak narysować jeden element tablicy 2D na piksel bez marginesów

9

Próbuję użyć gnuplot 5.0, aby wykreślić tablicę 2D danych bez marginesów, ramek lub osi ... tylko obraz 2D (.png lub .jpg) reprezentujący niektóre dane. Chciałbym mieć każdy element tablicy odpowiada dokładnie jeden piksel w obrazie z żadnym skalowanie / interpolacji etc i bez dodatkowych białych pikseli na krawędziach.

Do tej pory, kiedy próbuję ustawić marginesy na 0, a nawet przy użyciu pixelsflagi, wciąż jestem z rzędem białych pikseli po prawej i górnej granicy obrazu.

Jak mogę uzyskać tylko plik obrazu z reprezentacją tablicy danych piksel po pikselu i niczym więcej?

skrypt 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

Przykładowe dane z 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

dodatkowe piksele

HotDogCannon
źródło
czy byłoby dopuszczalne, gdyby dane były w x y zformacie listy?
theozh

Odpowiedzi:

5

Niektóre terminale gnuplot implementują „z obrazem”, tworząc osobny plik png zawierający obraz, a następnie łącząc się z nim wewnątrz wynikowego wykresu. Bezpośrednie użycie tego osobnego pliku obrazu png pozwoli uniknąć problemów z układem strony, marginesami itp. Tutaj używam terminala canvas. Sama fabuła zostaje wyrzucona; trzymamy tylko plik png utworzony z pożądanej treści.

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
źródło
To jest krótkie i szybkie i działa! Jedyne rzeczy, do których jeszcze mi się nie udało: 1. unikaj danych wyjściowych w systemie Windows, 2. podaj własną nazwę bez indeksu do pliku png lub przynajmniej przestań zwiększać indeks pliku png za każdym razem, gdy odtworzysz wykres.
theozh
Nie mogę pomóc z danymi wyjściowymi systemu Windows. Masz rację co do licznika w brezentowym terminalu; nigdy się nie resetuje. Ale możesz grać tą samą lewą, set term tikz externalimagesa terminal resetuje licznik za każdym „ustalonym terminem”. Nie możesz używać set output "/dev/null"z tikz, ale jeśli to nie zadziała, aby tłumić wyjście, możesz nie przejmować się. Terminale tkcanvas i svg to inne możliwości, ale zewnętrzny mechanizm png zależy od wersji gnuplot i opcji kompilacji.
Ethan,
To działa świetnie! Konieczna jest zmiana nazwy po fakcie, ale w końcu otrzymuję plik obrazu dokładnie taki jak w pikselach, tak jak szukałem. Dzięki!
HotDogCannon,
3

Nie używaj gnuplot.

Zamiast tego napisz skrypt, który odczytuje dane i konwertuje je na jeden z formatów Portable Anymap . Oto przykład w Pythonie:

#!/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()

Jeśli możesz zmodyfikować program, który generuje plik danych, możesz nawet pominąć powyższy krok i zamiast tego wygenerować dane bezpośrednio w formacie PNM.

Tak czy inaczej, możesz użyć ImageMagick, aby przekonwertować obraz na wybrany format:

./convert.py | convert - pic.png
użytkownik3840170
źródło
1
Rzeczywiście, używanie Gnuplot do tego rodzaju zadań jest jak używanie książki do wbijania gwoździ w coś. To może działać, ale książki nie są stworzone do tego zadania. Moim preferowanym narzędziem będzie GNU Octave, który pozostanie w domenie „GNU” :) imwriteFunkcji tej można użyć do zapisania danych 2D jako obrazu PNG.
blerontin,
Jest to interesująca trasa i zdecydowanie cenna kopia zapasowa, ale jeśli nie zamierzam jej użyć gnuplot, skorzystam z niej matplotlib(zobacz moją odpowiedź poniżej). Jest to świetny wkład, ale technicznie pytanie / zlecenie wymaga gnuplotrozwiązania, które wydaje się nieodpowiednie do tego konkretnego zadania.
HotDogCannon,
3

To powinno być łatwe zadanie, jednak najwyraźniej tak nie jest. Poniższe może być (kłopotliwe) rozwiązanie, ponieważ wszystkie inne próby zakończyły się niepowodzeniem. Podejrzewam, że w pewnej bibliotece graficznej występuje problem, którego prawdopodobnie nie można rozwiązać jako użytkownik gnuplot.

Wspomniałeś, że dane macierzy ASCII również są w porządku. „Sztuczka” polega tutaj na wykreślaniu danych, w with linesktórych dane są „przerywane” pustymi liniami, w zasadzie rysowaniem pojedynczych punktów. Zaznacz to, jeśli chcesz przenieść plik danych 1: 1 do bloku danych .

Jednakże, jeśli nie jest to już wystarczająco dziwne, wydaje się, że działa dla pngi gifterminal, ale nie dla pngcairolub wxt. Myślę, że to obejście jest prawdopodobnie powolne i nieefektywne, ale przynajmniej tworzy pożądane wyniki. Nie jestem pewien, czy istnieje ograniczenie rozmiaru. Testowany przy 100 x 100 pikseli z Win7, gnuplot 5.2.6. Komentarze i ulepszenia są mile widziane.

Kod:

### 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

Wynik: (100 x 100 pikseli)

wprowadź opis zdjęcia tutaj

(powiększony z czarnym tłem):

wprowadź opis zdjęcia tutaj

Obraz o wymiarach 400 x 200 pikseli (zajmuje około 22 sekund na moim 8-letnim laptopie).

wprowadź opis zdjęcia tutaj

theozh
źródło
a co, jeśli SizeX i SizeY nie są równe?
HotDogCannon
przepraszam, pomieszałem xi y. To wciąż działa, jeśli SizeXi SizeYnie są równe. Poprawię kod.
theozh
OK, ciekawe podejście, ale jak najpierw odczytać dane binarne z Fortran do tablicy lub strumienia takiego jak twój Data2?
HotDogCannon,
czy mógłbyś w jakiś sposób dostarczyć plik binarny 400x200 z danymi zmiennoprzecinkowymi do przetestowania?
theozh
1

Czego ostatecznie użyłem, aby uzyskać to, czego potrzebowałem, mimo że pytanie / zlecenie wymaga gnuplotrozwiązania:

matplotlibma funkcję matplotlib.pyplot.imsave, która robi to, czego szukałem ... tj. wykreśla „tylko piksele danych” i nie ma dodatków takich jak granice, marginesy, osie itp. Pierwotnie wiedziałem tylko o matplotlib.pyplot.imshow i musiałem wyciągnij wiele sztuczek, aby wyeliminować wszystkie dodatki z pliku obrazu i zapobiec jakiejkolwiek interpolacji / wygładzaniu itp. (i dlatego zwróciłem się gnuplotw pewnym momencie). Ze imsavejest to dość łatwe, więc jestem z powrotem do korzystania matplotlibz łatwym jeszcze wciąż elastyczny (pod względem colormap, skalowanie, itp) roztworu dla „piksel” dokładny działek. Oto przykład:

#!/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
źródło
1

OK, oto inne możliwe rozwiązanie (oddzieliłem je od mojego pierwszego niewygodnego podejścia). Tworzy fabułę natychmiast, mniej niż sekundę. Nie trzeba zmieniać nazwy ani tworzyć niepotrzebnego pliku.

Chyba kluczem jest użycie term pngi ps 0.1.

Nie mam dowodu, ale myślę, że ps 1byłoby ok. 6 pikseli dużych i tworzyłoby zachodzące na siebie i / lub białe piksele w rogu. Ponownie, z jakiegokolwiek powodu wydaje się, że działa, term pngale nie działa term pngcairo.

To, co przetestowałem (Win7, gnuplot 5.2.6), to plik binarny z 00 00 FFpowtarzającym się wzorem (nie mogę tutaj wyświetlić bajtów zerowych). Ponieważ gnuplot najwyraźniej odczytuje 4 bajty na element tablicy ( format="%d"), prowadzi to do naprzemiennego wzorca RGB, jeśli kreślę with lc rgb var.

W ten sam sposób (mam nadzieję) możemy dowiedzieć się, jak czytać format="%f"i używać go wraz z paletą kolorów. Chyba właśnie tego szukasz, prawda? Dalsze wyniki testu, komentarze, ulepszenia i wyjaśnienia są mile widziane.

Kod:

### 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

Wynik:

wprowadź opis zdjęcia tutaj

theozh
źródło