go-hep.org/x/hep@v0.38.1/cmd/fits2root/main.go (about)

     1  // Copyright ©2020 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // fits2root converts the content of a FITS table to a ROOT file and tree.
     6  //
     7  // Usage: fits2root [OPTIONS] -f input.fits
     8  //
     9  // Example:
    10  //
    11  //	$> fits2root -f ./input.fits -t MyHDU
    12  //
    13  // Options:
    14  //
    15  //	-f string
    16  //	  	path to input FITS file name
    17  //	-o string
    18  //	  	path to output ROOT file name (default "output.root")
    19  //	-t string
    20  //	  	name of the FITS table to convert
    21  package main
    22  
    23  import (
    24  	"flag"
    25  	"fmt"
    26  	"log"
    27  	"os"
    28  	"reflect"
    29  
    30  	"codeberg.org/astrogo/fitsio"
    31  	"go-hep.org/x/hep/groot"
    32  	"go-hep.org/x/hep/groot/rtree"
    33  )
    34  
    35  func main() {
    36  	log.SetPrefix("fits2root: ")
    37  	log.SetFlags(0)
    38  
    39  	fname := flag.String("f", "", "path to input FITS file name")
    40  	oname := flag.String("o", "output.root", "path to output ROOT file name")
    41  	tname := flag.String("t", "", "name of the FITS table to convert")
    42  
    43  	flag.Usage = func() {
    44  		fmt.Printf(`fits2root converts the content of a FITS table to a ROOT file and tree.
    45  
    46  Usage: fits2root [OPTIONS] -f input.fits
    47  
    48  Example:
    49  
    50   $> fits2root -f ./input.fits -t MyHDU
    51  
    52  Options:
    53  `)
    54  		flag.PrintDefaults()
    55  	}
    56  
    57  	flag.Parse()
    58  
    59  	if *fname == "" {
    60  		flag.Usage()
    61  		log.Fatalf("missing path to input FITS file argument")
    62  	}
    63  
    64  	err := process(*oname, *tname, *fname)
    65  	if err != nil {
    66  		log.Fatalf("%+v", err)
    67  	}
    68  }
    69  
    70  func process(oname, tname, fname string) error {
    71  	f, err := os.Open(fname)
    72  	if err != nil {
    73  		return fmt.Errorf("could not open input file %q: %w", fname, err)
    74  	}
    75  	defer f.Close()
    76  
    77  	fits, err := fitsio.Open(f)
    78  	if err != nil {
    79  		return fmt.Errorf("could not open FITS file %q: %w", fname, err)
    80  	}
    81  	defer fits.Close()
    82  
    83  	hdu := fits.Get(tname)
    84  	if hdu == nil {
    85  		return fmt.Errorf("could not retrieve HDU %q from FITS file %q", tname, fname)
    86  	}
    87  	defer hdu.Close()
    88  
    89  	tbl, ok := hdu.(*fitsio.Table)
    90  	if !ok {
    91  		return fmt.Errorf("HDU %q from FITS file %q is not a Table", tname, fname)
    92  	}
    93  
    94  	o, err := groot.Create(oname)
    95  	if err != nil {
    96  		return fmt.Errorf("could not create output ROOT file %q: %w", oname, err)
    97  	}
    98  	defer o.Close()
    99  
   100  	var (
   101  		wvars = make([]rtree.WriteVar, tbl.NumCols())
   102  		wargs = make([]any, tbl.NumCols())
   103  	)
   104  	for i, col := range tbl.Cols() {
   105  		wvars[i] = wvarFrom(col)
   106  		wargs[i] = wvars[i].Value
   107  	}
   108  
   109  	if tname == "" {
   110  		tname = "FITS"
   111  	}
   112  
   113  	tree, err := rtree.NewWriter(o, tname, wvars)
   114  	if err != nil {
   115  		return fmt.Errorf("could not create output ROOT tree %q: %w", tname, err)
   116  	}
   117  
   118  	rows, err := tbl.Read(0, tbl.NumRows())
   119  	if err != nil {
   120  		return fmt.Errorf("could not read FITS table range [0, %d): %w", tbl.NumRows(), err)
   121  	}
   122  	defer rows.Close()
   123  
   124  	var irow int64
   125  	for rows.Next() {
   126  		err = rows.Scan(wargs...)
   127  		if err != nil {
   128  			return fmt.Errorf("could not read row %d: %w", irow, err)
   129  		}
   130  
   131  		_, err = tree.Write()
   132  		if err != nil {
   133  			return fmt.Errorf("could not write row %d: %w", irow, err)
   134  		}
   135  
   136  		irow++
   137  	}
   138  
   139  	err = tree.Close()
   140  	if err != nil {
   141  		return fmt.Errorf("could not close ROOT tree writer: %w", err)
   142  	}
   143  
   144  	err = o.Close()
   145  	if err != nil {
   146  		return fmt.Errorf("could not close output ROOT file %q: %w", oname, err)
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func wvarFrom(col fitsio.Column) rtree.WriteVar {
   153  	rt := col.Type()
   154  	switch rt.Kind() {
   155  	case reflect.Bool:
   156  		return rtree.WriteVar{
   157  			Name:  col.Name,
   158  			Value: new(bool),
   159  		}
   160  
   161  	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   162  		return rtree.WriteVar{
   163  			Name:  col.Name,
   164  			Value: reflect.New(rt).Interface(),
   165  		}
   166  
   167  	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   168  		return rtree.WriteVar{
   169  			Name:  col.Name,
   170  			Value: reflect.New(rt).Interface(),
   171  		}
   172  
   173  	case reflect.Float32, reflect.Float64:
   174  		return rtree.WriteVar{
   175  			Name:  col.Name,
   176  			Value: reflect.New(rt).Interface(),
   177  		}
   178  
   179  	case reflect.String:
   180  		return rtree.WriteVar{
   181  			Name:  col.Name,
   182  			Value: new(string),
   183  		}
   184  
   185  	case reflect.Array:
   186  		return rtree.WriteVar{
   187  			Name:  col.Name,
   188  			Value: reflect.New(rt).Interface(),
   189  		}
   190  
   191  	default:
   192  		panic(fmt.Errorf("fits2root: invalid column type %+v (kind=%v)", col, rt.Kind()))
   193  	}
   194  }