{"action":"created","comment":{"url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/comments/68157181","id":68157181,"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":"I see, with your `getNextGroupOfRecords` re-implementation below, the point is made.","created_at":"2016-06-23T00:04:39Z","updated_at":"2016-06-23T00:04:39Z","html_url":"https://github.com/fulcrumgenomics/fgbio/pull/21#discussion_r68157181","pull_request_url":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/21","_links":{"self":{"href":"https://api.github.com/repos/fulcrumgenomics/fgbio/pulls/comments/68157181"},"html":{"href":"https://github.com/fulcrumgenomics/fgbio/pull/21#discussion_r68157181"},"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:04:39Z","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"}}