go-hep.org/x/hep@v0.38.1/groot/cmd/root-print/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  // root-print prints ROOT files contents to PDF, PNG, ... files.
     6  //
     7  // Examples:
     8  //
     9  //	$> root-print -f pdf ./testdata/histos.root
    10  //	$> root-print -f pdf ./testdata/histos.root:h1
    11  //	$> root-print -f pdf ./testdata/histos.root:h.*
    12  //	$> root-print -f pdf -o output ./testdata/histos.root:h1
    13  //
    14  //	$> root-print -h
    15  //	Usage: root-print [options] file.root [file.root [...]]
    16  //
    17  //	options:
    18  //	  -f string
    19  //	    	output format for plots (pdf, png, svg, ...) (default "pdf")
    20  //	  -o string
    21  //	    	output directory for plots
    22  //	  -v	enable verbose mode
    23  package main // import "go-hep.org/x/hep/groot/cmd/root-print"
    24  
    25  import (
    26  	"flag"
    27  	"fmt"
    28  	"log"
    29  	"os"
    30  	stdpath "path"
    31  	"path/filepath"
    32  	"regexp"
    33  	"strings"
    34  
    35  	"go-hep.org/x/hep/groot"
    36  	"go-hep.org/x/hep/groot/rhist"
    37  	"go-hep.org/x/hep/groot/riofs"
    38  	_ "go-hep.org/x/hep/groot/riofs/plugin/http"
    39  	_ "go-hep.org/x/hep/groot/riofs/plugin/xrootd"
    40  	"go-hep.org/x/hep/groot/root"
    41  	"go-hep.org/x/hep/hbook/rootcnv"
    42  	"go-hep.org/x/hep/hplot"
    43  	"gonum.org/v1/plot/plotutil"
    44  	"gonum.org/v1/plot/vg"
    45  )
    46  
    47  var (
    48  	colors = plotutil.SoftColors
    49  )
    50  
    51  func main() {
    52  	log.SetPrefix("root-print: ")
    53  	log.SetFlags(0)
    54  
    55  	var (
    56  		odirFlag    = flag.String("o", "", "output directory for plots")
    57  		fmtFlag     = flag.String("f", "pdf", "output format for plots (pdf, png, svg, ...)")
    58  		verboseFlag = flag.Bool("v", false, "enable verbose mode")
    59  	)
    60  
    61  	flag.Usage = func() {
    62  		fmt.Fprintf(
    63  			os.Stderr,
    64  			`Usage: root-print [options] file.root [file.root [...]]
    65  ex:
    66   $> root-print -f pdf ./testdata/histos.root
    67   $> root-print -f pdf ./testdata/histos.root:h1
    68   $> root-print -f pdf ./testdata/histos.root:h.*
    69   $> root-print -f pdf -o output ./testdata/histos.root:h1
    70  
    71  options:
    72  `,
    73  		)
    74  		flag.PrintDefaults()
    75  	}
    76  
    77  	flag.Parse()
    78  
    79  	if flag.NArg() < 1 {
    80  		flag.Usage()
    81  		log.Fatalf("need at least 1 input ROOT file")
    82  	}
    83  
    84  	err := rootprint(*odirFlag, flag.Args(), *fmtFlag, *verboseFlag)
    85  	if err != nil {
    86  		log.Fatalf("%+v", err)
    87  	}
    88  }
    89  
    90  func rootprint(odir string, fnames []string, otype string, verbose bool) error {
    91  	err := os.MkdirAll(odir, 0755)
    92  	if err != nil {
    93  		return fmt.Errorf("could not create output directory %q: %w", odir, err)
    94  	}
    95  
    96  	for _, fname := range fnames {
    97  		err := process(odir, fname, otype, verbose)
    98  		if err != nil {
    99  			return fmt.Errorf("could not process %q: %w", fname, err)
   100  		}
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  func process(odir, name, otyp string, verbose bool) error {
   107  	fname, hname, err := splitArg(name)
   108  	if err != nil {
   109  		return fmt.Errorf(
   110  			"invalid input file format. got %q. want: \"file.root:histo\"",
   111  			name,
   112  		)
   113  	}
   114  
   115  	f, err := groot.Open(fname)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	defer f.Close()
   120  
   121  	re, err := regexp.CompilePOSIX(hname)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	var objs []root.Object
   127  	err = riofs.Walk(f, func(path string, obj root.Object, err error) error {
   128  		if err != nil {
   129  			return err
   130  		}
   131  		name := path[len(f.Name()):]
   132  		if name == "" {
   133  			return nil
   134  		}
   135  		if !re.MatchString(name) {
   136  			return nil
   137  		}
   138  
   139  		if !filter(obj) {
   140  			return nil
   141  		}
   142  
   143  		objs = append(objs, obj)
   144  		return nil
   145  	})
   146  	if err != nil {
   147  		return fmt.Errorf("could not inspect input ROOT file: %w", err)
   148  	}
   149  
   150  	for _, obj := range objs {
   151  		err := printObject(odir, otyp, obj, verbose)
   152  		if err != nil {
   153  			return err
   154  		}
   155  	}
   156  	return err
   157  }
   158  
   159  func printObject(odir, otyp string, obj root.Object, verbose bool) error {
   160  	p := hplot.New()
   161  	name := obj.(root.Named).Name()
   162  	title := obj.(root.Named).Title()
   163  	if title == "" {
   164  		title = name
   165  	}
   166  	p.Title.Text = title
   167  
   168  	oname := stdpath.Join(odir, name+"."+otyp)
   169  	if verbose {
   170  		log.Printf("printing %q to %s...", name, oname)
   171  	}
   172  
   173  	switch o := obj.(type) {
   174  	case rhist.H2:
   175  		h := rootcnv.H2D(o)
   176  		p.Add(hplot.NewH2D(h, nil))
   177  
   178  	case rhist.H1:
   179  		h := rootcnv.H1D(o)
   180  		hh := hplot.NewH1D(h)
   181  		hh.Color = colors[2]
   182  		hh.LineStyle.Color = colors[2]
   183  		hh.LineStyle.Width = vg.Points(1.5)
   184  		hh.Infos.Style = hplot.HInfoSummary
   185  
   186  		p.Add(hh)
   187  
   188  	case rhist.GraphErrors:
   189  		h := rootcnv.S2D(o)
   190  		if name := h.Name(); name != "" {
   191  			p.Title.Text = name
   192  		}
   193  		g := hplot.NewS2D(h, hplot.WithXErrBars(true), hplot.WithYErrBars(true))
   194  		g.Color = colors[0]
   195  		p.Add(g)
   196  
   197  	case rhist.Graph:
   198  		h := rootcnv.S2D(o)
   199  		if name := h.Name(); name != "" {
   200  			p.Title.Text = name
   201  		}
   202  		g := hplot.NewS2D(h)
   203  		g.Color = colors[0]
   204  		p.Add(g)
   205  
   206  	default:
   207  		return fmt.Errorf("unknown type %T for %q", o, name)
   208  	}
   209  
   210  	p.Add(hplot.NewGrid())
   211  
   212  	//	ext := strings.ToLower(stdpath.Ext(oname))
   213  	//	if len(ext) > 0 {
   214  	//		ext = ext[1:]
   215  	//	}
   216  
   217  	err := p.Save(20*vg.Centimeter, -1, oname)
   218  	if err != nil {
   219  		return fmt.Errorf("could not print %q to %q: %w", name, oname, err)
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func filter(obj root.Object) bool {
   226  	switch obj.(type) {
   227  	case rhist.Graph, rhist.GraphErrors:
   228  		return true
   229  
   230  	case rhist.H2:
   231  		return true
   232  
   233  	case rhist.H1:
   234  		return true
   235  	}
   236  	return false
   237  }
   238  
   239  func splitArg(cmd string) (fname, sel string, err error) {
   240  	fname = cmd
   241  	prefix := ""
   242  	for _, p := range []string{"https://", "http://", "root://", "file://"} {
   243  		if strings.HasPrefix(cmd, p) {
   244  			prefix = p
   245  			break
   246  		}
   247  	}
   248  	fname = fname[len(prefix):]
   249  
   250  	vol := filepath.VolumeName(fname)
   251  	if vol != fname {
   252  		fname = fname[len(vol):]
   253  	}
   254  
   255  	if strings.Count(fname, ":") > 1 {
   256  		return "", "", fmt.Errorf("root-cp: too many ':' in %q", cmd)
   257  	}
   258  
   259  	i := strings.LastIndex(fname, ":")
   260  	switch {
   261  	case i > 0:
   262  		sel = fname[i+1:]
   263  		fname = fname[:i]
   264  	default:
   265  		sel = ".*"
   266  	}
   267  	if sel == "" {
   268  		sel = ".*"
   269  	}
   270  	fname = prefix + vol + fname
   271  	switch {
   272  	case strings.HasPrefix(sel, "/"):
   273  	case strings.HasPrefix(sel, "^/"):
   274  	case strings.HasPrefix(sel, "^"):
   275  		sel = "^/" + sel[1:]
   276  	default:
   277  		sel = "/" + sel
   278  	}
   279  	return fname, sel, err
   280  }