Util::FITSlist (version 1.4)


package Util::FITSlist;

##############################################################################
#
# DESCRIPTION: This class handles a list of FITS files. In particular it
# DESCRIPTION: provides an interface to the ffilecat and fmerge FTOOLs.
# DESCRIPTION: The Util::EventFileList sub-class does even more specific things
# DESCRIPTION: for FITS event files.
#
# HISTORY
# HISTORY: $Log: FITSlist.pm,v $
# HISTORY: Revision 1.2  2006/08/01 20:35:34  apsop
# HISTORY: Add in CVS history indicator.
# HISTORY:
# HISTORY: 1.0 -> 1.1 2000-07-05
# HISTORY: Reduced the maximum number of input files for ffilecat from 
# HISTORY: 999 to 998 to compensate for a possible bug in that FTOOL.
# HISTORY: 
# HISTORY: 1.1 -> 1.2 2000-08-08
# HISTORY: Added max_keyword, unique_keywords, and get_extension methods
# HISTORY: Now the select method will return the same type of object as
# HISTORY: is calling the method to facilitate inheritance.
# HISTORY: 
# HISTORY: 1.2 -> 1.3 2003-03-18
# HISTORY: Modified select method to do a better job of parsing the keyword
# HISTORY: names from the expression. Previously non-A-Z characters were
# HISTORY: treated as spaces.
# HISTORY: 
# HISTORY: 1.3 -> 1.4 2003-05-06
# HISTORY: Modified so that each FITS file can have a different extension.
#
# VERSION: 1.4
#
##############################################################################

use Util::FileList;
use Util::Ftool;

@ISA=("Util::FileList");

use strict;


###############################################################################
# This is not much different from the inherited constructor. It also
# sets the extension specifier to undef and initializes some internal
# things needed for cataloging.
###############################################################################
sub new {
    my $self=shift;

    #####################################
    # inherit generic list initialization
    #####################################
    $self=$self->SUPER::new(@_);

    $self->{EXT}=[];

    ########################################################
    # maximum number of files which can be fed to ffilecat
    # at one time
    ########################################################
    $self->{MAX_FFILECAT}=998; 

    $self->{CATALOG}="fits_list_catalog".$self->tmp_file_suffix();
    $self->{CATALOG_KEYS}={};


    return $self;


} # end of constructor

##################################
# ACCESSORS:
##################################

########################################################
# add a file to the end of the list. 
########################################################
sub add {
    my $self = shift;
    my $file = shift;
    my $ext  = shift;

    $self->SUPER::add($file);
    push @{$self->{EXT}}, ($ext);

    $self->{CATALOG_KEYS} = {};

}


########################################################
# return the file names without the extension specifier
########################################################
sub files {
    my $self=shift;
    return (@{$self->{FILES}});
}

########################################################
# return the file names with the extension specifier
########################################################
sub files_with_ext {
    my $self=shift;

    my @with_ext=();
    for(my $i=0; $i<$self->count(); $i++) {
        my $file = ${$self->{FILES}}[$i];
        my $ext  = ${$self->{EXT} }[$i];

        if(defined $ext) { push @with_ext, ("$file\[$ext\]"); }
        else             { push @with_ext, ($file);           }
    }

    return (@with_ext);
}

################################################################
# return the index-th file name without the extension specifier
################################################################
sub file {
    my $self=shift;
    my $index=shift;

    if(! defined $index ) {$index=0}

    return ${$self->{FILES}}[$index];
}

################################################################
# return the index-th file name with the extension specifier
################################################################
sub file_with_ext {
    my $self=shift;
    my $index=shift;

    if(! defined $index ) {$index=0}

    return ${$self->files_with_ext()}[$index];
}

################################################################
# This overrides the inherited method to return the files names
# with the extension specifier.
################################################################
sub files_for_param {
    my $self=shift;
    return ($self->files_with_ext());
}


####################################################################
####################################################################
# set the extension indicator in all the file names.
# Giving no argument will leave off the extension indicator.
####################################################################
sub extension {
    my $self=shift;
    my $ext = shift;

    for(my $i=0; $i<$self->count(); $i++) {

        ${$self->{EXT}}[$i] = $ext;
    }

    return $self;
}

####################################################################
####################################################################
# set separate extensions for each file. The arguments are a list of
# extensions in the same order as the files
####################################################################
sub extensions {
    my $self=shift;

    @{$self->{EXT}} = @_;

    return $self;
}


#############################################################################
#############################################################################
# return the extension of the first file
#############################################################################
sub get_extension {
    my $self=shift;

    return ${$self->{EXT}}[0];
}

#############################################################################
#############################################################################
# return the list of all extensions
#############################################################################
sub get_extensions {
    my $self=shift;

    return (@{$self->{EXT}});
}


    
########################
# METHODS:
########################



#############################################################################
#############################################################################
# Merge all the files in the list into a single file using the fmerge FTOOL 
# and return the name of the merged file.
# If the list contains only one file this method does nothing and returns
# the name of that one file.
# The second argument is the name of the EXTNAME of the merged file.
# Any additional agruments is a list of columns to be merged.
#
# Note this overrides the inherited method, which just concatenates the files.
#############################################################################
sub merge {
    my $self=shift;
    my $merged=shift;
    my $ext=shift || " ";

    if(@{$self->{FILES}} == 1 ) {
        ####################################################
        # only one file so just give the name of that file
        ####################################################
        return @{$self->{FILES}}[0];
    }

    unlink $merged;

    my $fmerge = Util::Ftool->new("fmerge") 
               ->params({infiles   => $self->as_param(),
                         outfile   => $merged,
                         mextname  => $ext,
                         copyprime => "yes",
                         lastkey   => " ",
                         history   => "no",
                         clobber   => "yes"  });

    $fmerge->params({columns => join(' ', @_)}) if @_;

    $fmerge->run();
            
    return $merged;

}

#############################################################################
#############################################################################
# Create a new list which is a subset of the total.
# This method is identical to the inherited method ,
# except that it also sets the extension in the new list to be the same as in
# this one.
#############################################################################
sub sublist {
    my $self=shift;
    my $from=shift;
    my $to=shift;

    ###############################################
    # inherit the geric list method
    ###############################################
    my $new_list=$self->SUPER::sublist($from,$to);

    if($new_list && $new_list != $self) { 
        ######################################################
        # created a new list, so set the extension
        ######################################################
        my @extensions = $self->get_extensions();
        @extensions = @extensions[$from...$to];
        $new_list->extensions(@extensions);
    } 

    #############################
    # return the result
    #############################
    return $new_list;

} # end of sublist method


#############################################################################
#############################################################################
# make a FITS catalog file out of the list
#############################################################################
sub catalog { #(catalog, key1, key2...)
    my $self=shift;
    my $catalog=shift;
    my @keys=@_;

    #######################################
    # set up the ffilecat FTOOL
    #######################################
    my $ffilecat=Util::Ftool->new("ffilecat")
                            ->params({outfile  => $catalog,
                                      keywords => join(' ', @keys),
                                      maxlen   => 20,
                                      minlen   => 0,
                                      aform    => "NONE",
                                      eform    => "NONE",
                                      iform    => "NONE",
                                      omit     => "yes",
                                      quiet    => "no"  });

    unlink $catalog;

    #######################################################################
    # ffilecat can only handle so many files at one time
    # but if an existing file is given for the output file
    # it will append to that file.
    # so we loop over sub-lists until we have cataloged the whole thing.
    ######################################################################
    my $list;
    my $from;
    for($from=0; 
        $list = $self->sublist($from, $from+$self->{MAX_FFILECAT}-1);
        $from += $self->{MAX_FFILECAT} ) {

        #####################################
        # make or append to the catalog
        #####################################
        $ffilecat->params({infile=>$list->as_param()})
                 ->run();
    }

} # end of catalog method

#############################################################################
#############################################################################
# make a temporary FITS catalog file for internal use.
# If we have already made a catalog containing the requested keywords,
# no new catalog will be created.
# The temporary catalog file will be deleted when the FITSlist object is
# destroyed.
#############################################################################
sub temp_catalog { #(key1, key2...)
    my $self=shift;
    my @keys=@_;

    ###################################################
    # have we already made a catalog with these keys?
    ###################################################
    my $match=1;
    my $key;
    foreach $key (@keys) {
        $match &&= exists($self->{CATALOG_KEYS}->{$key});
    }

    if($match) {return}

    ###########################################################
    # we don't already have a catalog which will do the trick
    # first record the list of new keys
    ###########################################################
    $self->{CATALOG_KEYS}={};
    foreach $key (@keys) {
        $self->{CATALOG_KEYS}->{$key}=1;
    }

    #####################################
    # now create the catalog file
    #####################################
    $self->catalog($self->{CATALOG},@keys);

    return $self;

} # end of temp_catalog method


#############################################################################
#############################################################################
# Return arrays of keyword values for all the files in the list.
# The arrays are returned in the same way as UTIL::FITSfile->table().
# Specifying "FILENAME" as one of the columns will give the corresponding file
# names. 
#############################################################################
sub keywords { #(key1, key2...)
    my $self=shift;
    my @cols=@_;

    my @keys = grep $_ ne "FILENAME", @cols;
    if(@cols && !@keys ) {
        #############################################
        # only the filename was requested, so just use the
        # files method
        ###################################################
        return ($self->files());
    }
    

    ##############################################
    # first make sure we have a catalog with the 
    # requested keywords in it
    ##############################################
    $self->temp_catalog(@keys);

    ##############################################
    # now dump the catalog
    ##############################################
    return (Util::FITSfile->new($self->{CATALOG})
                          ->cols(@cols)
                          ->table()  );

} # end of keywords method;

#############################################################################
#############################################################################
# find the maximum value of a given keyword
# In scalar context returns the maximum keyword value.
# In list context returns the name of the first file with the maximum value 
# and the maximum value.
#############################################################################
sub max_keyword {
    my $self=shift;
    my $key=shift;

    my %value=$self->keywords("FILENAME", $key);
    my @files=keys %value;

    my $max=shift @files;
    foreach (@files) {
        if($value{$_} > $value{$max} ) { $max = $_ }
    }

    if(wantarray() ) {
        return($max, $value{$max} );
    } else {
        return $value{$max};
    }

} # end of max_keyword method

#############################################################################
#############################################################################
# find the minimum value of a given keyword
# In scalar context returns the minimum keyword value.
# In list context returns the name of the first file with the minimum value 
# and the minimum value.
#############################################################################
sub min_keyword {
    my $self=shift;
    my $key=shift;

    my %value=$self->keywords("FILENAME", $key);
    my @files=keys %value;

    my $min=shift @files;
    foreach (@files) {
        if($value{$_} < $value{$min} ) { $min = $_ }
    }

    if(wantarray() ) {
        return($min, $value{$min} );
    } else {
        return $value{$min};
    }

} # end of min_keyword method

   
#############################################################################
#############################################################################
# sum the values of a given keyword
#############################################################################
sub sum_keywords {
    my $self=shift;
    my $key=shift;

    my $sum=0.;
    foreach ($self->keywords($key) ) {
        $sum += $_;
    }

    return $sum;

} # end of sum_keywords method

#############################################################################
#############################################################################
# return a list of the unique values of a given keyword.
# i.e. repeated values are dropped.
#############################################################################
sub unique_keywords {
    my $self=shift;
    my $key=shift;

    my %unique=();
    foreach ($self->keywords($key) ) {
        $unique{$_}=1;
    }

    return keys %unique ;

} # end of unique_keywords method


                  

#############################################################################
#############################################################################
# Create a new file list which gives a subset of this list 
# and which is selected according to an expression involving the
# header keywords.
#############################################################################
sub select {
    my $self=shift;
    my $expression=shift;

    ##################################################
    # parse the expression to get a list of keywords
    ##################################################
    my $text = $expression;
    $text =~ s/[0-9]+\.?[eE][+-]?[0-9]+//; # remove scientific notation numbers
    my @keys = $text =~  /([A-Z][A-Z0-9_-]{0,7})/g;

    my %uniq=();
    foreach (@keys) { $uniq{$_} = 1; }

    @keys = keys %uniq;

    ####################################################
    # make a temporary catalog file containing the
    # required keywords
    ####################################################
    $self->temp_catalog(@keys);

    ##############################################################
    # dump the catalog with the selection criteria applied
    ##############################################################
    my @files = Util::FITSfile->new($self->{CATALOG}, 1, "\[$expression\]")
                              ->cols("FILENAME")
                              ->table();

    #####################################################
    # remove any extension specifiers from the filenames
    #####################################################
    my @extensions=();
    foreach (@files) {
        push @extensions, (/\[(.*)\]$/);
        s/\[.*\]$//g;
    }

    ############################################################
    # return a reference to a new FITSlist which contains
    # only the selected files
    ############################################################
    return $self->new(@files)
                ->extensions(@extensions);

} # end of select method


#############################################################################
#############################################################################
# Returns another file list sorted by the given set of keywords.
#############################################################################
sub sort { #(key1, key2...)
    my $self=shift;
    my @keys=@_;

    ################################
    # sort in reverse order?
    ################################
    my $reverse;
    if(@keys[@keys-1] eq "reverse" ) {
        $reverse = pop @keys;
    }
        
 
    ############################
    # create temporary catalog
    ############################
    $self->temp_catalog(@keys);

    #############################
    # sort the catalog
    #############################
    my @sorted=Util::FITSfile->new($self->{CATALOG}, 1)
                             ->cols(@keys)
                             ->sort($reverse)
                             ->cols("FILENAME")
                             ->table();

    #####################################################
    # remove any extension specifiers from the filenames
    #####################################################
    my @extensions=();
    foreach (@sorted) {
        push @extensions,  (/\[(.*)\]$/);
        s/\[.*\]$//g;
    }


    #############################################
    # return a new list with the sorted files
    #############################################
    return Util::FITSlist->new(@sorted)
                         ->extensions(@extensions);

} # end of sort method
            

#############################################################################
#############################################################################
# destructor - deletes temporary file list and catalog. 
# This method should probably be
# called explicitly after using the as_param method.
#############################################################################
sub DESTROY {
    my $self=shift;

    $self->SUPER::DESTROY();

   unlink $self->{CATALOG};

}

1;