github.com/vertgenlab/gonomics@v1.0.0/cmd/wigPeaks/wigPeaks.go (about)

     1  // Command Group: "WIG Tools"
     2  // Command Usage: "Identifies peaks in a WIG file"
     3  
     4  // Takes wig file and finds peaks
     5  package main
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"log"
    11  
    12  	"github.com/vertgenlab/gonomics/bed"
    13  	"github.com/vertgenlab/gonomics/exception"
    14  	"github.com/vertgenlab/gonomics/fileio"
    15  	"github.com/vertgenlab/gonomics/wig"
    16  )
    17  
    18  func wigPeaks(inWig string, outBed string, threshold float64, findMinima bool) { //threshold is float64 because WigValue Value aka v2.Value is float64.
    19  	records := wig.Read(inWig) //type is []Wig, aka slice of Wig structs
    20  	var inPeak bool = false
    21  	var err error
    22  	var current bed.Bed              //to store the current peak as a bed entry
    23  	out := fileio.EasyCreate(outBed) //instead of answer, use "chan" method to write as we go. out has type EasyCreate defined as a gonomics struct, one of the fields is File which has type *os.File, needed for bed.WriteBed
    24  	var v2 float64
    25  
    26  	for _, v1 := range records { //in range for loop, i is index (0,1,2..) which we don't use in this instance, v is value (content of slice). Record is slice of wig struct, each iteration is slice/object of wig struct, which looks like a block and is often organized by chromosomes (or part of chromosome), and each wig struct will produce independent peaks. The v1 iteration has chr=chrom3,step=100, and a list of WigValues where there are positions+values, values like 11 22 100 etc.
    27  		inPeak = false //when entering a new wig struct, set inPeak to false
    28  		var wigPosition = v1.Start
    29  		for _, v2 = range v1.Values { //each v1 is a wig struct, whose Values is a []float64 which contains the value at each position. Each i2 is index which we don't use, and each v2 is a float64.
    30  			if passThreshold(v2, threshold, findMinima) { //either (from outside of a peak) start a new peak or inside of a peak
    31  				if !inPeak { //this means start a new peak if this is currently set to false.
    32  					inPeak = true                                                                                                                          //must remember to set inPeak to reflect Peak status
    33  					current = bed.Bed{Chrom: v1.Chrom, ChromStart: wigPosition, ChromEnd: wigPosition + 1, Name: "", Score: int(v2), FieldsInitialized: 5} //ChromEnd is +1 because bed has [open,closed) interval (note: in this program, the position that ends the peak is still >threshold, not the first position that dips <threshold), Score is the max Wig of the bed region, will update when inside the peak
    34  				} else { //this means already inside peak
    35  					current.ChromEnd = wigPosition + 1 //Update ChromEnd
    36  					if findMinima && v2 < float64(current.Score) {
    37  						current.Score = int(v2)
    38  					} else if !findMinima && v2 > float64(current.Score) { //Update Score if found new max wig score
    39  						current.Score = int(v2)
    40  					}
    41  				}
    42  			} else { //either (from inside of a peak) ending a peak or outside of a peak
    43  				if inPeak { //if inside of a peak ending a peak
    44  					inPeak = false             //must remember to set inPeak to reflect Peak status
    45  					bed.WriteBed(out, current) //instead of answer, use "chan" method to write as we go
    46  				}
    47  				//if outside of a peak, nothing to do
    48  			}
    49  			wigPosition = wigPosition + v1.Step
    50  		}
    51  		if inPeak { //after wig struct ends, if inPeak is still true (i.e. ended on a value>threshold), should end peak and append current
    52  			inPeak = false             //redundant since this will happen when enter a new wig struct
    53  			bed.WriteBed(out, current) //instead of answer, use "chan" method to write as we go
    54  		}
    55  	}
    56  	err = out.Close()
    57  	exception.PanicOnErr(err)
    58  }
    59  
    60  func passThreshold(v2 float64, threshold float64, findMinima bool) bool {
    61  	if findMinima {
    62  		return v2 <= threshold
    63  	} else {
    64  		return v2 >= threshold
    65  	}
    66  }
    67  
    68  func usage() {
    69  	fmt.Print(
    70  		"wigPeaks - takes wig file and finds peaks\n" +
    71  			"Usage:\n" +
    72  			" wigPeaks in.wig out.bed\n" +
    73  			"options:\n")
    74  	flag.PrintDefaults()
    75  }
    76  
    77  func main() {
    78  	var expectedNumArgs int = 2
    79  	var peakThreshold *float64 = flag.Float64("threshold", 20, "if number of reads >= threshold, start calling peak")
    80  	var findMinima *bool = flag.Bool("findMinima", false, "Report local minima peaks instead of maxima past the threshold. ")
    81  
    82  	flag.Usage = usage
    83  	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
    84  	flag.Parse()
    85  
    86  	if len(flag.Args()) != expectedNumArgs {
    87  		flag.Usage()
    88  		log.Fatalf("Error: expecting %d arguments, but got %d\n", expectedNumArgs, len(flag.Args()))
    89  	}
    90  
    91  	inWig := flag.Arg(0)
    92  	outBed := flag.Arg(1)
    93  
    94  	wigPeaks(inWig, outBed, *peakThreshold, *findMinima)
    95  }