go-hep.org/x/hep@v0.38.1/cmd/root2csv/main.go (about) 1 // Copyright ©2017 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 // root2csv converts the content of a ROOT TTree to a CSV file. 6 // 7 // Usage of root2csv: 8 // -f string 9 // path to input ROOT file name 10 // -o string 11 // path to output CSV file name (default "output.csv") 12 // -t string 13 // name of the tree or graph to convert (default "tree") 14 // 15 // By default, root2csv will write out a CSV file with ';' as a column delimiter. 16 // root2csv ignores the branches of the TTree that are not supported by CSV: 17 // - slices/arrays 18 // - C++ objects 19 // 20 // Example: 21 // 22 // $> root2csv -o out.csv -t tree -f testdata/small-flat-tree.root 23 // $> head out.csv 24 // ## Automatically generated from "testdata/small-flat-tree.root" 25 // Int32;Int64;UInt32;UInt64;Float32;Float64;Str;N 26 // 0;0;0;0;0;0;evt-000;0 27 // 1;1;1;1;1;1;evt-001;1 28 // 2;2;2;2;2;2;evt-002;2 29 // 3;3;3;3;3;3;evt-003;3 30 // 4;4;4;4;4;4;evt-004;4 31 // 5;5;5;5;5;5;evt-005;5 32 // 6;6;6;6;6;6;evt-006;6 33 // 7;7;7;7;7;7;evt-007;7 34 package main 35 36 import ( 37 "flag" 38 "fmt" 39 "log" 40 "reflect" 41 "strings" 42 43 "go-hep.org/x/hep/csvutil" 44 "go-hep.org/x/hep/groot" 45 "go-hep.org/x/hep/groot/rhist" 46 "go-hep.org/x/hep/groot/riofs" 47 _ "go-hep.org/x/hep/groot/riofs/plugin/http" 48 _ "go-hep.org/x/hep/groot/riofs/plugin/xrootd" 49 "go-hep.org/x/hep/groot/rtree" 50 ) 51 52 func main() { 53 log.SetPrefix("root2csv: ") 54 log.SetFlags(0) 55 56 fname := flag.String("f", "", "path to input ROOT file name") 57 oname := flag.String("o", "output.csv", "path to output CSV file name") 58 tname := flag.String("t", "tree", "name of the tree or graph to convert") 59 60 flag.Parse() 61 62 if *fname == "" { 63 flag.Usage() 64 log.Fatalf("missing input ROOT filename argument") 65 } 66 67 err := process(*oname, *fname, *tname) 68 if err != nil { 69 log.Fatal(err) 70 } 71 } 72 73 func process(oname, fname, tname string) error { 74 75 f, err := groot.Open(fname) 76 if err != nil { 77 return fmt.Errorf("could not open ROOT file: %w", err) 78 } 79 defer f.Close() 80 81 obj, err := riofs.Dir(f).Get(tname) 82 if err != nil { 83 return fmt.Errorf("could not get ROOT object: %w", err) 84 } 85 86 switch obj := obj.(type) { 87 case rtree.Tree: 88 return processTree(oname, fname, obj) 89 case rhist.GraphErrors: // Note: test rhist.GraphErrors before rhist.Graph 90 return processGraphErrors(oname, fname, obj) 91 case rhist.Graph: 92 return processGraph(oname, fname, obj) 93 default: 94 return fmt.Errorf("object %q in file %q is not a rtree.Tree nor a rhist.Graph", tname, fname) 95 } 96 } 97 98 func processTree(oname, fname string, tree rtree.Tree) error { 99 var nt = ntuple{n: tree.Entries()} 100 log.Printf("scanning leaves...") 101 for _, leaf := range tree.Leaves() { 102 kind := leaf.Type().Kind() 103 switch kind { 104 case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct: 105 log.Printf(">>> %q %v not supported (%v)", leaf.Name(), leaf.Class(), kind) 106 continue 107 case reflect.String: 108 // ok 109 case reflect.Bool, 110 reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 111 reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 112 reflect.Float32, reflect.Float64: 113 if leaf.LeafCount() != nil { 114 log.Printf(">>> %q %v not supported (slice)", leaf.Name(), leaf.Class()) 115 continue 116 } 117 if leaf.Len() > 1 { 118 log.Printf(">>> %q %v not supported (array)", leaf.Name(), leaf.Class()) 119 continue 120 } 121 default: 122 log.Printf(">>> %q %v not supported (%v) (unknown!)", leaf.Name(), leaf.Class(), kind) 123 continue 124 } 125 126 nt.add(leaf.Name(), leaf) 127 } 128 log.Printf("scanning leaves... [done]") 129 130 r, err := rtree.NewReader(tree, nt.args) 131 if err != nil { 132 return fmt.Errorf("could not create tree reader: %w", err) 133 } 134 defer r.Close() 135 136 nrows := 0 137 err = r.Read(func(ctx rtree.RCtx) error { 138 nt.fill() 139 nrows++ 140 return nil 141 }) 142 if err != nil { 143 return fmt.Errorf("could not read tree: %w", err) 144 } 145 146 tbl, err := csvutil.Create(oname) 147 if err != nil { 148 return fmt.Errorf("could not create output CSV file: %w", err) 149 } 150 defer tbl.Close() 151 tbl.Writer.Comma = ';' 152 153 names := make([]string, len(nt.cols)) 154 for i, col := range nt.cols { 155 names[i] = col.name 156 } 157 err = tbl.WriteHeader(fmt.Sprintf( 158 "## Automatically generated from %q\n%s\n", 159 fname, 160 strings.Join(names, string(tbl.Writer.Comma)), 161 )) 162 if err != nil { 163 return fmt.Errorf("could not write CSV header: %w", err) 164 } 165 166 row := make([]any, len(nt.cols)) 167 for irow := range nrows { 168 for i, col := range nt.cols { 169 row[i] = col.slice.Index(irow).Interface() 170 } 171 err = tbl.WriteRow(row...) 172 if err != nil { 173 return fmt.Errorf("could not write row %d to CSV file: %w", irow, err) 174 } 175 } 176 177 err = tbl.Close() 178 if err != nil { 179 return fmt.Errorf("could not close CSV output file: %w", err) 180 } 181 182 return nil 183 } 184 185 type ntuple struct { 186 n int64 187 cols []column 188 args []rtree.ReadVar 189 vars []any 190 } 191 192 func (nt *ntuple) add(name string, leaf rtree.Leaf) { 193 n := len(nt.cols) 194 nt.cols = append(nt.cols, newColumn(name, leaf, nt.n)) 195 col := &nt.cols[n] 196 nt.args = append(nt.args, rtree.ReadVar{ 197 Name: name, 198 Leaf: leaf.Name(), 199 Value: col.data.Addr().Interface(), 200 }) 201 nt.vars = append(nt.vars, col.data.Addr().Interface()) 202 } 203 204 func (nt *ntuple) fill() { 205 for i := range nt.cols { 206 col := &nt.cols[i] 207 col.fill() 208 } 209 } 210 211 type column struct { 212 name string 213 i int64 214 leaf rtree.Leaf 215 etype reflect.Type 216 shape []int 217 data reflect.Value 218 slice reflect.Value 219 } 220 221 func newColumn(name string, leaf rtree.Leaf, n int64) column { 222 etype := leaf.Type() 223 shape := []int{int(n)} 224 if leaf.Len() > 1 && leaf.Kind() != reflect.String { 225 etype = reflect.ArrayOf(leaf.Len(), etype) 226 shape = append(shape, leaf.Len()) 227 } 228 rtype := reflect.SliceOf(etype) 229 return column{ 230 name: name, 231 i: 0, 232 leaf: leaf, 233 etype: etype, 234 shape: shape, 235 data: reflect.New(etype).Elem(), 236 slice: reflect.MakeSlice(rtype, int(n), int(n)), 237 } 238 } 239 240 func (col *column) fill() { 241 col.slice.Index(int(col.i)).Set(col.data) 242 col.i++ 243 } 244 245 func processGraph(oname, fname string, g rhist.Graph) error { 246 names := []string{"x", "y"} 247 248 tbl, err := csvutil.Create(oname) 249 if err != nil { 250 return fmt.Errorf("could not create output CSV file: %w", err) 251 } 252 defer tbl.Close() 253 tbl.Writer.Comma = ';' 254 255 err = tbl.WriteHeader(fmt.Sprintf( 256 "## Automatically generated from %q\n%s\n", 257 fname, 258 strings.Join(names, string(tbl.Writer.Comma)), 259 )) 260 if err != nil { 261 return fmt.Errorf("could not write CSV header: %w", err) 262 } 263 264 n := g.Len() 265 for i := range n { 266 var ( 267 x, y = g.XY(i) 268 ) 269 err = tbl.WriteRow(x, y) 270 if err != nil { 271 return fmt.Errorf("could not write row %d to CSV file: %w", i, err) 272 } 273 } 274 275 err = tbl.Close() 276 if err != nil { 277 return fmt.Errorf("could not close CSV output file: %w", err) 278 } 279 280 return nil 281 } 282 283 func processGraphErrors(oname, fname string, g rhist.GraphErrors) error { 284 names := []string{"x", "y", "ex-lo", "ex-hi", "ey-lo", "ey-hi"} 285 286 tbl, err := csvutil.Create(oname) 287 if err != nil { 288 return fmt.Errorf("could not create output CSV file: %w", err) 289 } 290 defer tbl.Close() 291 tbl.Writer.Comma = ';' 292 293 err = tbl.WriteHeader(fmt.Sprintf( 294 "## Automatically generated from %q\n%s\n", 295 fname, 296 strings.Join(names, string(tbl.Writer.Comma)), 297 )) 298 if err != nil { 299 return fmt.Errorf("could not write CSV header: %w", err) 300 } 301 302 n := g.Len() 303 for i := range n { 304 var ( 305 x, y = g.XY(i) 306 xlo, xhi = g.XError(i) 307 ylo, yhi = g.YError(i) 308 ) 309 err = tbl.WriteRow(x, y, xlo, xhi, ylo, yhi) 310 if err != nil { 311 return fmt.Errorf("could not write row %d to CSV file: %w", i, err) 312 } 313 } 314 315 err = tbl.Close() 316 if err != nil { 317 return fmt.Errorf("could not close CSV output file: %w", err) 318 } 319 320 return nil 321 }