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 }