gnuplot: cómo trazar un elemento de matriz 2D por píxel sin márgenes

9

Estoy tratando de usar gnuplot 5.0 para trazar una matriz de datos 2D sin márgenes, bordes o ejes ... solo una imagen 2D (.png o .jpg) que representa algunos datos. Me gustaría tener cada elemento de la matriz para corresponder exactamente a un píxel de la imagen con ninguna escala / interpolación , etc y no hay píxeles blancos adicionales en los bordes.

Hasta ahora, cuando trato de establecer los márgenes en 0 e incluso usando la pixelsbandera, todavía me queda una fila de píxeles blancos en los bordes derecho e superior de la imagen.

¿Cómo puedo obtener solo un archivo de imagen con representación píxel por píxel de una matriz de datos y nada más?

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

Datos de ejemplo de 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

píxeles adicionales

HotDogCannon
fuente
¿sería aceptable si los datos están en x y zformato de lista?
theozh

Respuestas:

5

Algunos terminales gnuplot implementan "con imagen" al crear un archivo png separado que contiene la imagen y luego vincularlo dentro del gráfico resultante. Usar ese archivo de imagen png por separado directamente evitará cualquier problema de diseño de página, márgenes, etc. Aquí uso el terminal de lienzo. La trama en sí es desechada; todo lo que guardamos es el archivo png creado con el contenido deseado.

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
fuente
¡Esto es corto y rápido y funciona! Las únicas cosas que aún no he tenido éxito: 1. evitar la salida en Windows, 2. dar mi propio nombre sin índice al archivo png o al menos dejar de aumentar el índice del archivo png cada vez que vuelva a crear la trama.
theozh
No puedo ayudar con la salida de Windows. Tienes razón sobre el mostrador en la terminal del lienzo; nunca se restablece Pero puede jugar con el mismo truco set term tikz externalimagesy ese terminal restablece el contador en cada "término establecido". No se puede usar set output "/dev/null"con tikz, pero si eso no funciona para suprimir la salida para usted, entonces puede que no le importe. Los terminales tkcanvas y svg son otras posibilidades, pero el mecanismo png externo depende de la versión de gnuplot y las opciones de compilación.
Ethan
¡Esto funciona muy bien! Es necesario un pequeño cambio de nombre después del hecho, pero al final obtengo un archivo de imagen con píxeles exactos como el que estaba buscando. ¡Gracias!
HotDogCannon
3

No uses gnuplot.

En su lugar, escriba un script que lea sus datos y los convierta en uno de los formatos Portable Anymap . Aquí hay un ejemplo en 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()

Si puede modificar el programa que genera el archivo de datos, incluso puede omitir el paso anterior y en su lugar generar los datos directamente en un formato PNM.

De cualquier manera, puede usar ImageMagick para convertir la imagen al formato que elija:

./convert.py | convert - pic.png
usuario3840170
fuente
1
De hecho, usar Gnuplot para este tipo de tarea es como usar un libro para clavar un clavo en algo. Puede funcionar, pero los libros no están hechos para esta tarea. Mi herramienta preferida sería GNU Octave para permanecer en el dominio "GNU" :) La imwritefunción se puede usar para guardar datos 2D como una imagen PNG.
blerontin
Esta es una ruta interesante, y definitivamente una copia de seguridad valiosa, pero si no la voy a usar gnuplot, entonces solo la matplotlibusaré (vea mi respuesta a continuación). Esta es una gran contribución, pero técnicamente la pregunta / recompensa pide una gnuplotsolución, tan inadecuada como parece ser para esta tarea en particular.
HotDogCannon
3

Esto debería ser una tarea fácil, sin embargo, aparentemente no lo es. Lo siguiente podría ser una solución (engorrosa) porque todos los demás intentos fallaron. Sospecho que algunas bibliotecas de gráficos tienen un problema que probablemente no pueda resolver como usuario de gnuplot.

Usted mencionó que los datos de la matriz ASCII también están bien. El "truco" aquí es trazar los datos with linesdonde los datos son "interrumpidos" por líneas vacías, básicamente dibujando puntos únicos. Verifique esto en caso de que necesite obtener su archivo de datos 1: 1 en un bloque de datos .

Sin embargo, si aún no es lo suficientemente extraño, parece funcionar para pngy gifterminal pero no para pngcairoo wxt. Supongo que la solución es probablemente lenta e ineficiente, pero al menos crea el resultado deseado. No estoy seguro de si hay un límite de tamaño. Probado con 100x100 píxeles con Win7, gnuplot 5.2.6. Comentarios y mejoras son bienvenidos.

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 píxeles)

ingrese la descripción de la imagen aquí

(ampliado con fondo negro):

ingrese la descripción de la imagen aquí

Imagen con 400x200 píxeles (toma alrededor de 22 segundos en mi computadora portátil de 8 años).

ingrese la descripción de la imagen aquí

theozh
fuente
¿Y si SizeX y SizeY no son iguales?
HotDogCannon
lo siento, me confundí xy y. Todavía funciona si SizeXy SizeYno son iguales. Corregiré el código.
theozh
OK, enfoque interesante, pero ¿cómo leo primero los datos binarios de Fortran a una matriz o secuencia como la suya Data2?
HotDogCannon
¿podría de alguna manera proporcionar un archivo binario de 400x200 con datos de punto flotante para probar?
theozh
1

Lo que terminé usando en realidad para obtener lo que necesitaba a pesar de que la pregunta / recompensa pide una gnuplotsolución:

matplotlibtiene una función matplotlib.pyplot.imsave que hace lo que estaba buscando ... es decir, trazar 'solo píxeles de datos' y sin extras como bordes, márgenes, ejes, etc. Originalmente solo sabía sobre matplotlib.pyplot.imshow y tuve que Realice muchos trucos para eliminar todos los extras del archivo de imagen y evitar cualquier interpolación / suavizado, etc. (y, por lo tanto, gnuploten un determinado punto). Con imsaveesto es bastante fácil, así que vuelvo a usar matplotlibuna solución fácil pero flexible (en términos de mapa de colores, escala, etc.) para gráficos 'exactos de píxeles'. Aquí hay un ejemplo:

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

OK, aquí hay otra posible solución (la separé de mi primer enfoque engorroso). Crea la trama de inmediato, menos de un segundo. No es necesario renombrar ni crear un archivo inútil.

Supongo que la clave es usar term pngy ps 0.1.

No tengo una prueba pero creo ps 1que sería ca. 6 píxeles de gran tamaño y crearían algunos solapamientos y / o píxeles blancos en la esquina. Nuevamente, por alguna razón parece funcionar term pngpero no con term pngcairo.

Lo que probé (Win7, gnuplot 5.2.6) es un archivo binario que tiene el patrón 00 00 FFrepetido por todas partes (no puedo mostrar bytes nulos aquí). Dado que gnuplot aparentemente lee 4 bytes por elemento de matriz ( format="%d"), esto conduce a un patrón RGB alternativo si estoy tramando with lc rgb var.

De la misma manera (con suerte) podemos descubrir cómo leerlo format="%f"y usarlo junto con una paleta de colores. Supongo que eso es lo que estás buscando, ¿verdad? Otros resultados de la prueba, comentarios, mejoras y explicaciones son bienvenidos.

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:

ingrese la descripción de la imagen aquí

theozh
fuente