gnuplot:如何在没有边距的情况下每个像素绘制一个2D数组元素

10
我正在尝试使用gnuplot 5.0绘制一个2D数据数组的图像,没有边距、边框或轴线......只是一个代表某些数据的2D图像(.png或.jpg)。我希望每个数组元素都对应于图像中的一个像素,不进行缩放/插值等操作,并且在边缘没有额外的白色像素。
到目前为止,当我尝试将边距设置为0甚至使用“pixels”标志时,仍然会在图像的右侧和顶部边框上留下一行白色像素。
如何获得仅具有数据数组逐像素表示的图像文件,而没有任何其他内容?
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

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

extra pixels


数据以 x y z 列表格式是否可接受? - theozh
5个回答

5
一些 gnuplot 终端通过创建一个包含图像的独立 png 文件并在生成的图中链接到它来实现 "with image"。直接使用该独立的 png 图像文件将避免任何页面布局、边距等问题。这里我使用 canvas 终端。绘制本身被丢弃;我们保留的仅是创建所需内容的 png 文件。
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

这很简短、快速且有效!我还没有成功的两件事:1. 避免在Windows中输出,2. 给我的PNG文件命名为自己的名称,或者至少停止每次重新创建绘图时增加PNG文件索引的操作。 - theozh
我无法处理Windows输出。您关于画布终端中计数器的观点是正确的;它从不重置。但是您可以在“set term”中使用相同的技巧来设置set term tikz externalimages,该终端会在每次“set term”时重置计数器。您无法在tikz中使用set output "/dev/null",但是如果这对于您来说无法抑制输出,则可能您并不在意。 tkcanvas和svg终端是其他可能性,但是这些终端中的外部png机制取决于gnuplot版本和编译选项。 - Ethan
这个很棒!事后需要稍微改名,但最终我得到了一个像我想要的一样精确的像素图像文件。谢谢! - HotDogCannon

3

不要使用gnuplot。

相反,编写一个脚本来读取你的数据并将它转换成可移植任意图形格式之一。以下是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()

如果您可以修改生成数据文件的程序,甚至可以跳过上述步骤,直接以PNM格式生成数据。无论哪种方式,您都可以使用ImageMagick将图像转换为所需格式:
./convert.py | convert - pic.png

1
确实,对于这种任务使用Gnuplot就像用书来钉东西一样。它可能会起作用,但书并不是为此任务而制作的。我的首选工具将是GNU Octave,以保持在“GNU”领域中 :) imwrite函数可用于将2D数据保存为PNG图像。 - blerontin
这是一条有趣的路线,肯定是一个有价值的备选方案,但如果我不打算使用 gnuplot,那么我会选择使用 matplotlib(请参见我的下面的答案)。这是一个伟大的贡献,但从技术上讲,问题/悬赏要求提供一个 gnuplot 解决方案,尽管它似乎不适合这个特定的任务。 - HotDogCannon

3
这应该是一个简单的任务,但显然并不是这样。以下可能是一种(繁琐的)解决方案,因为所有其他尝试都失败了。我的怀疑是某些图形库存在问题,您可能无法作为gnuplot用户解决。
您提到ASCII矩阵数据也可以。这里的“技巧”是绘制使用with lines的数据,其中数据被空行“中断”,基本上绘制单个点。如果需要将数据文件1:1转换为数据块,请检查此内容。
然而,如果还不够奇怪,似乎对于pnggif终端有效,但对于pngcairowxt则无效。 我猜解决方法可能很慢且低效,但至少它创建了所需的输出。我不确定大小是否有限制。在Win7、gnuplot 5.2.6下测试了100x100像素。欢迎评论和改进。 代码:
### 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
结果:(100x100像素)

enter image description here

(黑色背景放大):

enter image description here

尺寸为400x200像素的图像(在我的8年老笔记本电脑上需要约22秒)。

enter image description here


如果SizeX和SizeY不相等怎么办? - HotDogCannon
抱歉,我混淆了 xy。如果 SizeXSizeY 不相等,它仍然可以工作。我会纠正代码。 - theozh
好的,有趣的方法,但是我该如何首先从Fortran读取二进制数据到像您的Data2这样的数组或流中呢? - HotDogCannon
你能否提供一个400x200的二进制文件,其中包含浮点数据以进行测试? - theozh

2

好的,这里是另一个可能的解决方案(我将其与我的第一种繁琐的方法分开)。它立即创建绘图,不到一秒钟。不需要重命名或创建无用文件。

我想关键是使用term pngps 0.1

我没有证据,但我认为ps 1大约会有6个像素大小,并且会在角落处创建一些重叠和/或白色像素。同样,出于某种原因,似乎可以使用term png但不能使用term pngcairo

我测试过的内容(Win7,gnuplot 5.2.6)是一个二进制文件SO59056181.bin,其中包含重复的00 00 FF模式(我无法在此显示空字节)。由于gnuplot显然每个数组项读取4个字节(format ="%d"),如果我正在绘制with lc rgb var,则会导致交替的RGB模式。

同样的方式(希望如此),我们可以弄清楚如何读取format="%f"并与调色板一起使用。我猜这就是你想要的,对吧? 欢迎进一步的测试结果、评论、改进和解释。

脚本:

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

SizeX = 400
SizeY = 200
set terminal png size SizeX,SizeY
set output "SO59056181.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 "SO59056181.bin" binary array=(SizeX,SizeY) format="%d" w p pt 5 ps 0.1 lc rgb var
### end of script

结果:SO59056181.png

enter image description here

放大(左上角,每个彩色方块实际上是一个像素):

enter image description here


1
为了得到我需要的东西,实际上我最后使用的是 matplotlib 而不是问题/悬赏要求的 gnuplot 解决方案:

matplotlib 有一个函数 matplotlib.pyplot.imsave 可以达到我所需的效果...即绘制 'just data pixels',没有边框、边距、坐标轴等额外内容。最初我只知道 matplotlib.pyplot.imshow 并且必须采取很多技巧从图像文件中消除所有额外的内容并防止任何插值/平滑等(因此在某个时候转向 gnuplot)。使用 imsave 相当容易,因此我又回归到使用 matplotlib 来实现易于操作但仍然灵活(例如对于colormap、scaling等)的“像素精确”绘图解决方案。下面是一个例子:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接