{"action":"created","comment":{"url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/comments/68156878","id":68156878,"diff_hunk":"@@ -0,0 +1,369 @@\n+/*\n+ * The MIT License\n+ *\n+ * Copyright (c) 2016 Fulcrum Genomics LLC\n+ *\n+ * Permission is hereby granted, free of charge, to any person obtaining a copy\n+ * of this software and associated documentation files (the \"Software\"), to deal\n+ * in the Software without restriction, including without limitation the rights\n+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n+ * copies of the Software, and to permit persons to whom the Software is\n+ * furnished to do so, subject to the following conditions:\n+ *\n+ * The above copyright notice and this permission notice shall be included in\n+ * all copies or substantial portions of the Software.\n+ *\n+ * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n+ * THE SOFTWARE.\n+ */\n+\n+package com.fulcrumgenomics.bam\n+\n+import com.fulcrumgenomics.bam.ConsensusCallerOptions._\n+import com.fulcrumgenomics.util.PhredValue.{OneProbability, ZeroPhred, ZeroProbability}\n+import com.fulcrumgenomics.util.{PhredValue, ProgressLogger}\n+import dagr.commons.CommonsDef._\n+import htsjdk.samtools._\n+import htsjdk.samtools.util.SequenceUtil\n+\n+import scala.collection.mutable\n+import scala.collection.mutable.ListBuffer\n+\n+object ConsensusCallerOptions {\n+  /** Various default values for the consensus caller. */\n+  val DefaultAttribute: String                       = \"RX\"\n+  val DefaultErrorRatePreUmi: PhredValue             = 50\n+  val DefaultErrorRatePostUmi: PhredValue            = 0\n+  val DefaultMaxBaseQuality: PhredValue              = 40\n+  val DefaultBaseQualityShift: PhredValue            = 10\n+  val DefaultMinConsensusBaseQuality: PhredValue     = 13\n+  val DefaultMinReads: Int                           = 1\n+  val DefaultMinMeanConsensusBaseQuality: PhredValue = 13\n+}\n+\n+/** Holds the parameters/options for consensus calling. */\n+case class ConsensusCallerOptions(attribute: String                       = DefaultAttribute,\n+                                  errorRatePreUmi: PhredValue             = DefaultErrorRatePreUmi,\n+                                  errorRatePostUmi: PhredValue            = DefaultErrorRatePostUmi,\n+                                  maxBaseQuality: PhredValue              = DefaultMaxBaseQuality,\n+                                  baseQualityShift: PhredValue            = DefaultBaseQualityShift,\n+                                  minConsensusBaseQuality: PhredValue     = DefaultMinConsensusBaseQuality,\n+                                  minReads: Int                           = DefaultMinReads,\n+                                  minMeanConsensusBaseQuality: PhredValue = DefaultMinMeanConsensusBaseQuality\n+                                 )\n+\n+object ConsensusCaller {\n+  /** Creates a consensus read from the given read and quality string tuples.  If no consensus read was created, None is returned. */\n+  def fromBasesAndQualities(basesAndQualities: Seq[(String, String)],\n+                            options: ConsensusCallerOptions = new ConsensusCallerOptions()\n+                           ): Option[(String, String)] = {\n+    if (basesAndQualities.exists { case (b, q) => b.length != q.length }) {\n+      throw new IllegalArgumentException(\"Found a read with the bases and qualities having different length\")\n+    }\n+    // check to see if we have enough reads.\n+    if (basesAndQualities.length < options.minReads) return None\n+\n+    // extract the bases and qualities, and adjust the qualities based on the given options.\n+    val bases: Seq[String] = basesAndQualities.map { _._1.toUpperCase }\n+    val quals: Seq[Seq[PhredValue]] = basesAndQualities.map(bq => SAMUtils.fastqToPhred(bq._2))\n+      .map { bq =>\n+        adjustBaseQualities(\n+          quals            = bq.map(q => PhredValue(q.toDouble)),\n+          maxBaseQuality   = options.maxBaseQuality,\n+          baseQualityShift = options.baseQualityShift,\n+          errorRatePostUmi = options.errorRatePostUmi\n+        )\n+      } // NB: binary Phred, not ASCII\n+\n+    // get the most likely consensus bases and qualities\n+    val (consensusBases, consensusQualities) = consensusCalls(\n+      bases                   = bases,\n+      quals                   = quals,\n+      errorRatePreUmi         = options.errorRatePreUmi,\n+      minConsensusBaseQuality = options.minConsensusBaseQuality,\n+      minReads                = options.minReads\n+      )\n+\n+    // check that the mean base quality is high enough\n+    if (PhredValue.mean(consensusQualities:_*) < options.minMeanConsensusBaseQuality) None\n+    else Some((consensusBases, SAMUtils.phredToFastq(consensusQualities.map(_.toByte).toArray[Byte])))\n+  }\n+\n+  /** Creates a consensus read from the given records.  If no consensus read was created, None is returned. */\n+  def fromSamRecords(records: Seq[SAMRecord],\n+                     options: ConsensusCallerOptions = new ConsensusCallerOptions()\n+                    ): Option[(String, String)] = {\n+    // TODO: if mapped, this gets the bases in genomic order and not sequencing order.  Does it matter?\n+    val bases = records.map { rec => rec.getReadString.toUpperCase }\n+    val quals = records.map { rec => rec.getBaseQualityString }\n+\n+    fromBasesAndQualities( basesAndQualities=bases.zip(quals), options = options)\n+  }\n+\n+  /** Get the most likely consensus bases and qualities. */\n+  private[bam] def consensusCalls(bases: Seq[String],\n+                                  quals: Seq[Seq[PhredValue]],\n+                                  errorRatePreUmi: PhredValue         = DefaultErrorRatePreUmi,\n+                                  minConsensusBaseQuality: PhredValue = DefaultMinConsensusBaseQuality,\n+                                  minReads: Int                       = DefaultMinReads): (String, Seq[PhredValue]) = {\n+    val maxBaseLength = bases.map(_.length).max\n+\n+    // go through each position in the read(s).\n+    val (consensusBases, consensusQualities) = Seq.range(0, maxBaseLength, 1).map { baseIdx =>\n+      val basesAtIdx = bases.filter { str => baseIdx < str.length}.map { str => str.charAt(baseIdx) }\n+\n+      // check to see if we heave a enough reads to produce a consensus base.\n+      if (basesAtIdx.length < minReads) (SequenceUtil.N.toChar, ZeroPhred)\n+      else {\n+        val qualsAtIdx         = quals.filter { q => baseIdx < q.length}.map { q => q(baseIdx) }\n+        val basesAndQualsAtIdx = basesAtIdx.zip(qualsAtIdx)\n+\n+        // TODO: handle Ns in the reads\n+        // get the phred-scaled probability of each potential DNA base\n+        var phreds = SequenceUtil.VALID_BASES_UPPER.map(_.toChar).map {\n+          candidateBase =>\n+            basesAndQualsAtIdx.map { case (base, qual) =>\n+              if (base == candidateBase) qual.inv() // probability of not an error\n+              else qual / 3.0 // probability of an error for this specific base\n+            }.foldLeft(OneProbability)((phred, qual) => phred * qual)\n+        }\n+        // normalize\n+        val phredsSum = phreds.foldLeft(ZeroProbability)((a, b) => a + b)\n+        phreds = phreds.map { phred => phred / phredsSum }\n+\n+        // Find the base with the maximum probability, which means the smallest phred\n+        val minPhred    = phreds.min\n+        val minPhredIdx = phreds.indexOf(minPhred)\n+        val baseQual    = minPhred.inv() // convert the probability of the base to the phred-scaled probability of *not* the base\n+        // Masks a base if the phred quality is too low\n+        if (baseQual.toInt < minConsensusBaseQuality) (SequenceUtil.N.toChar, baseQual)\n+        else (SequenceUtil.VALID_BASES_UPPER(minPhredIdx).toChar, baseQual)\n+      }\n+    }.unzip\n+\n+    // Factor in the pre-UMI error rate.\n+    val consensusQualitiesScaled = consensusQualities.map { phred =>\n+      // Pr(error) = Pr(any pre-UMI error AND correct consensus) + Pr(no pre-UMI error AND any error in consensus)\n+      //               + Pr(pre-UMI error AND error in consensus, that do not give us the correct bases)\n+      // The last term tries to capture the case where a pre-UMI error modifies the base (ex. A->C) but a sequencing\n+      // error calls it the correct base (C->A).  Only 2/3 times will the two errors result in the incorrect base.\n+      val p = probabilityOfErrorTwoTrials(errorRatePreUmi, phred)\n+      PhredValue(Math.min(SAMUtils.MAX_PHRED_SCORE, p.value)) // cap the quality\n+    }\n+    (consensusBases.mkString, consensusQualitiesScaled)\n+  }\n+\n+  /** Adjusts the given base qualities.  The base qualities are first shifted by `baseQualityShift`, then capped using\n+    * `maxBaseQuality`, and finally the `errorRatePostUmi` is incorporated.\n+    */\n+  private[bam] def adjustBaseQualities(quals: Seq[PhredValue],\n+                                       maxBaseQuality: PhredValue   = DefaultMaxBaseQuality,\n+                                       baseQualityShift: PhredValue = DefaultBaseQualityShift,\n+                                       errorRatePostUmi: PhredValue = DefaultErrorRatePostUmi\n+                                      ): Seq[PhredValue] = {\n+    quals.map { qual =>\n+      // shift the base qualities, then cap it\n+      PhredValue(Math.max(0.0, qual.value - baseQualityShift.value)) match {\n+        case q if maxBaseQuality < q => maxBaseQuality\n+        case q => q\n+      }\n+    }.map { phred =>\n+      // Pr(err) = Pr(no post-UMI error AND any sequencing error) + Pr(any post-UMI error and no sequencing error) +\n+      //             Pr(post-UMI error AND sequencing error)\n+      // The last term tries to capture the case where a post-UMI error modifies the base (ex. A->C) but a sequencing\n+      // error calls it the correct base (C->A).  Only 2/3 times will the two errors result in the incorrect base.\n+      probabilityOfErrorTwoTrials(errorRatePostUmi, phred)\n+    }\n+  }\n+\n+  /** Computes the probability of seeing an error in the base sequence if there are two independent error processes.\n+    * We sum three terms:\n+    * 1. the probability of an error in trial one and no error in trial two: Pr(A=Error, B=NoError).\n+    * 2. the probability of no error in trial one and an error in trial two: Pr(A=NoError, B=Error).\n+    * 3. the probability of an error in both trials, but when the second trial does not reverse the error in first one, which\n+    *    for DNA (4 bases) would only occur 2/3 times: Pr(A=x->y, B=y->z) * Pr(x!=z | x!=y, y!=z, x,y,z \\in {A,C,G,T})\n+    */\n+  private[bam] def probabilityOfErrorTwoTrials(prErrorTrialOne: PhredValue, prErrorTrialTwo: PhredValue): PhredValue = {\n+    val pr1 = prErrorTrialOne       * prErrorTrialTwo.inv()\n+    val pr2 = prErrorTrialOne.inv() * prErrorTrialTwo\n+    val pr3 = prErrorTrialOne       * prErrorTrialTwo       * 2.0 / 3.0\n+    pr1 + pr2 + pr3\n+  }\n+\n+  /** The type of consensus read to output. */\n+  private object ReadType extends Enumeration {\n+    val Fragment, FirstOfPair, SecondOfPair = Value\n+  }\n+\n+  /** Gets the longest common prefix of the given strings, None if there is only one string or if there is an empty string. */\n+  @annotation.tailrec\n+  private def longestCommonPrefix(strs: Iterable[String], accu: Option[String] = None): Option[String] = {\n+    if (strs.exists(_.isEmpty) || strs.size <= 1) accu\n+    else {\n+      val firsts = strs.map(_.head)\n+      if (firsts.toSet.size > 1) accu\n+      else longestCommonPrefix(strs.map(_.tail), Some(accu.getOrElse(\"\") + firsts.head))\n+    }\n+  }\n+}\n+\n+/** Calls consensus reads by grouping consecutive reads with the same attribute.\n+  *\n+  * Consecutive reads with the sam attribute are partitioned into fragments, first of pair, and\n+  * second of pair reads, and a consensus read is created for each partition.  A consensus read\n+  * for a given partition may not be returned if any of the conditions are not met (ex. minimum\n+  * number of reads, minimum mean consensus base quality, ...).\n+  * */\n+class ConsensusCaller\n+( input: Iterator[SAMRecord],\n+  val header: SAMFileHeader,\n+  val readNamePrefix: Option[String]   = None,\n+  val readGroup: String                = \"A\",\n+  val options: ConsensusCallerOptions  = new ConsensusCallerOptions(),\n+  val rejects: Option[SAMFileWriter]   = None,\n+  val progress: Option[ProgressLogger] = None\n+) extends Iterator[SAMRecord] {\n+  import ConsensusCaller.ReadType\n+  import ConsensusCaller.ReadType._\n+  import ConsensusCaller._\n+\n+  private val iter = input.buffered\n+  private val nextConsensusRecords: mutable.Queue[SAMRecord] = mutable.Queue[SAMRecord]() // one per UMI group\n+  private var readIdx = 1\n+\n+  /** True if there are more consensus reads, false otherwise. */\n+  def hasNext(): Boolean = {\n+    if (this.nextConsensusRecords.nonEmpty) true\n+    else this.advance()\n+  }\n+\n+  /** Returns the next consensus read. */\n+  def next(): SAMRecord = {\n+    if (!this.hasNext()) throw new IllegalStateException(\"Calling next() when hasNext() is false.\")\n+    this.nextConsensusRecords match {\n+      case mutable.Queue(x, _*) => this.nextConsensusRecords.dequeue()\n+      case mutable.Queue() => unreachable(\"hasNext() was true but nextConsensusRecords is empty\")\n+    }\n+  }\n+\n+  /** Consumes records until a consensus read can be created, or no more input records are available. Returns\n+    * true if a consensus read was created, false otherwise. */\n+  private def advance(): Boolean = {\n+    if (this.iter.isEmpty) return false\n+\n+    // get the records to create the consensus read\n+    val buffer = getNextGroupOfRecords(attr=this.iter.head.toAttr)","path":"src/main/scala/com/fulcrumgenomics/bam/ConsensusCaller.scala","position":260,"original_position":260,"commit_id":"aaa3bacf68a0e67dbc541f1e3ba331995f9880db","original_commit_id":"aaa3bacf68a0e67dbc541f1e3ba331995f9880db","user":{"login":"nh13","id":846274,"avatar_url":"https://avatars.githubusercontent.com/u/846274?v=3","gravatar_id":"","url":"https://api.github.com/users/nh13","html_url":"https://github.com/nh13","followers_url":"https://api.github.com/users/nh13/followers","following_url":"https://api.github.com/users/nh13/following{/other_user}","gists_url":"https://api.github.com/users/nh13/gists{/gist_id}","starred_url":"https://api.github.com/users/nh13/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nh13/subscriptions","organizations_url":"https://api.github.com/users/nh13/orgs","repos_url":"https://api.github.com/users/nh13/repos","events_url":"https://api.github.com/users/nh13/events{/privacy}","received_events_url":"https://api.github.com/users/nh13/received_events","type":"User","site_admin":false},"body":"This is the method recursively accumulates records that have the same tag.  We pass in the value of the tag from the first record, and all subsequent records match this tag (so multiple comparisons).  I don't see how to handle it in once place, since it is already being handled in one place, namely `getNextGroupOfRecords`","created_at":"2016-06-23T00:01:22Z","updated_at":"2016-06-23T00:01:22Z","html_url":"https://github.com/fulcrumgenomics/fgbio/pull/21#discussion_r68156878","pull_request_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21","_links":{"self":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/comments/68156878"},"html":{"href":"https://github.com/fulcrumgenomics/fgbio/pull/21#discussion_r68156878"},"pull_request":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21"}}},"pull_request":{"url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21","id":74153133,"html_url":"https://github.com/fulcrumgenomics/fgbio/pull/21","diff_url":"https://github.com/fulcrumgenomics/fgbio/pull/21.diff","patch_url":"https://github.com/fulcrumgenomics/fgbio/pull/21.patch","issue_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues/21","number":21,"state":"open","locked":false,"title":"WIP: Adding a simple consensus caller.","user":{"login":"nh13","id":846274,"avatar_url":"https://avatars.githubusercontent.com/u/846274?v=3","gravatar_id":"","url":"https://api.github.com/users/nh13","html_url":"https://github.com/nh13","followers_url":"https://api.github.com/users/nh13/followers","following_url":"https://api.github.com/users/nh13/following{/other_user}","gists_url":"https://api.github.com/users/nh13/gists{/gist_id}","starred_url":"https://api.github.com/users/nh13/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nh13/subscriptions","organizations_url":"https://api.github.com/users/nh13/orgs","repos_url":"https://api.github.com/users/nh13/repos","events_url":"https://api.github.com/users/nh13/events{/privacy}","received_events_url":"https://api.github.com/users/nh13/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-06-16T21:12:29Z","updated_at":"2016-06-23T00:01:22Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d654a8bf6a90f0455b87474fe6c0c8007f4423fd","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21/commits","review_comments_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21/comments","review_comment_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/comments{/number}","comments_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues/21/comments","statuses_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/statuses/aaa3bacf68a0e67dbc541f1e3ba331995f9880db","head":{"label":"fulcrumgenomics:nh_simple_consensus_caller","ref":"nh_simple_consensus_caller","sha":"aaa3bacf68a0e67dbc541f1e3ba331995f9880db","user":{"login":"fulcrumgenomics","id":13546013,"avatar_url":"https://avatars.githubusercontent.com/u/13546013?v=3","gravatar_id":"","url":"https://api.github.com/users/fulcrumgenomics","html_url":"https://github.com/fulcrumgenomics","followers_url":"https://api.github.com/users/fulcrumgenomics/followers","following_url":"https://api.github.com/users/fulcrumgenomics/following{/other_user}","gists_url":"https://api.github.com/users/fulcrumgenomics/gists{/gist_id}","starred_url":"https://api.github.com/users/fulcrumgenomics/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fulcrumgenomics/subscriptions","organizations_url":"https://api.github.com/users/fulcrumgenomics/orgs","repos_url":"https://api.github.com/users/fulcrumgenomics/repos","events_url":"https://api.github.com/users/fulcrumgenomics/events{/privacy}","received_events_url":"https://api.github.com/users/fulcrumgenomics/received_events","type":"Organization","site_admin":false},"repo":{"id":53011104,"name":"fgbio","full_name":"fulcrumgenomics/fgbio","owner":{"login":"fulcrumgenomics","id":13546013,"avatar_url":"https://avatars.githubusercontent.com/u/13546013?v=3","gravatar_id":"","url":"https://api.github.com/users/fulcrumgenomics","html_url":"https://github.com/fulcrumgenomics","followers_url":"https://api.github.com/users/fulcrumgenomics/followers","following_url":"https://api.github.com/users/fulcrumgenomics/following{/other_user}","gists_url":"https://api.github.com/users/fulcrumgenomics/gists{/gist_id}","starred_url":"https://api.github.com/users/fulcrumgenomics/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fulcrumgenomics/subscriptions","organizations_url":"https://api.github.com/users/fulcrumgenomics/orgs","repos_url":"https://api.github.com/users/fulcrumgenomics/repos","events_url":"https://api.github.com/users/fulcrumgenomics/events{/privacy}","received_events_url":"https://api.github.com/users/fulcrumgenomics/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/fulcrumgenomics/fgbio","description":"A set of tools for working with genomic and high throughput sequencing data.","fork":false,"url":"https://api.github.com/repos/fulcrumgenomics/fgbio","forks_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/forks","keys_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/teams","hooks_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/hooks","issue_events_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues/events{/number}","events_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/events","assignees_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/assignees{/user}","branches_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/branches{/branch}","tags_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/tags","blobs_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/refs{/sha}","trees_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/statuses/{sha}","languages_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/languages","stargazers_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/stargazers","contributors_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/contributors","subscribers_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/subscribers","subscription_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/subscription","commits_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/commits{/sha}","git_commits_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/commits{/sha}","comments_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/comments{/number}","issue_comment_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues/comments{/number}","contents_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/contents/{+path}","compare_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/merges","archive_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/downloads","issues_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues{/number}","pulls_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls{/number}","milestones_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/milestones{/number}","notifications_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/labels{/name}","releases_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/releases{/id}","deployments_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/deployments","created_at":"2016-03-03T01:44:56Z","updated_at":"2016-03-21T17:33:21Z","pushed_at":"2016-06-22T21:18:52Z","git_url":"git://github.com/fulcrumgenomics/fgbio.git","ssh_url":"git@github.com:fulcrumgenomics/fgbio.git","clone_url":"https://github.com/fulcrumgenomics/fgbio.git","svn_url":"https://github.com/fulcrumgenomics/fgbio","homepage":"","size":6252,"stargazers_count":2,"watchers_count":2,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":4,"forks":0,"open_issues":4,"watchers":2,"default_branch":"master"}},"base":{"label":"fulcrumgenomics:master","ref":"master","sha":"91281376b530efc66716d17dab9f55db26adc601","user":{"login":"fulcrumgenomics","id":13546013,"avatar_url":"https://avatars.githubusercontent.com/u/13546013?v=3","gravatar_id":"","url":"https://api.github.com/users/fulcrumgenomics","html_url":"https://github.com/fulcrumgenomics","followers_url":"https://api.github.com/users/fulcrumgenomics/followers","following_url":"https://api.github.com/users/fulcrumgenomics/following{/other_user}","gists_url":"https://api.github.com/users/fulcrumgenomics/gists{/gist_id}","starred_url":"https://api.github.com/users/fulcrumgenomics/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fulcrumgenomics/subscriptions","organizations_url":"https://api.github.com/users/fulcrumgenomics/orgs","repos_url":"https://api.github.com/users/fulcrumgenomics/repos","events_url":"https://api.github.com/users/fulcrumgenomics/events{/privacy}","received_events_url":"https://api.github.com/users/fulcrumgenomics/received_events","type":"Organization","site_admin":false},"repo":{"id":53011104,"name":"fgbio","full_name":"fulcrumgenomics/fgbio","owner":{"login":"fulcrumgenomics","id":13546013,"avatar_url":"https://avatars.githubusercontent.com/u/13546013?v=3","gravatar_id":"","url":"https://api.github.com/users/fulcrumgenomics","html_url":"https://github.com/fulcrumgenomics","followers_url":"https://api.github.com/users/fulcrumgenomics/followers","following_url":"https://api.github.com/users/fulcrumgenomics/following{/other_user}","gists_url":"https://api.github.com/users/fulcrumgenomics/gists{/gist_id}","starred_url":"https://api.github.com/users/fulcrumgenomics/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fulcrumgenomics/subscriptions","organizations_url":"https://api.github.com/users/fulcrumgenomics/orgs","repos_url":"https://api.github.com/users/fulcrumgenomics/repos","events_url":"https://api.github.com/users/fulcrumgenomics/events{/privacy}","received_events_url":"https://api.github.com/users/fulcrumgenomics/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/fulcrumgenomics/fgbio","description":"A set of tools for working with genomic and high throughput sequencing data.","fork":false,"url":"https://api.github.com/repos/fulcrumgenomics/fgbio","forks_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/forks","keys_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/teams","hooks_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/hooks","issue_events_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues/events{/number}","events_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/events","assignees_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/assignees{/user}","branches_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/branches{/branch}","tags_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/tags","blobs_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/refs{/sha}","trees_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/statuses/{sha}","languages_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/languages","stargazers_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/stargazers","contributors_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/contributors","subscribers_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/subscribers","subscription_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/subscription","commits_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/commits{/sha}","git_commits_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/git/commits{/sha}","comments_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/comments{/number}","issue_comment_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues/comments{/number}","contents_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/contents/{+path}","compare_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/merges","archive_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/downloads","issues_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues{/number}","pulls_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls{/number}","milestones_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/milestones{/number}","notifications_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/labels{/name}","releases_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/releases{/id}","deployments_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/deployments","created_at":"2016-03-03T01:44:56Z","updated_at":"2016-03-21T17:33:21Z","pushed_at":"2016-06-22T21:18:52Z","git_url":"git://github.com/fulcrumgenomics/fgbio.git","ssh_url":"git@github.com:fulcrumgenomics/fgbio.git","clone_url":"https://github.com/fulcrumgenomics/fgbio.git","svn_url":"https://github.com/fulcrumgenomics/fgbio","homepage":"","size":6252,"stargazers_count":2,"watchers_count":2,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":4,"forks":0,"open_issues":4,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21"},"html":{"href":"https://github.com/fulcrumgenomics/fgbio/pull/21"},"issue":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues/21"},"comments":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/issues/21/comments"},"review_comments":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21/comments"},"review_comment":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21/commits"},"statuses":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/statuses/aaa3bacf68a0e67dbc541f1e3ba331995f9880db"}}}}
{"actor":{"display_login":"nh13"}}