h5dumptopng



#!/usr/bin/env perl
# h5dumptopng - Converts an HDF5 dump to PNG images
# License:  Creative Commons Attribution-NonCommercial-ShareAlike 2.5
# Revision: 110121

#---------------------------------------------------------------------
#                           important note
#---------------------------------------------------------------------

# This software is provided on an  AS IS basis with ABSOLUTELY NO WAR-
# RANTY.  The  entire risk as to the  quality and  performance of  the
# software is with you.  Should the software prove defective,  you as-
# sume the cost of all necessary  servicing, repair or correction.  In
# no event will any of the developers,  or any other party, be  liable
# to anyone for damages arising out of use of the software, or inabil-
# ity to use the software.

#---------------------------------------------------------------------
#                              overview
#---------------------------------------------------------------------

my $USAGE_TEXT = << 'END_OF_USAGE_TEXT';
Usage: 
       h5dumptopng --bgr        foo.txt
or     h5dumptopng --rgb        foo.txt
or     h5dumptopng --grayscale  foo.txt

This program  takes one input file,  which should be a  text file pro-
duced previously by "h5dump" (which  itself takes an HDF5 data file as
input). For example:

      h5dump foo.hdf5 > foo.txt
      h5dumptopng --rgb foo.txt

Alternately, the two programs may be used together as follows:

      h5dump foo.hdf5 | h5dumptopng --rgb

Three types of data are supported  (though only one type  in any given
HDF5 input file):

      BGR color data    Use "--bgr"       for this
      RGB color data    Use "--rgb"       for this
      Grayscale data    Use "--grayscale" for this

This program expects to see a series of 2D datasets structured as fol-
lows (the exact number of spaces or tabs used doesn't matter):

      DATASET "0001" {
      DATATYPE  ...
      DATASPACE SIMPLE { ( 480, 704 ) / ( 480, 704 ) }
      DATA {
      (0,0):  -1, -1, -1, -1, -1, 93, -1, 87, -1, -1, -1, -1, -1, ...
      (0,15): -1, -1, -1, -1, -1, -1, -1,  0, -1, -1, -1, -1, -1, ...
      ...

The data lines are treated as pixel data, starting with pixel row off-
set 0 and filling  columns before rows.  For grayscale data, each data
value fills one pixel.  For color data,  each  data triplet  fills one
pixel.

BGR mode fills the blue component first, then the green component, and
finally the red component.

RGB mode fills the red  component first, then the green component, and
finally the blue component.

Data values may be numbers ranging from 0 to 255 or -1.  -1 is a spec-
ial case; presently it's simply mapped to 0.

The first number in a  DATASPACE geometry pair is  treated as an image
height (expressed in pixels).

The second number is assumed to be an image width, for grayscale data,
or three times the width, for color data.

As output, this program writes each DATASPACE to a PNG  file.  The PNG
files have names consisting of the input-file name (without the ".txt"
part) dash the dataset name plus ".png".  If the input-file is a pipe,
the prefix "data" is used instead of the input-file name.

This version doesn't support color data, but that feature may be added
at a later date.

h5dumptopng requires "pnmtopng" and "ppmtoppm" from the "netpbm" pack-
age.
END_OF_USAGE_TEXT

#---------------------------------------------------------------------
#                        standard module setup
#---------------------------------------------------------------------

require 5.8.1;
use strict;
use Carp;
use warnings;
                                # Trap warnings
$SIG{__WARN__} = sub { die @_; };

#---------------------------------------------------------------------
#                           basic constants
#---------------------------------------------------------------------

use constant ZERO  => 0;        # Zero
use constant ONE   => 1;        # One
use constant TWO   => 2;        # Two
use constant THREE => 3;        # Three

use constant FALSE => 0;        # Boolean FALSE
use constant TRUE  => 1;        # Boolean TRUE

#---------------------------------------------------------------------
#                         program parameters
#---------------------------------------------------------------------

my $PURPOSE  = 'Converts an hdf5 dump to PNG files';
my $REVISION = '110121';
                                # Error-message prefix string
my $IDPREFIX = 'Error: Invalid data case';
                                # Temporary-file pathname
my $TMPFILE  = "/tmp/h5dumptopng-$>-$$";
my $USE_LESS = TRUE;            # Flag: Use "less" for usage text

#---------------------------------------------------------------------
#                          global variables
#---------------------------------------------------------------------

my $PROGNAME;                   # Program name (without path)
   $PROGNAME =  $0;
   $PROGNAME =~ s@.*/@@;

my @DataBytes = ();             # Image byte data
my $BYTES_PER_PIXEL;            # Number of bytes per pixel
my $DATASET_NAME;               # Dataset name
my $OFPREFIX;                   # Output-pathname prefix

my $IS_BGR  = FALSE;            # Flag: BGR-color mode
my $IS_RGB  = FALSE;            # Flag: RGB-color mode
my $IS_GRAY = FALSE;            # Flag: Grayscale mode

my $PHEIGHT;                    # Image height in pixels
my $PWIDTH;                     # Image width  in pixels
my $BWIDTH;                     # Image width  in bytes

#---------------------------------------------------------------------
#                          utility routines
#---------------------------------------------------------------------

# Future change: Document this routine.

sub UsageError
{
    $USAGE_TEXT =~ s@^\s+@@s;

    $USAGE_TEXT = << "END";     # "END" must be double-quoted here
$PROGNAME $REVISION - $PURPOSE

$USAGE_TEXT
END
    $USAGE_TEXT =~ s@\s*\z@\n@s;

    if ($USE_LESS && (-t STDOUT) && open (OFD, "|/usr/bin/less"))
    {
                                # "END" must be double-quoted here
        $USAGE_TEXT = << "END";
To exit this "help" text, press "q" or "Q".  To scroll up or down, use
PGUP, PGDN, or the arrow keys.

$USAGE_TEXT
END
        print OFD $USAGE_TEXT;
        close OFD;
    }
    else
    {
        print "\n";
        print $USAGE_TEXT;
        print "\n";
    }

    exit ONE;
}

#---------------------------------------------------------------------
#                application-specific support routines
#---------------------------------------------------------------------

# Future change: Document this routine.

sub ResetFileParam
{
    @DataBytes = ();
    undef $DATASET_NAME;
    undef $PHEIGHT;
    undef $PWIDTH;
    undef $BWIDTH;
    undef;
}

#---------------------------------------------------------------------

# Future change: Document this routine.

sub CreateOutputFile
{
    my $ofname;                 # Output-file name or pathname
    my $n;                      # Scratch (integer )
    my $str;                    # Scratch (string  )

    my $AREA = $PHEIGHT * $BWIDTH;
    $n = scalar @DataBytes;

    if ($n != $AREA)
    {
        print STDERR << "END";
$IDPREFIX #0001: Dataset $DATASET_NAME contains $n bytes(s),
expected $AREA bytes(s)
END
        exit ONE;
    }

    $str = $IS_GRAY ? 'P2' : 'P3';

    my $DataPNM = << "END";
$str
$PWIDTH $PHEIGHT
255
END
    map { s@^-1\z@0@; } @DataBytes;

    for (@DataBytes)
    {
        next if ($_ =~ m@^[0-9]+\z@) && ($_ >= ZERO) || ($_ <= 255);
        die "$IDPREFIX #0002\n";
    }

    for my $row (ONE..$PHEIGHT)
    {
        for my $col (ONE..$PWIDTH)
        {
            my @pixel = ();

            for my $ii (ONE..$BYTES_PER_PIXEL)
            {
                push (@pixel, shift @DataBytes);
            }

            @pixel = reverse @pixel if $IS_BGR;

            for my $ii (ONE..$BYTES_PER_PIXEL)
            {
                $DataPNM .= shift @pixel;
                $DataPNM .= " " if $ii < $BYTES_PER_PIXEL;
            }

            $DataPNM .= " " if $col < $PWIDTH;
        }

        $DataPNM .= "\n";
    }

    $ofname = "$OFPREFIX-$DATASET_NAME.png";
    $str    = "|ppmtoppm 2>$TMPFILE|pnmtopng -force > $ofname";

    $SIG {CHLD} = 'IGNORE';
    open (OFD, "$str") ||
        die "Error: Can't open output pipe: $!\n$str\n";
    print OFD $DataPNM;
    close OFD;

    if ((!-f $TMPFILE) || (!-z $TMPFILE))
    {
        print "Error: pnmtopng stage failed:\n";
        system "cat $TMPFILE\n";
    }
    else
    {
        print "Wrote $ofname\n";
    }

    unlink $TMPFILE;
    undef;
}

#---------------------------------------------------------------------
#                            main routine
#---------------------------------------------------------------------

sub Main
{
    my $ifname;                 # Input  -file name or pathname
    my $ofname;                 # Output -file name or pathname

    my $have_mode = FALSE;      # Flag: Have color mode
    my $is_stdin  = FALSE;      # Flag: Using STDIN
    my $n;                      # Scratch (integer )
    my $str;                    # Scratch (string  )

#---------------------------------------------------------------------
# Initial setup.

    select STDERR; $| = ONE;    # Force STDERR flush on write
    select STDOUT; $| = ONE;    # Force STDOUT flush on write

#---------------------------------------------------------------------
# Process the command line.

    for my $arg (@ARGV)
    {
        if ($arg !~ m@^-@i)
        {
            die "Error: Presently, only one input file at a time\n"
                if defined $ifname;
            $ifname = $arg;
        }
        elsif ($arg =~ m@^-+(bgr|rgb|gr[ae]y\w+)\z@i)
        {
            die "Error: Presently, only one color mode at a time\n"
                if $have_mode;

            $have_mode       = TRUE;
            $str             = lc ($1);
            $IS_BGR          = TRUE if $str eq 'bgr';
            $IS_RGB          = TRUE if $str eq 'rgb';
            $BYTES_PER_PIXEL = $IS_GRAY ? ONE : THREE;
        }
        else
        {
            die "Invalid argument: $arg\n";
        }
    }
                            # Was a color mode specified?
    &UsageError() unless $have_mode;

#---------------------------------------------------------------------
# Set up input stream.

    if (!-t STDIN)              # Using STDIN ?
    {                           # Yes
        $is_stdin = TRUE;       # Flag it
        $OFPREFIX = 'data';     # Output-file prefix string
    }
    else
    {                           # No
                                # Verify that a file was specified
        &UsageError() unless defined $ifname;

                                # Check the input file
        die "Error: Can't access input file: $ifname\n"
            unless -r $ifname;
        die "Error: Not a valid input file: $ifname\n"
            unless -f $ifname;
                                # Open  the input file
        open (IFD, "<$ifname") ||
            die "Error: can't open input file: $!:\n$ifname\n";

                                # Build output-file prefix string
        $str      =  $ifname;
        $str      =~ s@(\S)\.(data?|h?5?du?mp|log|lst|te?xt)\z@$1@i;
        $OFPREFIX =  $str;
    }

#---------------------------------------------------------------------
# Perform operations.

    while (TRUE)
    {
                                # Get next input line
        $_ = $is_stdin ? <STDIN> : <IFD>;
        my $is_eof = $_ ? FALSE : TRUE;
        $_ = "" if $is_eof;
                                # Whitespace adjustments
        s@^\s+@@; s@\s+\z@@; s@\s+@ @g;
                                # Check for start of a dataset
        my ($new_dataset) = $_ =~ m@DATASET "([^ "]+)"@;

                                # Write current file to disk?
        if (scalar (@DataBytes) &&
            ($is_eof || defined ($new_dataset)))
        {                       # Yes
            &CreateOutputFile();
            &ResetFileParam();
        }

        if (defined ($new_dataset))
        {
            &ResetFileParam();
            $DATASET_NAME = $1;
        }

        last if $is_eof;        # Handle end of stream

        if (m@^DATASPACE\s*SIMPLE\s*{\s*\(\s*(\d+),\s*(\d+)\s*\)@i)
        {
            die "$IDPREFIX #0003\n"
                unless defined $DATASET_NAME;
            die "$IDPREFIX #0004\n"
                if (defined $PHEIGHT) || (defined $BWIDTH);

            ($PHEIGHT, $BWIDTH) = ($1, $2);
            die "$IDPREFIX #0005\n"
                if ($BWIDTH % $BYTES_PER_PIXEL) != ZERO;

            $PWIDTH = int ($BWIDTH / $BYTES_PER_PIXEL);
        }

        next unless m@^\s*\((\d+),(\d+)\):\s*([\-0-9, ]+)@;

        die "$IDPREFIX #0006\n" unless defined $DATASET_NAME;
        die "$IDPREFIX #0007\n"
            unless (defined $PHEIGHT) && (defined $BWIDTH);

        my ($row, $col, $curdata) = ($1, $2, $3);
        my @curdata = grep { length; } split (/[, ]+/, $curdata);
        push (@DataBytes, @curdata);
    }

    undef;
}

#---------------------------------------------------------------------
#                            main program
#---------------------------------------------------------------------

&Main();                        # Call the main routine
exit ZERO;                      # Normal exit



To continue, press the browser's Back button.