go-hep.org/x/hep@v0.38.1/cmd/root2fits/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  // root2fits converts the content of a ROOT tree to a FITS (binary) table.
     6  //
     7  // Usage: root2fits [OPTIONS] -f input.root
     8  //
     9  // Example:
    10  //
    11  //	$> root2fits -f ./input.root -t tree
    12  //
    13  // Options:
    14  //
    15  //	-f string
    16  //	  	path to input ROOT file name
    17  //	-o string
    18  //	  	path to output FITS file name (default "output.fits")
    19  //	-t string
    20  //	  	name of the ROOT tree 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/riofs"
    33  	"go-hep.org/x/hep/groot/rtree"
    34  )
    35  
    36  func main() {
    37  	log.SetPrefix("root2fits: ")
    38  	log.SetFlags(0)
    39  
    40  	fname := flag.String("f", "", "path to input ROOT file name")
    41  	oname := flag.String("o", "output.fits", "path to output FITS file name")
    42  	tname := flag.String("t", "", "name of the ROOT tree to convert")
    43  
    44  	flag.Usage = func() {
    45  		fmt.Printf(`root2fits converts the content of a ROOT tree to a FITS (binary) table.
    46  
    47  Usage: root2fits [OPTIONS] -f input.root
    48  
    49  Example:
    50  
    51   $> root2fits -f ./input.root -t tree
    52  
    53  Options:
    54  `)
    55  		flag.PrintDefaults()
    56  	}
    57  
    58  	flag.Parse()
    59  
    60  	if *fname == "" {
    61  		flag.Usage()
    62  		log.Fatalf("missing path to input ROOT file argument")
    63  	}
    64  
    65  	if *tname == "" {
    66  		flag.Usage()
    67  		log.Fatalf("missing ROOT tree name to convert")
    68  	}
    69  
    70  	err := process(*oname, *tname, *fname)
    71  	if err != nil {
    72  		log.Fatalf("%+v", err)
    73  	}
    74  }
    75  
    76  func process(oname, tname, fname string) error {
    77  	f, err := groot.Open(fname)
    78  	if err != nil {
    79  		return fmt.Errorf("could not open input ROOT file %q: %w", fname, err)
    80  	}
    81  	defer f.Close()
    82  
    83  	obj, err := riofs.Dir(f).Get(tname)
    84  	if err != nil {
    85  		return fmt.Errorf("could not retrieve ROOT tree %q from file %q: %w", tname, fname, err)
    86  	}
    87  
    88  	tree, ok := obj.(rtree.Tree)
    89  	if !ok {
    90  		return fmt.Errorf("ROOT object %q from file %q is not a tree", tname, fname)
    91  	}
    92  
    93  	o, err := os.Create(oname)
    94  	if err != nil {
    95  		return fmt.Errorf("could not create output file %q: %w", oname, err)
    96  	}
    97  	defer o.Close()
    98  
    99  	fits, err := fitsio.Create(o)
   100  	if err != nil {
   101  		return fmt.Errorf("could not create output FITS file %q: %w", oname, err)
   102  	}
   103  	defer fits.Close()
   104  
   105  	phdu, err := fitsio.NewPrimaryHDU(nil)
   106  	if err != nil {
   107  		return fmt.Errorf("could not create primary HDU: %w", err)
   108  	}
   109  	err = fits.Write(phdu)
   110  	if err != nil {
   111  		return fmt.Errorf("could not write primary HDU: %w", err)
   112  	}
   113  
   114  	tbl, err := tableFrom(tree)
   115  	if err != nil {
   116  		return fmt.Errorf("could not create output FITS table: %w", err)
   117  	}
   118  	defer tbl.Close()
   119  
   120  	rvars := rtree.NewReadVars(tree)
   121  	wvars := make([]any, len(rvars))
   122  	for i, rvar := range rvars {
   123  		wvars[i] = rvar.Value
   124  	}
   125  
   126  	r, err := rtree.NewReader(tree, rvars)
   127  	if err != nil {
   128  		return fmt.Errorf("could not create ROOT reader: %w", err)
   129  	}
   130  	defer r.Close()
   131  
   132  	err = r.Read(func(ctx rtree.RCtx) error {
   133  		err = tbl.Write(wvars...)
   134  		if err != nil {
   135  			return fmt.Errorf("could not write entry %d to FITS table: %w", ctx.Entry, err)
   136  		}
   137  		return nil
   138  	})
   139  	if err != nil {
   140  		return fmt.Errorf("could not read input ROOT file: %w", err)
   141  	}
   142  
   143  	err = fits.Write(tbl)
   144  	if err != nil {
   145  		return fmt.Errorf("could not write output FITS table %q: %w", tname, err)
   146  	}
   147  
   148  	err = tbl.Close()
   149  	if err != nil {
   150  		return fmt.Errorf("could not close output FITS table %q: %w", tname, err)
   151  	}
   152  
   153  	err = fits.Close()
   154  	if err != nil {
   155  		return fmt.Errorf("could not close output FITS file %q: %w", oname, err)
   156  	}
   157  
   158  	err = o.Close()
   159  	if err != nil {
   160  		return fmt.Errorf("could not close output file %q: %w", oname, err)
   161  	}
   162  	return nil
   163  }
   164  
   165  func tableFrom(tree rtree.Tree) (*fitsio.Table, error) {
   166  	rvars := rtree.NewReadVars(tree)
   167  	cols := make([]fitsio.Column, len(rvars))
   168  	for i, rvar := range rvars {
   169  		cols[i] = colFrom(rvar, tree)
   170  	}
   171  
   172  	return fitsio.NewTable(tree.Name(), cols, fitsio.BINARY_TBL)
   173  }
   174  
   175  func colFrom(rvar rtree.ReadVar, tree rtree.Tree) fitsio.Column {
   176  	var (
   177  		rt     = reflect.TypeOf(rvar.Value).Elem()
   178  		format = formatFrom(rt)
   179  	)
   180  
   181  	switch rt.Kind() {
   182  	case reflect.String:
   183  		var (
   184  			br   = tree.Branch(rvar.Name)
   185  			leaf = br.Leaf(rvar.Leaf).(*rtree.LeafC)
   186  			max  = leaf.Maximum()
   187  		)
   188  		format = fmt.Sprintf("%d%s", max, format)
   189  	}
   190  
   191  	return fitsio.Column{
   192  		Name:   rvar.Name,
   193  		Format: format,
   194  	}
   195  }
   196  
   197  func formatFrom(rt reflect.Type) string {
   198  	var format = ""
   199  	switch rt.Kind() {
   200  	case reflect.Bool:
   201  		format = "L"
   202  	case reflect.Int8, reflect.Uint8:
   203  		format = "B"
   204  	case reflect.Int16, reflect.Uint16:
   205  		format = "I"
   206  	case reflect.Int32, reflect.Uint32:
   207  		format = "J"
   208  	case reflect.Int64, reflect.Uint64:
   209  		format = "K"
   210  	case reflect.Float32:
   211  		format = "E"
   212  	case reflect.Float64:
   213  		format = "D"
   214  	case reflect.String:
   215  		format = "A"
   216  	case reflect.Array:
   217  		elmt := formatFrom(rt.Elem())
   218  		format = fmt.Sprintf("%d%s", rt.Len(), elmt)
   219  	default:
   220  		panic(fmt.Errorf("invalid branch type %T", reflect.New(rt).Elem().Interface()))
   221  	}
   222  
   223  	return format
   224  }