gnuplot: cara memetakan satu elemen array 2D per piksel tanpa margin

9

Saya mencoba menggunakan gnuplot 5.0 untuk memplot data 2D array tanpa margin atau batas atau sumbu ... hanya gambar 2D (.png atau .jpg) yang mewakili beberapa data. Saya ingin memiliki setiap elemen array untuk berkorespondensi dengan tepat satu pixel dalam gambar dengan tidak ada skala / interpolasi dll dan tidak ada piksel ekstra putih di tepi.

Sejauh ini, ketika saya mencoba mengatur margin ke 0 dan bahkan menggunakan pixelsbendera, saya masih tersisa dengan deretan piksel putih di kanan dan batas atas gambar.

Bagaimana saya bisa mendapatkan hanya file gambar dengan representasi pixel-by-pixel dari array data dan tidak ada tambahan?

skrip 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

Contoh data dari 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

piksel ekstra

HotDogCannon
sumber
apakah akan diterima jika data dalam x y zformat daftar?
theozh

Jawaban:

5

Beberapa terminal gnuplot menerapkan "dengan gambar" dengan membuat file png terpisah berisi gambar dan kemudian menautkannya di dalam plot yang dihasilkan. Menggunakan file gambar png yang terpisah secara langsung akan menghindari masalah tata letak halaman, margin, dll. Di sini saya menggunakan terminal kanvas. Plot itu sendiri dibuang; yang kami simpan hanyalah file png yang dibuat dengan konten yang diinginkan.

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
sumber
Ini singkat dan cepat dan berfungsi! Satu-satunya hal yang saya belum berhasil: 1. hindari output di Windows, 2. berikan nama saya sendiri tanpa indeks ke file png atau setidaknya berhenti meningkatkan indeks file png setiap kali Anda membuat ulang plot.
theozh
Saya tidak dapat membantu dengan output Windows. Anda benar tentang penghitung di terminal kanvas; tidak pernah me-reset. Tetapi Anda dapat memainkan trik yang sama dengan set term tikz externalimagesdan bahwa terminal me-reset penghitung pada setiap "istilah yang ditetapkan". Anda tidak dapat menggunakan set output "/dev/null"dengan tikz, tetapi jika itu tidak berhasil untuk menekan output untuk Anda maka Anda mungkin tidak peduli. Terminal tkcanvas dan svg adalah kemungkinan lain, tetapi mekanisme png eksternal pada mereka tergantung pada versi gnuplot dan opsi kompilasi.
Ethan
Ini sangat bagus! Sedikit mengganti nama setelah fakta diperlukan, tetapi pada akhirnya saya mendapatkan file gambar pixel-persis seperti yang saya cari. Terima kasih!
HotDogCannon
3

Jangan gunakan gnuplot.

Alih-alih, tulis skrip yang membaca data Anda dan mengubahnya menjadi salah satu format Portable Anymap . Berikut ini contoh dalam 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()

Jika Anda dapat memodifikasi program yang menghasilkan file data, Anda bahkan dapat melewatkan langkah di atas dan menghasilkan data secara langsung dalam format PNM.

Apa pun itu, Anda kemudian dapat menggunakan ImageMagick untuk mengonversi gambar ke format pilihan Anda:

./convert.py | convert - pic.png
pengguna3840170
sumber
1
Memang menggunakan Gnuplot untuk tugas semacam ini seperti menggunakan buku untuk mengarahkan paku ke sesuatu. Mungkin berhasil tetapi buku tidak dibuat untuk tugas ini. Alat pilihan saya adalah GNU Octave untuk tetap berada di domain "GNU" :) imwriteFungsi ini dapat digunakan untuk menyimpan data 2D sebagai gambar PNG.
blerontin
Ini adalah rute yang menarik, dan tentunya cadangan yang berharga, tetapi jika saya tidak akan menggunakan gnuplot, maka saya hanya akan menggunakan matplotlib(lihat jawaban saya di bawah). Ini adalah kontribusi yang besar, tetapi secara teknis pertanyaan / karunia meminta gnuplotsolusi, tidak layak seperti yang tampaknya untuk tugas khusus ini.
HotDogCannon
3

Ini seharusnya menjadi tugas yang mudah, namun ternyata tidak. Berikut ini mungkin solusi (rumit) karena semua upaya lainnya gagal. Kecurigaan saya adalah bahwa beberapa perpustakaan grafis memiliki masalah yang mungkin tidak dapat Anda selesaikan sebagai pengguna gnuplot.

Anda menyebutkan bahwa data matriks ASCII juga ok. "Trik" di sini adalah memplot data di with linesmana data "terputus" oleh garis kosong, pada dasarnya menggambar titik tunggal. Periksa ini jika Anda perlu memasukkan datafile 1: 1 Anda ke datablock .

Namun, jika itu belum cukup aneh, tampaknya berfungsi pngdan gifterminal tetapi tidak untuk pngcairoatau wxt. Saya kira solusinya mungkin lambat dan tidak efisien tetapi setidaknya itu menciptakan output yang diinginkan. Saya tidak yakin apakah ada batasan ukuran. Diuji dengan 100x100 piksel dengan Win7, gnuplot 5.2.6. Komentar dan peningkatan dipersilahkan.

Kode:

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

Hasil: (100x100 piksel)

masukkan deskripsi gambar di sini

(diperbesar dengan latar belakang hitam):

masukkan deskripsi gambar di sini

Gambar dengan 400x200 piksel (membutuhkan sekitar 22 detik pada laptop saya yang berusia 8 tahun).

masukkan deskripsi gambar di sini

theozh
sumber
dan bagaimana jika SizeX dan SizeY tidak sama?
HotDogCannon
maaf, saya campur aduk xdan y. Ini masih berfungsi jika SizeXdan SizeYtidak sama. Saya akan memperbaiki kodenya.
theozh
OK, pendekatan yang menarik, tetapi bagaimana saya pertama kali membaca dalam data biner dari Fortran ke array atau streaming seperti Anda Data2?
HotDogCannon
bisakah Anda memberikan file biner 400x200 dengan data floating point untuk diuji?
theozh
1

Apa yang akhirnya saya gunakan untuk mendapatkan apa yang saya butuhkan meskipun pertanyaan / karunia memintagnuplot solusi:

matplotlibmemiliki fungsi matplotlib.pyplot.imsave yang melakukan apa yang saya cari ... yaitu merencanakan 'hanya data piksel' dan tidak ada tambahan seperti batas, margin, sumbu, dll. Awalnya saya hanya tahu tentang matplotlib.pyplot.imshow dan harus tarik banyak trik untuk menghilangkan semua ekstra dari file gambar dan mencegah interpolasi / smoothing dll (dan karena itu beralih ke gnuplottitik tertentu). Dengan imsaveini cukup mudah, jadi saya kembali menggunakan matplotlibuntuk solusi yang mudah namun tetap fleksibel (dalam hal colormap, penskalaan, dll) untuk plot 'pixel tepat'. Ini sebuah contoh:

#!/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
sumber
1

OK, inilah solusi lain yang mungkin (saya memisahkannya dari pendekatan rumit pertama saya). Itu membuat plot segera, kurang dari satu detik. Tidak perlu mengganti nama atau membuat file yang tidak berguna.

Saya kira kuncinya adalah menggunakan term pngdan ps 0.1.

Saya tidak punya bukti tapi saya pikir ps 1akan ca. 6 piksel besar dan akan membuat beberapa piksel tumpang tindih dan / atau putih di sudutnya. Sekali lagi, untuk alasan apa pun tampaknya bekerja dengan term pngtetapi tidak dengan term pngcairo.

Apa yang saya uji (Win7, gnuplot 5.2.6) adalah file biner yang memiliki pola yang 00 00 FFberulang (saya tidak bisa menampilkan byte nol di sini). Karena gnuplot ternyata membaca 4 byte per item array (format="%d" ), ini mengarah ke pola RGB bergantian jika saya merencanakanwith lc rgb var .

Dengan cara yang sama (semoga) kita bisa mengetahui cara membaca format="%f"dan menggunakannya bersama dengan palet warna. Saya kira itu yang Anda cari, kan? Hasil tes lebih lanjut, komentar, perbaikan dan penjelasan dipersilahkan.

Kode:

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

Hasil:

masukkan deskripsi gambar di sini

theozh
sumber