package Util::SimpleFITS;
########################################################################
#
# DESCRIPTION: Lean and mean FITS interface
#
# HISTORY: $Log: SimpleFITS.pm,v $
# HISTORY: Revision 1.8 2005/09/17 16:19:47 apsop
# HISTORY: Load TBIT columns as strings.
# HISTORY:
# HISTORY: Revision 1.7 2005/08/30 14:33:13 apsop
# HISTORY: Change documentation comments to standard format.
# HISTORY:
# HISTORY: Revision 1.6 2005/08/16 21:20:00 apsop
# HISTORY: Added a method to load an entire table into a list of hash refs with
# HISTORY: keys corresponding to the column names.
# HISTORY:
# HISTORY: Revision 1.5 2005/07/06 20:36:55 apsop
# HISTORY: Implemented BAT position refinement.
# HISTORY:
# HISTORY: Revision 1.4 2005/05/13 19:36:52 apsop
# HISTORY: Allow user to specify output variable for status, nhdu calls.
# HISTORY:
# HISTORY: Revision 1.3 2005/04/01 14:52:44 apsop
# HISTORY: Update header comment section
# HISTORY:
#
# HISTORY: Revision 1.2 2004/11/16 14:15:15 apsop
# HISTORY: Added reopen and wrappers for open modes.
#
# HISTORY: Revision 1.1 2004/11/09 19:07:31 apsop
# HISTORY: Adding BAT pipeline support modules.
#
#
# VERSION: 0.0
#
###########################################################################
use strict;
use Astro::FITS::CFITSIO qw(:longnames :constants);
sub open {
my ($first,$inspec) = @_;
my ($fits,$mode, $modestr, $filename);
my $self = bless {};
my $status = 0;
if ($inspec =~ m/^([+<>]*)([^+<>].*)/) {
$modestr = $1 || '<';
$filename = $2;
if ($modestr eq ">" || ($modestr eq "+<" && ! -f $filename)) {
$fits = Astro::FITS::CFITSIO::create_file($filename,$status);
} else {
if ($modestr eq "<") {
$mode = READONLY;
} elsif ($modestr eq "+<") {
$mode = READWRITE;
} else {
die("bad inspec [$inspec]");
}
$fits = Astro::FITS::CFITSIO::open_file("$filename",$mode,$status);
# print "fits=$fits filename=$filename mode=$mode status=$status\n";
}
}
$self->{fits} = $fits;
$self->{status} = $status;
return $self;
}
sub readonly
{
my ($first, $path) = @_;
return $first->open("<$path");
}
sub create
{
my ($first, $path) = @_;
return $first->open(">$path");
}
sub update
{
my ($first, $path) = @_;
return $first->open("+<$path");
}
sub reopen
{
my ($self, $inspec) = @_;
my $fits = $self->open($inspec);
if ($fits->{status}) {
$self->{status} = $fits->{status};
}
else {
$self->close;
%$self = %$fits;
}
return $self;
}
sub close {
my ($self) = @_;
my ($fits);
return 0 unless ($self);
$fits = $self->{fits};
return $self unless ($fits);
my $status = 0;
$fits->close_file($status);
undef $self->{fits};
return $self;
}
sub status {
my ($self) = @_;
return NULL_INPUT_PTR unless ($self);
if (@_ >= 2) {
$_[1] = $self->{status};
return $self;
}
return $self->{status};
}
sub setstatus {
my ($self,$value) = @_;
return 0 unless ($self);
$self->{status} = $value;
return $self;
}
sub handle {
my ($self) = @_;
return 0 unless ($self);
return $self->{fits};
}
sub nhdu {
my ($self) = @_;
my ($nhdu);
return 0 unless ($self);
my $fits = $self->{fits};
return $self unless ($fits);
my $status = $self->{status};
$fits->get_num_hdus($nhdu,$status);
$self->{status} = $status;
if (@_ >= 2) {
$_[1] = $nhdu;
return $self;
}
return $nhdu;
}
sub move {
my ($self,$hdu) = @_;
return 0 unless ($self);
my $fits = $self->{fits};
return $self unless ($fits);
my $status = $self->{status};
if ($status) { return $self; }
if ($hdu =~ m/^[-+][0-9]+$/) {
$fits->movrel_hdu($hdu,undef,$status);
} elsif ($hdu =~ m/^[0-9]+$/) {
$fits->movabs_hdu($hdu,undef,$status);
} else {
$fits->movnam_hdu(ANY_HDU,$hdu,0,$status);
}
$self->{status} = $status;
return $self;
}
sub readheader {
my ($self,$header) = @_;
my ($fits, $status, $value, $comment, $dtype, $type);
return 0 unless ($self);
$fits = $self->{fits};
return $self unless ($fits);
$status = $self->{status};
if ($status) { return $self; }
($header, $status) = $fits->read_header();
$self->{status} = $status;
$_[1] = $header;
return $self;
}
sub readkey {
my ($self,$keyname) = @_;
my ($fits, $status, $value, $comment, $dtype, $type);
return 0 unless ($self);
$fits = $self->{fits};
return $self unless ($fits);
$status = $self->{status};
if ($status) { return $self; }
if ($_[4]) {
$type = $_[4];
} else {
$fits->read_keyword($keyname,$value,$comment,$status);
Astro::FITS::CFITSIO::fits_get_keytype($value,$dtype,$status);
$type = TSTRING;
if ($dtype eq 'L') { $type = TLOGICAL; }
elsif ($dtype eq 'I') { $type = TINT; }
elsif ($dtype eq 'F') { $type = TDOUBLE; }
}
$fits->read_key($type,$keyname,$value,$comment,$status);
$self->{status} = $status;
if ($#_ <= 0) {
return $value;
}
$_[2] = $value;
$_[3] = $comment;
return $self;
}
sub vtype {
my ($val) = @_;
my ($strval);
$strval = "$val";
if ($strval =~ m/^[-+]?[0-9]+$/) {
return TLONG;
} elsif ($strval =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) {
# m/^[-+]?[0-9.]+[eE]?([-+][0-9]+)?/ my old one
return TDOUBLE;
}
return TSTRING;
}
sub writekey {
my ($self,$keyname,$value) = @_;
my ($fits, $status, $comment, $type);
return 0 unless ($self);
$fits = $self->{fits};
return $self unless ($fits);
$status = $self->{status};
if ($status) { return $self; }
$comment = 0;
if ($_[3]) { $comment = "$_[3]"; }
if ($_[4]) { $type = $_[4]; } else { $type = vtype($value); }
$fits->update_key($type,$keyname,$value,$comment,$status);
$self->{status} = $status;
return $self;
}
sub readcol {
my ($self,$column,$type,$rrows) = @_;
my ($fits, $status, $colnum, $nrows);
my ($typecode, $repeat, $width, $r, $nel, $rowstart, $data, $comment);
my @rows;
return 0 unless ($self);
$fits = $self->{fits};
return $self unless ($fits);
$status = $self->{status};
if ($status) { return $self; }
if ($column =~ m/^[0-9]$/) {
$colnum = $column;
} else {
$fits->get_colnum(CASEINSEN,$column,$colnum,$status);
}
$fits->get_num_rows($nrows,$status) unless $status;
$fits->get_coltype($colnum,$typecode,$repeat,$width,$status) unless $status;
if ($status) {
$self->{status} = $status;
return $self;
}
if ($typecode == TSTRING) { $repeat /= $width; }
if (!defined($type)) { $type = $typecode; }
$r = ref($rrows);
if ($r eq "SCALAR") {
@rows = ($$rrows,$$rrows);
} elsif ($r eq "ARRAY") {
@rows = @$rrows;
if ($#rows == -1) { @rows = (1, $nrows); }
if ($#rows == 0) { @rows = ($rows[0], $rows[0]); }
} elsif ($rrows) {
@rows = ($rrows, $rrows);
} else {
@rows = (1, $nrows);
}
$rowstart = $rows[0];
$nel = ($rows[1] - $rows[0] + 1)*$repeat;
my $passedArray = UNIVERSAL::isa($_[4], 'ARRAY');
if ($passedArray) {
$data = $_[4];
}
$fits->read_col($type,$colnum,$rowstart,1,$nel,undef,$data,undef,$status);
$self->{status} = $status;
if ($#_ == (3-1)) {
return @$data;
}
if (not $passedArray) {
$_[4] = $data;
}
return $self;
}
sub writecol {
my ($self,$column,$options,$data) = @_;
my ($rrows,$type);
my ($fits, $status, $colnum, $nrows);
my ($typecode, $repeat, $width, $r, $nel, $rowstart, $comment);
my @rows;
return 0 unless ($self);
$fits = $self->{fits};
return $self unless ($fits);
$status = $self->{status};
if ($status) { return $self; }
if ($column =~ m/^[0-9]$/) {
$colnum = $column;
} else {
$fits->get_colnum(CASEINSEN,$column,$colnum,$status);
}
$fits->get_num_rows($nrows,$status) unless $status;
$fits->get_coltype($colnum,$typecode,$repeat,$width,$status) unless $status;
if ($status) {
$self->{status} = $status;
return $self;
}
if ($typecode == TSTRING) { $repeat /= $width; }
$type = $options->{type};
if (!defined($options->{type})) { $type = $typecode; }
$rrows = $options->{rows};
$rrows = [] unless($rrows);
$r = ref($rrows);
if ($r eq "SCALAR") {
@rows = ($$rrows,$$rrows);
} elsif ($r eq "ARRAY") {
@rows = @$rrows;
if ($#rows == -1) { @rows = (1, $nrows); }
if ($#rows == 0) { @rows = ($rows[0], $rows[0]); }
} elsif ($rrows) {
@rows = ($rrows, $rrows);
} else {
@rows = (1, $nrows);
}
$rowstart = $rows[0];
$nel = ($rows[1] - $rows[0] + 1)*$repeat;
$fits->write_col($type,$colnum,$rowstart,1,$nel,$data,$status);
$self->{status} = $status;
return $self;
}
sub parsekeys {
my ($arg) = @_;
if (ref($arg) eq "SCALAR") {
return ("$$arg",undef,TSTRING);
} elsif (ref($arg) eq "ARRAY") {
return ("$$arg[0]", $$arg[1], $$arg[2]);
} else {
return ("$arg",undef,TSTRING);
}
}
sub createtab {
my ($self,$extname) = @_;
my ($fits, $status, $colnum, $nrows);
my @rows;
return 0 unless ($self);
$fits = $self->{fits};
return $self unless ($fits);
$status = $self->{status};
if ($status) { return $self; }
$fits->create_tbl(BINARY_TBL,0,0,undef,undef,undef,$extname,$status);
$self->{status} = $status;
return $self;
}
sub insertcol {
my ($self,$colkeys,$column) = @_;
my ($fits, $status, $colnum, $ncols);
my ($ttype,$type,$comment,$tform,$value);
my ($typecode, $repeat, $width, $r, $nel, $rowstart);
my @rows;
my %defcomments;
return 0 unless ($self);
$defcomments{TUNIT} = "physical unit of field";
$defcomments{TSCAL} = "data scale";
$defcomments{TZERO} = "data offset";
$defcomments{TNULL} = "data null value";
$defcomments{TDIM} = "Array dimensions";
$defcomments{TDMIN} = "Minimum column data value";
$defcomments{TDMAX} = "Maximum column data value";
$defcomments{TLMIN} = "Minimum legal value";
$defcomments{TLMAX} = "Maximum legal value";
$fits = $self->{fits};
return $self unless ($fits);
$status = $self->{status};
if ($status) { return $self; }
if (!defined($colkeys)) {return $self; }
if (!defined($$colkeys{TTYPE}) || !defined($$colkeys{TFORM})) {
$self->{status} = -1;
return $self;
}
$fits->get_num_cols($ncols,$status);
if (!defined($column)) {
$colnum = $ncols+1;
} elsif ($column =~ m/^[0-9]$/) {
$colnum = $column;
} else {
$fits->get_colnum(CASEINSEN,$column,$colnum,$status);
}
if ($status) {
$self->{status} = $status;
return $self;
}
($ttype,$comment) = parsekeys($$colkeys{TTYPE});
if (! $ttype ) {
$self->{status} = -1;
return $self;
}
($tform) = parsekeys($$colkeys{TFORM});
$self->{status} = $status;
$fits->insert_col($colnum,$ttype,$tform,$status);
if ($comment) { $self->writekey("TTYPE$colnum",$ttype,$comment,TSTRING); }
foreach my $key (keys %$colkeys) {
if ($key ne "TTYPE" && $key ne "TFORM") {
undef $type; undef $comment;
($value,$comment,$type) = parsekeys($$colkeys{$key});
if (!defined($type)) { $type = vtype($value); }
if (!defined($comment) && $defcomments{$key}) {
$comment = $defcomments{$key};
}
$self->writekey("$key$colnum",$value,$comment,$type);
}
}
return $self;
}
sub loadTable
{
my ($self, $dataref, $tableref) = @_;
return 0 unless ($self);
my $fits = $self->{fits};
return $self unless ($fits);
my $status = $self->{status};
my $nrows;
my $ncols;
$fits->get_num_rows($nrows, $status) unless $status;
$fits->get_num_cols($ncols, $status) unless $status;
my @column;
for (my $i = 1; not $status and $i <= $ncols; ++$i) {
my $colname;
my $comment;
my $type;
my $repeat;
my $width;
my @data;
$fits->read_key_str("TTYPE$i", $colname, $comment, $status);
$colname =~ s/^'//;
$colname =~ s/\s*'$//;
$fits->get_coltype($i, $type, $repeat, $width, $status)
unless $status;
my $readstr = $type == TSTRING || $type == TBIT;
my $nelem = $readstr ? $nrows : $nrows * $repeat;
my $nulls;
if ($status) {
}
elsif ($readstr) {
$fits->read_col_str($i, 1, 1, $nelem, 0,
\@data, $nulls, $status)
}
else {
$fits->read_col($type, $i, 1, 1, $nelem, 0,
\@data, $nulls, $status)
}
if (not $status and $type == TBIT) {
@data = map { substr($_, 0, $repeat) } @data;
}
my %column = (
index => $i,
name => uc($colname),
type => $type,
data => \@data,
);
push(@column, \%column);
}
if ($status) {
$self->{status} = $status;
}
else {
for (my $i = 0; $i < $nrows; ++$i) {
my %record = map { $_->{name} => $_->{data}[$i] } @column;
push(@$dataref, \%record);
}
}
return $self;
}
1;