I am trying to use gnuplot 5.0 to plot a 2D array of data with no margins or borders or axes... just a 2D image (.png or .jpg) representing some data. I would like to have each array element to correspond to exactly one pixel in the image with no scaling / interpolation etc and no extra white pixels at the edges.
So far, when I try to set the margins to 0 and even using the pixels flag, I am still left with a row of white pixels on the right and top borders of the image.
How can I get just an image file with pixel-by-pixel representation of a data array and nothing extra?
gnuplot script:
#!/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
Example data from 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
Some gnuplot terminals implement "with image" by creating a separate png file containing the image and then linking to it inside the resulting plot. Using that separate png image file directly will avoid any issues of page layout, margins, etc. Here I use the canvas terminal. The plot itself is thrown away; all we keep is the png file created with the desired content.
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
Don't use gnuplot.
Instead, write a script that reads your data and converts it into one of the Portable Anymap formats. Here's an example in 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()
If you can modify the program which generates the data file, you can even skip the above step and instead generate the data directly in a PNM format.
Either way, you can then use ImageMagick to convert the image to a format of your choice:
./convert.py | convert - pic.png
This should be an easy task, however, apparently it's not.
The following might be a (cumbersome) solution because all other attempts failed. My suspicion is that some graphics library has an issue which you probably cannot solve as a gnuplot user.
You mentioned that ASCII matrix data is also ok. The "trick" here is to plot data with lines where the data is "interrupted" by empty lines, basically drawing single points. Check this in case you need to get your datafile 1:1 into a datablock.
However, if it is not already strange enough, it seems to work for png and gif terminal but not for pngcairo or wxt.
I guess the workaround is probably slow and inefficient but at least it creates the desired output. I'm not sure if there is a limit on size. Tested with 100x100 pixels with Win7, gnuplot 5.2.6. Comments and improvements are welcome.
Code:
### 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
Result: (100x100 pixels)
(enlarged with black background):
Image with 400x200 pixels (takes about 22 sec on my 8 year old laptop).
What I ended up actually using to get what I needed even though the question / bounty asks for a gnuplot solution:
matplotlib has a function matplotlib.pyplot.imsave which does what I was looking for... i.e. plotting 'just data pixels' and no extras like borders, margins, axes, etc. Originally I only knew about matplotlib.pyplot.imshow and had to pull a lot of tricks to eliminate all the extras from the image file and prevent any interpolation/smoothing etc (and therefore turned to gnuplot at a certain point). With imsave it's fairly easy, so I'm back to using matplotlib for an easy yet still flexible (in terms of colormap, scaling, etc) solution for 'pixel exact' plots. Here's an example:
#!/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')
OK, here is another possible solution (I separated it from my first cumbersome approach). It creates the plot immediately, less than a second. No renaming necessary or creation of a useless file.
I guess key is to use term png and ps 0.1.
I don't have a proof but I think ps 1 would be ca. 6 pixels large and would create some overlap and/or white pixels at the corner. Again, for whatever reason it seems to work with term png but not with term pngcairo.
What I tested (Win7, gnuplot 5.2.6) is a binary file having the pattern 00 00 FF repeated all over (I can't display null bytes here). Since gnuplot apparently reads 4 bytes per array item (format="%d"), this leads to an alternating RGB pattern if I am plotting with lc rgb var.
In the same way (hopefully) we can figure out how to read format="%f" and use it together with a color palette. I guess that's what you are looking for, right?
Further test results, comments, improvements and explanations are welcome.
Code:
### 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
Result:
When I plot with image in gnuplot, the label that I set is not displayed. Everything else is correct. Here is my code:
#! /bin/sh
#
# Plotting the color map of correlation using the default Matlab palette
#
gnuplot <<EOF
reset
set terminal pngcairo size 700,524 enhanced font 'Verdana,10'
unset key
# border
set style line 11 lc rgb '#808080' lt 1
set border 3 front ls 11
set tics nomirror out scale 0.75
set xrange [0:20]
set yrange [0:20]
set xlabel 'Distance x/D_j [-]'
set ylabel '{/Symbol t} u_j/D_j [-]'
# disable colorbar tics
set cbtics scale 0
# matlab palette colors
set palette defined ( 0 "#000090",\
1 "#000fff",\
2 "#0090ff",\
3 "#0fffee",\
4 "#90ff70",\
5 "#ffee00",\
6 "#ff7000",\
7 "#ee0000",\
8 "#7f0000")
set output 'test.png'
set label 'aaa' at 2,17
plot 'Cuup_nf_a090_r050Dj_average' u 1:2:3 with image
EOF
What is strange is: if I plot the data file using a column which doesn't exist as the third data series, for example:
plot 'Cuup_nf_a090_r050Dj_average' u 1:2:4 with image
(I have only 3 columns in the file 'Cuup_nf_a090_r050Dj_average')
Sure, I get only blank (no data) in my image, but the label is displayed correctly.
So it seems that the label is covered by my data palette... I have tried to put 'set label' at the end of code, but it doesn't work either.
Does someone have an idea?
ps: my gnuplot version: Version 4.6 patchlevel 4
Thanks a lot in advance.
Labels have an option front|back to position them on the front or back layer. Default setting is back, so that labels not specifying an explicit layer are hidden when plotting with image:
$data <<EOD
1 2
3 4
EOD
set label 'default, hidden' at graph 0.6, graph 0.7 font ",20"
set label back 'back, hidden' at graph 0.6, graph 0.5 font ",20"
set label front 'front, visible' at graph 0.6, graph 0.3 font ",20"
plot $data matrix with image
I am using gnuplot in version 5.0 in order to plot graphs from Log files which are received from another script. Initially, a script runs which creates the Log files, and inside the same script (upon finishing) a new script which will plot the graphs is executed by receiving some command line arguments.
One of the command line argument for the plotting graph script ($4 specifically) is the path where the data (which will be used by plot command) is located. Upon finishing of execution:
line 0: warning: Cannot find or open file "/comperative_data.txt"
where the command line argument is not used at all. See the source code of the script below:
gnuplot<<EOF
set size 1.0,1.0;
set terminal postscript landscape color "Times-Roman" 14 linewidth 2
set timestamp "%d/%m/%y %H:%M"
set key left top Left noreverse enhanced box linetype -1 linewidth 1.000 sample 4 spacing 1 width 0 height 0 autotitles
set grid back lt 0 lw 1
set xlabel "TCP Window Size (Bytes)"
set ylabel "Average Transfer Speed (KiB/s)
set xtics (131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432)
set xtics rotate
set logscale x
set format x "%10.f"
set yrange [0:5000000]
set output "$filename"
set title "$title"
fileLocation=system("echo $4/comperative_data.txt")
plot fileLocation using 1:2 title 'Streams_1' with linespoints, fileLocation using 1:3 title 'Streams_2' with linespoints , fileLocation using 1:4 title 'Streams_4' with linespoints , fileLocation using 1:5 title 'Streams_8' with linespoints , fileLocation using 1:6 title 'Streams_16' with linespoints , fileLocation using 1:7 title 'Streams_32' with linespoints , fileLocation using 1:8 title 'Streams_64' with linespoints
EOF
I converted a PNG image to hex and am wondering if it is possible to decompress the hex into this type of format for each pixel of the image:
Opacity(0-255)-Red(0-255)-Green(0-255)-Blue(0-255)
I'm using a site/program that has heavy restrictions on images you can upload(quality, size, amount, etc,) but I can create images pixel by pixel. I was hoping to decompress the hex that I converted from the original PNG file to the format above so that I can create a simple function to build it on the screen. Come to think of it, is there a way to pull the RGB and transparency from the hex PNG file without any need for reformatting?
It is very easy you have to cut your hexvalue,
and applicate an HEX to DECIMAL CONVERSION.
Ex : FFAACC00
FF = R = 255
AA = G = 170
CC = B = 204
00 = Transparency Full (FF = Transparency OFF)
If you need a program to do that you can use PNG2HEX.exe :
Download
(just drop your PNG File on it and it will create 2 files :
1- fileHex.txt with the value of each pixel in Hexa
2- FileRGB.txt with the value of each pixel in R G B
You can then modify values in these files and rebuild the png with the modification using
HEX2PNG.exe
I read the section OTHER OUTPUT FORMATS of Plotchart documentation, but still can't figure out how to do it.
I want to:
Save canvas as image without displaying it. So I can run it in batch mode.
Save in other format. (ex: jpeg, png...)
A brief example is appreciated.
I didn't try this solution, but the man page you linked describes a saveplot command to store the plot into a Postscript (or other image format) file.
Once you created your plot widget, you can do something like
.plot saveplot filename.ps -plotregion bbox
where the -plotregion bbox says to save all the plot and not just the visible part (-plotregion window, which is the default).
I found Img library is capable to converts Postscript into various formats, and a quick and dirty way do not display the canvas is to run exit immediately.
Here is an example:
package require Plotchart
package require Img
canvas .c -background white -width 400 -height 200
pack .c -fill both
set s [::Plotchart::createXYPlot .c {0.0 100.0 10.0} {0.0 100.0 20.0}]
foreach {x y} {0.0 32.0 10.0 50.0 25.0 60.0 78.0 11.0 } {
$s plot series1 $x $y
}
$s title "Data series"
set file "test.ps"
$s saveplot $file
set root [file rootname $file]
set image [image create photo -file $file]
foreach {f suffix} {JPEG jpg GIF gif PNG png} {
$image write $root.$suffix -format $f
}
exit